{"version":3,"file":"filters-drawer-section.min.js","mappings":"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","sources":["webpack://lange/./sections/filters-drawer/filters/index.ts","webpack://lange/./sections/filters-drawer/filters-group/index.ts","webpack://lange/./sections/filters-drawer/filters-sort-by/index.ts","webpack://lange/./sections/filters-drawer/index.ts","webpack://lange/./lib/dom-ready.ts"],"sourcesContent":["import type { CustomElement } from '@integrabeauty/custom-elements';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * This custom element is a wrapper for the filters on the primary collection page. It manages the\n * current state of these filters, taking care of their application and removal. The filter and sort\n * related HTML markup should be passed through the default slot.\n */\nclass FiltersElement extends HTMLElement implements CustomElement {\n  declare readonly dataset: {\n    /**\n     * The count of products that match the current filters.\n     */\n    productCount: string;\n  };\n\n  /**\n   * The filters are represented as search parameters. Therefore, this component is designed to\n   * store the current page's search parameters and update them when the user alters the filters.\n   * This object stores all search params not just filters.\n   */\n  private searchParams = new URLSearchParams(location.search);\n\n  private onFiltersSortByChangedBound = this.onFiltersSortByChanged.bind(this);\n  private onFiltersSortByInStockChangedBound = this.onFiltersSortByInStockChanged.bind(this);\n  private onApplyClickedBound = this.onApplyClicked.bind(this);\n  private onClearClickedBound = this.onClearClicked.bind(this);\n  private onFiltersGroupFilterChangedBound = this.onFiltersGroupFilterChanged.bind(this);\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    // The apply and clear buttons are part of the shadow root that initializeShadowRoot populated\n    // synchronously one line above, so they are guaranteed present here. Their listeners are\n    // removed again in the disconnected callback, keeping registration balanced across\n    // reconnection.\n    const apply = this.shadowRoot.querySelector<HTMLElement>('.apply-button');\n    apply.addEventListener('click', this.onApplyClickedBound);\n\n    const clearButton = this.shadowRoot.querySelector<HTMLElement>('.clear-button');\n    clearButton.addEventListener('click', this.onClearClickedBound);\n\n    addEventListener('filters-sort-by-changed', this.onFiltersSortByChangedBound);\n    addEventListener('filters-sort-by-in-stock-changed', this.onFiltersSortByInStockChangedBound);\n    addEventListener('filters-group-filter-changed', this.onFiltersGroupFilterChangedBound);\n\n    this.render();\n  }\n\n  public disconnectedCallback() {\n    const apply = this.shadowRoot.querySelector<HTMLElement>('.apply-button');\n    apply.removeEventListener('click', this.onApplyClickedBound);\n\n    const clearButton = this.shadowRoot.querySelector<HTMLElement>('.clear-button');\n    clearButton.removeEventListener('click', this.onClearClickedBound);\n\n    removeEventListener('filters-sort-by-changed', this.onFiltersSortByChangedBound);\n    removeEventListener('filters-sort-by-in-stock-changed',\n      this.onFiltersSortByInStockChangedBound);\n    removeEventListener('filters-group-filter-changed', this.onFiltersGroupFilterChangedBound);\n  }\n\n  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open' });\n      this.shadowRoot.innerHTML = `<style>${styles}</style>${html}`;\n    }\n  }\n\n  private onFiltersSortByChanged(event: WindowEventMap['filters-sort-by-changed']) {\n    const { name, value } = event.detail;\n    this.searchParams.set(name, value);\n  }\n\n  /**\n   * Handles the \"filters-sort-by-in-stock-changed\" event.\n   *\n   * Shopify's filter for availability has two possible values: \"1\" for in-stock products and \"0\"\n   * for out-of-stock products. In our UI, when the \"In Stock\" option is unchecked, we should\n   * display all products regardless of their availability. Hence, we remove the filter from the\n   * search parameters.\n   *\n   * The fact that there's a specific case for the \"In stock\" filter option we cannot handle it as\n   * other filters or storing options.\n   */\n  private onFiltersSortByInStockChanged(event: WindowEventMap['filters-sort-by-in-stock-changed']) {\n    if (event.detail.value) {\n      this.searchParams.set('filter.v.availability', '1');\n    } else {\n      this.searchParams.delete('filter.v.availability');\n    }\n\n    this.showBackdrop();\n    this.update()\n      .then(() => {\n        this.render();\n      })\n      .catch(console.warn)\n      .finally(() => {\n        this.hideBackdrop();\n      });\n  }\n\n  private onApplyClicked(_event: MouseEvent) {\n    const url = new URL(location.href);\n    url.search = this.searchParams.toString();\n    location.href = url.toString();\n  }\n\n  private onClearClicked(_event: MouseEvent) {\n    // The creation of a new array \"keys\" from this.searchParams.keys() is necessary because we're\n    // modifying \"this.searchParams\" during its iteration in the for loop. Modifying a collection\n    // during iteration can result in unexpected behavior or errors due to index misalignment. By\n    // creating and iterating over a separate array \"keys\", we avoid these issues.\n    for (const key of [...this.searchParams.keys()]) {\n      // We should only remove the search parameters that are related to filters and sorting. In\n      // Shopify, \"sort_by\" parameter is dedicated for sorting. All filter parameters start with\n      // \"filter.\".\n      if (key === 'sort_by' || /(^sort_by$|^filter\\.)/.test(key)) {\n        this.searchParams.delete(key);\n      }\n    }\n\n    const url = new URL(location.href);\n    url.search = this.searchParams.toString();\n    location.href = url.toString();\n  }\n\n  /**\n   * Handles grouped checkbox filters\n   */\n  private onFiltersGroupFilterChanged(event: WindowEventMap['filters-group-filter-changed']) {\n    const { active, name, value } = event.detail;\n    if (active) {\n      this.searchParams.append(name, value);\n    } else {\n      this.searchParams.delete(name, value);\n    }\n\n    this.showBackdrop();\n    this.update()\n      .then(() => {\n        this.render();\n      })\n      .catch(console.warn)\n      .finally(() => {\n        this.hideBackdrop();\n      });\n  }\n\n  /**\n   * Updates the options displayed on the current page based on the search parameters.\n   *\n   * This method fetches the filters-drawer section from Shopify with applied filters, then the HTML\n   * of the sections is parsed and the filters-group elements and products count are updated.\n   */\n  private async update() {\n    const url = new URL(location.href);\n    url.search = this.searchParams.toString();\n    url.searchParams.append('sections', 'filters-drawer');\n    const response = await fetch(url.toString());\n    const data = await response.json();\n    const htmlString = data['filters-drawer'];\n\n    const html = document.createElement('div');\n    html.innerHTML = htmlString;\n\n    const groups = html.querySelectorAll('filters-group');\n    for (const group of groups) {\n      const selector = `filters-group[data-param-name=\"${group.dataset.paramName}\"]`;\n      const oldGroup = this.querySelector(selector);\n      oldGroup.parentElement.replaceChild(group, oldGroup);\n    }\n\n    const newFiltersElement = html.querySelector('filters-element');\n    this.dataset.productCount = newFiltersElement.dataset.productCount;\n  }\n\n  private showBackdrop() {\n    const backdrop = this.shadowRoot.querySelector<HTMLElement>('#filters-backdrop');\n    backdrop.hidden = false;\n  }\n\n  private hideBackdrop() {\n    const backdrop = this.shadowRoot.querySelector<HTMLElement>('#filters-backdrop');\n    backdrop.hidden = true;\n  }\n\n  private render() {\n    const applyButton = this.shadowRoot.querySelector('.apply-button');\n    applyButton.textContent = `Show ${this.dataset.productCount} products`;\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'filters-element': FiltersElement;\n  }\n}\n\nif (!customElements.get('filters-element')) {\n  customElements.define('filters-element', FiltersElement);\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * A custom element that renders and manages a group of filter options. Filter options are rendered\n * based on <filter-group-option> elements passed to the default slot. Each <filter-group-option>\n * holds the initial data needed to render the respective option.\n */\nclass FiltersGroup extends HTMLElement implements CustomElement {\n  declare readonly dataset: {\n    /**\n     * The query parameter name this filter group maps to. It is emitted as the name in the filter\n     * changed event so listeners can build the corresponding query string.\n     */\n    paramName: string;\n  };\n\n  private onChangedBound = this.onChanged.bind(this);\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    this.render();\n\n    // listening on the bubbled change event from the child element to avoid collision with other\n    // filter groups\n    this.shadowRoot.addEventListener('change', this.onChangedBound);\n  }\n\n  public disconnectedCallback() {\n    this.shadowRoot.removeEventListener('change', this.onChangedBound);\n  }\n\n  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open' });\n      this.shadowRoot.innerHTML = `<style>${styles}</style>${html}`;\n    }\n  }\n\n  private render() {\n    const fragment = document.createDocumentFragment();\n\n    const options = this.querySelectorAll('filters-group-option');\n    for (const option of options) {\n      const input = document.createElement('input');\n      input.id = option.dataset.value;\n      input.name = option.dataset.value;\n      input.checked = option.dataset.active === 'true';\n      input.type = 'checkbox';\n\n      if (option.dataset.count === '0') {\n        input.disabled = true;\n      }\n\n      fragment.appendChild(input);\n\n      const label = document.createElement('label');\n      label.setAttribute('for', option.dataset.value);\n      label.textContent = `${option.dataset.value} (${option.dataset.count})`;\n\n      fragment.appendChild(label);\n    }\n\n    this.shadowRoot.appendChild(fragment);\n  }\n\n  private onChanged(event: Event) {\n    if (!(event.target instanceof HTMLInputElement)) {\n      return;\n    }\n\n    type Detail = FiltersGroupFilterChangedEvent['detail'];\n\n    const changedEvent = new CustomEvent<Detail>('filters-group-filter-changed', {\n      bubbles: true,\n      cancelable: false,\n      composed: true,\n      detail: {\n        name: this.dataset.paramName,\n        value: event.target.name,\n        active: event.target.checked\n      }\n    });\n    this.dispatchEvent(changedEvent);\n  }\n}\n\ntype FiltersGroupFilterChangedEvent =\n  CustomEvent<{ active: boolean; name: string; value: string; }>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'filters-group-filter-changed': FiltersGroupFilterChangedEvent;\n  }\n\n  interface HTMLElementTagNameMap {\n    'filters-group': FiltersGroup;\n  }\n}\n\nif (!customElements.get('filters-group')) {\n  customElements.define('filters-group', FiltersGroup);\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * This element represents the sort by options when viewing products in a collection.\n *\n * @see https://shopify.dev/docs/storefronts/themes/architecture/templates/collection#sort-products-in-a-collection\n */\nclass FiltersSortBy extends HTMLElement implements CustomElement {\n  declare readonly dataset: {\n    /**\n     * Represents whether the in stock sorting option is selected or not\n     */\n    inStock: 'false' | 'true';\n\n    /**\n     * Selected Sort By option\n     */\n    selectedOption: string;\n  };\n\n  private onOptionChangedBound = this.onOptionChanged.bind(this);\n  private onInStockChangedBound = this.onInStockChanged.bind(this);\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    const radios = this.shadowRoot.querySelectorAll('input[name=\"sort_by\"]');\n    for (const radio of radios) {\n      radio.addEventListener('change', this.onOptionChangedBound);\n    }\n\n    const inStock = this.shadowRoot.querySelector('#in-stock');\n    inStock.addEventListener('change', this.onInStockChangedBound);\n\n    this.render();\n  }\n\n  public disconnectedCallback() {\n    const radios = this.shadowRoot.querySelectorAll('input[name=\"sort_by\"]');\n    for (const radio of radios) {\n      radio.removeEventListener('change', this.onOptionChangedBound);\n    }\n\n    const inStock = this.shadowRoot.querySelector('#in-stock');\n    inStock.removeEventListener('change', this.onInStockChangedBound);\n  }\n\n  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open' });\n      this.shadowRoot.innerHTML = `<style>${styles}</style>${html}`;\n    }\n  }\n\n  private render() {\n    if (this.dataset.selectedOption) {\n      const selectedRadio = this.shadowRoot.querySelector<HTMLInputElement>(\n        `input[value=\"${this.dataset.selectedOption}\"]`);\n      if (selectedRadio) {\n        selectedRadio.checked = true;\n      }\n    }\n\n    if (this.dataset.inStock === 'true') {\n      const inStock = this.shadowRoot.querySelector<HTMLInputElement>('#in-stock');\n      inStock.checked = true;\n    }\n  }\n\n  private onOptionChanged(event: Event) {\n    if (!(event.target instanceof HTMLInputElement)) {\n      return;\n    }\n\n    type Detail = FiltersSortByChangedEvent['detail'];\n\n    const changedEvent = new CustomEvent<Detail>('filters-sort-by-changed', {\n      bubbles: true,\n      cancelable: false,\n      composed: true,\n      detail: {\n        name: event.target.name,\n        value: event.target.value\n      }\n    });\n\n    this.dispatchEvent(changedEvent);\n  }\n\n  private onInStockChanged(event: Event) {\n    if (event.target instanceof HTMLInputElement) {\n      type Detail = FiltersSortByInStockChangedEvent['detail'];\n      const changedEvent = new CustomEvent<Detail>('filters-sort-by-in-stock-changed', {\n        bubbles: true,\n        cancelable: false,\n        composed: true,\n        detail: {\n          value: event.target.checked\n        }\n      });\n\n      this.dispatchEvent(changedEvent);\n    }\n  }\n}\n\ntype FiltersSortByChangedEvent = CustomEvent<{\n  /**\n   * Element name\n   */\n  name: string;\n\n  /**\n   * Element value\n   */\n  value: string;\n}>;\n\ntype FiltersSortByInStockChangedEvent = CustomEvent<{\n  /**\n   * Value of the in-stock control, true if checked, false otherwise\n   */\n  value: boolean;\n}>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'filters-sort-by-changed': FiltersSortByChangedEvent;\n    'filters-sort-by-in-stock-changed': FiltersSortByInStockChangedEvent;\n  }\n\n  interface HTMLElementTagNameMap {\n    'filters-sort-by': FiltersSortBy;\n  }\n}\n\nif (!customElements.get('filters-sort-by')) {\n  customElements.define('filters-sort-by', FiltersSortBy);\n}\n","import './filters/index.js';\nimport './filters-group/index.js';\nimport './filters-sort-by/index.js';\nimport { ready } from '../../lib/dom-ready.js';\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'filters-drawer-open-requested': Event;\n  }\n}\n\nfunction onReady() {\n  const closeButton = document.querySelector<HTMLElement>('.filters-drawer .close');\n  closeButton.addEventListener('click', onCloseButtonClicked);\n\n  // The open request originates from a button outside this drawer's subtree (it lives in another\n  // section), so a listener bound to the drawer element would never receive it. The request bubbles\n  // up to the document root instead, which is where this listens for it.\n  document.addEventListener('filters-drawer-open-requested', onFiltersDrawerOpenRequested);\n}\n\nfunction onCloseButtonClicked(_event: MouseEvent) {\n  closeDrawer();\n}\n\nfunction onFiltersDrawerOpenRequested(\n  _event: GlobalEventHandlersEventMap['filters-drawer-open-requested']) {\n  openDrawer();\n}\n\n/**\n * Closes the filters drawer when the user performs a light-dismiss gesture (a click on the backdrop\n * or an escape key press) while it is the open modal. The modal manager broadcasts this request to\n * every open modal; this listener is only attached while the filters drawer is open.\n */\nfunction onModalDismissRequested(_event: WindowEventMap['modal-dismiss-requested']) {\n  closeDrawer();\n}\n\n/**\n * Reflects the drawer's open state onto every button that controls the drawer, so screen readers\n * announce the correct expanded state.\n *\n * Heads up: this reaches outside this section. The controlling buttons can be rendered by other\n * sections (for example the open button on the collection page), and this intentionally writes\n * their expanded state. Any section that renders a button controlling this drawer is relying on\n * this to keep that button in sync.\n */\nfunction setTogglesExpanded(expanded: boolean) {\n  const toggles = document.querySelectorAll('[aria-controls=\"filters-drawer\"]');\n  for (const toggle of toggles) {\n    toggle.setAttribute('aria-expanded', expanded ? 'true' : 'false');\n  }\n}\n\n/**\n * Opens the filters drawer.\n */\nfunction openDrawer() {\n  const drawer = document.querySelector<HTMLElement>('.filters-drawer');\n  drawer.classList.add('open');\n  drawer.inert = false;\n\n  const openRequestedEvent = new Event('modal-open-requested', { bubbles: true, composed: true });\n  drawer.dispatchEvent(openRequestedEvent);\n\n  addEventListener('modal-dismiss-requested', onModalDismissRequested);\n\n  const closeButton = drawer.querySelector<HTMLElement>('.close');\n  closeButton.focus({ focusVisible: true });\n\n  setTogglesExpanded(true);\n}\n\n/**\n * Closes the filters drawer.\n */\nfunction closeDrawer() {\n  removeEventListener('modal-dismiss-requested', onModalDismissRequested);\n\n  const drawer = document.querySelector<HTMLElement>('.filters-drawer');\n  drawer.classList.remove('open');\n  drawer.inert = true;\n\n  const closeRequestedEvent = new Event('modal-close-requested', { bubbles: true, composed: true });\n  drawer.dispatchEvent(closeRequestedEvent);\n\n  setTogglesExpanded(false);\n}\n\nready(onReady);\n","/**\n * Releases the handle returned from the ready() function.\n *\n * This is idempotent. Additional calls are pointless but harmless.\n */\nexport type ReleaseHandle = () => void;\n\n/**\n * A subset of events that pertain to document ready state.\n */\ntype LoadingEventMap = Pick<DocumentEventMap, 'DOMContentLoaded' | 'load'>;\n\n/**\n * Calls the provided callback when the document is ready. This works both when the document is\n * already ready and when it is not, so callers do not have to care. This exists because it ensures\n * that each caller consistently expresses its dependency on document readiness, and to save a bit\n * of boilerplate.\n *\n * A document is considered ready when it has been parsed. This corresponds to the ready state of\n * 'interactive'. When the document has been fully loaded, the ready state is 'complete'. The\n * callback runs either way, so callers do not have to check which one they are in.\n *\n * Ready means the html is parsed and the DOM is there to query. It does not mean the page's other\n * scripts have run. Deferred scripts and module scripts run after the ready state turns\n * 'interactive' and before DOMContentLoaded fires, so a callback here can run before them. When a\n * caller needs some other script to have run already, this is the wrong tool, and it should wait on\n * whatever that script produces instead.\n *\n * That remedy has a catch of its own when the calling bundle is loaded with async. An async bundle\n * can execute after DOMContentLoaded has already fired, so a plain listener for that event never\n * runs and the work silently never happens. Nothing here can detect that, because by the time this\n * file executes the event is long gone. Catching it needs something registered at parse time,\n * before any bundle loads.\n *\n * There is a second trap right next to that one, and it applies only when waiting for\n * DOMContentLoaded. The ready state stays 'interactive' after DOMContentLoaded has fired, all the\n * way until 'complete', so the ready state cannot tell you whether DOMContentLoaded is still coming\n * or has already gone. Waiting for load is not exposed to this, because the ready state turns\n * 'complete' and load fires in the same task, so seeing 'complete' means load has fired too.\n *\n * When the document is already ready, the callback is invoked via setTimeout. This way, if the\n * callback throws, the error surfaces to the global error handler in a way that is similar to what\n * happens when an event listener throws.\n *\n * We might one day want to move this to an NPM library.\n *\n * Beware of a quirk here. setTimeout yields, and the busier the frontend, the longer it takes to\n * fire. Page load is exactly when the frontend is busiest. The timeout is a floor and not a\n * guarantee, so there is no real bound on how long that yield lasts. What this means is that using\n * this library can introduce more delay than you might expect. Compare code that runs on async load\n * and checks ready state and invokes immediately to code that runs after setTimeout. The former\n * runs in the same tick, the latter runs in \"some later tick\". That later tick can be surprisingly\n * much later. That can cause initial paint issues.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event\n * @see https://html.spec.whatwg.org/multipage/parsing.html#the-end\n */\nexport function ready(callback: (event?: Event) => void,\n  type: keyof LoadingEventMap = 'DOMContentLoaded') {\n  if (document.readyState === 'complete' || (type === 'DOMContentLoaded' &&\n    document.readyState === 'interactive')) {\n    const timer = setTimeout(callback, 0);\n    const handle: ReleaseHandle = () => clearTimeout(timer);\n    return handle;\n  } else {\n    addEventListener(type, callback);\n    const handle: ReleaseHandle = () => removeEventListener(type, callback);\n    return handle;\n  }\n}\n"],"names":["FiltersElement","HTMLElement","searchParams","URLSearchParams","location","search","onFiltersSortByChangedBound","this","onFiltersSortByChanged","bind","onFiltersSortByInStockChangedBound","onFiltersSortByInStockChanged","onApplyClickedBound","onApplyClicked","onClearClickedBound","onClearClicked","onFiltersGroupFilterChangedBound","onFiltersGroupFilterChanged","connectedCallback","initializeShadowRoot","shadowRoot","querySelector","addEventListener","render","disconnectedCallback","removeEventListener","attachShadow","mode","innerHTML","event","name","value","detail","set","delete","showBackdrop","update","then","catch","console","warn","finally","hideBackdrop","_event","url","URL","href","toString","key","keys","test","active","append","response","fetch","htmlString","json","html","document","createElement","groups","querySelectorAll","group","selector","dataset","paramName","oldGroup","parentElement","replaceChild","newFiltersElement","productCount","hidden","textContent","customElements","get","define","FiltersGroup","onChangedBound","onChanged","fragment","createDocumentFragment","options","option","input","id","checked","type","count","disabled","appendChild","label","setAttribute","target","HTMLInputElement","changedEvent","CustomEvent","bubbles","cancelable","composed","dispatchEvent","FiltersSortBy","onOptionChangedBound","onOptionChanged","onInStockChangedBound","onInStockChanged","radios","radio","selectedOption","selectedRadio","inStock","onCloseButtonClicked","closeDrawer","onFiltersDrawerOpenRequested","drawer","classList","add","inert","openRequestedEvent","Event","onModalDismissRequested","focus","focusVisible","setTogglesExpanded","openDrawer","expanded","toggles","toggle","remove","closeRequestedEvent","callback","readyState","timer","setTimeout","clearTimeout","ready"],"sourceRoot":""}