|
| 1 | +import { Controller } from '@hotwired/stimulus'; |
| 2 | + |
| 3 | +/** |
| 4 | + * Moves the single search bar element into the desktop or mobile anchor |
| 5 | + * depending on the Bootstrap lg breakpoint (992px), avoiding DOM duplication. |
| 6 | + */ |
| 7 | +export default class extends Controller { |
| 8 | + static targets = ['bar']; |
| 9 | + |
| 10 | + /** |
| 11 | + * CSS selector used to locate the desktop anchor in the document. |
| 12 | + * @type {string} |
| 13 | + */ |
| 14 | + static DESKTOP_ANCHOR_SELECTOR = '[data-gally-search-bar-anchor="desktop"]'; |
| 15 | + |
| 16 | + /** |
| 17 | + * CSS selector used to locate the mobile anchor in the document. |
| 18 | + * @type {string} |
| 19 | + */ |
| 20 | + static MOBILE_ANCHOR_SELECTOR = '[data-gally-search-bar-anchor="mobile"]'; |
| 21 | + |
| 22 | + connect() { |
| 23 | + // Read the target once: after the first _place() call, the bar element |
| 24 | + // is moved outside this.element, so Stimulus no longer considers it part |
| 25 | + // of this controller's target scope and this.barTarget would throw. |
| 26 | + this._bar = this.barTarget; |
| 27 | + this._mql = window.matchMedia('(min-width: 992px)'); |
| 28 | + this._handler = (e) => this._place(e.matches); |
| 29 | + this._mql.addEventListener('change', this._handler); |
| 30 | + this._place(this._mql.matches); |
| 31 | + } |
| 32 | + |
| 33 | + disconnect() { |
| 34 | + this._mql.removeEventListener('change', this._handler); |
| 35 | + } |
| 36 | + |
| 37 | + /** |
| 38 | + * Moves the bar element to the appropriate anchor. |
| 39 | + * |
| 40 | + * @param {boolean} isDesktop true when the viewport is ≥ lg (992 px) |
| 41 | + */ |
| 42 | + _place(isDesktop) { |
| 43 | + const selector = isDesktop |
| 44 | + ? this.constructor.DESKTOP_ANCHOR_SELECTOR |
| 45 | + : this.constructor.MOBILE_ANCHOR_SELECTOR; |
| 46 | + |
| 47 | + const anchor = document.querySelector(selector); |
| 48 | + const bar = this._bar; |
| 49 | + |
| 50 | + if (!anchor) { |
| 51 | + // Anchor not yet in the DOM (e.g. Turbo page transition) — keep bar |
| 52 | + // in its current position and retry on the next breakpoint change. |
| 53 | + return; |
| 54 | + } |
| 55 | + |
| 56 | + // Only move if not already in the right place to avoid layout thrashing. |
| 57 | + if (anchor.firstElementChild !== bar) { |
| 58 | + // Remove style="display:none" from controller element on first placement |
| 59 | + this.element.removeAttribute('style'); |
| 60 | + this.element.removeAttribute('aria-hidden'); |
| 61 | + anchor.appendChild(bar); |
| 62 | + } |
| 63 | + } |
| 64 | +} |
0 commit comments