{"version":3,"file":"grid-layout-toggle-element-section.min.js","mappings":"wZAUA,MAAMA,yBAAyBC,YAarBC,MAAuB,KAGvBC,cAAgBC,KAAKC,SAASC,KAAKF,MACnCG,aAAeH,KAAKI,QAAQF,KAAKF,MAElC,iBAAAK,GACLL,KAAKM,uBAELN,KAAKF,MA2DT,WACE,IAEE,OADcS,aAAaC,QAAQ,uBACnB,GAClB,CAAE,MAAOC,GAEP,MAAO,GACT,CACF,CAnEiBC,GAEbV,KAAKW,WAAWC,iBAAiB,SAAUZ,KAAKD,eAEhDC,KAAKa,aCsBF,SAAeC,EACpBC,GACA,QAD2B,IAA3BA,IAAAA,EAA8B,oBACF,aAAxBC,SAASC,YAAuC,qBAATF,GACjB,gBAAxBC,SAASC,WAA+B,CACxC,MAAMC,EAAQC,WAAWL,EAAU,GAEnC,MAD8B,IAAMM,aAAaF,EAEnD,CAGE,OAFAN,iBAAiBG,EAAMD,GACO,IAAMO,oBAAoBN,EAAMD,EAGlE,CDlCwBQ,CAAMtB,KAAKG,aACjC,CAEO,oBAAAoB,GACLvB,KAAKW,WAAWU,oBAAoB,SAAUrB,KAAKD,eAE/CC,KAAKa,cACPb,KAAKa,cAET,CAEQ,oBAAAP,GACDN,KAAKW,aACRX,KAAKwB,aAAa,CAAEC,KAAM,SAC1BzB,KAAKW,WAAWe,UAAY,69EAEhC,CAEQ,MAAAC,GACN,MAAMC,EAAQ5B,KAAKW,WAAWkB,cAAc,gBAAgB7B,KAAKF,WAC7D8B,aAAiBE,mBACnBF,EAAMG,SAAU,GAKlB,MAAMC,EAAWhC,KAAKW,WAAWkB,cAAc,YAC3CG,IACFA,EAASC,QAAQC,cAAgBlC,KAAKF,OAGxC,MAAMqC,EAAWnC,KAAKiC,QAAQG,OAC9B,GAAID,EAAU,CACZ,MAAME,EAASrC,KAAKsC,QAAQH,GACxBE,GACFA,EAAOE,aAAa,sBAAuBvC,KAAKF,MAEpD,CACF,CAEQ,OAAAM,GACFJ,KAAKwC,aACPxC,KAAK2B,QAET,CAEQ,QAAA1B,CAASwC,GACXA,EAAML,kBAAkBN,mBAC1B9B,KAAKF,MAAQ2C,EAAML,OAAOtC,MAC1BE,KAAK2B,SAgBX,SAAyB7B,GACvB,IACES,aAAamC,QAAQ,qBAAsB5C,EAC7C,CAAE,MAAOW,GACP,CAEJ,CArBMkC,CAAgBF,EAAML,OAAOtC,OAEjC,EA2BG8C,eAAeC,IAAI,uBACtBD,eAAeE,OAAO,qBAAsBlD","sources":["webpack://lange/./sections/grid-layout-toggle-element/grid-layout-toggle/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 * A toggle that lets mobile visitors switch the product grid between a 1-column and 2-column\n * layout. The choice is persisted in localStorage so it carries across page loads. The element sets\n * `data-mobile-columns` on the nearest ancestor matching the `data-target` selector.\n */\nclass GridLayoutToggle extends HTMLElement implements CustomElement {\n  declare readonly dataset: {\n    /**\n     * CSS selector for the ancestor element that receives the `data-mobile-columns` attribute.\n     * There is no default. Without it the element sets nothing, and its stylesheet hides it.\n     */\n    target: string;\n  };\n\n  /**\n   * The current value of the toggle. Either '1' or '2'. This is read from localStorage on\n   * initialization and updated when the user changes the toggle.\n   */\n  private value: string | null = null;\n  private declare releaseReady: ReleaseHandle;\n\n  private onChangeBound = this.onChange.bind(this);\n  private onReadyBound = this.onReady.bind(this);\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    this.value = readPreference();\n\n    this.shadowRoot.addEventListener('change', this.onChangeBound);\n\n    this.releaseReady = ready(this.onReadyBound);\n  }\n\n  public disconnectedCallback() {\n    this.shadowRoot.removeEventListener('change', this.onChangeBound);\n\n    if (this.releaseReady) {\n      this.releaseReady();\n    }\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 input = this.shadowRoot.querySelector(`input[value=\"${this.value}\"]`);\n    if (input instanceof HTMLInputElement) {\n      input.checked = true;\n    }\n\n    // Set the `data-selected-value` attribute on the fieldset so that the CSS can style the\n    // selected label with fancy animated frame.\n    const fieldset = this.shadowRoot.querySelector('fieldset');\n    if (fieldset) {\n      fieldset.dataset.selectedValue = this.value;\n    }\n\n    const selector = this.dataset.target;\n    if (selector) {\n      const parent = this.closest(selector);\n      if (parent) {\n        parent.setAttribute('data-mobile-columns', this.value);\n      }\n    }\n  }\n\n  private onReady() {\n    if (this.isConnected) {\n      this.render();\n    }\n  }\n\n  private onChange(event: Event) {\n    if (event.target instanceof HTMLInputElement) {\n      this.value = event.target.value;\n      this.render();\n      writePreference(event.target.value);\n    }\n  }\n}\n\nfunction readPreference() {\n  try {\n    const value = localStorage.getItem('plp_mobile_columns');\n    return value || '2';\n  } catch (error) {\n    // In-app browsers (TikTok, Instagram, Facebook) can disable DOM storage entirely.\n    return '2';\n  }\n}\n\nfunction writePreference(value: string) {\n  try {\n    localStorage.setItem('plp_mobile_columns', value);\n  } catch (error) {\n    // In-app browsers (TikTok, Instagram, Facebook) can disable DOM storage entirely.\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'grid-layout-toggle': GridLayoutToggle;\n  }\n}\n\nif (!customElements.get('grid-layout-toggle')) {\n  customElements.define('grid-layout-toggle', GridLayoutToggle);\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":["GridLayoutToggle","HTMLElement","value","onChangeBound","this","onChange","bind","onReadyBound","onReady","connectedCallback","initializeShadowRoot","localStorage","getItem","error","readPreference","shadowRoot","addEventListener","releaseReady","callback","type","document","readyState","timer","setTimeout","clearTimeout","removeEventListener","ready","disconnectedCallback","attachShadow","mode","innerHTML","render","input","querySelector","HTMLInputElement","checked","fieldset","dataset","selectedValue","selector","target","parent","closest","setAttribute","isConnected","event","setItem","writePreference","customElements","get","define"],"sourceRoot":""}