@codenhub/theme 0.0.2 → 0.0.3
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 +171 -62
- package/dist/index.d.ts +116 -42
- package/dist/index.js +397 -112
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
# @codenhub/theme
|
|
2
2
|
|
|
3
|
-
Small zero-dependency theme preference helper for browser apps. It applies a theme name to the document, updates `document.documentElement.style.colorScheme`, and
|
|
3
|
+
Small zero-dependency theme preference helper for browser apps. It applies a theme name to the document, updates `document.documentElement.style.colorScheme`, and supports managing dynamic CSS tokens.
|
|
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.
|
|
4
11
|
|
|
5
12
|
## Installation
|
|
6
13
|
|
|
7
14
|
```sh
|
|
8
15
|
pnpm add @codenhub/theme
|
|
16
|
+
npm install @codenhub/theme
|
|
17
|
+
yarn add @codenhub/theme
|
|
18
|
+
bun add @codenhub/theme
|
|
9
19
|
```
|
|
10
20
|
|
|
11
21
|
## Usage
|
|
@@ -13,9 +23,9 @@ pnpm add @codenhub/theme
|
|
|
13
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`.
|
|
14
24
|
|
|
15
25
|
```ts
|
|
16
|
-
import {
|
|
26
|
+
import { createTheme } from "@codenhub/theme";
|
|
17
27
|
|
|
18
|
-
const theme =
|
|
28
|
+
const theme = createTheme({ isTailwindCss: false, shouldApplyClass: true });
|
|
19
29
|
|
|
20
30
|
theme.init();
|
|
21
31
|
theme.set("dark");
|
|
@@ -24,6 +34,50 @@ theme.toggle();
|
|
|
24
34
|
|
|
25
35
|
Call `destroy()` during app or test cleanup when the instance is no longer used.
|
|
26
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
|
+
|
|
27
81
|
## Reference
|
|
28
82
|
|
|
29
83
|
### `@codenhub/theme`
|
|
@@ -31,9 +85,10 @@ Call `destroy()` during app or test cleanup when the instance is no longer used.
|
|
|
31
85
|
Primary entrypoint for the theme preference API.
|
|
32
86
|
|
|
33
87
|
```ts
|
|
34
|
-
import {
|
|
88
|
+
import { createTheme, DARK_THEME, LIGHT_THEME, THEME_CHANGE_EVENT } from "@codenhub/theme";
|
|
35
89
|
import type {
|
|
36
90
|
SystemThemeMap,
|
|
91
|
+
Theme,
|
|
37
92
|
ThemeChangeDetail,
|
|
38
93
|
ThemeChangeListener,
|
|
39
94
|
ThemeChangeSource,
|
|
@@ -49,63 +104,72 @@ Supported import paths:
|
|
|
49
104
|
| ----------------- | ----------------------------------- |
|
|
50
105
|
| `@codenhub/theme` | Main JavaScript and TypeScript API. |
|
|
51
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
|
+
|
|
52
117
|
#### `Theme`
|
|
53
118
|
|
|
54
|
-
Manages the active theme, storage preference, DOM attribute, `colorScheme` style, classes, system preference listener, and change notifications.
|
|
119
|
+
Manages the active theme, storage preference, DOM attribute, `colorScheme` style, classes, system preference listener, dynamic tokens, and change notifications.
|
|
55
120
|
|
|
56
121
|
```ts
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
clearPreference(): ThemeDefinition;
|
|
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>;
|
|
64
128
|
getStored(): string | null;
|
|
65
|
-
getSystem(): ThemeDefinition
|
|
66
|
-
subscribe(listener: ThemeChangeListener): () => void;
|
|
129
|
+
getSystem(): ThemeDefinition<TSchema>;
|
|
130
|
+
subscribe(listener: ThemeChangeListener<TSchema>): () => void;
|
|
67
131
|
destroy(): void;
|
|
68
132
|
}
|
|
69
133
|
```
|
|
70
134
|
|
|
71
135
|
Import from `@codenhub/theme`.
|
|
72
136
|
|
|
73
|
-
The
|
|
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.
|
|
74
138
|
|
|
75
139
|
##### `init()`
|
|
76
140
|
|
|
77
|
-
Registers the system preference listener, resolves the initial theme, applies it, and emits a change with source `"init"`.
|
|
78
|
-
|
|
79
|
-
Repeated calls do not register duplicate system preference listeners.
|
|
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.
|
|
80
142
|
|
|
81
143
|
```ts
|
|
82
|
-
function init(): this;
|
|
144
|
+
function init(tokens?: Partial<Record<keyof TSchema, string>>): this;
|
|
83
145
|
```
|
|
84
146
|
|
|
147
|
+
Repeated calls do not register duplicate system preference listeners.
|
|
148
|
+
|
|
85
149
|
##### `get()`
|
|
86
150
|
|
|
87
|
-
Returns the active theme definition.
|
|
151
|
+
Returns the active theme definition including any active merged tokens. If a token in `tokenSchema` is not defined in JS for the active theme, its value is dynamically resolved from the computed styles of the DOM in browser environments.
|
|
88
152
|
|
|
89
153
|
```ts
|
|
90
|
-
function get(): ThemeDefinition
|
|
154
|
+
function get(): ThemeDefinition<TSchema>;
|
|
91
155
|
```
|
|
92
156
|
|
|
93
157
|
##### `set()`
|
|
94
158
|
|
|
95
|
-
Activates a configured theme by name and stores the explicit preference when browser storage is available.
|
|
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).
|
|
96
160
|
|
|
97
161
|
```ts
|
|
98
|
-
function set(name: string): ThemeDefinition
|
|
162
|
+
function set(name: string, tokens?: Partial<Record<keyof TSchema, string>>): ThemeDefinition<TSchema>;
|
|
99
163
|
```
|
|
100
164
|
|
|
101
165
|
Throws `Error` when `name` is not configured.
|
|
102
166
|
|
|
103
167
|
##### `toggle()`
|
|
104
168
|
|
|
105
|
-
Toggles between the configured system light and dark theme
|
|
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).
|
|
106
170
|
|
|
107
171
|
```ts
|
|
108
|
-
function toggle(): ThemeDefinition
|
|
172
|
+
function toggle(tokens?: Partial<Record<keyof TSchema, string>>): ThemeDefinition<TSchema>;
|
|
109
173
|
```
|
|
110
174
|
|
|
111
175
|
##### `clearPreference()`
|
|
@@ -113,7 +177,7 @@ function toggle(): ThemeDefinition;
|
|
|
113
177
|
Removes the stored preference and activates the current system theme.
|
|
114
178
|
|
|
115
179
|
```ts
|
|
116
|
-
function clearPreference(): ThemeDefinition
|
|
180
|
+
function clearPreference(): ThemeDefinition<TSchema>;
|
|
117
181
|
```
|
|
118
182
|
|
|
119
183
|
##### `getStored()`
|
|
@@ -131,7 +195,7 @@ Returns `null` during SSR, when storage is unavailable, when storage access thro
|
|
|
131
195
|
Returns the configured theme for the current `prefers-color-scheme` value.
|
|
132
196
|
|
|
133
197
|
```ts
|
|
134
|
-
function getSystem(): ThemeDefinition
|
|
198
|
+
function getSystem(): ThemeDefinition<TSchema>;
|
|
135
199
|
```
|
|
136
200
|
|
|
137
201
|
Returns the default theme during SSR or when `matchMedia` is unavailable.
|
|
@@ -141,14 +205,14 @@ Returns the default theme during SSR or when `matchMedia` is unavailable.
|
|
|
141
205
|
Registers an in-process listener for theme changes.
|
|
142
206
|
|
|
143
207
|
```ts
|
|
144
|
-
function subscribe(listener: ThemeChangeListener): () => void;
|
|
208
|
+
function subscribe(listener: ThemeChangeListener<TSchema>): () => void;
|
|
145
209
|
```
|
|
146
210
|
|
|
147
211
|
Returns an unsubscribe function.
|
|
148
212
|
|
|
149
213
|
##### `destroy()`
|
|
150
214
|
|
|
151
|
-
Removes the system preference listener
|
|
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()`.
|
|
152
216
|
|
|
153
217
|
```ts
|
|
154
218
|
function destroy(): void;
|
|
@@ -159,42 +223,46 @@ Call this during app or test cleanup when the instance is no longer used.
|
|
|
159
223
|
#### `ThemeOptions`
|
|
160
224
|
|
|
161
225
|
```ts
|
|
162
|
-
interface ThemeOptions {
|
|
163
|
-
themes?: readonly ThemeDefinition[];
|
|
226
|
+
interface ThemeOptions<TSchema extends Record<string, string> = Record<string, string>> {
|
|
227
|
+
themes?: readonly ThemeDefinition<TSchema>[];
|
|
164
228
|
defaultTheme?: string;
|
|
165
229
|
systemTheme?: SystemThemeMap;
|
|
166
230
|
storageKey?: string;
|
|
167
231
|
attribute?: string;
|
|
168
|
-
|
|
169
|
-
|
|
232
|
+
isTailwindCss?: boolean;
|
|
233
|
+
shouldApplyClass?: boolean | ThemeClassResolver<TSchema>;
|
|
234
|
+
tokenSchema?: TSchema;
|
|
170
235
|
}
|
|
171
236
|
```
|
|
172
237
|
|
|
173
|
-
| Option
|
|
174
|
-
|
|
|
175
|
-
| `themes`
|
|
176
|
-
| `defaultTheme`
|
|
177
|
-
| `systemTheme`
|
|
178
|
-
| `storageKey`
|
|
179
|
-
| `attribute`
|
|
180
|
-
| `
|
|
181
|
-
| `
|
|
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. |
|
|
182
248
|
|
|
183
249
|
When class application is enabled, each theme application removes classes for all configured themes, then adds the class for the active theme.
|
|
184
250
|
|
|
185
251
|
#### `ThemeDefinition`
|
|
186
252
|
|
|
187
253
|
```ts
|
|
188
|
-
interface ThemeDefinition {
|
|
254
|
+
interface ThemeDefinition<TSchema extends Record<string, string> = Record<string, string>> {
|
|
189
255
|
name: string;
|
|
190
256
|
colorScheme: "light" | "dark";
|
|
257
|
+
tokens?: Partial<Record<keyof TSchema, string>>;
|
|
191
258
|
}
|
|
192
259
|
```
|
|
193
260
|
|
|
194
|
-
| Field | Type
|
|
195
|
-
| ------------- |
|
|
196
|
-
| `name` | `string`
|
|
197
|
-
| `colorScheme` | `"light" \| "dark"`
|
|
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. |
|
|
198
266
|
|
|
199
267
|
#### `SystemThemeMap`
|
|
200
268
|
|
|
@@ -215,7 +283,9 @@ interface SystemThemeMap {
|
|
|
215
283
|
Returns the class name applied to `document.documentElement` for a theme.
|
|
216
284
|
|
|
217
285
|
```ts
|
|
218
|
-
type ThemeClassResolver =
|
|
286
|
+
type ThemeClassResolver<TSchema extends Record<string, string> = Record<string, string>> = (
|
|
287
|
+
theme: ThemeDefinition<TSchema>,
|
|
288
|
+
) => string;
|
|
219
289
|
```
|
|
220
290
|
|
|
221
291
|
The returned class name must be a single non-empty class token without whitespace.
|
|
@@ -227,7 +297,9 @@ The returned class name must be a single non-empty class token without whitespac
|
|
|
227
297
|
Listener passed to `theme.subscribe()`.
|
|
228
298
|
|
|
229
299
|
```ts
|
|
230
|
-
type ThemeChangeListener =
|
|
300
|
+
type ThemeChangeListener<TSchema extends Record<string, string> = Record<string, string>> = (
|
|
301
|
+
detail: ThemeChangeDetail<TSchema>,
|
|
302
|
+
) => void;
|
|
231
303
|
```
|
|
232
304
|
|
|
233
305
|
#### `THEME_CHANGE_EVENT`
|
|
@@ -241,10 +313,10 @@ const THEME_CHANGE_EVENT = "themechange";
|
|
|
241
313
|
#### `ThemeChangeDetail`
|
|
242
314
|
|
|
243
315
|
```ts
|
|
244
|
-
interface ThemeChangeDetail {
|
|
316
|
+
interface ThemeChangeDetail<TSchema extends Record<string, string> = Record<string, string>> {
|
|
245
317
|
name: string;
|
|
246
|
-
theme: ThemeDefinition
|
|
247
|
-
source:
|
|
318
|
+
theme: ThemeDefinition<TSchema>;
|
|
319
|
+
source: ThemeChangeSource;
|
|
248
320
|
}
|
|
249
321
|
```
|
|
250
322
|
|
|
@@ -275,12 +347,46 @@ type ThemeChangeSource = "init" | "set" | "toggle" | "clearPreference" | "system
|
|
|
275
347
|
Built-in theme definitions.
|
|
276
348
|
|
|
277
349
|
```ts
|
|
278
|
-
const
|
|
279
|
-
const
|
|
350
|
+
const LIGHT_THEME: ThemeDefinition = { name: "light", colorScheme: "light" };
|
|
351
|
+
const DARK_THEME: ThemeDefinition = { name: "dark", colorScheme: "dark" };
|
|
280
352
|
```
|
|
281
353
|
|
|
282
354
|
## Examples
|
|
283
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
|
+
|
|
284
390
|
### Define CSS Tokens
|
|
285
391
|
|
|
286
392
|
```css
|
|
@@ -304,12 +410,12 @@ body {
|
|
|
304
410
|
### Add More Themes
|
|
305
411
|
|
|
306
412
|
```ts
|
|
307
|
-
import {
|
|
413
|
+
import { createTheme, DARK_THEME, LIGHT_THEME } from "@codenhub/theme";
|
|
308
414
|
|
|
309
|
-
const theme =
|
|
310
|
-
themes: [
|
|
415
|
+
const theme = createTheme({
|
|
416
|
+
themes: [LIGHT_THEME, DARK_THEME, { name: "high-contrast", colorScheme: "dark" }],
|
|
311
417
|
systemTheme: { light: "light", dark: "high-contrast" },
|
|
312
|
-
|
|
418
|
+
shouldApplyClass: (definition) => `mode-${definition.name}`,
|
|
313
419
|
});
|
|
314
420
|
|
|
315
421
|
theme.init();
|
|
@@ -319,9 +425,9 @@ theme.set("high-contrast");
|
|
|
319
425
|
### Listen For Changes
|
|
320
426
|
|
|
321
427
|
```ts
|
|
322
|
-
import {
|
|
428
|
+
import { createTheme, THEME_CHANGE_EVENT, type ThemeChangeDetail } from "@codenhub/theme";
|
|
323
429
|
|
|
324
|
-
const theme =
|
|
430
|
+
const theme = createTheme().init();
|
|
325
431
|
|
|
326
432
|
const unsubscribe = theme.subscribe((detail) => {
|
|
327
433
|
console.log(detail.name, detail.theme, detail.source);
|
|
@@ -342,7 +448,7 @@ theme.destroy();
|
|
|
342
448
|
- Browser integration uses `document.documentElement`, `document.documentElement.style.colorScheme`, `window.matchMedia`, `localStorage`, and `CustomEvent`.
|
|
343
449
|
- SSR is supported; DOM, storage, media query, and event work is skipped when browser APIs are unavailable.
|
|
344
450
|
- System preference changes update the active theme only when there is no valid stored preference.
|
|
345
|
-
- `localStorage` read, write, and remove errors are
|
|
451
|
+
- `localStorage` read, write, and remove errors are logged to the console via `console.error` and treated as unavailable storage.
|
|
346
452
|
- Consumers own CSS variables, selectors, visual tokens, and persistence consent requirements.
|
|
347
453
|
- No CSS file, design tokens, framework adapter, or peer dependency is provided.
|
|
348
454
|
|
|
@@ -351,5 +457,8 @@ theme.destroy();
|
|
|
351
457
|
- Does not provide design tokens or generated CSS.
|
|
352
458
|
- Does not provide React, Vue, or other framework bindings.
|
|
353
459
|
- Does not provide server-side persistence.
|
|
354
|
-
- Does not synchronize theme changes across tabs.
|
|
355
460
|
- Does not manage user consent requirements for storage.
|
|
461
|
+
|
|
462
|
+
## License
|
|
463
|
+
|
|
464
|
+
This project is licensed under the [Apache-2.0](LICENSE) license.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
//#region src/
|
|
2
|
-
/** Window event name dispatched with `ThemeChangeDetail` after a theme change is applied in browser environments. */
|
|
3
|
-
declare const THEME_CHANGE_EVENT = "themechange";
|
|
1
|
+
//#region src/types.d.ts
|
|
4
2
|
/** Theme option stored, applied to the configured DOM attribute, and mapped to the browser color scheme. */
|
|
5
|
-
interface ThemeDefinition {
|
|
3
|
+
interface ThemeDefinition<TSchema extends Record<string, string> = Record<string, string>> {
|
|
6
4
|
/** Unique configured theme name used for storage, DOM attributes, and generated default classes. */
|
|
7
5
|
name: string;
|
|
8
6
|
/** Browser color scheme applied to `document.documentElement.style.colorScheme`. */
|
|
9
7
|
colorScheme: "light" | "dark";
|
|
8
|
+
/** Optional theme-specific static token values. */
|
|
9
|
+
tokens?: Partial<Record<keyof TSchema, string>>;
|
|
10
10
|
}
|
|
11
11
|
/** Mapping from OS color-scheme preferences to configured theme names. */
|
|
12
12
|
interface SystemThemeMap {
|
|
@@ -16,24 +16,24 @@ interface SystemThemeMap {
|
|
|
16
16
|
dark: string;
|
|
17
17
|
}
|
|
18
18
|
/** Resolves the single DOM class token applied for a theme when custom class application is enabled. */
|
|
19
|
-
type ThemeClassResolver = (theme: ThemeDefinition) => string;
|
|
19
|
+
type ThemeClassResolver<TSchema extends Record<string, string> = Record<string, string>> = (theme: ThemeDefinition<TSchema>) => string;
|
|
20
20
|
/** Reason a theme change notification was emitted. */
|
|
21
21
|
type ThemeChangeSource = "init" | "set" | "toggle" | "clearPreference" | "system";
|
|
22
22
|
/** Payload passed to subscribers and the browser `themechange` event after a theme change. */
|
|
23
|
-
interface ThemeChangeDetail {
|
|
23
|
+
interface ThemeChangeDetail<TSchema extends Record<string, string> = Record<string, string>> {
|
|
24
24
|
/** Active theme name after the change. */
|
|
25
25
|
name: string;
|
|
26
26
|
/** Active theme definition after the change. */
|
|
27
|
-
theme: ThemeDefinition
|
|
27
|
+
theme: ThemeDefinition<TSchema>;
|
|
28
28
|
/** Operation or browser signal that caused the change notification. */
|
|
29
29
|
source: ThemeChangeSource;
|
|
30
30
|
}
|
|
31
31
|
/** In-process callback registered with `Theme.subscribe()` for applied theme changes. */
|
|
32
|
-
type ThemeChangeListener = (detail: ThemeChangeDetail) => void;
|
|
32
|
+
type ThemeChangeListener<TSchema extends Record<string, string> = Record<string, string>> = (detail: ThemeChangeDetail<TSchema>) => void;
|
|
33
33
|
/** Configuration for theme definitions, persistence, DOM application, and system preference mapping. */
|
|
34
|
-
interface ThemeOptions {
|
|
35
|
-
/** Available themes. Names must be unique, non-empty, and valid default class tokens when `
|
|
36
|
-
themes?: readonly ThemeDefinition[];
|
|
34
|
+
interface ThemeOptions<TSchema extends Record<string, string> = Record<string, string>> {
|
|
35
|
+
/** Available themes. Names must be unique, non-empty, and valid default class tokens when `shouldApplyClass` is `true`. */
|
|
36
|
+
themes?: readonly ThemeDefinition<TSchema>[];
|
|
37
37
|
/** Configured theme name used before initialization and when browser APIs are unavailable. */
|
|
38
38
|
defaultTheme?: string;
|
|
39
39
|
/** Configured theme names selected for OS light and dark color-scheme preferences. */
|
|
@@ -43,42 +43,116 @@ interface ThemeOptions {
|
|
|
43
43
|
/** Attribute set on `document.documentElement` with the active theme name. */
|
|
44
44
|
attribute?: string;
|
|
45
45
|
/** Whether to toggle Tailwind CSS's `dark` class for themes with `colorScheme: "dark"`. */
|
|
46
|
-
|
|
46
|
+
isTailwindCss?: boolean;
|
|
47
47
|
/** Whether and how to apply a theme-specific class to `document.documentElement`. */
|
|
48
|
-
|
|
48
|
+
shouldApplyClass?: boolean | ThemeClassResolver<TSchema>;
|
|
49
|
+
/** Schema mapping theme token names to their corresponding CSS Custom Property names. */
|
|
50
|
+
tokenSchema?: TSchema;
|
|
49
51
|
}
|
|
50
|
-
/** Built-in light theme used by default and available for custom theme lists. */
|
|
51
|
-
declare const lightTheme: ThemeDefinition;
|
|
52
|
-
/** Built-in dark theme used by default and available for custom theme lists. */
|
|
53
|
-
declare const darkTheme: ThemeDefinition;
|
|
54
52
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* Theme application throws `Error` when a requested theme is missing or a class resolver returns an invalid class token.
|
|
53
|
+
* Core theme preference handler. Manages initialization, switching themes,
|
|
54
|
+
* persistence to localStorage, synchronizing with the OS prefers-color-scheme preference,
|
|
55
|
+
* dynamic token mapping to CSS Custom Properties, and dispatching change events.
|
|
59
56
|
*/
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
57
|
+
interface Theme<TSchema extends Record<string, string> = Record<string, string>> {
|
|
58
|
+
/**
|
|
59
|
+
* Initializes the theme manager. Resolves the active theme (using the stored preference if valid,
|
|
60
|
+
* falling back to the current OS color-scheme preference), applies classes/attributes to the DOM,
|
|
61
|
+
* and registers the media query listener for automatic system preference updates.
|
|
62
|
+
*
|
|
63
|
+
* @param tokens - Optional runtime override token values to merge and apply.
|
|
64
|
+
* @returns The current `Theme` manager instance for method chaining.
|
|
65
|
+
* @sideEffect Registers a media query event listener on `window` and updates root DOM element attributes/styles. Dispatches a "themechange" event.
|
|
66
|
+
*/
|
|
67
|
+
init(tokens?: Partial<Record<keyof TSchema, string>>): this;
|
|
68
|
+
/**
|
|
69
|
+
* Retrieves the active theme configuration including static and computed tokens.
|
|
70
|
+
*
|
|
71
|
+
* 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.
|
|
73
|
+
* 2. Theme static tokens — values defined in `ThemeDefinition.tokens` for the active theme.
|
|
74
|
+
* 3. Runtime overrides — values passed to `init()`, `set()`, `toggle()`, or other methods.
|
|
75
|
+
*
|
|
76
|
+
* @returns The active `ThemeDefinition` object. If `tokenSchema` is configured and a token is not
|
|
77
|
+
* 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.
|
|
79
|
+
*/
|
|
80
|
+
get(): ThemeDefinition<TSchema>;
|
|
81
|
+
/**
|
|
82
|
+
* Activates a configured theme by name and updates the stored preference in `localStorage`.
|
|
83
|
+
*
|
|
84
|
+
* @param name - The name of the configured theme to activate.
|
|
85
|
+
* @param tokens - Optional runtime override token values to apply. Active overrides persist across subsequent theme changes unless cleared (by passing new overrides or an empty object).
|
|
86
|
+
* @returns The activated `ThemeDefinition` with merged and resolved tokens.
|
|
87
|
+
* @throws {Error} If the specified theme name is not found in the configured themes list.
|
|
88
|
+
* @sideEffect Updates root DOM attributes, colorscheme styles, classes, and saves preference to `localStorage`. Dispatches a "themechange" event.
|
|
89
|
+
*/
|
|
90
|
+
set(name: string, tokens?: Partial<Record<keyof TSchema, string>>): ThemeDefinition<TSchema>;
|
|
91
|
+
/**
|
|
92
|
+
* Switches the theme between the configured system light and dark themes and persists the change.
|
|
93
|
+
* The next theme is always selected from `systemTheme.light` or `systemTheme.dark` based on the
|
|
94
|
+
* active theme's `colorScheme`, not by cycling the active theme name. In multi-theme setups where
|
|
95
|
+
* the active theme is not one of the system themes, `toggle()` still targets `systemTheme.light`
|
|
96
|
+
* or `systemTheme.dark`.
|
|
97
|
+
*
|
|
98
|
+
* @param tokens - Optional runtime override token values to apply. Active overrides persist across subsequent theme changes unless cleared (by passing new overrides or an empty object).
|
|
99
|
+
* @returns The activated `ThemeDefinition` with merged and resolved tokens.
|
|
100
|
+
* @sideEffect Updates root DOM attributes, colorscheme styles, classes, and saves preference to `localStorage`. Dispatches a "themechange" event.
|
|
101
|
+
*/
|
|
102
|
+
toggle(tokens?: Partial<Record<keyof TSchema, string>>): ThemeDefinition<TSchema>;
|
|
103
|
+
/**
|
|
104
|
+
* Removes the explicit user theme preference from storage and resets the theme to match the OS system preference.
|
|
105
|
+
*
|
|
106
|
+
* @returns The activated system `ThemeDefinition` with merged and resolved tokens.
|
|
107
|
+
* @sideEffect Deletes the storage key from `localStorage`, updates root DOM attributes, styles, classes. Dispatches a "themechange" event.
|
|
108
|
+
*/
|
|
109
|
+
clearPreference(): ThemeDefinition<TSchema>;
|
|
110
|
+
/**
|
|
111
|
+
* Retrieves the currently stored theme preference name from `localStorage`.
|
|
112
|
+
*
|
|
113
|
+
* @returns The stored theme name if valid and currently configured; otherwise `null` (e.g. during SSR, if storage is empty/unavailable, or if the stored theme name is not configured).
|
|
114
|
+
*/
|
|
75
115
|
getStored(): string | null;
|
|
76
|
-
/**
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Resolves the configured theme that matches the active OS color-scheme preference.
|
|
118
|
+
*
|
|
119
|
+
* @returns The matching `ThemeDefinition`. Falls back to the default theme during SSR or if `matchMedia` is unavailable.
|
|
120
|
+
*/
|
|
121
|
+
getSystem(): ThemeDefinition<TSchema>;
|
|
122
|
+
/**
|
|
123
|
+
* Registers a callback listener to receive notifications when the theme or its tokens change.
|
|
124
|
+
*
|
|
125
|
+
* @param listener - Callback function invoked on theme changes.
|
|
126
|
+
* @returns An unsubscribe function to remove the registered listener.
|
|
127
|
+
* @sideEffect Adds the listener to the internal callbacks registry.
|
|
128
|
+
*/
|
|
129
|
+
subscribe(listener: ThemeChangeListener<TSchema>): () => void;
|
|
130
|
+
/**
|
|
131
|
+
* Cleans up the theme instance by removing all in-process change listeners and the system
|
|
132
|
+
* preference media query listener. Resets active tokens and the active theme name to the
|
|
133
|
+
* configured `defaultTheme` so the instance can be safely re-initialized with `init()`.
|
|
134
|
+
*
|
|
135
|
+
* @sideEffect Removes event listeners from `window` and clears internal subscriber sets.
|
|
136
|
+
*/
|
|
81
137
|
destroy(): void;
|
|
82
138
|
}
|
|
83
139
|
//#endregion
|
|
84
|
-
|
|
140
|
+
//#region src/constants.d.ts
|
|
141
|
+
/** Window event name dispatched with `ThemeChangeDetail` after a theme change is applied in browser environments. */
|
|
142
|
+
declare const THEME_CHANGE_EVENT = "themechange";
|
|
143
|
+
/** Built-in light theme used by default and available for custom theme lists. */
|
|
144
|
+
declare const LIGHT_THEME: ThemeDefinition;
|
|
145
|
+
/** Built-in dark theme used by default and available for custom theme lists. */
|
|
146
|
+
declare const DARK_THEME: ThemeDefinition;
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/theme.d.ts
|
|
149
|
+
/**
|
|
150
|
+
* Factory function that creates and returns a `Theme` instance.
|
|
151
|
+
*
|
|
152
|
+
* @param options - Configuration options for theme definitions, persistence keys, DOM attributes, custom class resolvers, and dynamic token schemas.
|
|
153
|
+
* @returns A `Theme` instance.
|
|
154
|
+
* @throws {Error} If configured theme names are empty, duplicated, invalid for CSS class application, or if the default/system themes are not present in the configured list.
|
|
155
|
+
*/
|
|
156
|
+
declare function createTheme<TSchema extends Record<string, string> = Record<string, string>>(options?: ThemeOptions<TSchema>): Theme<TSchema>;
|
|
157
|
+
//#endregion
|
|
158
|
+
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 };
|