@marianmeres/stuic 3.90.0 → 3.92.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.
@@ -3,13 +3,26 @@
3
3
  export interface Props {}
4
4
  </script>
5
5
 
6
+ <script lang="ts">
7
+ import { ColorScheme } from "./color-scheme.svelte.js";
8
+ $effect(() => {
9
+ // Bootstrap <script> below has run by the time this effect fires;
10
+ // re-seed the runtime from whatever the DOM is now showing.
11
+ ColorScheme.syncFromDom();
12
+ });
13
+ </script>
14
+
6
15
  <!--
7
- Similar to PrefersColorScheme, except that it never reads window.matchMedia and only
8
- relies on the local userland setting
16
+ Similar to ColorSchemeSystemAware, except that it never reads window.matchMedia and only
17
+ relies on the local userland setting.
18
+
19
+ Uses the hardcoded default storage key "stuic-color-scheme". Apps that
20
+ override the runtime key via `ColorScheme.configure({ key })` should ship
21
+ their own inline bootstrap in `app.html` if FOUC-free hydration matters.
9
22
  -->
10
23
  <svelte:head>
11
24
  <script>
12
- const KEY = "stuic-color-scheme";
25
+ const KEY = window.__COLOR_SCHEME_KEY__ ?? "stuic-color-scheme";
13
26
  const cls = window.document.documentElement.classList;
14
27
  localStorage.getItem(KEY) === "dark" ? cls.add("dark") : cls.remove("dark");
15
28
  </script>
@@ -1,21 +1,6 @@
1
1
  /** ColorSchemeLocal has no props - it's a pure hydration component */
2
2
  export interface Props {
3
3
  }
4
- interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
5
- new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
6
- $$bindings?: Bindings;
7
- } & Exports;
8
- (internal: unknown, props: {
9
- $$events?: Events;
10
- $$slots?: Slots;
11
- }): Exports & {
12
- $set?: any;
13
- $on?: any;
14
- };
15
- z_$$bindings?: Bindings;
16
- }
17
- declare const ColorSchemeLocal: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
18
- [evt: string]: CustomEvent<any>;
19
- }, {}, {}, string>;
20
- type ColorSchemeLocal = InstanceType<typeof ColorSchemeLocal>;
4
+ declare const ColorSchemeLocal: import("svelte").Component<Record<string, never>, {}, "">;
5
+ type ColorSchemeLocal = ReturnType<typeof ColorSchemeLocal>;
21
6
  export default ColorSchemeLocal;
@@ -3,12 +3,25 @@
3
3
  export interface Props {}
4
4
  </script>
5
5
 
6
+ <script lang="ts">
7
+ import { ColorScheme } from "./color-scheme.svelte.js";
8
+ $effect(() => {
9
+ // Bootstrap <script> below has run by the time this effect fires;
10
+ // re-seed the runtime from whatever the DOM is now showing.
11
+ ColorScheme.syncFromDom();
12
+ });
13
+ </script>
14
+
6
15
  <!--
7
- If you do not wish to take the system preference into account use LocalColorScheme sibling
16
+ If you do not wish to take the system preference into account use ColorSchemeLocal sibling.
17
+
18
+ Uses the hardcoded default storage key "stuic-color-scheme". Apps that
19
+ override the runtime key via `ColorScheme.configure({ key })` should ship
20
+ their own inline bootstrap in `app.html` if FOUC-free hydration matters.
8
21
  -->
9
22
  <svelte:head>
10
23
  <script>
11
- const KEY = "stuic-color-scheme";
24
+ const KEY = window.__COLOR_SCHEME_KEY__ ?? "stuic-color-scheme";
12
25
  const cls = window.document.documentElement.classList;
13
26
  if (KEY in localStorage) {
14
27
  localStorage.getItem(KEY) === "dark" ? cls.add("dark") : cls.remove("dark");
@@ -1,21 +1,6 @@
1
1
  /** ColorSchemeSystemAware has no props - it's a pure hydration component */
2
2
  export interface Props {
3
3
  }
4
- interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
5
- new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
6
- $$bindings?: Bindings;
7
- } & Exports;
8
- (internal: unknown, props: {
9
- $$events?: Events;
10
- $$slots?: Slots;
11
- }): Exports & {
12
- $set?: any;
13
- $on?: any;
14
- };
15
- z_$$bindings?: Bindings;
16
- }
17
- declare const ColorSchemeSystemAware: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
18
- [evt: string]: CustomEvent<any>;
19
- }, {}, {}, string>;
20
- type ColorSchemeSystemAware = InstanceType<typeof ColorSchemeSystemAware>;
4
+ declare const ColorSchemeSystemAware: import("svelte").Component<Record<string, never>, {}, "">;
5
+ type ColorSchemeSystemAware = ReturnType<typeof ColorSchemeSystemAware>;
21
6
  export default ColorSchemeSystemAware;
