{"version":3,"sources":["../../src/use-favicon/index.ts"],"sourcesContent":["import { useEffect } from 'react'\nimport { useGetterRef } from '../use-getter-ref'\nimport { useLatest } from '../use-latest'\nimport { useMount } from '../use-mount'\nimport { usePrevious } from '../use-previous'\nimport { useSafeState } from '../use-safe-state'\nimport { useStableFn } from '../use-stable-fn'\nimport { useUnmount } from '../use-unmount'\nimport { useUpdateEffect } from '../use-update-effect'\nimport { isString } from '../utils/basic'\n\nimport type { ReactSetState } from '../use-safe-state'\n\nexport type FaviconType = string | null | undefined\n\nexport interface UseFaviconOptions {\n  /**\n   * Base URL for the favicon\n   *\n   * @defaultValue ''\n   */\n  baseUrl?: string\n  /**\n   * The rel attribute of the favicon link element\n   *\n   * @defaultValue 'icon'\n   */\n  rel?: string\n  /**\n   * Sync the favicon in document `<link rel='icon' />` to hooks state on mount\n   *\n   * @defaultValue true\n   */\n  syncOnMount?: boolean\n  /**\n   * Restore the previous favicon on unmount\n   *\n   * @defaultValue false\n   */\n  restoreOnUnmount?: boolean\n}\n\nexport interface UseFaviconReturns {\n  /**\n   * The current favicon URL\n   */\n  href: string | null\n  /**\n   * Set the favicon URL\n   */\n  setFavicon: ReactSetState<string | null>\n  /**\n   * Sync the favicon in document `<link rel='icon' />` to hooks state\n   */\n  syncFavicon(isInitial?: boolean): void\n  /**\n   * Set the favicon to an emoji\n   */\n  setEmojiFavicon: (emoji: string) => void\n  /**\n   * Set the favicon to the previous favicon\n   */\n  setPreviousFavicon(): void\n}\n\nfunction emojiSvgHref(emoji: string): string {\n  return `data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\"><text y=\"1em\" font-size=\"90\">${emoji}</text></svg>`\n}\n\nfunction getFavicon(rel = 'icon'): string {\n  const elements = document.head.querySelectorAll<HTMLLinkElement>(`link[rel*=\"${rel}\"]`)\n  const href = Array.from(elements)\n    .filter((el) => el.href)\n    .map((el) => el.href)[0]\n  return href\n}\n\n/**\n * A React Hook that allows you to change the favicon of the page.\n */\nexport function useFavicon(newIcon: FaviconType = null, options: UseFaviconOptions = {}): UseFaviconReturns {\n  const { baseUrl = '', rel = 'icon', restoreOnUnmount = false, syncOnMount = true } = options\n  const [faviconHref, setFaviconHref] = useSafeState(newIcon ?? null)\n  const previousFavicon = usePrevious(faviconHref)\n\n  const [initialFaviconRef, initialFavicon] = useGetterRef<string>('')\n  const latest = useLatest({ restoreOnUnmount, faviconHref, previousFavicon })\n\n  const applyIcon = useStableFn((icon: string) => {\n    const elements = document.head.querySelectorAll<HTMLLinkElement>(`link[rel*=\"${rel}\"]`)\n    const isDataUrl = icon.startsWith('data:')\n    const iconType = isDataUrl ? icon.split('data:')[1].split(',')[0] : `image/${icon.split('.').pop() ?? 'png'}`\n    const iconHref = isDataUrl ? icon : `${baseUrl}${icon}`\n\n    if (!elements || elements.length === 0) {\n      const link = document.createElement('link')\n      link.rel = rel\n      link.href = iconHref\n      link.type = iconType\n      document.head.append(link)\n    } else {\n      for (const el of Array.from(elements)) {\n        el.href = iconHref\n        el.type = iconType\n      }\n    }\n  })\n\n  const syncFavicon = useStableFn((isInitial: boolean = false) => {\n    const href = getFavicon(rel)\n\n    if (href && href !== latest.current.faviconHref) {\n      if (isInitial) {\n        initialFaviconRef.current = href\n      }\n\n      setFaviconHref(href)\n    }\n  })\n\n  useMount(() => !faviconHref && syncOnMount && syncFavicon(true))\n\n  useEffect(() => {\n    const isUpdatedIcon = isString(faviconHref) && faviconHref !== latest.current.previousFavicon\n    isUpdatedIcon && applyIcon(faviconHref)\n  }, [faviconHref])\n\n  useUpdateEffect(() => void setFaviconHref(newIcon), [newIcon])\n\n  useUnmount(() => {\n    if (latest.current.restoreOnUnmount) {\n      applyIcon(initialFavicon())\n    }\n  })\n\n  const setPreviousFavicon = useStableFn(() => {\n    const { previousFavicon } = latest.current\n    if (!previousFavicon) return\n    applyIcon(previousFavicon)\n    setFaviconHref(previousFavicon)\n  })\n\n  const setEmojiFavicon = useStableFn((emoji: string) => {\n    setFaviconHref(emojiSvgHref(encodeURIComponent(emoji)))\n  })\n\n  return {\n    href: faviconHref,\n    setFavicon: setFaviconHref,\n    syncFavicon,\n    setEmojiFavicon,\n    setPreviousFavicon,\n  }\n}\n"],"names":["useFavicon","emojiSvgHref","emoji","getFavicon","rel","elements","document","head","querySelectorAll","href","Array","from","filter","el","map","newIcon","options","baseUrl","restoreOnUnmount","syncOnMount","useSafeState","faviconHref","setFaviconHref","previousFavicon","usePrevious","useGetterRef","initialFaviconRef","initialFavicon","latest","useLatest","applyIcon","useStableFn","icon","isDataUrl","startsWith","iconType","split","pop","iconHref","length","link","createElement","type","append","syncFavicon","isInitial","current","useMount","useEffect","isUpdatedIcon","isString","useUpdateEffect","useUnmount","setPreviousFavicon","setEmojiFavicon","encodeURIComponent","setFavicon"],"mappings":"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"}