@codenhub/theme 0.0.3 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -201
- package/README.md +20 -436
- package/dist/index.d.ts +31 -6
- package/dist/index.js +1 -483
- package/docs/index.md +52 -0
- package/docs/reference.md +104 -0
- package/llms-full.txt +204 -0
- package/llms.txt +13 -0
- package/package.json +17 -7
package/README.md
CHANGED
|
@@ -1,464 +1,48 @@
|
|
|
1
1
|
# @codenhub/theme
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **Factory API**: Instantiate with `createTheme()`.
|
|
8
|
-
- **Dynamic CSS Tokens**: Define a `tokenSchema` at initialization for type-safe, dynamic inline CSS custom property styling.
|
|
9
|
-
- **Zero Dependencies**: Tiny footprint and pure TypeScript.
|
|
10
|
-
- **Flexible Styling**: Works with standard CSS variables, class toggles, or Tailwind CSS.
|
|
3
|
+
Zero-dependency browser theme preference, persistence, DOM, and token manager.
|
|
11
4
|
|
|
12
5
|
## Installation
|
|
13
6
|
|
|
14
7
|
```sh
|
|
15
8
|
pnpm add @codenhub/theme
|
|
16
|
-
npm install @codenhub/theme
|
|
17
|
-
yarn add @codenhub/theme
|
|
18
|
-
bun add @codenhub/theme
|
|
19
9
|
```
|
|
20
10
|
|
|
21
11
|
## Usage
|
|
22
12
|
|
|
23
|
-
By default, `init()` uses a valid stored preference first. If there is no valid stored preference, it maps the OS color scheme to `light` or `dark`.
|
|
24
|
-
|
|
25
13
|
```ts
|
|
26
|
-
import { createTheme } from "@codenhub/theme";
|
|
14
|
+
import { createTheme, getPrePaintScript } from "@codenhub/theme";
|
|
27
15
|
|
|
28
|
-
const theme = createTheme(
|
|
29
|
-
|
|
30
|
-
theme.init();
|
|
16
|
+
const theme = createTheme().init();
|
|
31
17
|
theme.set("dark");
|
|
32
|
-
theme.toggle();
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Call `destroy()` during app or test cleanup when the instance is no longer used.
|
|
36
|
-
|
|
37
|
-
### Preventing Flash of Unstyled Content (FOUC)
|
|
38
|
-
|
|
39
|
-
Because the client-side JS bundle loads asynchronously, there can be a brief flash of the default theme before the theme manager initializes. To prevent this, inject a tiny blocking script in your HTML `<head>` before any stylesheet or content:
|
|
40
|
-
|
|
41
|
-
```html
|
|
42
|
-
<script>
|
|
43
|
-
(function () {
|
|
44
|
-
try {
|
|
45
|
-
const key = "app-theme-preference";
|
|
46
|
-
const attribute = "data-theme";
|
|
47
|
-
let theme = "light";
|
|
48
|
-
try {
|
|
49
|
-
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
|
50
|
-
theme = "dark";
|
|
51
|
-
}
|
|
52
|
-
} catch (_) {}
|
|
53
|
-
try {
|
|
54
|
-
const stored = localStorage.getItem(key);
|
|
55
|
-
// Add all your configured theme names here to validate the stored value.
|
|
56
|
-
const allowed = ["light", "dark"];
|
|
57
|
-
if (stored && allowed.includes(stored)) {
|
|
58
|
-
theme = stored;
|
|
59
|
-
}
|
|
60
|
-
} catch (_) {}
|
|
61
|
-
document.documentElement.setAttribute(attribute, theme);
|
|
62
|
-
|
|
63
|
-
// List the names of all your dark-scheme themes here.
|
|
64
|
-
// Using an explicit set avoids false matches from substring checks (e.g. "midnight" is dark
|
|
65
|
-
// but would not match "dark"; "darkroom" would match but may not be a dark theme).
|
|
66
|
-
const darkThemes = new Set(["dark"]);
|
|
67
|
-
const isDark = darkThemes.has(theme);
|
|
68
|
-
document.documentElement.style.colorScheme = isDark ? "dark" : "light";
|
|
69
|
-
document.documentElement.classList.add("theme-" + theme);
|
|
70
|
-
|
|
71
|
-
// If using Tailwind CSS, also toggle "dark" class:
|
|
72
|
-
// document.documentElement.classList.toggle("dark", isDark);
|
|
73
|
-
} catch (_) {}
|
|
74
|
-
})();
|
|
75
|
-
</script>
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
> [!WARNING]
|
|
79
|
-
> If you customize `systemTheme`, `themes`, `storageKey`, or `attribute` in `createTheme`, make sure to update the inline FOUC script to match those values. Mismatched configurations will cause a flash of the wrong theme. Also, if you set `shouldApplyClass` to `false` or use a custom class resolver, update or remove the FOUC class application block (`classList.add`) accordingly to prevent visual shifts on initialization.
|
|
80
|
-
|
|
81
|
-
## Reference
|
|
82
|
-
|
|
83
|
-
### `@codenhub/theme`
|
|
84
|
-
|
|
85
|
-
Primary entrypoint for the theme preference API.
|
|
86
|
-
|
|
87
|
-
```ts
|
|
88
|
-
import { createTheme, DARK_THEME, LIGHT_THEME, THEME_CHANGE_EVENT } from "@codenhub/theme";
|
|
89
|
-
import type {
|
|
90
|
-
SystemThemeMap,
|
|
91
|
-
Theme,
|
|
92
|
-
ThemeChangeDetail,
|
|
93
|
-
ThemeChangeListener,
|
|
94
|
-
ThemeChangeSource,
|
|
95
|
-
ThemeClassResolver,
|
|
96
|
-
ThemeDefinition,
|
|
97
|
-
ThemeOptions,
|
|
98
|
-
} from "@codenhub/theme";
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
Supported import paths:
|
|
102
|
-
|
|
103
|
-
| Path | Description |
|
|
104
|
-
| ----------------- | ----------------------------------- |
|
|
105
|
-
| `@codenhub/theme` | Main JavaScript and TypeScript API. |
|
|
106
|
-
|
|
107
|
-
#### `createTheme()`
|
|
108
|
-
|
|
109
|
-
Factory function that returns a `Theme` manager instance.
|
|
110
|
-
|
|
111
|
-
```ts
|
|
112
|
-
function createTheme<TSchema extends Record<string, string> = Record<string, string>>(
|
|
113
|
-
options?: ThemeOptions<TSchema>,
|
|
114
|
-
): Theme<TSchema>;
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
#### `Theme`
|
|
118
|
-
|
|
119
|
-
Manages the active theme, storage preference, DOM attribute, `colorScheme` style, classes, system preference listener, dynamic tokens, and change notifications.
|
|
120
|
-
|
|
121
|
-
```ts
|
|
122
|
-
interface Theme<TSchema extends Record<string, string> = Record<string, string>> {
|
|
123
|
-
init(tokens?: Partial<Record<keyof TSchema, string>>): this;
|
|
124
|
-
get(): ThemeDefinition<TSchema>;
|
|
125
|
-
set(name: string, tokens?: Partial<Record<keyof TSchema, string>>): ThemeDefinition<TSchema>;
|
|
126
|
-
toggle(tokens?: Partial<Record<keyof TSchema, string>>): ThemeDefinition<TSchema>;
|
|
127
|
-
clearPreference(): ThemeDefinition<TSchema>;
|
|
128
|
-
getStored(): string | null;
|
|
129
|
-
getSystem(): ThemeDefinition<TSchema>;
|
|
130
|
-
subscribe(listener: ThemeChangeListener<TSchema>): () => void;
|
|
131
|
-
destroy(): void;
|
|
132
|
-
}
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
Import from `@codenhub/theme`.
|
|
136
|
-
|
|
137
|
-
The factory throws `Error` when configured theme names are empty, duplicated, invalid for CSS class application, or referenced by `defaultTheme` or `systemTheme` without being configured.
|
|
138
|
-
|
|
139
|
-
##### `init()`
|
|
140
|
-
|
|
141
|
-
Registers the system preference listener, resolves the initial theme, applies it, and emits a change with source `"init"`. Can optionally accept initial token value overrides.
|
|
142
|
-
|
|
143
|
-
```ts
|
|
144
|
-
function init(tokens?: Partial<Record<keyof TSchema, string>>): this;
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
Repeated calls do not register duplicate system preference listeners.
|
|
148
18
|
|
|
149
|
-
|
|
19
|
+
// Get inline IIFE script string for <head> to prevent FOUC:
|
|
20
|
+
const script = theme.getPrePaintScript(); // or standalone getPrePaintScript()
|
|
150
21
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
```ts
|
|
154
|
-
function get(): ThemeDefinition<TSchema>;
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
##### `set()`
|
|
158
|
-
|
|
159
|
-
Activates a configured theme by name, applies any dynamic token overrides, and stores the explicit preference when browser storage is available. Active overrides persist across subsequent theme changes unless cleared (by passing new overrides or an empty object).
|
|
160
|
-
|
|
161
|
-
```ts
|
|
162
|
-
function set(name: string, tokens?: Partial<Record<keyof TSchema, string>>): ThemeDefinition<TSchema>;
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
Throws `Error` when `name` is not configured.
|
|
166
|
-
|
|
167
|
-
##### `toggle()`
|
|
168
|
-
|
|
169
|
-
Toggles between the configured system light and dark themes based on the active theme's `colorScheme`, applies any dynamic token overrides, then stores the explicit preference when browser storage is available. The target name is always taken from `systemTheme.light` or `systemTheme.dark`, not by cycling the active theme name. In multi-theme setups where the active theme is not one of the system themes, `toggle()` still targets `systemTheme.light` or `systemTheme.dark`. Active overrides persist across subsequent theme changes unless cleared (by passing new overrides or an empty object).
|
|
170
|
-
|
|
171
|
-
```ts
|
|
172
|
-
function toggle(tokens?: Partial<Record<keyof TSchema, string>>): ThemeDefinition<TSchema>;
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
##### `clearPreference()`
|
|
176
|
-
|
|
177
|
-
Removes the stored preference and activates the current system theme.
|
|
178
|
-
|
|
179
|
-
```ts
|
|
180
|
-
function clearPreference(): ThemeDefinition<TSchema>;
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
##### `getStored()`
|
|
184
|
-
|
|
185
|
-
Returns the stored theme name when it exists and is configured.
|
|
186
|
-
|
|
187
|
-
```ts
|
|
188
|
-
function getStored(): string | null;
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
Returns `null` during SSR, when storage is unavailable, when storage access throws, or when the stored name is not configured.
|
|
192
|
-
|
|
193
|
-
##### `getSystem()`
|
|
194
|
-
|
|
195
|
-
Returns the configured theme for the current `prefers-color-scheme` value.
|
|
196
|
-
|
|
197
|
-
```ts
|
|
198
|
-
function getSystem(): ThemeDefinition<TSchema>;
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
Returns the default theme during SSR or when `matchMedia` is unavailable.
|
|
202
|
-
|
|
203
|
-
##### `subscribe()`
|
|
204
|
-
|
|
205
|
-
Registers an in-process listener for theme changes.
|
|
206
|
-
|
|
207
|
-
```ts
|
|
208
|
-
function subscribe(listener: ThemeChangeListener<TSchema>): () => void;
|
|
22
|
+
// Remove media-query/storage listeners and subscribers on teardown.
|
|
23
|
+
theme.destroy({ revertDom: true });
|
|
209
24
|
```
|
|
210
25
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
##### `destroy()`
|
|
214
|
-
|
|
215
|
-
Removes the system preference listener, clears in-process subscribers, and resets active tokens and the active theme name to `defaultTheme`. Safe to call before re-initializing with `init()`.
|
|
216
|
-
|
|
217
|
-
```ts
|
|
218
|
-
function destroy(): void;
|
|
219
|
-
```
|
|
26
|
+
## Documentation
|
|
220
27
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
#### `ThemeOptions`
|
|
224
|
-
|
|
225
|
-
```ts
|
|
226
|
-
interface ThemeOptions<TSchema extends Record<string, string> = Record<string, string>> {
|
|
227
|
-
themes?: readonly ThemeDefinition<TSchema>[];
|
|
228
|
-
defaultTheme?: string;
|
|
229
|
-
systemTheme?: SystemThemeMap;
|
|
230
|
-
storageKey?: string;
|
|
231
|
-
attribute?: string;
|
|
232
|
-
isTailwindCss?: boolean;
|
|
233
|
-
shouldApplyClass?: boolean | ThemeClassResolver<TSchema>;
|
|
234
|
-
tokenSchema?: TSchema;
|
|
235
|
-
}
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
| Option | Type | Default | Description |
|
|
239
|
-
| ------------------ | ------------------------------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
240
|
-
| `themes` | `readonly ThemeDefinition[]` | `[LIGHT_THEME, DARK_THEME]` | Defines available themes. |
|
|
241
|
-
| `defaultTheme` | `string` | `"light"` | Theme used before init and when browser APIs are unavailable. |
|
|
242
|
-
| `systemTheme` | `SystemThemeMap` | `{ light: "light", dark: "dark" }` | Maps OS light and dark preferences to configured theme names. |
|
|
243
|
-
| `storageKey` | `string` | `"app-theme-preference"` | Key used for `localStorage`. |
|
|
244
|
-
| `attribute` | `string` | `"data-theme"` | Attribute set on `document.documentElement`. |
|
|
245
|
-
| `isTailwindCss` | `boolean` | `false` | Toggles the `dark` class when the active theme has `colorScheme: "dark"`. |
|
|
246
|
-
| `shouldApplyClass` | `boolean` or `(theme: ThemeDefinition) => string` | `true` | Adds `theme-${name}`, no class, or a resolver-provided class to `document.documentElement`. |
|
|
247
|
-
| `tokenSchema` | `TSchema` | `undefined` | Schema mapping theme token names to their corresponding CSS Custom Property names. |
|
|
248
|
-
|
|
249
|
-
When class application is enabled, each theme application removes classes for all configured themes, then adds the class for the active theme.
|
|
250
|
-
|
|
251
|
-
#### `ThemeDefinition`
|
|
252
|
-
|
|
253
|
-
```ts
|
|
254
|
-
interface ThemeDefinition<TSchema extends Record<string, string> = Record<string, string>> {
|
|
255
|
-
name: string;
|
|
256
|
-
colorScheme: "light" | "dark";
|
|
257
|
-
tokens?: Partial<Record<keyof TSchema, string>>;
|
|
258
|
-
}
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
| Field | Type | Description |
|
|
262
|
-
| ------------- | ---------------------------------------- | ---------------------------------------------------------------- |
|
|
263
|
-
| `name` | `string` | Unique theme name used for storage, attributes, and class names. |
|
|
264
|
-
| `colorScheme` | `"light" \| "dark"` | Browser color scheme applied through `style.colorScheme`. |
|
|
265
|
-
| `tokens` | `Partial<Record<keyof TSchema, string>>` | Optional theme-specific static token values. |
|
|
266
|
-
|
|
267
|
-
#### `SystemThemeMap`
|
|
268
|
-
|
|
269
|
-
```ts
|
|
270
|
-
interface SystemThemeMap {
|
|
271
|
-
light: string;
|
|
272
|
-
dark: string;
|
|
273
|
-
}
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
| Field | Type | Description |
|
|
277
|
-
| ------- | -------- | ----------------------------------------------------------- |
|
|
278
|
-
| `light` | `string` | Configured theme name used when the OS preference is light. |
|
|
279
|
-
| `dark` | `string` | Configured theme name used when the OS preference is dark. |
|
|
280
|
-
|
|
281
|
-
#### `ThemeClassResolver`
|
|
282
|
-
|
|
283
|
-
Returns the class name applied to `document.documentElement` for a theme.
|
|
284
|
-
|
|
285
|
-
```ts
|
|
286
|
-
type ThemeClassResolver<TSchema extends Record<string, string> = Record<string, string>> = (
|
|
287
|
-
theme: ThemeDefinition<TSchema>,
|
|
288
|
-
) => string;
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
The returned class name must be a single non-empty class token without whitespace.
|
|
292
|
-
|
|
293
|
-
`init()`, `set()`, `toggle()`, `clearPreference()`, or system preference changes throw `Error` if the resolver returns an empty class name or a class name containing whitespace.
|
|
294
|
-
|
|
295
|
-
#### `ThemeChangeListener`
|
|
296
|
-
|
|
297
|
-
Listener passed to `theme.subscribe()`.
|
|
298
|
-
|
|
299
|
-
```ts
|
|
300
|
-
type ThemeChangeListener<TSchema extends Record<string, string> = Record<string, string>> = (
|
|
301
|
-
detail: ThemeChangeDetail<TSchema>,
|
|
302
|
-
) => void;
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
#### `THEME_CHANGE_EVENT`
|
|
306
|
-
|
|
307
|
-
Window event name dispatched after theme changes in browser environments.
|
|
308
|
-
|
|
309
|
-
```ts
|
|
310
|
-
const THEME_CHANGE_EVENT = "themechange";
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
#### `ThemeChangeDetail`
|
|
314
|
-
|
|
315
|
-
```ts
|
|
316
|
-
interface ThemeChangeDetail<TSchema extends Record<string, string> = Record<string, string>> {
|
|
317
|
-
name: string;
|
|
318
|
-
theme: ThemeDefinition<TSchema>;
|
|
319
|
-
source: ThemeChangeSource;
|
|
320
|
-
}
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
| Field | Type | Description |
|
|
324
|
-
| -------- | ------------------- | ----------------------------------------- |
|
|
325
|
-
| `name` | `string` | Active theme name after the change. |
|
|
326
|
-
| `theme` | `ThemeDefinition` | Active theme definition after the change. |
|
|
327
|
-
| `source` | `ThemeChangeSource` | Reason the theme change was emitted. |
|
|
328
|
-
|
|
329
|
-
#### `ThemeChangeSource`
|
|
330
|
-
|
|
331
|
-
Reason a theme change was emitted.
|
|
332
|
-
|
|
333
|
-
```ts
|
|
334
|
-
type ThemeChangeSource = "init" | "set" | "toggle" | "clearPreference" | "system";
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
| Value | Emitted when |
|
|
338
|
-
| ------------------- | -------------------------------------------------- |
|
|
339
|
-
| `"init"` | `init()` resolves and applies the initial theme. |
|
|
340
|
-
| `"set"` | `set()` applies an explicit theme preference. |
|
|
341
|
-
| `"toggle"` | `toggle()` switches between system light and dark. |
|
|
342
|
-
| `"clearPreference"` | `clearPreference()` removes stored preference. |
|
|
343
|
-
| `"system"` | OS color scheme changes with no stored preference. |
|
|
344
|
-
|
|
345
|
-
#### Built-In Themes
|
|
346
|
-
|
|
347
|
-
Built-in theme definitions.
|
|
348
|
-
|
|
349
|
-
```ts
|
|
350
|
-
const LIGHT_THEME: ThemeDefinition = { name: "light", colorScheme: "light" };
|
|
351
|
-
const DARK_THEME: ThemeDefinition = { name: "dark", colorScheme: "dark" };
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
## Examples
|
|
355
|
-
|
|
356
|
-
### Dynamic CSS Tokens
|
|
357
|
-
|
|
358
|
-
```ts
|
|
359
|
-
import { createTheme } from "@codenhub/theme";
|
|
360
|
-
|
|
361
|
-
// 1. Define schema
|
|
362
|
-
const tokenSchema = {
|
|
363
|
-
primary: "--color-primary",
|
|
364
|
-
background: "--color-bg",
|
|
365
|
-
} as const;
|
|
366
|
-
|
|
367
|
-
// 2. Initialize with schema and optional theme static token values
|
|
368
|
-
const theme = createTheme({
|
|
369
|
-
tokenSchema,
|
|
370
|
-
themes: [
|
|
371
|
-
{
|
|
372
|
-
name: "light",
|
|
373
|
-
colorScheme: "light",
|
|
374
|
-
tokens: { primary: "#0070f3", background: "#ffffff" },
|
|
375
|
-
},
|
|
376
|
-
{
|
|
377
|
-
name: "dark",
|
|
378
|
-
colorScheme: "dark",
|
|
379
|
-
tokens: { primary: "#3291ff", background: "#000000" },
|
|
380
|
-
},
|
|
381
|
-
],
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
theme.init();
|
|
385
|
-
|
|
386
|
-
// 3. Switch theme and pass runtime overrides (e.g. from dynamic branding API)
|
|
387
|
-
theme.set("dark", { primary: "#ff007f" });
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
### Define CSS Tokens
|
|
391
|
-
|
|
392
|
-
```css
|
|
393
|
-
:root,
|
|
394
|
-
[data-theme="light"] {
|
|
395
|
-
--color-background: white;
|
|
396
|
-
--color-foreground: black;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
[data-theme="dark"] {
|
|
400
|
-
--color-background: black;
|
|
401
|
-
--color-foreground: white;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
body {
|
|
405
|
-
background: var(--color-background);
|
|
406
|
-
color: var(--color-foreground);
|
|
407
|
-
}
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
### Add More Themes
|
|
411
|
-
|
|
412
|
-
```ts
|
|
413
|
-
import { createTheme, DARK_THEME, LIGHT_THEME } from "@codenhub/theme";
|
|
414
|
-
|
|
415
|
-
const theme = createTheme({
|
|
416
|
-
themes: [LIGHT_THEME, DARK_THEME, { name: "high-contrast", colorScheme: "dark" }],
|
|
417
|
-
systemTheme: { light: "light", dark: "high-contrast" },
|
|
418
|
-
shouldApplyClass: (definition) => `mode-${definition.name}`,
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
theme.init();
|
|
422
|
-
theme.set("high-contrast");
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
### Listen For Changes
|
|
426
|
-
|
|
427
|
-
```ts
|
|
428
|
-
import { createTheme, THEME_CHANGE_EVENT, type ThemeChangeDetail } from "@codenhub/theme";
|
|
429
|
-
|
|
430
|
-
const theme = createTheme().init();
|
|
431
|
-
|
|
432
|
-
const unsubscribe = theme.subscribe((detail) => {
|
|
433
|
-
console.log(detail.name, detail.theme, detail.source);
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
window.addEventListener(THEME_CHANGE_EVENT, (event) => {
|
|
437
|
-
const detail = (event as CustomEvent<ThemeChangeDetail>).detail;
|
|
438
|
-
|
|
439
|
-
console.log(detail.name);
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
unsubscribe();
|
|
443
|
-
theme.destroy();
|
|
444
|
-
```
|
|
28
|
+
- [Documentation overview](docs/index.md)
|
|
29
|
+
- [API, persistence, DOM, and SSR behavior](docs/reference.md)
|
|
445
30
|
|
|
446
31
|
## Requirements
|
|
447
32
|
|
|
448
|
-
- Browser integration uses `document.documentElement`, `
|
|
449
|
-
|
|
450
|
-
-
|
|
451
|
-
|
|
452
|
-
- Consumers
|
|
453
|
-
|
|
33
|
+
- Browser integration uses `document.documentElement`, `localStorage`,
|
|
34
|
+
`matchMedia`, `storage` events, and `CustomEvent`.
|
|
35
|
+
- SSR is supported by skipping unavailable browser work and using the configured
|
|
36
|
+
default theme.
|
|
37
|
+
- Consumers provide CSS selectors, variables, visual tokens, and any pre-paint
|
|
38
|
+
script needed to prevent a theme flash.
|
|
454
39
|
|
|
455
40
|
## Notes
|
|
456
41
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
- Does not manage user consent requirements for storage.
|
|
42
|
+
Construction validates theme names, mappings, attributes, classes, and token
|
|
43
|
+
schemas and throws on invalid configuration. Storage failures are reported to
|
|
44
|
+
the console and treated as unavailable storage.
|
|
461
45
|
|
|
462
46
|
## License
|
|
463
47
|
|
|
464
|
-
|
|
48
|
+
Licensed under Apache-2.0.
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ interface ThemeDefinition<TSchema extends Record<string, string> = Record<string
|
|
|
7
7
|
colorScheme: "light" | "dark";
|
|
8
8
|
/** Optional theme-specific static token values. */
|
|
9
9
|
tokens?: Partial<Record<keyof TSchema, string>>;
|
|
10
|
+
/** Optional theme name activated when calling `toggle()` from this theme. */
|
|
11
|
+
pairedTheme?: string;
|
|
10
12
|
}
|
|
11
13
|
/** Mapping from OS color-scheme preferences to configured theme names. */
|
|
12
14
|
interface SystemThemeMap {
|
|
@@ -69,15 +71,18 @@ interface Theme<TSchema extends Record<string, string> = Record<string, string>>
|
|
|
69
71
|
* Retrieves the active theme configuration including static and computed tokens.
|
|
70
72
|
*
|
|
71
73
|
* Token values are merged in this priority order (last wins):
|
|
72
|
-
* 1. CSS computed style — values read from `window.getComputedStyle` for tokens not defined in JS.
|
|
74
|
+
* 1. CSS computed style — values read from `window.getComputedStyle` for tokens not defined in JS (unless `options.skipComputed` is true).
|
|
73
75
|
* 2. Theme static tokens — values defined in `ThemeDefinition.tokens` for the active theme.
|
|
74
76
|
* 3. Runtime overrides — values passed to `init()`, `set()`, `toggle()`, or other methods.
|
|
75
77
|
*
|
|
78
|
+
* @param options - Optional getter options. Set `skipComputed: true` to avoid reading computed styles from the DOM via `window.getComputedStyle`, preventing potential layout reflows.
|
|
76
79
|
* @returns The active `ThemeDefinition` object. If `tokenSchema` is configured and a token is not
|
|
77
80
|
* explicitly defined in JS, its value is dynamically resolved from the computed style of the root DOM element in browser environments.
|
|
78
|
-
* @warning Reading computed styles from the DOM via `window.getComputedStyle` can trigger a synchronous layout reflow. Avoid calling `get()` frequently or inside high-performance loops
|
|
81
|
+
* @warning Reading computed styles from the DOM via `window.getComputedStyle` can trigger a synchronous layout reflow. Avoid calling `get()` frequently or inside high-performance loops, or pass `{ skipComputed: true }`.
|
|
79
82
|
*/
|
|
80
|
-
get(
|
|
83
|
+
get(options?: {
|
|
84
|
+
skipComputed?: boolean;
|
|
85
|
+
}): ThemeDefinition<TSchema>;
|
|
81
86
|
/**
|
|
82
87
|
* Activates a configured theme by name and updates the stored preference in `localStorage`.
|
|
83
88
|
*
|
|
@@ -127,14 +132,24 @@ interface Theme<TSchema extends Record<string, string> = Record<string, string>>
|
|
|
127
132
|
* @sideEffect Adds the listener to the internal callbacks registry.
|
|
128
133
|
*/
|
|
129
134
|
subscribe(listener: ThemeChangeListener<TSchema>): () => void;
|
|
135
|
+
/**
|
|
136
|
+
* Generates a synchronous inline IIFE script string to inject into document `<head>`.
|
|
137
|
+
* Prevents Flash of Unstyled Content (FOUC) by applying storage or system theme before render.
|
|
138
|
+
*
|
|
139
|
+
* @returns Minified JavaScript script string.
|
|
140
|
+
*/
|
|
141
|
+
getPrePaintScript(): string;
|
|
130
142
|
/**
|
|
131
143
|
* Cleans up the theme instance by removing all in-process change listeners and the system
|
|
132
144
|
* preference media query listener. Resets active tokens and the active theme name to the
|
|
133
145
|
* configured `defaultTheme` so the instance can be safely re-initialized with `init()`.
|
|
134
146
|
*
|
|
135
|
-
* @
|
|
147
|
+
* @param options - Optional cleanup options. Set `revertDom: true` to remove configured DOM attributes, classes, and CSS custom properties applied to `document.documentElement`.
|
|
148
|
+
* @sideEffect Removes event listeners from `window` and clears internal subscriber sets. If `revertDom: true`, removes theme attributes, classes, and custom properties from the DOM root.
|
|
136
149
|
*/
|
|
137
|
-
destroy(
|
|
150
|
+
destroy(options?: {
|
|
151
|
+
revertDom?: boolean;
|
|
152
|
+
}): void;
|
|
138
153
|
}
|
|
139
154
|
//#endregion
|
|
140
155
|
//#region src/constants.d.ts
|
|
@@ -155,4 +170,14 @@ declare const DARK_THEME: ThemeDefinition;
|
|
|
155
170
|
*/
|
|
156
171
|
declare function createTheme<TSchema extends Record<string, string> = Record<string, string>>(options?: ThemeOptions<TSchema>): Theme<TSchema>;
|
|
157
172
|
//#endregion
|
|
158
|
-
|
|
173
|
+
//#region src/pre-paint.d.ts
|
|
174
|
+
/**
|
|
175
|
+
* Generates a synchronous inline IIFE script string to inject into document `<head>`.
|
|
176
|
+
* Prevents Flash of Unstyled Content (FOUC) by applying storage or system theme before render.
|
|
177
|
+
*
|
|
178
|
+
* @param options - Configuration options used to determine storage keys, attributes, default/system themes, custom class resolvers, and token schemas.
|
|
179
|
+
* @returns Minified JavaScript script string.
|
|
180
|
+
*/
|
|
181
|
+
declare function getPrePaintScript<TSchema extends Record<string, string> = Record<string, string>>(options?: ThemeOptions<TSchema>): string;
|
|
182
|
+
//#endregion
|
|
183
|
+
export { DARK_THEME, LIGHT_THEME, type SystemThemeMap, THEME_CHANGE_EVENT, type Theme, type ThemeChangeDetail, type ThemeChangeListener, type ThemeChangeSource, type ThemeClassResolver, type ThemeDefinition, type ThemeOptions, createTheme, getPrePaintScript };
|