@@ -1,47 +1,81 @@
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
  *
5
- * Handles system preferences, localStorage persistence, and DOM class toggling.
18
+ * Handles localStorage persistence and DOM class toggling.
6
19
  * Works with Tailwind CSS dark mode (class-based strategy).
7
20
  *
8
21
  * The `ColorScheme.current` getter is reactive (Svelte 5 `$state`): reading it
9
- * inside a `$derived`/`$effect`/template will react to toggles, cross-tab
10
- * `storage` events, and OS-level system-preference changes.
22
+ * inside a `$derived`/`$effect`/template will react to toggles and cross-tab
23
+ * `storage` events.
11
24
  *
12
25
  * @example
13
26
  * ```ts
14
27
  * // Reactive current value (use in templates / $derived / $effect):
15
28
  * const scheme = ColorScheme.current; // 'light' or 'dark'
16
29
  *
17
- * // Backward-compatible non-reactive read:
18
- * const v = ColorScheme.getValue();
19
- *
20
30
  * // Toggle between light and dark
21
31
  * ColorScheme.toggle();
22
32
  *
23
- * // Check system preference only
24
- * const systemPref = ColorScheme.getSystemValue();
25
- *
26
33
  * // Reset to system preference
27
34
  * ColorScheme.reset();
35
+ *
36
+ * // Use a custom localStorage key (call early in app boot):
37
+ * ColorScheme.configure({ key: "myapp:color-scheme" });
28
38
  * ```
29
39
  *
30
40
  * @remarks
31
- * - Uses localStorage key: "stuic-color-scheme"
41
+ * - Default localStorage key: "stuic-color-scheme" (override via `configure`)
32
42
  * - Adds/removes "dark" class on `<html>` element
33
43
  * - Works with Tailwind's `darkMode: 'class'` configuration
44
+ * - Pair with `<ColorSchemeLocal />` or `<ColorSchemeSystemAware />` for
45
+ * FOUC-free initial paint
34
46
  */
