@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.
@@ -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) === "dark" ? cls.add("dark") : cls.remove("dark");
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
- If you do not wish to take the system preference into account use ColorSchemeLocal sibling.
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 document root based on localStorage and optionally system preference.
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 to determine dark mode. Does not check system preference.
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, then falls back to system preference (`prefers-color-scheme: dark`).
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** - they are pure hydration components.
25
+ Both components have **no props** they are pure hydration components.
18
26
 
19
27
  ## Storage Key
20
28
 
21
- Both components use the localStorage key: `stuic-color-scheme`
29
+ Default localStorage key: `stuic-color-scheme`.
22
30
 
23
31
  - Value `"dark"` → adds `dark` class to `<html>`
24
- - Any other value → removes `dark` class
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
- ### System-Aware (Recommended)
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
- ### Local Only
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
- ### Toggle Dark Mode
86
+ ### Apply system preference on demand
49
87
 
50
88
  ```svelte
51
89
  <script lang="ts">
52
- import { ColorSchemeSystemAware } from "stuic";
90
+ import { ColorScheme } from "stuic";
53
91
 
54
- function toggleDarkMode() {
55
- const root = document.documentElement;
56
- const isDark = root.classList.toggle("dark");
57
- localStorage.setItem("stuic-color-scheme", isDark ? "dark" : "light");
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
- <ColorSchemeSystemAware />
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
- * // Reset to system preference
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
- * Resets color scheme to system preference by removing the localStorage value
80
- * and re-applying the resolved scheme.
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
- return ColorScheme.getSystemValue();
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
- * // Reset to system preference
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
- * Resets color scheme to system preference by removing the localStorage value
126
- * and re-applying the resolved scheme.
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
- // Seed from localStorage (with system-pref fallback). Reading from the DOM
135
- // here is unreliable: module init runs before the hydration component's
136
- // inline <script> is appended to <head>, so the dark class isn't there yet.
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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "3.91.0",
3
+ "version": "3.93.0",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && pnpm run prepack",