@marianmeres/stuic 3.91.0 → 3.93.0
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/dist/components/ColorScheme/ColorSchemeLocal.svelte +10 -2
- package/dist/components/ColorScheme/ColorSchemeSystemAware.svelte +5 -2
- package/dist/components/ColorScheme/README.md +59 -18
- package/dist/components/ColorScheme/color-scheme.svelte.d.ts +35 -4
- package/dist/components/ColorScheme/color-scheme.svelte.js +40 -8
- package/package.json +1 -1
|
@@ -16,14 +16,22 @@
|
|
|
16
16
|
Similar to ColorSchemeSystemAware, except that it never reads window.matchMedia and only
|
|
17
17
|
relies on the local userland setting.
|
|
18
18
|
|
|
19
|
+
If no preference is stored, this bootstrap leaves the `<html>` class
|
|
20
|
+
alone — meaning the app's SSR'd default wins. Ship `<html class="dark">`
|
|
21
|
+
from your `app.html` / SSR to default the app to dark; users can still
|
|
22
|
+
override via `ColorScheme.toggle()` later.
|
|
23
|
+
|
|
19
24
|
Uses the hardcoded default storage key "stuic-color-scheme". Apps that
|
|
20
25
|
override the runtime key via `ColorScheme.configure({ key })` should ship
|
|
21
26
|
their own inline bootstrap in `app.html` if FOUC-free hydration matters.
|
|
22
27
|
-->
|
|
23
28
|
<svelte:head>
|
|
24
29
|
<script>
|
|
25
|
-
const KEY = "stuic-color-scheme";
|
|
30
|
+
const KEY = window.__COLOR_SCHEME_KEY__ ?? "stuic-color-scheme";
|
|
26
31
|
const cls = window.document.documentElement.classList;
|
|
27
|
-
localStorage.getItem(KEY)
|
|
32
|
+
const v = localStorage.getItem(KEY);
|
|
33
|
+
if (v === "dark") cls.add("dark");
|
|
34
|
+
else if (v === "light") cls.remove("dark");
|
|
35
|
+
// else: no stored preference — leave the SSR'd class alone.
|
|
28
36
|
</script>
|
|
29
37
|
</svelte:head>
|
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
</script>
|
|
14
14
|
|
|
15
15
|
<!--
|
|
16
|
-
|
|
16
|
+
Explicit opt-in for OS-aware first paint. The `ColorScheme` runtime itself
|
|
17
|
+
never auto-derives from `prefers-color-scheme` — only this bootstrap does,
|
|
18
|
+
and only at hydration when no preference is stored. If you do not want
|
|
19
|
+
system preference taken into account, use the ColorSchemeLocal sibling.
|
|
17
20
|
|
|
18
21
|
Uses the hardcoded default storage key "stuic-color-scheme". Apps that
|
|
19
22
|
override the runtime key via `ColorScheme.configure({ key })` should ship
|
|
@@ -21,7 +24,7 @@
|
|
|
21
24
|
-->
|
|
22
25
|
<svelte:head>
|
|
23
26
|
<script>
|
|
24
|
-
const KEY = "stuic-color-scheme";
|
|
27
|
+
const KEY = window.__COLOR_SCHEME_KEY__ ?? "stuic-color-scheme";
|
|
25
28
|
const cls = window.document.documentElement.classList;
|
|
26
29
|
if (KEY in localStorage) {
|
|
27
30
|
localStorage.getItem(KEY) === "dark" ? cls.add("dark") : cls.remove("dark");
|
|
@@ -1,31 +1,62 @@
|
|
|
1
1
|
# ColorScheme
|
|
2
2
|
|
|
3
|
-
Hydration components for dark mode support. Add/remove the `dark` class on the
|
|
3
|
+
Hydration components and a small runtime for dark mode support. Add/remove the `dark` class on the `<html>` element based on a stored user preference. The runtime has **no opinion**: when `localStorage` is empty, it defers to whatever class the SSR'd `<html>` already has — so apps can ship dark-by-default just by SSR'ing `<html class="dark">`.
|
|
4
|
+
|
|
5
|
+
## Contract
|
|
6
|
+
|
|
7
|
+
- `localStorage["stuic-color-scheme"]` is `"dark"` or `"light"` → that wins.
|
|
8
|
+
- Otherwise → defer to the current `<html>` class (set by SSR, `app.html`, or your own bootstrap).
|
|
9
|
+
- `prefers-color-scheme` is **never** consulted implicitly. It is read only when:
|
|
10
|
+
- You explicitly mount `<ColorSchemeSystemAware />` (which reads it once, at first paint, to seed the DOM when no preference is stored), or
|
|
11
|
+
- You call `ColorScheme.getSystemValue()` from your own UI (e.g. a "Use system preference" button).
|
|
4
12
|
|
|
5
13
|
## Components
|
|
6
14
|
|
|
7
15
|
### ColorSchemeLocal
|
|
8
16
|
|
|
9
|
-
Uses only the localStorage setting
|
|
17
|
+
Uses only the localStorage setting. If no preference is stored, the inline bootstrap leaves the `<html>` class alone — the SSR'd default wins.
|
|
10
18
|
|
|
11
19
|
### ColorSchemeSystemAware
|
|
12
20
|
|
|
13
|
-
Checks localStorage first,
|
|
21
|
+
Explicit opt-in for OS-aware first paint. Checks localStorage first; if empty, falls back to `prefers-color-scheme: dark`. Use this if you want the OS to influence first paint.
|
|
14
22
|
|
|
15
23
|
## Props
|
|
16
24
|
|
|
17
|
-
Both components have **no props**
|
|
25
|
+
Both components have **no props** — they are pure hydration components.
|
|
18
26
|
|
|
19
27
|
## Storage Key
|
|
20
28
|
|
|
21
|
-
|
|
29
|
+
Default localStorage key: `stuic-color-scheme`.
|
|
22
30
|
|
|
23
31
|
- Value `"dark"` → adds `dark` class to `<html>`
|
|
24
|
-
-
|
|
32
|
+
- Value `"light"` → removes `dark` class
|
|
33
|
+
- Anything else / absent → no DOM change (defers to existing class)
|
|
34
|
+
|
|
35
|
+
Override via `ColorScheme.configure({ key: "myapp:color-scheme" })`. Call early in app boot. For FOUC-free hydration with a custom key, ship your own inline bootstrap in `app.html`.
|
|
25
36
|
|
|
26
37
|
## Usage
|
|
27
38
|
|
|
28
|
-
###
|
|
39
|
+
### Local (no system fallback)
|
|
40
|
+
|
|
41
|
+
```svelte
|
|
42
|
+
<script lang="ts">
|
|
43
|
+
import { ColorSchemeLocal } from "stuic";
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<ColorSchemeLocal />
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Dark by default
|
|
50
|
+
|
|
51
|
+
Just SSR the `dark` class on `<html>` (e.g. in `src/app.html`):
|
|
52
|
+
|
|
53
|
+
```html
|
|
54
|
+
<html lang="en" class="dark"></html>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
With `<ColorSchemeLocal />` mounted, the app starts dark; toggling persists `"light"` to localStorage; calling `ColorScheme.reset()` clears the preference (the next reload restores the dark default).
|
|
58
|
+
|
|
59
|
+
### System-aware
|
|
29
60
|
|
|
30
61
|
```svelte
|
|
31
62
|
<script lang="ts">
|
|
@@ -35,30 +66,40 @@ Both components use the localStorage key: `stuic-color-scheme`
|
|
|
35
66
|
<ColorSchemeSystemAware />
|
|
36
67
|
```
|
|
37
68
|
|
|
38
|
-
###
|
|
69
|
+
### Toggle / reset / read
|
|
39
70
|
|
|
40
71
|
```svelte
|
|
41
72
|
<script lang="ts">
|
|
42
|
-
import { ColorSchemeLocal } from "stuic";
|
|
73
|
+
import { ColorScheme, ColorSchemeLocal } from "stuic";
|
|
43
74
|
</script>
|
|
44
75
|
|
|
45
76
|
<ColorSchemeLocal />
|
|
77
|
+
|
|
78
|
+
<button onclick={() => ColorScheme.toggle()}>
|
|
79
|
+
Current: {ColorScheme.current}
|
|
80
|
+
</button>
|
|
81
|
+
|
|
82
|
+
<!-- Clear persisted preference. Visual stays as-is until next reload. -->
|
|
83
|
+
<button onclick={() => ColorScheme.reset()}>Reset</button>
|
|
46
84
|
```
|
|
47
85
|
|
|
48
|
-
###
|
|
86
|
+
### Apply system preference on demand
|
|
49
87
|
|
|
50
88
|
```svelte
|
|
51
89
|
<script lang="ts">
|
|
52
|
-
import {
|
|
90
|
+
import { ColorScheme } from "stuic";
|
|
53
91
|
|
|
54
|
-
function
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
92
|
+
function useSystem() {
|
|
93
|
+
const v = ColorScheme.getSystemValue();
|
|
94
|
+
localStorage.setItem(ColorScheme.KEY, v);
|
|
95
|
+
document.documentElement.classList.toggle("dark", v === "dark");
|
|
96
|
+
ColorScheme.syncFromDom();
|
|
58
97
|
}
|
|
59
98
|
</script>
|
|
60
99
|
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
<button onclick={toggleDarkMode}>Toggle Dark Mode</button>
|
|
100
|
+
<button onclick={useSystem}>Use system preference</button>
|
|
64
101
|
```
|
|
102
|
+
|
|
103
|
+
## Notes on `reset()`
|
|
104
|
+
|
|
105
|
+
`ColorScheme.reset()` removes the persisted preference from `localStorage`. It does **not** change the visual state in the current session — `toggle()` already painted the DOM, and the lib does not track an "original SSR default" to revert to. The next page load will paint from the SSR'd `<html>` class. If you need a same-session revert, persist your default explicitly (e.g. `localStorage.setItem(ColorScheme.KEY, "dark")` then call your toggle code).
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
type Scheme = "dark" | "light";
|
|
2
|
+
declare global {
|
|
3
|
+
interface Window {
|
|
4
|
+
/**
|
|
5
|
+
* Optional global override for the localStorage key the inline bootstrap
|
|
6
|
+
* `<script>` in `<ColorSchemeLocal />` / `<ColorSchemeSystemAware />` reads
|
|
7
|
+
* from. Set this before the hydration component mounts (e.g. in
|
|
8
|
+
* `app.html` or at the very top of the root layout's `<script>`) to use a
|
|
9
|
+
* custom key with FOUC-free hydration. `ColorScheme.configure({ key })`
|
|
10
|
+
* also writes this global to keep runtime and bootstrap in sync.
|
|
11
|
+
*/
|
|
12
|
+
__COLOR_SCHEME_KEY__?: string;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
2
15
|
/**
|
|
3
16
|
* A utility class for managing light/dark color scheme preferences.
|
|
4
17
|
*
|
|
@@ -17,7 +30,7 @@ type Scheme = "dark" | "light";
|
|
|
17
30
|
* // Toggle between light and dark
|
|
18
31
|
* ColorScheme.toggle();
|
|
19
32
|
*
|
|
20
|
-
* //
|
|
33
|
+
* // Clear persisted preference (visual reverts on next reload)
|
|
21
34
|
* ColorScheme.reset();
|
|
22
35
|
*
|
|
23
36
|
* // Use a custom localStorage key (call early in app boot):
|
|
@@ -30,6 +43,11 @@ type Scheme = "dark" | "light";
|
|
|
30
43
|
* - Works with Tailwind's `darkMode: 'class'` configuration
|
|
31
44
|
* - Pair with `<ColorSchemeLocal />` or `<ColorSchemeSystemAware />` for
|
|
32
45
|
* FOUC-free initial paint
|
|
46
|
+
* - Runtime never auto-derives from `prefers-color-scheme`. When no
|
|
47
|
+
* preference is stored, it defers to whatever class the SSR'd `<html>`
|
|
48
|
+
* already has. To support OS-aware paint, mount
|
|
49
|
+
* `<ColorSchemeSystemAware />` (opt-in) or call `getSystemValue()` from
|
|
50
|
+
* your own UI.
|
|
33
51
|
*/
|
|
34
52
|
export declare class ColorScheme {
|
|
35
53
|
static readonly DARK: "dark";
|
|
@@ -43,6 +61,10 @@ export declare class ColorScheme {
|
|
|
43
61
|
* Configure the runtime. Call early in app boot, before any consumer reads
|
|
44
62
|
* `ColorScheme.current`. Mutates module state; safe to call more than once
|
|
45
63
|
* (last write wins). Calling without changes is a no-op.
|
|
64
|
+
*
|
|
65
|
+
* Also writes `window.__COLOR_SCHEME_KEY__` so a subsequent hydration
|
|
66
|
+
* component mount (or any external bootstrap script that reads the global)
|
|
67
|
+
* stays in sync with the runtime.
|
|
46
68
|
*/
|
|
47
69
|
static configure(opts: {
|
|
48
70
|
key?: string;
|
|
@@ -60,7 +82,12 @@ export declare class ColorScheme {
|
|
|
60
82
|
*/
|
|
61
83
|
static syncFromDom(): void;
|
|
62
84
|
/**
|
|
63
|
-
* Reads the `prefers-color-scheme` system setting
|
|
85
|
+
* Reads the `prefers-color-scheme` system setting. Manual-only utility —
|
|
86
|
+
* the runtime never invokes this implicitly. Wire it to a custom button if
|
|
87
|
+
* your app wants OS-aware behavior, e.g.
|
|
88
|
+
* `localStorage.setItem(ColorScheme.KEY, ColorScheme.getSystemValue())`
|
|
89
|
+
* followed by a re-sync, or mount `<ColorSchemeSystemAware />` for
|
|
90
|
+
* OS-aware first paint.
|
|
64
91
|
*/
|
|
65
92
|
static getSystemValue(): Scheme;
|
|
66
93
|
/**
|
|
@@ -76,8 +103,12 @@ export declare class ColorScheme {
|
|
|
76
103
|
*/
|
|
77
104
|
static toggle(): void;
|
|
78
105
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
106
|
+
* Clears the persisted preference from `localStorage`. Does NOT change the
|
|
107
|
+
* current visual state in the active session — `_sync()` will read the
|
|
108
|
+
* (now empty) storage and the DOM (last applied class) and find no change.
|
|
109
|
+
* The visual revert to the app's SSR'd default happens on the next page
|
|
110
|
+
* load. The runtime never consults `prefers-color-scheme` on its own; call
|
|
111
|
+
* `ColorScheme.getSystemValue()` explicitly if you want that behavior.
|
|
81
112
|
*/
|
|
82
113
|
static reset(): void;
|
|
83
114
|
}
|
|
@@ -5,7 +5,12 @@ function _compute() {
|
|
|
5
5
|
const local = globalThis.localStorage?.getItem(_key);
|
|
6
6
|
if (local === "dark" || local === "light")
|
|
7
7
|
return local;
|
|
8
|
-
|
|
8
|
+
// No persisted preference: defer to whatever the DOM (SSR'd <html> class
|
|
9
|
+
// or a prior bootstrap) currently shows. The runtime has no opinion of
|
|
10
|
+
// its own and never auto-derives from prefers-color-scheme.
|
|
11
|
+
return globalThis?.document?.documentElement.classList.contains(ColorScheme.DARK)
|
|
12
|
+
? ColorScheme.DARK
|
|
13
|
+
: ColorScheme.LIGHT;
|
|
9
14
|
}
|
|
10
15
|
function _applyDom(scheme) {
|
|
11
16
|
globalThis?.document?.documentElement.classList.toggle(ColorScheme.DARK, scheme === ColorScheme.DARK);
|
|
@@ -35,7 +40,7 @@ function _sync() {
|
|
|
35
40
|
* // Toggle between light and dark
|
|
36
41
|
* ColorScheme.toggle();
|
|
37
42
|
*
|
|
38
|
-
* //
|
|
43
|
+
* // Clear persisted preference (visual reverts on next reload)
|
|
39
44
|
* ColorScheme.reset();
|
|
40
45
|
*
|
|
41
46
|
* // Use a custom localStorage key (call early in app boot):
|
|
@@ -48,6 +53,11 @@ function _sync() {
|
|
|
48
53
|
* - Works with Tailwind's `darkMode: 'class'` configuration
|
|
49
54
|
* - Pair with `<ColorSchemeLocal />` or `<ColorSchemeSystemAware />` for
|
|
50
55
|
* FOUC-free initial paint
|
|
56
|
+
* - Runtime never auto-derives from `prefers-color-scheme`. When no
|
|
57
|
+
* preference is stored, it defers to whatever class the SSR'd `<html>`
|
|
58
|
+
* already has. To support OS-aware paint, mount
|
|
59
|
+
* `<ColorSchemeSystemAware />` (opt-in) or call `getSystemValue()` from
|
|
60
|
+
* your own UI.
|
|
51
61
|
*/
|
|
52
62
|
export class ColorScheme {
|
|
53
63
|
static DARK = "dark";
|
|
@@ -63,10 +73,16 @@ export class ColorScheme {
|
|
|
63
73
|
* Configure the runtime. Call early in app boot, before any consumer reads
|
|
64
74
|
* `ColorScheme.current`. Mutates module state; safe to call more than once
|
|
65
75
|
* (last write wins). Calling without changes is a no-op.
|
|
76
|
+
*
|
|
77
|
+
* Also writes `window.__COLOR_SCHEME_KEY__` so a subsequent hydration
|
|
78
|
+
* component mount (or any external bootstrap script that reads the global)
|
|
79
|
+
* stays in sync with the runtime.
|
|
66
80
|
*/
|
|
67
81
|
static configure(opts) {
|
|
68
82
|
if (opts?.key && opts.key !== _key) {
|
|
69
83
|
_key = opts.key;
|
|
84
|
+
if (typeof window !== "undefined")
|
|
85
|
+
window.__COLOR_SCHEME_KEY__ = opts.key;
|
|
70
86
|
_sync();
|
|
71
87
|
}
|
|
72
88
|
}
|
|
@@ -93,7 +109,12 @@ export class ColorScheme {
|
|
|
93
109
|
_current = next;
|
|
94
110
|
}
|
|
95
111
|
/**
|
|
96
|
-
* Reads the `prefers-color-scheme` system setting
|
|
112
|
+
* Reads the `prefers-color-scheme` system setting. Manual-only utility —
|
|
113
|
+
* the runtime never invokes this implicitly. Wire it to a custom button if
|
|
114
|
+
* your app wants OS-aware behavior, e.g.
|
|
115
|
+
* `localStorage.setItem(ColorScheme.KEY, ColorScheme.getSystemValue())`
|
|
116
|
+
* followed by a re-sync, or mount `<ColorSchemeSystemAware />` for
|
|
117
|
+
* OS-aware first paint.
|
|
97
118
|
*/
|
|
98
119
|
static getSystemValue() {
|
|
99
120
|
return globalThis.matchMedia?.(`(prefers-color-scheme: ${ColorScheme.DARK})`).matches
|
|
@@ -122,8 +143,12 @@ export class ColorScheme {
|
|
|
122
143
|
_current = next;
|
|
123
144
|
}
|
|
124
145
|
/**
|
|
125
|
-
*
|
|
126
|
-
*
|
|
146
|
+
* Clears the persisted preference from `localStorage`. Does NOT change the
|
|
147
|
+
* current visual state in the active session — `_sync()` will read the
|
|
148
|
+
* (now empty) storage and the DOM (last applied class) and find no change.
|
|
149
|
+
* The visual revert to the app's SSR'd default happens on the next page
|
|
150
|
+
* load. The runtime never consults `prefers-color-scheme` on its own; call
|
|
151
|
+
* `ColorScheme.getSystemValue()` explicitly if you want that behavior.
|
|
127
152
|
*/
|
|
128
153
|
static reset() {
|
|
129
154
|
globalThis.localStorage?.removeItem(_key);
|
|
@@ -131,9 +156,16 @@ export class ColorScheme {
|
|
|
131
156
|
}
|
|
132
157
|
}
|
|
133
158
|
if (typeof window !== "undefined") {
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
159
|
+
// If the app declared a custom key via the global (typical place: app.html,
|
|
160
|
+
// before the bootstrap <script> runs), adopt it so the runtime reads/writes
|
|
161
|
+
// the same key the bootstrap painted from.
|
|
162
|
+
if (window.__COLOR_SCHEME_KEY__)
|
|
163
|
+
_key = window.__COLOR_SCHEME_KEY__;
|
|
164
|
+
// Seed from localStorage; if empty, fall back to the current <html> class
|
|
165
|
+
// (set by SSR, app.html, or a bootstrap <script> in `<svelte:head>` that
|
|
166
|
+
// has already executed inline). In CSR-only setups the bootstrap may run
|
|
167
|
+
// after this read — that is fine, the hydration component's `$effect`
|
|
168
|
+
// later calls `syncFromDom()` to reconcile.
|
|
137
169
|
_current = _compute();
|
|
138
170
|
window.addEventListener("storage", (e) => {
|
|
139
171
|
if (e.key === _key)
|