35
47
  export declare class ColorScheme {
36
- static readonly KEY: "stuic-color-scheme";
37
48
  static readonly DARK: "dark";
38
49
  static readonly LIGHT: "light";
50
+ /**
51
+ * The active localStorage key. Default is `"stuic-color-scheme"`. Override via
52
+ * `ColorScheme.configure({ key })` or the `key` prop on the hydration components.
53
+ */
54
+ static get KEY(): string;
55
+ /**
56
+ * Configure the runtime. Call early in app boot, before any consumer reads
57
+ * `ColorScheme.current`. Mutates module state; safe to call more than once
58
+ * (last write wins). Calling without changes is a no-op.
59
+ *
60
+ * Also writes `window.__COLOR_SCHEME_KEY__` so a subsequent hydration
61
+ * component mount (or any external bootstrap script that reads the global)
62
+ * stays in sync with the runtime.
63
+ */
64
+ static configure(opts: {
65
+ key?: string;
66
+ }): void;
39
67
  /**
40
68
  * Reactive current value. Read inside `$derived`, `$effect`, or a Svelte
41
- * template to react to scheme changes (including cross-tab `storage`
42
- * events and OS-level system-preference changes).
69
+ * template to react to scheme changes (including cross-tab `storage` events).
43
70
  */
44
71
  static get current(): Scheme;
72
+ /**
73
+ * Re-seed `current` from the actual `<html>` class state. Used by the
74
+ * hydration components after their inline bootstrap `<script>` has run, so
75
+ * the runtime matches whichever strategy actually painted the DOM. Does NOT
76
+ * write to the DOM.
77
+ */
78
+ static syncFromDom(): void;
45
79
  /**
46
80
  * Reads the `prefers-color-scheme` system setting
47
81
  */
@@ -51,15 +85,16 @@ export declare class ColorScheme {
51
85
  */
52
86
  static getLocalValue(fallback?: Scheme): Scheme;
53
87
  /**
54
- * Tries local first, fallbacks to system. Backward-compatible alias for `current`.
88
+ * Backward-compatible alias for `current`.
55
89
  */
56
90
  static getValue(): Scheme;
57
91
  /**
58
- * Sets and saves the opposite of current.
92
+ * Toggles between light and dark, persists to localStorage, and updates the DOM.
59
93
  */
60
94
  static toggle(): void;
61
95
  /**
62
- * Resets color scheme to system preference by removing localStorage value and classes.
96
+ * Resets color scheme to system preference by removing the localStorage value
97
+ * and re-applying the resolved scheme.
63
98
  */
64
99
  static reset(): void;
65
100
  }
@@ -1,57 +1,103 @@
1
+ const DEFAULT_KEY = "stuic-color-scheme";
2
+ let _key = DEFAULT_KEY;
1
3
  let _current = $state("light");
2
4
  function _compute() {
3
- return ColorScheme.getLocalValue(ColorScheme.getSystemValue());
5
+ const local = globalThis.localStorage?.getItem(_key);
6
+ if (local === "dark" || local === "light")
7
+ return local;
8
+ return ColorScheme.getSystemValue();
9
+ }
10
+ function _applyDom(scheme) {
11
+ globalThis?.document?.documentElement.classList.toggle(ColorScheme.DARK, scheme === ColorScheme.DARK);
4
12
  }
5
13
  function _sync() {
6
14
  const next = _compute();
7
- if (next !== _current)
15
+ if (next !== _current) {
8
16
  _current = next;
17
+ _applyDom(next);
18
+ }
9
19
  }
10
20
  /**
11
21
  * A utility class for managing light/dark color scheme preferences.
12
22
  *
13
- * Handles system preferences, localStorage persistence, and DOM class toggling.
23
+ * Handles localStorage persistence and DOM class toggling.
14
24
  * Works with Tailwind CSS dark mode (class-based strategy).
15
25
  *
16
26
  * The `ColorScheme.current` getter is reactive (Svelte 5 `$state`): reading it
17
- * inside a `$derived`/`$effect`/template will react to toggles, cross-tab
18
- * `storage` events, and OS-level system-preference changes.
27
+ * inside a `$derived`/`$effect`/template will react to toggles and cross-tab
28
+ * `storage` events.
19
29
  *
20
30
  * @example
21
31
  * ```ts
22
32
  * // Reactive current value (use in templates / $derived / $effect):
23
33
  * const scheme = ColorScheme.current; // 'light' or 'dark'
24
34
  *
25
- * // Backward-compatible non-reactive read:
26
- * const v = ColorScheme.getValue();
27
- *
28
35
  * // Toggle between light and dark
29
36
  * ColorScheme.toggle();
30
37
  *
31
- * // Check system preference only
32
- * const systemPref = ColorScheme.getSystemValue();
33
- *
34
38
  * // Reset to system preference
35
39
  * ColorScheme.reset();
40
+ *
41
+ * // Use a custom localStorage key (call early in app boot):
42
+ * ColorScheme.configure({ key: "myapp:color-scheme" });
36
43
  * ```
37
44
  *
38
45
  * @remarks
39
- * - Uses localStorage key: "stuic-color-scheme"
46
+ * - Default localStorage key: "stuic-color-scheme" (override via `configure`)
40
47
  * - Adds/removes "dark" class on `<html>` element
41
48
  * - Works with Tailwind's `darkMode: 'class'` configuration
49
+ * - Pair with `<ColorSchemeLocal />` or `<ColorSchemeSystemAware />` for
50
+ * FOUC-free initial paint
42
51
  */
43
52
  export class ColorScheme {
44
- static KEY = "stuic-color-scheme";
45
53
  static DARK = "dark";
46
54
  static LIGHT = "light";
55
+ /**
56
+ * The active localStorage key. Default is `"stuic-color-scheme"`. Override via
57
+ * `ColorScheme.configure({ key })` or the `key` prop on the hydration components.
58
+ */
59
+ static get KEY() {
60
+ return _key;
61
+ }
62
+ /**
63
+ * Configure the runtime. Call early in app boot, before any consumer reads
64
+ * `ColorScheme.current`. Mutates module state; safe to call more than once
65
+ * (last write wins). Calling without changes is a no-op.
66
+ *
67
+ * Also writes `window.__COLOR_SCHEME_KEY__` so a subsequent hydration
68
+ * component mount (or any external bootstrap script that reads the global)
69
+ * stays in sync with the runtime.
70
+ */
71
+ static configure(opts) {
72
+ if (opts?.key && opts.key !== _key) {
73
+ _key = opts.key;
74
+ if (typeof window !== "undefined")
75
+ window.__COLOR_SCHEME_KEY__ = opts.key;
76
+ _sync();
77
+ }
78
+ }
47
79
  /**
48
80
  * Reactive current value. Read inside `$derived`, `$effect`, or a Svelte
49
- * template to react to scheme changes (including cross-tab `storage`
50
- * events and OS-level system-preference changes).
81
+ * template to react to scheme changes (including cross-tab `storage` events).
51
82
  */
52
83
  static get current() {
53
84
  return _current;
54
85
  }
86
+ /**
87
+ * Re-seed `current` from the actual `<html>` class state. Used by the
88
+ * hydration components after their inline bootstrap `<script>` has run, so
89
+ * the runtime matches whichever strategy actually painted the DOM. Does NOT
90
+ * write to the DOM.
91
+ */
92
+ static syncFromDom() {
93
+ if (typeof document === "undefined")
94
+ return;
95
+ const next = document.documentElement.classList.contains(ColorScheme.DARK)
96
+ ? ColorScheme.DARK
97
+ : ColorScheme.LIGHT;
98
+ if (next !== _current)
99
+ _current = next;
100
+ }
55
101
  /**
56
102
  * Reads the `prefers-color-scheme` system setting
57
103
  */
@@ -64,39 +110,44 @@ export class ColorScheme {
64
110
  * Reads locally (localStorage) saved value
65
111
  */
66
112
  static getLocalValue(fallback = "light") {
67
- return (globalThis.localStorage?.getItem(ColorScheme.KEY) || fallback);
113
+ return globalThis.localStorage?.getItem(_key) || fallback;
68
114
  }
69
115
  /**
70
- * Tries local first, fallbacks to system. Backward-compatible alias for `current`.
116
+ * Backward-compatible alias for `current`.
71
117
  */
72
118
  static getValue() {
73
- return ColorScheme.current;
119
+ return _current;
74
120
  }
75
121
  /**
76
- * Sets and saves the opposite of current.
122
+ * Toggles between light and dark, persists to localStorage, and updates the DOM.
77
123
  */
78
124
  static toggle() {
79
- // returns bool, indicating whether token is in the list after the call or not.
80
- const isDark = globalThis?.document?.documentElement.classList.toggle(ColorScheme.DARK);
81
- globalThis.localStorage?.setItem(ColorScheme.KEY, isDark ? ColorScheme.DARK : ColorScheme.LIGHT);
82
- _sync();
125
+ const next = _current === ColorScheme.DARK ? ColorScheme.LIGHT : ColorScheme.DARK;
126
+ globalThis.localStorage?.setItem(_key, next);
127
+ _applyDom(next);
128
+ _current = next;
83
129
  }
84
130
  /**
85
- * Resets color scheme to system preference by removing localStorage value and classes.
131
+ * Resets color scheme to system preference by removing the localStorage value
132
+ * and re-applying the resolved scheme.
86
133
  */
87
134
  static reset() {
88
- globalThis.localStorage?.removeItem(ColorScheme.KEY);
89
- globalThis?.document?.documentElement.classList.remove(ColorScheme.DARK, ColorScheme.LIGHT);
135
+ globalThis.localStorage?.removeItem(_key);
90
136
  _sync();
91
137
  }
92
138
  }
93
139
  if (typeof window !== "undefined") {
140
+ // If the app declared a custom key via the global (typical place: app.html,
141
+ // before the bootstrap <script> runs), adopt it so the runtime reads/writes
142
+ // the same key the bootstrap painted from.
143
+ if (window.__COLOR_SCHEME_KEY__)
144
+ _key = window.__COLOR_SCHEME_KEY__;
145
+ // Seed from localStorage (with system-pref fallback). Reading from the DOM
146
+ // here is unreliable: module init runs before the hydration component's
147
+ // inline <script> is appended to <head>, so the dark class isn't there yet.
94
148
  _current = _compute();
95
149
  window.addEventListener("storage", (e) => {
96
- if (e.key === ColorScheme.KEY)
150
+ if (e.key === _key)
97
151
  _sync();
98
152
  });
99
- window
100
- .matchMedia?.(`(prefers-color-scheme: ${ColorScheme.DARK})`)
101
- .addEventListener("change", () => _sync());
102
153
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "3.90.0",
3
+ "version": "3.92.0",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && pnpm run prepack",