{"version":3,"file":"index.mjs","names":["delta: number","zoomFactor: number","dom: HTMLElement","transform: Transform","cb?: (val: Transform) => void","target: HTMLElement","transform: Transform","e: MouseEvent","e: WheelEvent","offsetX: number","offsetY: number","ratio: number","root: Node | null | undefined","n: Node | null","option: Options","prepend?: Prepend","container: ContainerType","css: string","key: string","path: string","options?: LoadCSSOptions","unload","src: string","options?: LoadScriptOptions","variables: Record<string, string | null>","root: HTMLElement","selector: string","options?: WaitForOptions","feature: 'lossy' | 'lossless' | 'alpha' | 'animation'","el: Element","cls: string"],"sources":["../src/panzoom/utils.ts","../src/panzoom/core.ts","../src/canUseDom.ts","../src/domContains.ts","../src/dynamicCSS.ts","../src/isAlipay.ts","../src/isAndroid.ts","../src/isIOS.ts","../src/isMobile.ts","../src/isWeixin.ts","../src/loadCSS.ts","../src/loadScript.ts","../src/setCssVar.ts","../src/waitForSelector.ts","../src/webp.ts","../src/styleUtil.ts"],"sourcesContent":["import type { Transform } from './type'\n\nexport function getScaleMultiplier(delta: number, zoomFactor: number): number {\n  const sign = Math.sign(delta)\n  const deltaAdjustedSpeed = Math.min(0.25, Math.abs(zoomFactor * delta / 128))\n  return 1 - sign * deltaAdjustedSpeed\n}\n\nexport function applyTransform(dom: HTMLElement, transform: Transform, cb?: (val: Transform) => void): void {\n  dom.style.transformOrigin = '0 0 0'\n  dom.style.transform = `matrix(${transform.scale}, 0, 0, ${transform.scale}, ${transform.x}, ${transform.y})`\n  cb?.(transform)\n}\n","import type { PanZoomOptions, Transform } from './type'\nimport { applyTransform, getScaleMultiplier } from './utils'\n\ntype PanZoomReturn = {\n  destroy: () => void\n}\n/**\n * @linkcode https://github.com/GreatAuk/utopia-utils/blob/main/packages/dom/src/panzoom/index.ts\n */\nexport function panzoom(target: HTMLElement, {\n  zoomFactor = 1,\n  maxZoom = 5,\n  minZoom = 0.2,\n  onTransform,\n}: PanZoomOptions = {}): PanZoomReturn {\n  const parentNode = target.parentNode as HTMLElement\n\n  if (!parentNode)\n    throw new Error('target element must have a parent node!')\n\n  // TODO 为了缩放时以鼠标为中心， parent 最好是 inline-block, 并且宽高自动由 target 决定。target 也不要有 absolute 定位。\n\n  parentNode.style.overflow = 'hidden'\n  parentNode.style.userSelect = 'none'\n\n  const transform: Transform = { x: 0, y: 0, scale: 1 }\n\n  let isDragging = false\n  let lastMousePosition = {\n    x: 0,\n    y: 0,\n  }\n\n  const mousemove = (e: MouseEvent) => {\n    if (!isDragging)\n      return\n    const dx = e.clientX - lastMousePosition.x\n    const dy = e.clientY - lastMousePosition.y\n    transform.x = transform.x + dx\n    transform.y = transform.y + dy\n    window.requestAnimationFrame(() => applyTransform(target, transform, onTransform))\n    lastMousePosition = {\n      x: e.clientX,\n      y: e.clientY,\n    }\n  }\n  const mouseupHandler = () => {\n    isDragging = false\n    document.removeEventListener('mousemove', mousemove)\n    document.removeEventListener('mouseup', mouseupHandler)\n  }\n  const mousedownHandler = (e: MouseEvent) => {\n    isDragging = true\n    lastMousePosition = {\n      x: e.clientX,\n      y: e.clientY,\n    }\n    document.addEventListener('mousemove', mousemove)\n    document.addEventListener('mouseup', mouseupHandler)\n  }\n  parentNode.addEventListener('mousedown', mousedownHandler)\n\n  function getOffsetXY(e: WheelEvent) {\n    // use e.offsetX has a bug for zoom\n    const ownerRect = parentNode.getBoundingClientRect()\n    const offsetX = e.clientX - ownerRect.left\n    const offsetY = e.clientY - ownerRect.top\n\n    return { x: offsetX, y: offsetY }\n  }\n  const mouseWheelHandler = (e: WheelEvent) => {\n    let delta = e.deltaY\n    if (e.deltaMode > 0)\n      delta *= 100\n\n    const scaleMultiplier = getScaleMultiplier(delta, zoomFactor)\n\n    if (scaleMultiplier !== 1) {\n      const offset = getOffsetXY(e)\n      zoomTo(offset.x, offset.y, scaleMultiplier)\n      window.requestAnimationFrame(() => applyTransform(target, transform, onTransform))\n\n      // Need to prevent the default here or it conflicts with regular page scroll\n      e.preventDefault()\n    }\n  }\n  parentNode.addEventListener('wheel', mouseWheelHandler)\n\n  function zoomTo(offsetX: number, offsetY: number, ratio: number) {\n    if (Number.isNaN(offsetX) || Number.isNaN(offsetY) || Number.isNaN(ratio))\n      throw new Error('zoom requires valid numbers')\n\n    const newScale = transform.scale * ratio\n\n    if (newScale < minZoom) {\n      if (transform.scale === minZoom)\n        return\n\n      ratio = minZoom / transform.scale\n    }\n    if (newScale > maxZoom) {\n      if (transform.scale === maxZoom)\n        return\n\n      ratio = maxZoom / transform.scale\n    }\n\n    transform.x = offsetX - ratio * (offsetX - transform.x)\n    transform.y = offsetY - ratio * (offsetY - transform.y)\n\n    transform.scale *= ratio\n    window.requestAnimationFrame(() => applyTransform(target, transform, onTransform))\n  }\n\n  function destroy() {\n    parentNode.removeEventListener('mousedown', mousedownHandler)\n    parentNode.removeEventListener('wheel', mouseWheelHandler)\n  }\n\n  return {\n    destroy,\n  }\n}\n","/**\n * The function checks if the DOM (Document Object Model) can be used in the current environment.\n * @returns a boolean value indicating whether the DOM (Document Object Model) can be used.\n * @linkcode https://github.com/GreatAuk/utopia-utils/blob/main/packages/dom/src/canUseDom.ts\n */\nexport function canUseDom(): boolean {\n  return !!(\n    typeof window !== 'undefined' &&\n    window &&\n    window.document &&\n    window.document.createElement\n  )\n}\n","/**\n * The function checks if a given node is contained within a root node, using either the native\n * `contains` method or a fallback method for older browsers.\n * @param {Node | null | undefined} root - The `root` parameter represents the root node of a tree or\n * subtree.\n * @param {Node | null} n - The parameter `n` represents a Node object that we want to check if it is\n * contained within the `root` Node object.\n * @returns a boolean value. It returns `true` if the `root` node contains the `n` node, and `false`\n * otherwise.\n * @linkcode https://github.com/GreatAuk/utopia-utils/blob/main/packages/dom/src/contains.ts\n */\nexport function domContains(root: Node | null | undefined, n: Node | null): boolean {\n  if (!root)\n    return false\n\n  // Use native if support\n  if (root.contains)\n    return root.contains(n)\n\n  // `document.contains` not support with IE11\n  let node = n\n  while (node) {\n    if (node === root)\n      return true\n\n    node = node.parentNode\n  }\n\n  return false\n}\n","import { canUseDom } from './canUseDom'\nimport { domContains } from './domContains'\n\nconst APPEND_ORDER = 'data-rc-order'\nconst APPEND_PRIORITY = 'data-rc-priority'\nconst MARK_KEY = 'rc-util-key'\n\nconst containerCache = new Map<ContainerType, Node & ParentNode>()\n\nexport type ContainerType = Element | ShadowRoot\nexport type Prepend = boolean | 'queue'\nexport type AppendType = 'prependQueue' | 'append' | 'prepend'\n\ninterface Options {\n  attachTo?: ContainerType\n  csp?: { nonce?: string }\n  prepend?: Prepend\n  /**\n   * Config the `priority` of `prependQueue`. Default is `0`.\n   * It's useful if you need to insert style before other style.\n   */\n  priority?: number\n  mark?: string\n}\n\nfunction getMark({ mark }: Options = {}): string {\n  if (mark)\n    return mark.startsWith('data-') ? mark : `data-${mark}`\n\n  return MARK_KEY\n}\n\nfunction getContainer(option: Options) {\n  if (option.attachTo)\n    return option.attachTo\n\n  const head = document.querySelector('head')\n  return head || document.body\n}\n\nfunction getOrder(prepend?: Prepend): AppendType {\n  if (prepend === 'queue')\n    return 'prependQueue'\n\n  return prepend ? 'prepend' : 'append'\n}\n\n/**\n * Find style which inject by rc-util\n */\nfunction findStyles(container: ContainerType): HTMLStyleElement[] {\n  return Array.from(\n    (containerCache.get(container) || container).children,\n  ).filter(node => node.tagName === 'STYLE') as HTMLStyleElement[]\n}\n\nexport function injectCSS(css: string, option: Options = {}): HTMLStyleElement | null {\n  if (!canUseDom())\n    return null\n\n  const { csp, prepend, priority = 0 } = option\n  const mergedOrder = getOrder(prepend)\n  const isPrependQueue = mergedOrder === 'prependQueue'\n\n  const styleNode = document.createElement('style')\n  styleNode.setAttribute(APPEND_ORDER, mergedOrder)\n\n  if (isPrependQueue && priority)\n    styleNode.setAttribute(APPEND_PRIORITY, `${priority}`)\n\n  if (csp?.nonce)\n    styleNode.nonce = csp?.nonce\n\n  styleNode.innerHTML = css\n\n  const container = getContainer(option)\n  const { firstChild } = container\n\n  if (prepend) {\n    // If is queue `prepend`, it will prepend first style and then append rest style\n    if (isPrependQueue) {\n      const existStyle = findStyles(container).filter((node) => {\n        // Ignore style which not injected by rc-util with prepend\n        if (\n          !['prepend', 'prependQueue'].includes(node.getAttribute(APPEND_ORDER)!)\n        )\n          return false\n\n        // Ignore style which priority less then new style\n        const nodePriority = Number(node.getAttribute(APPEND_PRIORITY) || 0)\n        return priority >= nodePriority\n      })\n\n      if (existStyle.length) {\n        container.insertBefore(\n          styleNode,\n          existStyle[existStyle.length - 1].nextSibling,\n        )\n\n        return styleNode\n      }\n    }\n\n    // Use `insertBefore` as `prepend`\n    container.insertBefore(styleNode, firstChild)\n  }\n  else {\n    container.appendChild(styleNode)\n  }\n\n  return styleNode\n}\n\nfunction findExistNode(key: string, option: Options = {}): HTMLStyleElement | undefined {\n  const container = getContainer(option)\n\n  return findStyles(container).find(\n    node => node.getAttribute(getMark(option)) === key,\n  )\n}\n\nexport function removeCSS(key: string, option: Options = {}): void {\n  const existNode = findExistNode(key, option)\n  if (existNode) {\n    const container = getContainer(option)\n    container.removeChild(existNode)\n  }\n}\n\n/**\n * qiankun will inject `appendChild` to insert into other\n */\nfunction syncRealContainer(container: ContainerType, option: Options): void {\n  const cachedRealContainer = containerCache.get(container)\n\n  // Find real container when not cached or cached container removed\n  if (!cachedRealContainer || !domContains(document, cachedRealContainer)) {\n    const placeholderStyle = injectCSS('', option)\n    if (!placeholderStyle)\n      return\n    const { parentNode } = placeholderStyle\n    if (!parentNode)\n      return\n    containerCache.set(container, parentNode)\n    container.removeChild(placeholderStyle)\n  }\n}\n\n/**\n * manually clear container cache to avoid global cache in unit testes\n */\nexport function clearContainerCache(): void {\n  containerCache.clear()\n}\n\n/**\n * Update CSS text in style element\n * @param css CSS text\n * @param key style key\n * @param option Options\n * @returns style element\n * @fork https://github.com/react-component/util/blob/master/src/Dom/dynamicCSS.ts\n * @linkcode https://github.com/GreatAuk/utopia-utils/blob/main/packages/dom/src/dynamicCSS.ts\n * @example\n * ```js\n * updateCSS('body { color: red }', 'my-style')\n * ```\n */\nexport function updateCSS(css: string, key: string, option: Options = {}): HTMLStyleElement | undefined {\n  const container = getContainer(option)\n\n  // Sync real parent\n  syncRealContainer(container, option)\n\n  const existNode = findExistNode(key, option)\n\n  if (existNode) {\n    if (option.csp?.nonce && existNode.nonce !== option.csp?.nonce)\n      existNode.nonce = option.csp?.nonce\n\n    if (existNode.innerHTML !== css)\n      existNode.innerHTML = css\n\n    return existNode\n  }\n\n  const newNode = injectCSS(css, option)\n  if (!newNode)\n    return\n  newNode.setAttribute(getMark(option), key)\n  return newNode\n}\n","/**\n * Check if the current environment is Alipay. (支付宝)\n * @returns A boolean value.\n * @linkcode https://github.com/GreatAuk/utopia-utils/blob/main/packages/dom/src/isAlipay.ts\n */\nexport function isAlipay(): boolean {\n  const ua = navigator.userAgent.toLowerCase()\n  return /Alipay/i.test(ua)\n}\n","/**\n * Returns a boolean value indicating whether the current platform is Android.\n * @returns A boolean value.\n * @linkcode https://github.com/GreatAuk/utopia-utils/blob/main/packages/dom/src/isAndroid.ts\n */\nexport function isAndroid(): boolean {\n  if (typeof navigator === 'undefined')\n    return false\n  return /android/i.test(navigator.userAgent)\n}\n","/**\n * If the user agent string contains the word 'iPad', 'iPhone', or 'iPod', then we're on iOS.\n * @returns A boolean value.\n * @linkcode https://github.com/GreatAuk/utopia-utils/blob/main/packages/dom/src/isIOS.ts\n */\nexport function isIOS(): boolean {\n  if (typeof navigator === 'undefined')\n    return false\n  return /iPad|iPhone|iPod/.test(navigator.userAgent)\n}\n","/**\n * detect current environment is a mobile device\n * @returns A boolean value.\n * @linkcode https://github.com/GreatAuk/utopia-utils/blob/main/packages/dom/src/isMobile.ts\n */\nexport function isMobile(): boolean {\n  if (typeof navigator === 'undefined' || typeof window === 'undefined')\n    return false\n\n  const agent\n    = navigator.userAgent || navigator.vendor || (window as any).opera\n  return (\n    /(android|bb\\d+|meego).+mobile|avantgo|bada\\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(\n      agent,\n    )\n    || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(\n      agent?.substr(0, 4),\n    )\n  )\n}\n","/**\n * Check if the current environment is Weixin. (微信)\n * @returns A boolean value.\n * @linkcode https://github.com/GreatAuk/utopia-utils/blob/main/packages/dom/src/isWeixin.ts\n */\nexport function isWeixin(): boolean {\n  const ua = navigator.userAgent.toLowerCase()\n  return /MicroMessenger/i.test(ua)\n}\n","/**\n * Options for {@link loadCSS} function\n */\ninterface LoadCSSOptions {\n  /**\n   * Media query for styles to apply\n   * @example 'print', 'all', 'screen and (max-width: 768px)' and etc.\n   */\n  media?: string\n  /**\n   * Add custom attribute to the script tag\n   *\n   */\n  attrs?: Record<string, string>\n}\n\n/**\n * return type of {@link loadCSS} function\n */\ninterface LoadCSSReturn {\n  unload: () => void\n  linkTag: HTMLLinkElement\n}\n\n/**\n * It loads a CSS file into the page\n * @param {string} path - the path to the CSS file\n * @param {LoadCSSOptions} [options] - {\n * @returns An object with two properties:\n *   - unload: a function that removes the script tag\n *   - linkTag: the link tag that was created\n * @linkcode https://github.com/GreatAuk/utopia-utils/blob/main/packages/dom/src/loadCSS.ts\n */\nexport function loadCSS(path: string, options?: LoadCSSOptions): LoadCSSReturn {\n  const {\n    attrs = {},\n    media,\n  } = options || {}\n  let linkEl = document.querySelector<HTMLLinkElement>(`link[href=\"${path}\"]`)\n\n  if (!linkEl) {\n    linkEl = document.createElement('link')\n    linkEl.rel = 'stylesheet'\n    linkEl.href = path\n\n    if (media)\n      linkEl.media = media\n\n    Object.entries(attrs).forEach(([name, value]) => linkEl?.setAttribute(name, value))\n\n    document.head.appendChild(linkEl)\n  }\n\n  return {\n    /** remove the script tag */\n    unload: () => unload(path),\n    linkTag: linkEl,\n  }\n}\n\n/** remove the script tag */\nfunction unload(path: string): void {\n  const linkEl = document.querySelector(`link[href=\"${path}\"]`)\n  if (linkEl)\n    linkEl.remove()\n}\n","/**\n * options type for {@link loadScript} function\n */\ninterface LoadScriptOptions {\n  /**\n   * Add `async` attribute to the script tag\n   * @default true\n   */\n  async?: boolean\n  /**\n   * Add `defer` attribute to the script tag\n   */\n  defer?: boolean\n  /**\n   * Script type\n   *\n   * @default 'text/javascript'\n   * @see https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/script#attr-type\n   */\n  type?: string\n  crossOrigin?: 'anonymous' | 'use-credentials'\n  /**\n   * @see https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/script#attr-referrerpolicy\n   */\n  referrerPolicy?: 'no-referrer' | 'no-referrer-when-downgrade' | 'origin' | 'origin-when-cross-origin' | 'same-origin' | 'strict-origin' | 'strict-origin-when-cross-origin' | 'unsafe-url'\n  noModule?: boolean\n  /**\n   * Add custom attribute to the script tag\n   *\n   */\n  attrs?: Record<string, string>\n  /**\n   * Append the script tag to the document.head or document.body\n   * @default 'head'\n   */\n  appendPosition?: 'head' | 'body'\n  onStatusChange?: (status: 'loading' | 'loaded' | 'error') => void\n}\n\n/**\n * return type of {@link loadScript} function\n */\ninterface LoadScriptReturn {\n  unload: () => void\n  scriptTag: HTMLScriptElement\n}\n\n/**\n * It loads a script tag into the DOM\n * @param {string} src - The URL of the script to load.\n * @param {LoadScriptOptions} [options] - LoadScriptOptions\n * @returns An object with two properties:\n *   - unload: a function that removes the script tag\n *   - scriptTag: the script tag that was created\n * @linkcode https://github.com/GreatAuk/utopia-utils/blob/main/packages/dom/src/loadScript.ts\n */\nexport function loadScript(src: string, options?: LoadScriptOptions): LoadScriptReturn {\n  const {\n    async = true,\n    defer,\n    type = 'text/javascript',\n    crossOrigin,\n    referrerPolicy,\n    attrs = {},\n    noModule,\n    appendPosition = 'head',\n    onStatusChange,\n  } = options || {}\n  let scriptTag = document.querySelector<HTMLScriptElement>(`script[src=\"${src}\"]`)\n\n  // if the script is exist, it has high probability loaded\n  if (scriptTag)\n    onStatusChange?.('loaded')\n\n  if (!scriptTag) {\n    scriptTag = document.createElement('script')\n    scriptTag.src = src\n    scriptTag.type = type\n    scriptTag.async = async\n\n    if (defer)\n      scriptTag.defer = defer\n    if (crossOrigin)\n      scriptTag.crossOrigin = crossOrigin\n    if (referrerPolicy)\n      scriptTag.referrerPolicy = referrerPolicy\n    if (noModule)\n      scriptTag.noModule = noModule\n    Object.entries(attrs).forEach(([name, value]) => scriptTag?.setAttribute(name, value))\n\n    const appendTarget = appendPosition === 'head' ? document.head : document.body\n    appendTarget.appendChild(scriptTag)\n    onStatusChange?.('loading')\n  }\n\n  scriptTag?.addEventListener('load', () => {\n    onStatusChange?.('loaded')\n  })\n  scriptTag?.addEventListener('error', () => {\n    onStatusChange?.('error')\n  })\n  scriptTag?.addEventListener('abort', () => {\n    onStatusChange?.('error')\n  })\n\n  return {\n    /** remove the script tag */\n    unload: () => unload(src),\n    scriptTag,\n  }\n}\n\n/** remove the script tag */\nfunction unload(src: string): void {\n  const script = document.querySelector(`script[src=\"${src}\"]`)\n  if (script)\n    script.remove()\n}\n","import { isPlainObject } from '@utopia-utils/share'\n\n/**\n * The function sets CSS variables on the root element of a document using the provided variables\n * object.\n * @param variables - The `variables` parameter is an object that contains key-value pairs representing\n * CSS variable names and their corresponding values.\n * @param root - The `root` parameter is an optional parameter that represents the root element where\n * the CSS variables should be set. By default, it is set to `window?.document?.documentElement`, which\n * refers to the root element of the current document.\n * @linkcode https://github.com/GreatAuk/utopia-utils/blob/main/packages/dom/src/setCssVar.ts\n * @example\n * ```ts\n * setCssVar({ '--color': 'red', '--size': '10px' })\n * setCssVar({ '--color': 'red', '--size': '10px' }, document.body)\n * ```\n */\nexport function setCssVar(variables: Record<string, string | null>, root: HTMLElement = window?.document?.documentElement): void {\n  if (variables && isPlainObject(variables) && root) {\n    Object.keys(variables).forEach((key) => {\n      root.style.setProperty(key, variables[key])\n    })\n  }\n}\n","/**\n * Options for {@link waitForSelector}\n */\ninterface WaitForOptions {\n  /** Maximum time to wait in milliseconds, default is 30000 (30s) */\n  timeoutMillisecond?: number\n  /**\n   * Instructs the user agent to observe a given target (a node)\n   * @default document.body\n   */\n  target?: Node\n}\n\n/**\n * waits for an element to appear in the DOM\n * @param {string} selector - The CSS selector to wait for.\n * @param {WaitForOptions} [options]\n * @returns A promise that resolves to an element that matches the selector.\n * @linkcode https://github.com/GreatAuk/utopia-utils/blob/main/packages/dom/src/waitForSelector.ts\n */\nexport function waitForSelector<T extends Element>(selector: string, options?: WaitForOptions): Promise<T | null> {\n  return new Promise((resolve) => {\n    if (typeof window.MutationObserver === 'undefined')\n      throw new Error('MutationObserver is not supported in this browser')\n\n    const { timeoutMillisecond = 30000, target = document.body } = options || {}\n    const timeoutId = setTimeout(onTimeoutDone, timeoutMillisecond)\n\n    const observer = new MutationObserver((mutations) => {\n      if (mutations.some(mutation => Array.from(mutation.addedNodes).some(node => node instanceof Element && node.matches(selector)))) {\n        clearTimeout(timeoutId)\n        checkDom()\n      }\n    })\n    observer.observe(target, {\n      childList: true,\n      subtree: true,\n    })\n\n    checkDom()\n\n    function checkDom() {\n      const dom = document.querySelector(selector)\n      if (dom) {\n        observer.disconnect()\n        resolve(dom as T)\n      }\n    }\n\n    function onTimeoutDone() {\n      observer.disconnect()\n      resolve(null)\n    }\n  })\n}\n","/**\n * Checks the browser's support for specific WebP features.\n * @param feature The WebP feature to check for. Defaults to 'lossy'.\n * @returns A promise that resolves to a boolean indicating whether the feature is supported.\n * @link Official way by Google: https://developers.google.com/speed/webp/faq?hl=zh-cn#how_can_i_detect_browser_support_for_webp\n */\nexport async function checkWebpFeature(feature: 'lossy' | 'lossless' | 'alpha' | 'animation' = 'lossy'): Promise<boolean> {\n  return new Promise<boolean>((resolve) => {\n    const kTestImages = {\n      lossy: 'UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA',\n      lossless: 'UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==',\n      alpha: 'UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==',\n      animation: 'UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA',\n    }\n    const img = new Image()\n    img.onload = function () {\n      const res = (img.width > 0) && (img.height > 0)\n      resolve(res)\n    }\n    img.onerror = function () {\n      resolve(false)\n    }\n    img.src = `data:image/webp;base64,${kTestImages[feature]}`\n  })\n}\n\n/**\n * Checks the browser's support for WebP image format.\n * @returns A promise that resolves to a boolean indicating whether WebP is supported.\n */\nexport async function checkWebpSupport(): Promise<boolean> {\n  return checkWebpFeature('lossy')\n}\n","/**\n * Split the className string into an array of class names, and filter out any empty strings.\n * @param [cls] - The className string\n */\nexport function classNameToArray(cls = ''): string[] {\n  return cls.split(' ').filter(v => !!v.trim())\n}\n\nexport function hasClass(el: Element, cls: string): boolean {\n  if (!el || !cls)\n    return false\n  if (cls.includes(' '))\n    throw new Error('className should not contain space.')\n  return el.classList.contains(cls)\n}\n\nexport function addClass(el: Element, cls: string): void {\n  if (!el || !cls.trim())\n    return\n  el.classList.add(...classNameToArray(cls))\n}\n\nexport function removeClass(el: Element, cls: string): void {\n  if (!el || !cls.trim())\n    return\n  el.classList.remove(...classNameToArray(cls))\n}\n"],"mappings":"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"}