{"version":3,"file":"theme.cjs","sources":["../../../components/theme-provider/theme.tsx"],"sourcesContent":["'use client';\n\nimport {\n  createContext,\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState\n} from 'react';\nimport { IconProvider } from '~/icons/create-icon';\nimport type {\n  ScopeRef,\n  ThemeProviderProps,\n  UseThemeOptions,\n  UseThemeProps\n} from './types';\nimport { COLOR_SCHEMES } from './types';\n\nconst colorSchemes: readonly string[] = COLOR_SCHEMES;\nconst MEDIA = '(prefers-color-scheme: dark)';\nconst isServer = typeof window === 'undefined';\nconst ThemeContext = createContext<UseThemeProps | undefined>(undefined);\nconst defaultContext: UseThemeProps = { setTheme: _ => {}, themes: [] };\n\n/**\n * Read the current theme state from the nearest `<Theme>` ancestor (default)\n * or from a specific ancestor by its `storageKey` when one is provided.\n *\n * `setTheme` from the return value updates *that* scope only — it never\n * propagates outward. To flip the page-level theme from inside a scope,\n * pass the root provider's `storageKey` (default `\"theme\"`).\n */\nexport const useTheme = (options?: UseThemeOptions): UseThemeProps => {\n  const ctx = useContext(ThemeContext) ?? defaultContext;\n  if (options?.storageKey) {\n    const target = ctx.scopes?.[options.storageKey];\n    if (target) {\n      return { ...ctx, theme: target.theme, setTheme: target.setTheme };\n    }\n  }\n  return ctx;\n};\n\nexport function Theme({ icons, children, ...props }: ThemeProviderProps) {\n  const context = useContext(ThemeContext);\n\n  // Mount the icon registry only when the consumer configures it, so a tree\n  // without icon overrides gains no provider and no extra render work.\n  // Nesting layers per icon key, matching how `Scoped` layers theme tokens.\n  const { components, props: iconProps } = icons ?? {};\n  const content =\n    components || iconProps ? (\n      <IconProvider components={components} props={iconProps}>\n        {children}\n      </IconProvider>\n    ) : (\n      children\n    );\n\n  // Nested usage: scoped subtree. Render a wrapper element that overrides\n  // theme tokens locally via `data-*` attributes; the parent provider's\n  // global state remains the source of truth for descendants reading\n  // `useTheme()`.\n  if (context) return <Scoped {...props}>{content}</Scoped>;\n  return <Root {...props}>{content}</Root>;\n}\n\nTheme.displayName = 'Theme';\n\n/**\n * @deprecated Use `Theme` instead. `ThemeProvider` is kept as an alias for\n * backward compatibility and will be removed in a future major release.\n */\nexport const ThemeProvider = Theme;\n\nconst readScopeStorage = (key: string): string | undefined => {\n  if (isServer) return undefined;\n  try {\n    return localStorage.getItem(key) ?? undefined;\n  } catch {\n    return undefined;\n  }\n};\n\nconst Scoped = ({\n  storageKey,\n  defaultTheme,\n  forcedTheme,\n  accentColor,\n  grayColor,\n  style,\n  children\n}: ThemeProviderProps) => {\n  const parent = useContext(ThemeContext);\n  const isPersistent = !!storageKey;\n  const hasOverrides = !!(\n    forcedTheme ||\n    accentColor ||\n    grayColor ||\n    style ||\n    defaultTheme\n  );\n\n  // Every active scope owns its theme state so `useTheme()` always targets\n  // the nearest scope — independent of persistence. Persistent scopes seed\n  // their state from localStorage on first mount; stateless ones start from\n  // `defaultTheme` (or undefined) and live only in memory.\n  const [stored, setStored] = useState<string | undefined>(() =>\n    isPersistent\n      ? (readScopeStorage(storageKey!) ?? defaultTheme)\n      : defaultTheme\n  );\n\n  // Re-sync if the storageKey itself changes mid-life.\n  useEffect(() => {\n    if (!isPersistent) return;\n    setStored(readScopeStorage(storageKey!) ?? defaultTheme);\n    // defaultTheme is the seed only when storage is empty; intentionally\n    // excluded from deps to avoid re-applying it on prop changes.\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [storageKey, isPersistent]);\n\n  // Persist on change; clear when unset. Compare against the current\n  // storage value first so initial mounts (and StrictMode double-effects)\n  // don't write back what we just read or fire spurious storage events to\n  // other tabs.\n  useEffect(() => {\n    if (!isPersistent) return;\n    try {\n      const current = localStorage.getItem(storageKey!);\n      if (stored === undefined) {\n        if (current !== null) localStorage.removeItem(storageKey!);\n      } else if (current !== stored) {\n        localStorage.setItem(storageKey!, stored);\n      }\n    } catch {\n      // unsupported (private mode, quota exceeded)\n    }\n  }, [isPersistent, storageKey, stored]);\n\n  // Cross-tab sync.\n  useEffect(() => {\n    if (!isPersistent) return;\n    const onStorage = (e: StorageEvent) => {\n      if (e.key !== storageKey) return;\n      setStored(e.newValue ?? undefined);\n    };\n    window.addEventListener('storage', onStorage);\n    return () => window.removeEventListener('storage', onStorage);\n  }, [isPersistent, storageKey]);\n\n  // `forcedTheme` wins for display; otherwise the scope's own stored value\n  // (which falls back to the parent's via `resolvedTheme` below when empty).\n  const displayed = forcedTheme ?? stored;\n\n  // Layer scope overrides on top of the parent's context so `useTheme()`\n  // inside the scope sees the effective values. Every active scope (persistent\n  // or with overrides) owns its own `theme`/`setTheme` — persistence is\n  // orthogonal. Scopes with a `storageKey` register themselves into `scopes`\n  // so `useTheme({ storageKey })` can address them past the nearest one.\n  const layered = useMemo<UseThemeProps | undefined>(() => {\n    if (!parent) return undefined;\n    if (!isPersistent && !hasOverrides) return parent;\n    const ownRef: ScopeRef = { theme: stored, setTheme: setStored };\n    const scopes = storageKey\n      ? { ...parent.scopes, [storageKey]: ownRef }\n      : parent.scopes;\n    return {\n      ...parent,\n      theme: stored,\n      setTheme: setStored,\n      forcedTheme: forcedTheme ?? parent.forcedTheme,\n      resolvedTheme: displayed ?? parent.resolvedTheme,\n      style: style ?? parent.style,\n      accentColor: accentColor ?? parent.accentColor,\n      grayColor: grayColor ?? parent.grayColor,\n      scopes\n    };\n  }, [\n    parent,\n    isPersistent,\n    hasOverrides,\n    storageKey,\n    stored,\n    displayed,\n    forcedTheme,\n    style,\n    accentColor,\n    grayColor\n  ]);\n\n  // No-op nesting: a stateless scope with no overrides passes children\n  // through without a wrapper or new provider. Persistent scopes always\n  // render the wrapper because descendants rely on the scope's context.\n  if (!isPersistent && !hasOverrides) return <>{children}</>;\n\n  // Mirror the layered (own + inherited) values onto the wrapper so CSS rules\n  // that combine attributes — e.g. `[data-accent-color='orange'][data-theme='dark']` —\n  // match even when the consumer overrides only one attribute.\n  return (\n    <ThemeContext value={layered}>\n      <div\n        data-slot='theme-scope'\n        data-theme={layered?.resolvedTheme}\n        data-accent-color={layered?.accentColor}\n        data-gray-color={layered?.grayColor}\n        data-style={layered?.style}\n      >\n        {children}\n      </div>\n    </ThemeContext>\n  );\n};\n\nScoped.displayName = 'Theme.Scoped';\n\nconst defaultThemes: string[] = [...COLOR_SCHEMES];\n\nconst Root = ({\n  forcedTheme,\n  disableTransitionOnChange = false,\n  enableSystem = true,\n  enableColorScheme = true,\n  storageKey = 'theme',\n  themes = defaultThemes,\n  defaultTheme = enableSystem ? 'system' : 'light',\n  attribute = 'data-theme',\n  value,\n  children,\n  nonce,\n  style = 'modern',\n  accentColor = 'indigo',\n  grayColor = 'gray',\n  onThemeChange\n}: ThemeProviderProps) => {\n  const [theme, setThemeState] = useState(() =>\n    getTheme(storageKey, defaultTheme)\n  );\n  const [resolvedTheme, setResolvedTheme] = useState<string | undefined>(\n    undefined\n  );\n  const attrs = !value ? themes : Object.values(value);\n\n  const applyTheme = useCallback(\n    (theme: string | undefined) => {\n      let resolved = theme;\n      if (!resolved) return;\n\n      // If theme is system, resolve it before setting theme\n      if (theme === 'system' && enableSystem) {\n        resolved = getSystemTheme();\n      }\n\n      const name = value ? value[resolved] : resolved;\n      const enable = disableTransitionOnChange ? disableAnimation() : null;\n      const d = document.documentElement;\n\n      if (attribute === 'class') {\n        d.classList.remove(...attrs);\n\n        if (name) d.classList.add(name);\n      } else {\n        if (name) {\n          d.setAttribute(attribute, name);\n        } else {\n          d.removeAttribute(attribute);\n        }\n      }\n\n      d.setAttribute('data-style', style);\n      d.setAttribute('data-accent-color', accentColor);\n      d.setAttribute('data-gray-color', grayColor);\n\n      if (enableColorScheme) {\n        const fallback = colorSchemes.includes(defaultTheme)\n          ? defaultTheme\n          : null;\n        const colorScheme = colorSchemes.includes(resolved)\n          ? resolved\n          : fallback;\n        d.style.colorScheme = colorScheme ?? '';\n      }\n\n      enable?.();\n    },\n    [\n      style,\n      accentColor,\n      grayColor,\n      attribute,\n      attrs,\n      value,\n      enableSystem,\n      enableColorScheme,\n      defaultTheme\n    ]\n  );\n\n  const setTheme = useCallback(\n    (theme: string | undefined) => {\n      // Root has no parent to inherit from, so `undefined` is a no-op here.\n      // (Persistent scopes use `undefined` to clear and re-inherit.)\n      if (theme === undefined) return;\n      setThemeState(theme);\n\n      // Save to storage\n      try {\n        localStorage.setItem(storageKey, theme);\n      } catch (e) {\n        // Unsupported\n      }\n    },\n    [storageKey]\n  );\n\n  const handleMediaQuery = useCallback(\n    (e: MediaQueryListEvent | MediaQueryList) => {\n      const resolved = getSystemTheme(e);\n      setResolvedTheme(resolved);\n\n      if (theme === 'system' && enableSystem && !forcedTheme) {\n        applyTheme('system');\n      }\n    },\n    [theme, forcedTheme, enableSystem, applyTheme]\n  );\n\n  // Always listen to System preference\n  useEffect(() => {\n    const media = window.matchMedia(MEDIA);\n\n    media.addEventListener('change', handleMediaQuery);\n    handleMediaQuery(media);\n\n    return () => media.removeEventListener('change', handleMediaQuery);\n  }, [handleMediaQuery]);\n\n  // localStorage event handling\n  useEffect(() => {\n    const handleStorage = (e: StorageEvent) => {\n      if (e.key !== storageKey) {\n        return;\n      }\n\n      // If default theme set, use it if localstorage === null (happens on local storage manual deletion)\n      const theme = e.newValue || defaultTheme;\n      setTheme(theme);\n    };\n\n    window.addEventListener('storage', handleStorage);\n    return () => window.removeEventListener('storage', handleStorage);\n  }, [setTheme]);\n\n  // Ref-held callback so consumer render churn doesn't drive effect cadence.\n  const onThemeChangeRef = useRef(onThemeChange);\n  onThemeChangeRef.current = onThemeChange;\n  const lastRef = useRef<{ theme: string; resolved: string } | undefined>(\n    undefined\n  );\n\n  // Apply on theme/forcedTheme change, then notify on real changes.\n  useEffect(() => {\n    const target = forcedTheme ?? theme;\n    if (target) applyTheme(target);\n\n    if (!theme) return;\n    const resolved =\n      forcedTheme ?? (theme === 'system' ? resolvedTheme : theme);\n    if (!resolved) return;\n\n    const prev = lastRef.current;\n    lastRef.current = { theme, resolved };\n\n    if (\n      prev !== undefined &&\n      (prev.theme !== theme || prev.resolved !== resolved)\n    ) {\n      onThemeChangeRef.current?.(theme, resolved);\n    }\n  }, [forcedTheme, theme, resolvedTheme, applyTheme]);\n\n  const providerValue = useMemo(\n    () => ({\n      theme,\n      setTheme,\n      forcedTheme,\n      resolvedTheme:\n        forcedTheme ?? (theme === 'system' ? resolvedTheme : theme),\n      themes: enableSystem ? [...themes, 'system'] : themes,\n      systemTheme: (enableSystem ? resolvedTheme : undefined) as\n        | 'light'\n        | 'dark'\n        | undefined,\n      style,\n      accentColor,\n      grayColor,\n      // Register the root in the scopes registry so descendants can target\n      // it explicitly via `useTheme({ storageKey })`.\n      scopes: { [storageKey]: { theme, setTheme } satisfies ScopeRef }\n    }),\n    [\n      theme,\n      setTheme,\n      forcedTheme,\n      resolvedTheme,\n      enableSystem,\n      themes,\n      style,\n      accentColor,\n      grayColor,\n      storageKey\n    ]\n  );\n\n  return (\n    <ThemeContext value={providerValue}>\n      <ThemeScript\n        {...{\n          forcedTheme,\n          disableTransitionOnChange,\n          enableSystem,\n          enableColorScheme,\n          storageKey,\n          themes,\n          defaultTheme,\n          attribute,\n          value,\n          children,\n          attrs,\n          nonce,\n          style,\n          accentColor,\n          grayColor\n        }}\n      />\n      {children}\n    </ThemeContext>\n  );\n};\n\nRoot.displayName = 'Theme.Root';\n\nconst ThemeScript = memo(\n  ({\n    forcedTheme,\n    storageKey,\n    attribute,\n    enableSystem,\n    enableColorScheme,\n    defaultTheme,\n    value,\n    attrs,\n    nonce,\n    style,\n    accentColor,\n    grayColor\n  }: ThemeProviderProps & { attrs: string[]; defaultTheme: string }) => {\n    const defaultSystem = defaultTheme === 'system';\n\n    // Code-golfing the amount of characters in the script\n    const optimization = (() => {\n      if (attribute === 'class') {\n        const removeClasses = `c.remove(${attrs\n          .map((t: string) => `'${t}'`)\n          .join(',')})`;\n\n        return `var d=document.documentElement,c=d.classList;${removeClasses};`;\n      } else {\n        return `var d=document.documentElement,n='${attribute}',s='setAttribute';`;\n      }\n    })();\n\n    const fallbackColorScheme = (() => {\n      if (!enableColorScheme) {\n        return '';\n      }\n\n      const fallback = colorSchemes.includes(defaultTheme)\n        ? defaultTheme\n        : null;\n\n      if (fallback) {\n        return `if(e==='light'||e==='dark'||!e)d.style.colorScheme=e||'${defaultTheme}'`;\n      } else {\n        return `if(e==='light'||e==='dark')d.style.colorScheme=e`;\n      }\n    })();\n\n    const updateDOM = (\n      name: string,\n      literal: boolean = false,\n      setColorScheme = true\n    ) => {\n      const resolvedName = value ? value[name] : name;\n      const val = literal ? name : `'${resolvedName}'`;\n      let text = '';\n\n      // MUCH faster to set colorScheme alongside HTML attribute/class\n      // as it only incurs 1 style recalculation rather than 2\n      // This can save over 250ms of work for pages with big DOM\n      if (\n        enableColorScheme &&\n        setColorScheme &&\n        !literal &&\n        colorSchemes.includes(name)\n      ) {\n        text += `d.style.colorScheme = '${name}';`;\n      }\n\n      if (attribute === 'class') {\n        if (literal) {\n          text += `if(${val})c.add(${val})`;\n        } else if (resolvedName) {\n          text += `c.add(${val})`;\n        } else {\n          text += `null`;\n        }\n      } else {\n        if (literal) {\n          text += `if(${val})d[s](n,${val})`;\n        } else if (resolvedName) {\n          text += `d[s](n,${val})`;\n        }\n      }\n\n      return text;\n    };\n\n    const scriptSrc = (() => {\n      if (forcedTheme) {\n        return `!function(){${optimization}${updateDOM(forcedTheme)};d.setAttribute('data-style','${style}');d.setAttribute('data-accent-color','${accentColor}');d.setAttribute('data-gray-color','${grayColor}');}()`;\n      }\n\n      if (enableSystem) {\n        return `!function(){try{${optimization}var e=localStorage.getItem('${storageKey}');if('system'===e||(!e&&${defaultSystem})){var t='${MEDIA}',m=window.matchMedia(t);if(m.media!==t||m.matches){${updateDOM(\n          'dark'\n        )}}else{${updateDOM('light')}}}else if(e){${\n          value ? `var x=${JSON.stringify(value)};` : ''\n        }${updateDOM(value ? `x[e]` : 'e', true)}}${\n          !defaultSystem\n            ? `else{` + updateDOM(defaultTheme, false, false) + '}'\n            : ''\n        }${fallbackColorScheme};d.setAttribute('data-style','${style}');d.setAttribute('data-accent-color','${accentColor}');d.setAttribute('data-gray-color','${grayColor}');}catch(e){}}()`;\n      }\n\n      return `!function(){try{${optimization}var e=localStorage.getItem('${storageKey}');if(e){${\n        value ? `var x=${JSON.stringify(value)};` : ''\n      }${updateDOM(value ? `x[e]` : 'e', true)}}else{${updateDOM(\n        defaultTheme,\n        false,\n        false\n      )};}${fallbackColorScheme};d.setAttribute('data-style','${style}');d.setAttribute('data-accent-color','${accentColor}');d.setAttribute('data-gray-color','${grayColor}');}catch(t){}}();`;\n    })();\n\n    return (\n      <script\n        nonce={nonce}\n        data-slot='theme-script'\n        dangerouslySetInnerHTML={{ __html: scriptSrc }}\n      />\n    );\n  },\n  // Never re-render this component\n  () => true\n);\n\n// Helpers\nconst getTheme = (key: string, fallback?: string) => {\n  if (isServer) return undefined;\n  let theme;\n  try {\n    theme = localStorage.getItem(key) || undefined;\n  } catch (e) {\n    // Unsupported\n  }\n  return theme || fallback;\n};\n\nconst disableAnimation = () => {\n  const css = document.createElement('style');\n  css.appendChild(\n    document.createTextNode(\n      `*{-webkit-transition:none!important;transition:none!important}`\n    )\n  );\n  document.head.appendChild(css);\n\n  return () => {\n    // Force restyle\n    (() => window.getComputedStyle(document.body))();\n\n    // Wait for next tick before removing\n    setTimeout(() => {\n      document.head.removeChild(css);\n    }, 1);\n  };\n};\n\nconst getSystemTheme = (e?: MediaQueryList | MediaQueryListEvent) => {\n  if (!e) e = window.matchMedia(MEDIA);\n  const isDark = e.matches;\n  const systemTheme = isDark ? 'dark' : 'light';\n  return systemTheme;\n};\n"],"names":[],"mappings":";;;;;;;;AAqBA;AACA;AACA;AACA;AACA;AAEA;;;;;;;AAOG;AACU;;AAEX;;;AAGI;;;AAGJ;AACF;AAEM;AACJ;;;;;AAMA;;;;;AAaA;AAAa;AACb;AACF;AAEA;AAEA;;;AAGG;AACI;AAEP;AACE;AAAc;AACd;;;AAEE;AACA;;AAEJ;AAEA;AASE;AACA;AACA;;;;AAKE;;;;;;;;;;AAeA;;;;;;AAKF;;;;;;AAOE;;AACA;;AAEE;;AACwB;;AACjB;AACL;;;AAEF;;;;;;AAOF;;AACA;AACE;;AACA;AACF;AACA;;AAEF;;;AAIA;;;;;;AAOA;AACE;AAAa;AACb;AAAoC;;;AAGlC;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAGJ;;;;;;;;;;;AAWC;;;;AAKD;;;;;AAKA;AAaF;AAEA;AAEA;AAEA;AAiBE;;AAMA;AAEA;;AAGI;;;AAGA;;;AAIA;AACA;AACA;AAEA;;AAGE;AAAU;;;;AAGR;;;AAEA;;;AAIJ;AACA;AACA;;AAGE;AACE;;AAEF;AACE;;;;;AAMN;;;;;;;;;;AAWC;AAGH;;;;;;;AAQI;AACE;;;;;AAIJ;AAIF;AAEI;;;;;;;;;AAcF;;;AAIF;;;AAIE;AACE;;;;AAKA;;AAEF;AAEA;;AAEF;;AAGA;AACA;AACA;;;AAME;AACA;;AAEA;;AACA;AAEA;;AAEA;;;AAKE;;;;AAMJ;;;;AAKI;AAEA;;;;;;;;AAWD;;;;;;;;;;;AAYA;;;;;;;;;;;;;;;AAqBK;AAMV;AAEA;AAEA;AAeI;;AAGA;AACE;;;AAGK;;;;;;;AAQP;;AAEI;;AAGF;AACE;;;;;;AAMA;;;AAIJ;AAKE;AACA;;;;;AAMA;;AAGE;AACA;AAEA;;AAGF;;AAEI;;;AAEA;;;;;;;;AAMA;;;AAEA;;;AAIJ;AACF;AAEA;;AAEI;;;;AAUI;;;;;AAcR;AAOF;AACA;AACA;AAGF;AACA;AACE;AAAc;AACd;AACA;;;;;;;AAMF;AAEA;;;AAOE;AAEA;;AAEE;;;AAIE;;AAEJ;AACF;AAEA;AACE;AAAQ;AACR;;AAEA;AACF;;;;"}