{"version":3,"file":"before-after-section.min.js","mappings":"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","sources":["webpack://lange/./lib/dom-ready.ts","webpack://lange/./sections/before-after/before-after/index.ts","webpack://lange/./sections/before-after/init-models.ts","webpack://lange/./sections/before-after/init-tabs.ts","webpack://lange/./lib/css-dom.ts","webpack://lange/./sections/before-after/init-styles.ts"],"sourcesContent":["/**\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","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 visual comparison of 2 selected images. Images are divided with separator, there is\n * option to enable separator dragging and option for adding labels for images\n */\nclass BeforeAfter extends HTMLElement implements CustomElement {\n  declare readonly dataset: {\n    /**\n     * Defines if separator is draggable or static\n     */\n    draggableSeparator: string;\n\n    /**\n     * Represents index of before/after image groupings, used when more than one model is configured\n     */\n    index: string;\n\n    /**\n     * Indicates whether only one image is provided\n     */\n    singleImage: string;\n  };\n\n  /**\n   * Track if we're currently dragging the slider\n   */\n  private isDraggingSlider = false;\n\n  /**\n   * Store initial touch position to detect horizontal vs vertical movement\n   */\n  private initialTouchX = 0;\n\n  private initialTouchY = 0;\n\n  private onRangeInputBound = this.onRangeInput.bind(this);\n  private onReadyBound = this.onReady.bind(this);\n  private onTouchStartedBound = this.onTouchStarted.bind(this);\n  private onTouchMovedBound = this.onTouchMoved.bind(this);\n\n  private declare releaseReady: ReleaseHandle;\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    this.releaseReady = ready(this.onReadyBound);\n  }\n\n  public disconnectedCallback() {\n    const sliderRange = this.shadowRoot.querySelector('.slides-container [type=\"range\"]');\n    sliderRange?.removeEventListener('input', this.onRangeInputBound);\n\n    const slidesContainer = this.shadowRoot.querySelector<HTMLElement>('.slides-container');\n    slidesContainer?.removeEventListener('touchstart', this.onTouchStartedBound);\n    slidesContainer?.removeEventListener('touchmove', this.onTouchMovedBound);\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 updateSliderPosition(value: number) {\n    // Ensure value is between 0 and 100\n    const boundedValue = Math.max(0, Math.min(100, value));\n\n    // Update range input value\n    const sliderRange = this.shadowRoot.querySelector<HTMLInputElement>(\n      '.slides-container [type=\"range\"]');\n    if (sliderRange) {\n      sliderRange.value = boundedValue.toString();\n      sliderRange.setAttribute('aria-valuenow', boundedValue.toString());\n    }\n\n    // Update visual elements\n    const separator = this.shadowRoot.querySelector<HTMLElement>('.separator');\n    const dragHandler = this.shadowRoot.querySelector<HTMLElement>('.drag-handler');\n\n    if (separator && dragHandler) {\n      const dragHandlerWidth = parseInt(getComputedStyle(dragHandler).width, 10);\n      separator.style.left = `calc(${boundedValue}%)`;\n      dragHandler.style.left = `calc(${boundedValue}% - ${dragHandlerWidth / 2}px)`;\n    }\n\n    // Update overlay width\n    const overlay = this.querySelector<HTMLElement>('.overlay');\n    if (overlay) {\n      overlay.style.width = `${boundedValue}%`;\n    }\n  }\n\n  private onRangeInput(event: Event) {\n    if (event.target instanceof HTMLInputElement) {\n      const value = parseFloat(event.target.value);\n      this.updateSliderPosition(value);\n    }\n  }\n\n  private onTouchStarted(event: TouchEvent) {\n    // Store initial touch position\n    if (event.touches.length > 0) {\n      this.initialTouchX = event.touches[0].clientX;\n      this.initialTouchY = event.touches[0].clientY;\n\n      // Start in non-dragging state\n      this.isDraggingSlider = false;\n    }\n  }\n\n  private onTouchMoved(event: TouchEvent) {\n    if (event.touches.length === 0) {\n      return;\n    }\n\n    const touch = event.touches[0];\n    const deltaX = Math.abs(touch.clientX - this.initialTouchX);\n    const deltaY = Math.abs(touch.clientY - this.initialTouchY);\n\n    // If this is our first significant movement, determine if it's horizontal (slider) or vertical\n    // (scroll)\n    if (!this.isDraggingSlider && (deltaX > 5 || deltaY > 5)) {\n      // If horizontal movement is dominant, we're using the slider\n      this.isDraggingSlider = deltaX > deltaY;\n    }\n\n    // Only prevent default and adjust slider if we're in slider dragging mode\n    if (this.isDraggingSlider) {\n      event.preventDefault();\n      this.handleTouchPosition(event);\n    }\n    // Otherwise let the default scroll behavior happen naturally\n  }\n\n  private handleTouchPosition(event: TouchEvent) {\n    const slidesContainer = this.shadowRoot.querySelector('.slides-container');\n    if (!slidesContainer || event.touches.length === 0) {\n      return;\n    }\n\n    const touch = event.touches[0];\n    const rect = slidesContainer.getBoundingClientRect();\n\n    // Calculate percentage position relative to the container\n    const containerWidth = rect.width;\n    const touchXRelative = touch.clientX - rect.left;\n    const percentage = (touchXRelative / containerWidth) * 100;\n\n    // Update slider position\n    this.updateSliderPosition(percentage);\n  }\n\n  private onReady() {\n    if (!this.isConnected) {\n      return;\n    }\n\n    if (this.dataset.draggableSeparator === 'true') {\n      const sliderRange = this.shadowRoot.querySelector('.slides-container [type=\"range\"]');\n      const slidesContainer = this.shadowRoot.querySelector<HTMLElement>('.slides-container');\n\n      // Add standard input event for range slider\n      sliderRange.addEventListener('input', this.onRangeInputBound);\n\n      // Add touch events for mobile support\n      slidesContainer.addEventListener('touchstart', this.onTouchStartedBound, { passive: true });\n      slidesContainer.addEventListener('touchmove', this.onTouchMovedBound, { passive: false });\n    }\n\n    this.render();\n  }\n\n  private render() {\n    if (this.dataset.singleImage === 'true') {\n      const separatorParts = this.shadowRoot.querySelectorAll<HTMLElement>('.separator-part');\n      for (const part of separatorParts) {\n        part.hidden = true;\n      }\n    } else {\n      if (this.dataset.draggableSeparator === 'true') {\n        const slidesContainer = this.shadowRoot.querySelector('.slides-container');\n        slidesContainer.classList.add('draggable');\n      }\n\n      // imageBefore & imageAfter are inside the slot there's no guarantee that they are available\n      // during render\n      const imageBefore = this.querySelector<HTMLElement>('span:first-child');\n      imageBefore?.classList.add('active');\n      imageBefore?.classList.add('overlay');\n\n      if (imageBefore?.dataset.label) {\n        imageBefore.classList.add('slide-label');\n      }\n\n      const imageAfter = this.querySelector<HTMLElement>('span:last-child');\n      if (imageAfter?.dataset.label) {\n        imageAfter.classList.add('slide-label');\n      }\n    }\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'before-after': BeforeAfter;\n  }\n}\n\nif (!customElements.get('before-after')) {\n  customElements.define('before-after', BeforeAfter);\n}\n","import { ready } from '../../lib/dom-ready.js';\n\n/**\n * Wires up the model picker within each before-after tab: binds click handlers to the thumbnail\n * buttons, hides all model panels, then activates the first model. The thumbnail strip stays hidden\n * unless a wrapper has more than one model.\n */\nfunction onReady() {\n  const selector = '.before-after-section .model-and-navigation-wrapper';\n  const wrappers = document.querySelectorAll<HTMLElement>(selector);\n  for (const wrapper of wrappers) {\n    const thumbsContainer = wrapper.querySelector<HTMLElement>('.model-thumbs');\n    const thumbs = thumbsContainer?.querySelectorAll('button');\n\n    // Because we use optional chaining when assigning a value to thumbs, we need the fallback to\n    // empty array here\n\n    for (const thumb of thumbs || []) {\n      thumb.addEventListener('click', onThumbClicked);\n    }\n\n    // The markup ships every panel hidden so none flash before this runs. Revealing the active one\n    // is left entirely to activateModel below.\n\n    if (thumbs?.length > 0 && thumbs[0]) {\n      activateModel(thumbs[0]);\n    }\n\n    if (thumbs?.length > 1) {\n      thumbsContainer.hidden = false;\n    }\n  }\n}\n\nfunction onThumbClicked(event: MouseEvent) {\n  if (event.currentTarget instanceof Element) {\n    activateModel(event.currentTarget);\n  }\n}\n\n/**\n * Selects the given thumbnail and reveals its model: marks the thumb aria-selected (clearing the\n * others in the same strip) and shows the matching model panel via aria-controls, hiding the rest\n * within the same wrapper.\n */\nfunction activateModel(thumb: Element) {\n  const thumbsAncestor = thumb.closest('.model-thumbs');\n  if (!thumbsAncestor) {\n    return;\n  }\n\n  const allThumbs = thumbsAncestor.querySelectorAll('button');\n  for (const thumbCandidate of allThumbs) {\n    if (thumbCandidate === thumb) {\n      thumbCandidate.setAttribute('aria-selected', 'true');\n    } else {\n      thumbCandidate.setAttribute('aria-selected', 'false');\n    }\n  }\n\n  const wrapper = thumbsAncestor.closest('.model-and-navigation-wrapper');\n  if (!wrapper) {\n    return;\n  }\n\n  const modelId = thumb.getAttribute('aria-controls');\n  const models = wrapper.querySelectorAll<HTMLElement>('[role=\"tabpanel\"]');\n\n  // Visibility is driven purely by the native hidden attribute: a global stylesheet rule hides any\n  // element carrying it, so we flag every panel but the active one. These panels are custom\n  // elements that style their own host from inside their shadow tree. The host still sits in the\n  // light dom, so the forced global rule reaches it and wins, and no local rule is needed.\n\n  for (const model of models) {\n    model.hidden = model.id !== modelId;\n  }\n}\n\nready(onReady);\n","import { ready } from '../../lib/dom-ready.js';\n\n/**\n * Wires up the before-after section's tab strip: binds click handlers to each tab, hides all tab\n * panels, then activates the first tab.\n *\n * The tab bar stays hidden unless there is more than one tab.\n */\nfunction onReady() {\n  // Placed via a JSON template, so the section wrapper id varies per page and cannot be hardcoded.\n  // Match an element in the section by a css class instead. The section schema caps it at one per\n  // page, so a single match is safe.\n  const section = document.querySelector('.before-after-section');\n  if (!section) {\n    return;\n  }\n\n  const tabsAncestor = section.querySelector<HTMLElement>('.tabs');\n  if (!tabsAncestor) {\n    return;\n  }\n\n  const tabs = tabsAncestor.querySelectorAll<HTMLElement>('[role=\"tab\"]');\n\n  for (const tab of tabs) {\n    tab.addEventListener('click', onTabClicked);\n  }\n\n  // The markup ships every panel hidden so none flash before this runs. Revealing the active one is\n  // left entirely to activateTab below.\n\n  if (tabs.length > 0 && tabs[0]) {\n    activateTab(tabs[0]);\n  }\n\n  if (tabs.length > 1) {\n    tabsAncestor.hidden = false;\n  }\n}\n\nfunction onTabClicked(event: MouseEvent) {\n  if (event.currentTarget instanceof Element) {\n    activateTab(event.currentTarget);\n  }\n}\n\nfunction activateTab(tab: Element) {\n  // Nothing to do if this tab is already active; bail before touching the DOM. Safe on init because\n  // every tab ships with aria-selected=\"false\", so the first activation still runs.\n  if (isActiveTab(tab)) {\n    return;\n  }\n\n  // Climb to the containing section via its own hook. This element is located within a dynamically\n  // rendered Shopify section, so we cannot determine the section by id. Walk up the DOM instead.\n  const section = tab.closest('.before-after-section');\n  if (!section) {\n    return;\n  }\n\n  const tabs = section.querySelectorAll<HTMLElement>('.tabs [role=\"tab\"]');\n  for (const candidateTab of tabs) {\n    if (candidateTab === tab) {\n      candidateTab.setAttribute('aria-selected', 'true');\n    } else {\n      candidateTab.setAttribute('aria-selected', 'false');\n    }\n  }\n\n  const tabId = tab.getAttribute('aria-controls');\n\n  // This selector has to be careful about the descendants it matches. The nested model elements\n  // within each tab also use role=\"tabpanel\", but do not use the class tabpanel, so we match by\n  // class instead of role to avoid clobbering them.\n\n  const panels = section.querySelectorAll<HTMLElement>('.content .tabpanel');\n\n  // Visibility is driven purely by the native hidden attribute, the same as the model panels. These\n  // panels set their own display, but the global hide rule is forced, so it wins on its own.\n\n  for (const panel of panels) {\n    panel.hidden = panel.id !== tabId;\n  }\n}\n\nfunction isActiveTab(tab: Element) {\n  return tab.getAttribute('aria-selected') === 'true';\n}\n\nready(onReady);\n","/**\n * Generates a style element containing the given content and appends it to the dom.\n *\n * This is an alternative to the webpack style-loader that circumvents the overly complex webpack\n * configuration and plugin behavior. This intentionally avoids using CSSStyleSheet or any other\n * kind of feature that is not broadly supported or accurately polyfilled.\n *\n * This is safe to call before the dom has loaded.\n *\n * This is safe to call on platforms where document.head is not defined.\n *\n * This annotates appended style attributes with a data-lazy attribute so that it is trivial to\n * identify which elements were appended to the dom in this manner. This extra annotation is not\n * expected to have a material performance impact.\n */\nexport function appendCSSText(content: string) {\n  const style = document.createElement('style');\n  style.dataset.lazy = 'true';\n  const node = document.createTextNode(content);\n  style.appendChild(node);\n  const parent = document.head || document.body || document.documentElement;\n  parent.appendChild(style);\n}\n","import { appendCSSText } from '../../lib/css-dom.js';\nimport styles from './index.scss';\n\nappendCSSText(styles);\n"],"names":["ready","callback","type","document","readyState","timer","setTimeout","clearTimeout","addEventListener","removeEventListener","BeforeAfter","HTMLElement","isDraggingSlider","initialTouchX","initialTouchY","onRangeInputBound","this","onRangeInput","bind","onReadyBound","onReady","onTouchStartedBound","onTouchStarted","onTouchMovedBound","onTouchMoved","connectedCallback","initializeShadowRoot","releaseReady","disconnectedCallback","sliderRange","shadowRoot","querySelector","slidesContainer","attachShadow","mode","innerHTML","updateSliderPosition","value","boundedValue","Math","max","min","toString","setAttribute","separator","dragHandler","dragHandlerWidth","parseInt","getComputedStyle","width","style","left","overlay","event","target","HTMLInputElement","parseFloat","touches","length","clientX","clientY","touch","deltaX","abs","deltaY","preventDefault","handleTouchPosition","rect","getBoundingClientRect","containerWidth","percentage","isConnected","dataset","draggableSeparator","passive","render","singleImage","separatorParts","querySelectorAll","part","hidden","classList","add","imageBefore","label","imageAfter","onThumbClicked","currentTarget","Element","activateModel","thumb","thumbsAncestor","closest","allThumbs","thumbCandidate","wrapper","modelId","getAttribute","models","model","id","customElements","get","define","wrappers","thumbsContainer","thumbs","onTabClicked","activateTab","tab","isActiveTab","section","tabs","candidateTab","tabId","panels","panel","content","createElement","lazy","node","createTextNode","appendChild","head","body","documentElement","appendCSSText","tabsAncestor"],"sourceRoot":""}