{"version":3,"sources":["../src/index.ts","../src/is-dark-reader-active.ts","../src/watch-dark-reader.ts","../src/theme.ts","../src/mount-theme-toggle-button.ts"],"sourcesContent":["export { isDarkReaderActive } from './is-dark-reader-active.js';\nexport { watchDarkReader } from './watch-dark-reader.js';\nexport {\n  getTheme,\n  setTheme,\n  getCurrentTheme,\n  toggleTheme,\n  initThemeEarly,\n  type Theme,\n  type ThemeOptions,\n} from './theme.js';\nexport {\n  mountThemeToggleButton,\n  type MountOptions,\n} from './mount-theme-toggle-button.js';\n","/**\n * Returns true when the Dark Reader browser extension is actively modifying\n * the page. Checks all known signals across dynamic, filter, and static modes.\n */\nexport function isDarkReaderActive(doc: Document = document): boolean {\n  const html = doc.documentElement;\n  return (\n    html.hasAttribute('data-darkreader-scheme') ||\n    html.hasAttribute('data-darkreader-mode') ||\n    doc.querySelector('meta[name=\"darkreader\"]') != null ||\n    doc.getElementById('dark-reader-style') != null\n  );\n}\n","import { isDarkReaderActive } from './is-dark-reader-active.js';\n\n/**\n * Watch for Dark Reader being enabled or disabled. Calls `onChange` immediately\n * and whenever DR signals appear or disappear. Returns an unsubscribe function.\n */\nexport function watchDarkReader(\n  onChange: (active: boolean) => void,\n  doc: Document = document,\n): () => void {\n  const html = doc.documentElement;\n  const check = () => onChange(isDarkReaderActive(doc));\n\n  check();\n\n  const htmlObserver = new MutationObserver(check);\n  htmlObserver.observe(html, {\n    attributes: true,\n    attributeFilter: ['data-darkreader-scheme', 'data-darkreader-mode'],\n  });\n\n  let headObserver: MutationObserver | null = null;\n  if (doc.head) {\n    headObserver = new MutationObserver(check);\n    headObserver.observe(doc.head, { childList: true, subtree: true });\n  }\n\n  return () => {\n    htmlObserver.disconnect();\n    headObserver?.disconnect();\n  };\n}\n","export type Theme = 'light' | 'dark';\n\nexport interface ThemeOptions {\n  /** localStorage key. Default: `theme` */\n  storageKey?: string;\n  /** Attribute on `<html>`. Default: `data-theme` */\n  attribute?: string;\n  /** Attribute value for dark mode. Default: `dark` */\n  darkValue?: string;\n  /** Use prefers-color-scheme when no saved preference exists. Default: false */\n  respectSystemPreference?: boolean;\n}\n\nfunction getStorage(doc: Document): Storage | null {\n  return doc.defaultView?.localStorage ?? null;\n}\n\nfunction prefersDark(doc: Document): boolean {\n  return doc.defaultView?.matchMedia('(prefers-color-scheme: dark)').matches ?? false;\n}\n\n/** Read saved theme, optionally falling back to system preference. */\nexport function getTheme(options: ThemeOptions = {}, doc: Document = document): Theme {\n  const { storageKey = 'theme', respectSystemPreference = false } = options;\n  const saved = getStorage(doc)?.getItem(storageKey);\n  if (saved === 'dark' || saved === 'light') return saved;\n  if (respectSystemPreference && prefersDark(doc)) return 'dark';\n  return 'light';\n}\n\n/** Apply theme to `<html>` and persist to localStorage. */\nexport function setTheme(theme: Theme, options: ThemeOptions = {}, doc: Document = document): void {\n  const { storageKey = 'theme', attribute = 'data-theme', darkValue = 'dark' } = options;\n  const html = doc.documentElement;\n  if (theme === 'dark') {\n    html.setAttribute(attribute, darkValue);\n  } else {\n    html.removeAttribute(attribute);\n  }\n  getStorage(doc)?.setItem(storageKey, theme);\n}\n\n/** Read current theme from the DOM attribute (not localStorage). */\nexport function getCurrentTheme(options: ThemeOptions = {}, doc: Document = document): Theme {\n  const { attribute = 'data-theme', darkValue = 'dark' } = options;\n  return doc.documentElement.getAttribute(attribute) === darkValue ? 'dark' : 'light';\n}\n\n/** Toggle theme; returns the new value. */\nexport function toggleTheme(options: ThemeOptions = {}, doc: Document = document): Theme {\n  const next: Theme = getCurrentTheme(options, doc) === 'dark' ? 'light' : 'dark';\n  setTheme(next, options, doc);\n  return next;\n}\n\n/** Apply saved theme before first paint to avoid flash. Call from an inline `<head>` script. */\nexport function initThemeEarly(options: ThemeOptions = {}, doc: Document = document): Theme {\n  const theme = getTheme(options, doc);\n  setTheme(theme, options, doc);\n  return theme;\n}\n","import { isDarkReaderActive } from './is-dark-reader-active.js';\nimport { watchDarkReader } from './watch-dark-reader.js';\nimport {\n  getTheme,\n  setTheme,\n  toggleTheme,\n  type Theme,\n  type ThemeOptions,\n} from './theme.js';\n\nexport interface MountOptions extends ThemeOptions {\n  /** Button element or CSS selector */\n  button: string | HTMLElement;\n  /** Label element or CSS selector (optional; falls back to button textContent) */\n  label?: string | HTMLElement;\n  /** Button label text in each theme state */\n  labels?: { light: string; dark: string };\n  /** Hide the button while Dark Reader is active. Default: true */\n  hideWhenDarkReaderActive?: boolean;\n  /** Called after initial mount and on each toggle */\n  onThemeChange?: (theme: Theme) => void;\n}\n\nfunction resolveElement(ref: string | HTMLElement, doc: Document): HTMLElement | null {\n  return typeof ref === 'string' ? doc.querySelector<HTMLElement>(ref) : ref;\n}\n\n/**\n * Wire a button to toggle `data-theme` on `<html>`, persist preference, update\n * label text, and hide the button when Dark Reader is active.\n * Returns an unmount function.\n */\nexport function mountThemeToggleButton(\n  options: MountOptions,\n  doc: Document = document,\n): () => void {\n  const button = resolveElement(options.button, doc);\n  if (!button) {\n    throw new Error('mountThemeToggleButton: button element not found');\n  }\n\n  const labelEl = options.label ? resolveElement(options.label, doc) : null;\n  const labels = options.labels ?? { light: 'Dark', dark: 'Light' };\n  const hideWhenDR = options.hideWhenDarkReaderActive !== false;\n\n  const updateLabel = (theme: Theme) => {\n    const text = theme === 'dark' ? labels.dark : labels.light;\n    if (labelEl) {\n      labelEl.textContent = text;\n    } else {\n      button.textContent = text;\n    }\n  };\n\n  const theme = getTheme(options, doc);\n  setTheme(theme, options, doc);\n  updateLabel(theme);\n  options.onThemeChange?.(theme);\n\n  const onClick = () => {\n    const next = toggleTheme(options, doc);\n    updateLabel(next);\n    options.onThemeChange?.(next);\n  };\n  button.addEventListener('click', onClick);\n\n  let unwatchDR: (() => void) | undefined;\n  if (hideWhenDR) {\n    unwatchDR = watchDarkReader((active) => {\n      button.style.visibility = active ? 'hidden' : '';\n    }, doc);\n  }\n\n  return () => {\n    button.removeEventListener('click', onClick);\n    unwatchDR?.();\n  };\n}\n\nexport { isDarkReaderActive, watchDarkReader, getTheme, setTheme, toggleTheme };\nexport type { Theme, ThemeOptions };\n"],"mappings":"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","names":["theme"]}