{"version":3,"file":"country-selector-modal-section.min.js","mappings":"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","sources":["webpack://lange/./sections/country-selector-modal/country-selector-form/index.ts","webpack://lange/./lib/dom-ready.ts"],"sourcesContent":["import type { CustomElement } from '@integrabeauty/custom-elements';\nimport { ready, type ReleaseHandle } from '../../../lib/dom-ready.js';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * Renders the country selector modal. The modal renders the required localization form with a\n * single usable drop down listing all available markets as indicated in the Shopify global objects.\n * Once the user selects a country, the disabled currency input will populate the currency ISO code\n * for that country. Once a user saves the form will be submitted and on next page load the country\n * and currency locale will reflect their choice.\n */\nclass CountrySelectorForm extends HTMLElement implements CustomElement {\n  public static get observedAttributes() {\n    return [\n      'data-country-code',\n      'data-country-name',\n      'data-currency-code',\n      'data-language-code'\n    ];\n  }\n\n  declare readonly dataset: {\n    /**\n     * Country ISO Code. Required.\n     */\n    countryCode: string;\n\n    /**\n     * Country Name. Required.\n     */\n    countryName: string;\n\n    /**\n     * Currency ISO Code. Required.\n     */\n    currencyCode: string;\n\n    /**\n     * Language ISO Code. Required.\n     */\n    languageCode: string;\n  };\n\n  private onReadyBound = this.onReady.bind(this);\n  private onItemClickBound = this.onItemClick.bind(this);\n  private onContainerKeyUpBound = this.onContainerKeyUp.bind(this);\n  private onButtonClickBound = this.onButtonClick.bind(this);\n  private onButtonFocusOutBound = this.onButtonFocusOut.bind(this);\n  private declare releaseReady: ReleaseHandle;\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    this.releaseReady = ready(this.onReadyBound);\n\n    this.addEventListener('keyup', this.onContainerKeyUpBound);\n  }\n\n  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open', delegatesFocus: true });\n      this.shadowRoot.innerHTML = `<style>${styles}</style>${html}`;\n    }\n  }\n\n  /**\n   * This queries the light dom, which may not be ready by the time of connected callback\n   */\n  private onReady() {\n    const button = this.shadowRoot.querySelector<HTMLButtonElement>('button.disclosure-button');\n    button.addEventListener('click', this.onButtonClickBound);\n    button.addEventListener('focusout', this.onButtonFocusOutBound);\n\n    const anchors = this.querySelectorAll('a');\n    for (const anchor of anchors) {\n      // remove in case this is an additional run\n      anchor.removeEventListener('click', this.onItemClickBound);\n      anchor.addEventListener('click', this.onItemClickBound);\n    }\n\n    this.render();\n  }\n\n  public attributeChangedCallback(_name: string, oldValue: string, newValue: string) {\n    this.initializeShadowRoot();\n\n    if (this.isConnected && oldValue !== newValue) {\n      this.render();\n    }\n  }\n\n  public disconnectedCallback() {\n    if (this.releaseReady) {\n      this.releaseReady();\n    }\n\n    const button = this.shadowRoot.querySelector<HTMLButtonElement>('button.disclosure-button');\n    if (button) {\n      button.removeEventListener('click', this.onButtonClickBound);\n      button.removeEventListener('focusout', this.onButtonFocusOutBound);\n    }\n\n    this.removeEventListener('keyup', this.onContainerKeyUpBound);\n\n    const anchors = this.querySelectorAll('a');\n    for (const anchor of anchors) {\n      anchor.removeEventListener('click', this.onItemClickBound);\n    }\n  }\n\n  private onButtonClick(_event: MouseEvent) {\n    const button = this.shadowRoot.querySelector<HTMLButtonElement>('button.disclosure-button');\n    button.focus();\n\n    // The button's aria-expanded holds the open state, so we read it before changing anything. That\n    // tells us where the panel stood going into this click, and both halves of the toggle follow\n    // from it. Expanded means the click is closing, so the panel gets hidden.\n    const isExpanded = button.getAttribute('aria-expanded') === 'true';\n    button.setAttribute('aria-expanded', isExpanded ? 'false' : 'true');\n\n    const panel = this.querySelector('ul');\n    panel.hidden = isExpanded;\n  }\n\n  private onButtonFocusOut(event: FocusEvent) {\n    if (event.relatedTarget === null || event.relatedTarget instanceof HTMLButtonElement) {\n      this.hidePanel();\n    }\n  }\n\n  private hidePanel() {\n    const button = this.shadowRoot.querySelector<HTMLButtonElement>('button.disclosure-button');\n    button?.setAttribute('aria-expanded', 'false');\n\n    const panel = this.querySelector('ul');\n    panel.hidden = true;\n  }\n\n  private onContainerKeyUp(event: KeyboardEvent) {\n    if (event.code.toUpperCase() === 'ESCAPE') {\n      this.hidePanel();\n\n      const button = this.shadowRoot.querySelector<HTMLButtonElement>('button.disclosure-button');\n      button.focus();\n    }\n  }\n\n  private onItemClick(event: MouseEvent) {\n    event.preventDefault();\n\n    this.hidePanel();\n\n    const target = event.currentTarget;\n    if (!(target instanceof HTMLElement)) {\n      return;\n    }\n\n    const span = this.shadowRoot.querySelector<HTMLButtonElement>('button.disclosure-button span');\n    span.textContent = target.dataset.countryName;\n\n    const input = this.shadowRoot.querySelector<HTMLInputElement>('input[name=\"country_code\"]');\n    input.value = target.dataset.value;\n\n    const currencyInput = this.shadowRoot.querySelector<HTMLInputElement>('input.currency-code');\n    currencyInput.value = target.dataset.currencyCode;\n\n    this.updateSelectedAttribute(target.parentElement.id);\n\n    const button = this.shadowRoot.querySelector<HTMLButtonElement>('button.disclosure-button');\n    button.focus({ focusVisible: true });\n  }\n\n  private updateSelectedAttribute(selectedId: string) {\n    const options = this.querySelectorAll<HTMLElement>('[slot=\"language-list\"] li');\n    for (const option of options) {\n      if (option.id === selectedId) {\n        option.setAttribute('aria-selected', 'true');\n      } else {\n        option.setAttribute('aria-selected', 'false');\n      }\n    }\n  }\n\n  private render() {\n    const buttonSpan = this.shadowRoot.querySelector<HTMLButtonElement>(\n      'button.disclosure-button span');\n    buttonSpan.textContent = this.dataset.countryName;\n\n    const countryInput = this.shadowRoot.querySelector<HTMLInputElement>(\n      'input[name=\"country_code\"]');\n    countryInput.value = this.dataset.countryCode;\n\n    const currencyInput = this.shadowRoot.querySelector<HTMLInputElement>('input.currency-code');\n    currencyInput.value = this.dataset.currencyCode;\n\n    const languageInput = this.shadowRoot.querySelector<HTMLInputElement>(\n      'input[name=\"language_code\"]');\n    languageInput.value = this.dataset.languageCode;\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'country-selector-form': CountrySelectorForm;\n  }\n}\n\nif (!customElements.get('country-selector-form')) {\n  customElements.define('country-selector-form', CountrySelectorForm);\n}\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":["CountrySelectorForm","HTMLElement","observedAttributes","onReadyBound","this","onReady","bind","onItemClickBound","onItemClick","onContainerKeyUpBound","onContainerKeyUp","onButtonClickBound","onButtonClick","onButtonFocusOutBound","onButtonFocusOut","connectedCallback","initializeShadowRoot","releaseReady","callback","type","document","readyState","timer","setTimeout","clearTimeout","addEventListener","removeEventListener","ready","shadowRoot","attachShadow","mode","delegatesFocus","innerHTML","button","querySelector","anchors","querySelectorAll","anchor","render","attributeChangedCallback","_name","oldValue","newValue","isConnected","disconnectedCallback","_event","focus","isExpanded","getAttribute","setAttribute","hidden","event","relatedTarget","HTMLButtonElement","hidePanel","code","toUpperCase","preventDefault","target","currentTarget","textContent","dataset","countryName","value","currencyCode","updateSelectedAttribute","parentElement","id","focusVisible","selectedId","options","option","countryCode","languageCode","customElements","get","define"],"sourceRoot":""}