{"version":3,"file":"image-element-section.min.js","mappings":"wpBACA,IAAAA,EAAA,GAGA,SAAAC,EAAAC,GAEA,IAAAC,EAAAH,EAAAE,GACA,QAAAE,IAAAD,EACA,OAAAA,EAAAE,QAGA,IAAAC,EAAAN,EAAAE,GAAA,CAGAG,QAAA,IAOA,OAHAE,EAAAL,GAAAI,EAAAA,EAAAD,QAAAJ,GAGAK,EAAAD,OACA,CCtBAJ,EAAAO,EAAA,oBAAAC,UAAAA,SAAAC,SAAAC,KAAAC,SAAAC,KCeA,MAAMC,EAAwC,CAC5CC,QAAS,IACTC,OAAQ,IACRC,KAAM,GACNC,MAAO,IACPC,OAAQ,IACRC,SAAU,KACVC,KAAM,GACNC,MAAO,IACPC,MAAO,ICvBT,IAIAC,EAFA,aAFA,IAAAC,IAAyCxB,EAAA,sHAAAA,EAAAO,GAEzC,KCcA,MAAMkB,qBAAqBC,YACzB,6BAAkBC,GAChB,MAAO,CACL,aACA,cACA,oBACA,WACA,WAEJ,CA+BO,iBAAAC,GACLC,KAAKC,uBAELD,KAAKE,QACP,CAEO,wBAAAC,CAAyBC,EAAeC,EAAkBC,GAC/DN,KAAKC,uBAEDD,KAAKO,aAAeF,IAAaC,GACnCN,KAAKE,QAET,CAEQ,oBAAAD,GACDD,KAAKQ,aACRR,KAAKS,aAAa,CAAEC,KAAM,SAC1BV,KAAKQ,WAAWG,UAAYjB,EAEhC,CAEQ,MAAAQ,GAEN,IAAKF,KAAKY,QAAQC,KAA4B,cAArBb,KAAKY,QAAQC,IACpC,OAGF,MAAMC,EAASC,SAASf,KAAKY,QAAQE,OAAQ,IACvCE,EAAcC,WAAWjB,KAAKY,QAAQI,aAEtCE,EADQH,SAASf,KAAKY,QAAQO,MAAO,KACjBL,EAASE,EAE7BI,EAAQpB,KAAKQ,WAAWa,cAAc,OAC5CD,EAAME,MAAMJ,SAAW,GAAGA,MAC1BE,EAAMG,IAAMvB,KAAKY,QAAQW,IAEzB,MAAMC,EAAOxB,KAAKyB,qBAClBL,EAAMP,IFzBH,SAAeA,EAAaa,GAGjC,MAAMC,EAAWd,EAAIe,WAAW,MAAQ,SAASf,IAAQA,EACnDgB,EAAM,IAAIlC,IAAIgC,GAIdG,GAAQJ,EAAQF,MAASE,EAAQP,OAAUO,EAAQZ,YAEvDxC,EADAU,EAAgB0C,EAAQF,MAEpBL,EAAQO,EAAQP,OAASW,EACzBhB,EAASY,EAAQZ,QAAUgB,EAkBjC,OAhBIX,GACFU,EAAIE,aAAaC,IAAI,QAAS,GAAGb,KAG/BL,GACFe,EAAIE,aAAaC,IAAI,SAAU,GAAGlB,KAGhCY,EAAQO,MACVJ,EAAIE,aAAaC,IAAI,OAAQN,EAAQO,MAGnCP,EAAQQ,SACVL,EAAIE,aAAaC,IAAI,UAAW,GAAGN,EAAQQ,WAGtCL,EAAIM,WAAWC,QAAQ,WAAY,GAC5C,CENgBC,CAAMrC,KAAKY,QAAQC,IAAKW,EACtC,CAEQ,kBAAAC,GACN,MAAMN,EAAQJ,SAASf,KAAKY,QAAQO,MAAO,IACrCL,EAASC,SAASf,KAAKY,QAAQE,OAAQ,IACvCE,EAAcC,WAAWjB,KAAKY,QAAQI,aAEtCU,EAA2B,CAAC,EAiBlC,OAbIP,GAASL,GACXY,EAAQZ,OAAkB,EAATA,EACjBY,EAAQP,MAAgB,EAARA,GACPH,GAAeG,GACxBO,EAAQZ,OAASK,EAAQH,EAAc,EACvCU,EAAQP,MAAgB,EAARA,GACPH,GAAeF,GACxBY,EAAQZ,OAAkB,EAATA,EACjBY,EAAQP,MAAQL,EAASE,EAAc,GAEvCU,EAAQF,KAAO,SAGVE,CACT,EASGY,eAAeC,IAAI,kBACtBD,eAAeE,OAAO,gBAAiB5C","sources":["webpack://lange/webpack/bootstrap","webpack://lange/webpack/runtime/jsonp chunk loading","webpack://lange/./lib/shopify/image-url.ts","webpack://lange/./sections/image-element/image-element/index.html","webpack://lange/./sections/image-element/image-element/index.ts"],"sourcesContent":["// The module cache\nlet __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tlet cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tlet module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","__webpack_require__.b = (typeof document !== 'undefined' && document.baseURI) || self.location.href;\n\n// no installed chunks\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n// no on chunks loaded\n\n// no jsonp function","// Shopify serves resized images two ways. The old way splices the size into the filename, so\n// `foo.jpg` becomes `foo_120x120.jpg`, and that is what the deprecated `img_url` Liquid filter\n// emits. It is also what the retired slate fork alongside this file does. The current way, what the\n// `image_url` filter emits and what Hydrogen's image loader builds from JS, is query params on the\n// original URL. Both come back byte identical, so this is the same images through the shape Shopify\n// still maintains.\n\ntype Crop = 'bottom' | 'center' | 'left' | 'right' | 'top';\n\ntype Size = 'compact' | 'grande' | 'icon' | 'large' | 'medium' | 'original' | 'pico' | 'small' |\n  'thumb';\n\n// Shopify's named sizes are all square, and these are the pixel values its docs give for each one.\n// There is no entry for master because master means the original image at whatever size it happens\n// to be, and asking for no dimensions at all already gets you that.\nconst namedSizePixels: Record<Size, number> = {\n  compact: 160,\n  grande: 600,\n  icon: 32,\n  large: 480,\n  medium: 240,\n  original: 1024,\n  pico: 16,\n  small: 100,\n  thumb: 50\n};\n\nexport interface ImageUrlOptions {\n  /**\n   * Which part of the image to keep when the requested dimensions do not match its aspect ratio.\n   */\n  crop?: Crop;\n\n  /**\n   * Requested height in pixels.\n   */\n  height?: number;\n\n  /**\n   * Encoding quality from 10 to 90. Leave it out to let Shopify pick one for the output format.\n   */\n  quality?: number;\n\n  /**\n   * One of Shopify's named sizes, used only when neither a width nor a height is given.\n   */\n  size?: Size;\n\n  /**\n   * Requested width in pixels.\n   */\n  width?: number;\n}\n\n/**\n * Builds a URL for a Shopify CDN image at a particular size.\n *\n * Pass a width, a height, or both. You can pass one of Shopify's named sizes instead, and with none\n * of the three you get the original image back. Shopify tops out at 5760 in either direction, and\n * asking for something bigger than the original just hands back the original rather than failing.\n *\n * The returned URL is protocol relative so that nothing gets forced onto `http`.\n *\n * There is no format option. Shopify's own only accepts `jpg` and `pjpg`, and it negotiates webp\n * and avif off the `Accept` header anyway.\n *\n * @see https://shopify.dev/docs/api/liquid/filters/image_url\n * @example build('//langehair.com/cdn/shop/files/Foo.jpg?v=1765908254', { width: 120 })\n */\nexport function build(src: string, options: ImageUrlOptions) {\n  // Real image URLs on the site are protocol relative, and URL needs a scheme to parse anything. So\n  // we bolt one on for parsing and take it back off at the end.\n  const absolute = src.startsWith('//') ? `https:${src}` : src;\n  const url = new URL(absolute);\n\n  // A named size is only there for callers that have no dimensions of their own, so an explicit\n  // width or height wins over it. The named sizes are square, so the one number covers both.\n  const named = options.size && !options.width && !options.height ?\n    namedSizePixels[options.size] :\n    undefined;\n  const width = options.width || named;\n  const height = options.height || named;\n\n  if (width) {\n    url.searchParams.set('width', `${width}`);\n  }\n\n  if (height) {\n    url.searchParams.set('height', `${height}`);\n  }\n\n  if (options.crop) {\n    url.searchParams.set('crop', options.crop);\n  }\n\n  if (options.quality) {\n    url.searchParams.set('quality', `${options.quality}`);\n  }\n\n  return url.toString().replace(/^https?:/, '');\n}\n","// Imports\nvar ___HTML_LOADER_IMPORT_0___ = new URL(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=\", import.meta.url);\n// Module\nvar code = \"<img src=\\\"\" + ___HTML_LOADER_IMPORT_0___ + \"\\\">\";\n// Exports\nexport default code;","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport { build, type ImageUrlOptions } from '../../../lib/shopify/image-url.js';\nimport html from './index.html';\n\n/**\n * This is a lower level element that renders an image.\n *\n * This element will request a sized image from Shopify based on:\n * - either a height or width and an aspect ratio (width/height)\n * - an explicit height and width\n * - if no dimensions are specified then it will render a medium sized image by default.\n *\n * Warning: building an image and then assigning dataset attributes one-by-one will trigger\n * rerenders.\n *\n * This does not work for SVG elements.\n */\nclass ImageElement extends HTMLElement implements CustomElement {\n  public static get observedAttributes() {\n    return [\n      'data-width',\n      'data-height',\n      'data-aspect-ratio',\n      'data-src',\n      'data-alt'\n    ];\n  }\n\n  declare readonly dataset: {\n    /**\n     * Alternative text (to set the alt attribute of the image).\n     */\n    alt: string;\n\n    /**\n     * CSS aspect ratio.\n     *\n     * Floating point number. Use parseFloat not parseInt when handling.\n     */\n    aspectRatio: string;\n\n    /**\n     * Image height in pixels\n     */\n    height: string;\n\n    /**\n     * Image source url\n     */\n    src: string;\n\n    /**\n     * Image width in pixels\n     */\n    width: string;\n  };\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\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  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open' });\n      this.shadowRoot.innerHTML = html;\n    }\n  }\n\n  private render() {\n    // Possible 'undefined' to be a string type when passed undefined value via data attribute\n    if (!this.dataset.src || this.dataset.src === 'undefined') {\n      return;\n    }\n\n    const height = parseInt(this.dataset.height, 10);\n    const aspectRatio = parseFloat(this.dataset.aspectRatio);\n    const width = parseInt(this.dataset.width, 10);\n    const maxWidth = width || height * aspectRatio;\n\n    const image = this.shadowRoot.querySelector('img');\n    image.style.maxWidth = `${maxWidth}px`;\n    image.alt = this.dataset.alt;\n\n    const size = this.calculateImageSize();\n    image.src = build(this.dataset.src, size);\n  }\n\n  private calculateImageSize() {\n    const width = parseInt(this.dataset.width, 10);\n    const height = parseInt(this.dataset.height, 10);\n    const aspectRatio = parseFloat(this.dataset.aspectRatio);\n\n    const options: ImageUrlOptions = {};\n\n    // Currently making the image double the size until we can use the <picture> element and support\n    // retina devices, etc\n    if (width && height) {\n      options.height = height * 2;\n      options.width = width * 2;\n    } else if (aspectRatio && width) {\n      options.height = width * aspectRatio * 2;\n      options.width = width * 2;\n    } else if (aspectRatio && height) {\n      options.height = height * 2;\n      options.width = height * aspectRatio * 2;\n    } else {\n      options.size = 'medium';\n    }\n\n    return options;\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'image-element': ImageElement;\n  }\n}\n\nif (!customElements.get('image-element')) {\n  customElements.define('image-element', ImageElement);\n}\n"],"names":["__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","exports","module","__webpack_modules__","b","document","baseURI","self","location","href","namedSizePixels","compact","grande","icon","large","medium","original","pico","small","thumb","image_element","URL","ImageElement","HTMLElement","observedAttributes","connectedCallback","this","initializeShadowRoot","render","attributeChangedCallback","_name","oldValue","newValue","isConnected","shadowRoot","attachShadow","mode","innerHTML","dataset","src","height","parseInt","aspectRatio","parseFloat","maxWidth","width","image","querySelector","style","alt","size","calculateImageSize","options","absolute","startsWith","url","named","searchParams","set","crop","quality","toString","replace","build","customElements","get","define"],"sourceRoot":""}