/** What a person picked. `"system"` means "follow the operating system", live. */ export type ThemeMode = "light" | "dark" | "system"; /** What is on screen right now. */ export type ResolvedTheme = "light" | "dark"; export interface ThemeOptions { /** * The `localStorage` key holding the choice. Keep the key your app already uses: a new key * resets everybody's choice once. */ key: string; /** * What an empty, unreadable or unrecognised key means. Defaults to `"system"`. */ defaultMode?: ThemeMode; /** * Where the resolved theme goes on ``. `"class"` toggles `dark`, which is what * Tailwind's `dark:` variant and `@gusnips/tokens` read. `"data-theme"` writes `"light"` or * `"dark"` into that attribute, for a stylesheet whose `@custom-variant` reads it. Defaults * to `"class"`. */ attribute?: "class" | "data-theme"; /** * The browser toolbar colour for each theme, written to every ``. * Leave it out and the tags are not touched. It is needed whenever the choice can differ from * the OS setting: a `media` query on the tag follows the OS, not your app. */ themeColor?: Readonly>; } export interface ThemeSnapshot { readonly mode: ThemeMode; readonly resolved: ResolvedTheme; } export interface ThemeController { getSnapshot(): ThemeSnapshot; /** Applies at once, and is remembered when storage allows it. */ setMode(mode: ThemeMode): void; subscribe(listener: () => void): () => void; } declare global { interface Window { /** The page's one theme controller. See {@link startTheme}. */ __frontkitTheme?: ThemeController; } } /** * Reads the choice, paints it, and keeps it painted: when the OS switches (a laptop that goes * dark at sunset), when another tab picks a theme, and when the page comes back from the * back-forward cache. * * **This function is serialized.** `@gusnips/vite/theme` writes `startTheme.toString()` into the * pre-paint file, so it must not reference anything outside its own body: no imports, no helpers * or constants from this module. It would still pass here and throw a `ReferenceError` in that * file, before first paint. The vite package's test runs the serialized copy in a bare context * to catch that. Comments go up here rather than in the body, because the body ships. * * **One per page.** The first call stores the controller on `window` and later calls return it, * whatever options they pass. Two would both follow the OS, and the one that missed a `setMode` * would repaint its stale choice on the next change. So the pre-paint script and the app must * pass the same options, which is easiest when both import them from one module. * * **Blocked storage is not an empty key.** When storage throws, the default applies at boot, and * a choice made afterwards lasts until the page is left. A resync never replaces that choice * with the default, because a read that failed says nothing about what was chosen. * * "system" is stored as the word, not as an empty key. An empty key means the default, and the * default is not always "system". * * **It writes to the page when the theme changes, and at no other time.** A page may override the * class for its own lifetime, as a public booking page that is always light does, and keeps that * override until the theme really changes. Repainting on every resync would undo it a moment later, * because `pageshow` fires on every load, not only on a back-forward restore. */ export declare function startTheme(options: ThemeOptions): ThemeController; export interface Theme { /** The choice and what it resolves to, re-rendering when either changes. */ useTheme(): ThemeSnapshot & { setMode(mode: ThemeMode): void; }; /** For code outside React: a keyboard shortcut, a command palette. Browser only. */ setMode(mode: ThemeMode): void; /** For code outside React. Answers the prerender snapshot where there is no window. */ getSnapshot(): ThemeSnapshot; } /** * The React side of {@link startTheme}. Nothing starts at import, so a module calling this at * its top level is safe to prerender. The controller starts on first use, or is found if the * pre-paint script already started it. * * During a prerender, and while hydrating, `useTheme` answers the default. `"system"` cannot be * resolved without a window, so it reads as light, as `color-scheme` does before the pre-paint * script runs. React then re-renders with the real snapshot, so a toggle hydrates cleanly and * shows the right choice a frame later instead of failing to hydrate. */ export declare function createTheme(options: ThemeOptions): Theme; //# sourceMappingURL=theme.d.ts.map