@marianmeres/stuic 3.90.0 → 3.91.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,9 +3,22 @@
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>
@@ -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,8 +3,21 @@
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>
@@ -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;
@@ -2,46 +2,63 @@ type Scheme = "dark" | "light";
2
2
  /**
3
3
  * A utility class for managing light/dark color scheme preferences.
4
4
  *
5
- * Handles system preferences, localStorage persistence, and DOM class toggling.
5
+ * Handles localStorage persistence and DOM class toggling.
6
6
  * Works with Tailwind CSS dark mode (class-based strategy).
7
7
  *
8
8
  * 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.
9
+ * inside a `$derived`/`$effect`/template will react to toggles and cross-tab
10
+ * `storage` events.
11
11
  *
12
12
  * @example
13
13
  * ```ts
14
14
  * // Reactive current value (use in templates / $derived / $effect):
15
15
  * const scheme = ColorScheme.current; // 'light' or 'dark'
16
16
  *
17
- * // Backward-compatible non-reactive read:
18
- * const v = ColorScheme.getValue();
19
- *
20
17
  * // Toggle between light and dark
21
18
  * ColorScheme.toggle();
22
19
  *
23
- * // Check system preference only
24
- * const systemPref = ColorScheme.getSystemValue();
25
- *
26
20
  * // Reset to system preference
27
21
  * ColorScheme.reset();
22
+ *
23
+ * // Use a custom localStorage key (call early in app boot):
24
+ * ColorScheme.configure({ key: "myapp:color-scheme" });
28
25
  * ```
29
26
  *
30
27
  * @remarks
31
- * - Uses localStorage key: "stuic-color-scheme"
28
+ * - Default localStorage key: "stuic-color-scheme" (override via `configure`)
32
29
  * - Adds/removes "dark" class on `<html>` element
33
30
  * - Works with Tailwind's `darkMode: 'class'` configuration
31
+ * - Pair with `<ColorSchemeLocal />` or `<ColorSchemeSystemAware />` for
32
+ * FOUC-free initial paint
34
33
  */
35
34
  export declare class ColorScheme {
36
- static readonly KEY: "stuic-color-scheme";
37
35
  static readonly DARK: "dark";
38
36
  static readonly LIGHT: "light";
37
+ /**
38
+ * The active localStorage key. Default is `"stuic-color-scheme"`. Override via
39
+ * `ColorScheme.configure({ key })` or the `key` prop on the hydration components.
40
+ */
41
+ static get KEY(): string;
42
+ /**
43
+ * Configure the runtime. Call early in app boot, before any consumer reads
44
+ * `ColorScheme.current`. Mutates module state; safe to call more than once
45
+ * (last write wins). Calling without changes is a no-op.
46
+ */
47
+ static configure(opts: {
48
+ key?: string;
49
+ }): void;
39
50
  /**
40
51
  * 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).
52
+ * template to react to scheme changes (including cross-tab `storage` events).
43
53
  */
44
54
  static get current(): Scheme;
55
+ /**
56
+ * Re-seed `current` from the actual `<html>` class state. Used by the
57
+ * hydration components after their inline bootstrap `<script>` has run, so
58
+ * the runtime matches whichever strategy actually painted the DOM. Does NOT
59
+ * write to the DOM.
60
+ */
61
+ static syncFromDom(): void;
45
62
  /**
46
63
  * Reads the `prefers-color-scheme` system setting
47
64
  */
@@ -51,15 +68,16 @@ export declare class ColorScheme {
51
68
  */
52
69
  static getLocalValue(fallback?: Scheme): Scheme;
53
70
  /**
54
- * Tries local first, fallbacks to system. Backward-compatible alias for `current`.
71
+ * Backward-compatible alias for `current`.
55
72
  */
56
73
  static getValue(): Scheme;
57
74
  /**
58
- * Sets and saves the opposite of current.
75
+ * Toggles between light and dark, persists to localStorage, and updates the DOM.
59
76
  */
60
77
  static toggle(): void;
61
78
  /**
62
- * Resets color scheme to system preference by removing localStorage value and classes.
79
+ * Resets color scheme to system preference by removing the localStorage value
80
+ * and re-applying the resolved scheme.
63
81
  */
64
82
  static reset(): void;
65
83
  }
@@ -1,57 +1,97 @@
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
+ static configure(opts) {
68
+ if (opts?.key && opts.key !== _key) {
69
+ _key = opts.key;
70
+ _sync();
71
+ }
72
+ }
47
73
  /**
48
74
  * 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).
75
+ * template to react to scheme changes (including cross-tab `storage` events).
51
76
  */
52
77
  static get current() {
53
78
  return _current;
54
79
  }
80
+ /**
81
+ * Re-seed `current` from the actual `<html>` class state. Used by the
82
+ * hydration components after their inline bootstrap `<script>` has run, so
83
+ * the runtime matches whichever strategy actually painted the DOM. Does NOT
84
+ * write to the DOM.
85
+ */
86
+ static syncFromDom() {
87
+ if (typeof document === "undefined")
88
+ return;
89
+ const next = document.documentElement.classList.contains(ColorScheme.DARK)
90
+ ? ColorScheme.DARK
91
+ : ColorScheme.LIGHT;
92
+ if (next !== _current)
93
+ _current = next;
94
+ }
55
95
  /**
56
96
  * Reads the `prefers-color-scheme` system setting
57
97
  */
@@ -64,39 +104,39 @@ export class ColorScheme {
64
104
  * Reads locally (localStorage) saved value
65
105
  */
66
106
  static getLocalValue(fallback = "light") {
67
- return (globalThis.localStorage?.getItem(ColorScheme.KEY) || fallback);
107
+ return globalThis.localStorage?.getItem(_key) || fallback;
68
108
  }
69
109
  /**
70
- * Tries local first, fallbacks to system. Backward-compatible alias for `current`.
110
+ * Backward-compatible alias for `current`.
71
111
  */
72
112
  static getValue() {
73
- return ColorScheme.current;
113
+ return _current;
74
114
  }
75
115
  /**
76
- * Sets and saves the opposite of current.
116
+ * Toggles between light and dark, persists to localStorage, and updates the DOM.
77
117
  */
78
118
  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();
119
+ const next = _current === ColorScheme.DARK ? ColorScheme.LIGHT : ColorScheme.DARK;
120
+ globalThis.localStorage?.setItem(_key, next);
121
+ _applyDom(next);
122
+ _current = next;
83
123
  }
84
124
  /**
85
- * Resets color scheme to system preference by removing localStorage value and classes.
125
+ * Resets color scheme to system preference by removing the localStorage value
126
+ * and re-applying the resolved scheme.
86
127
  */
87
128
  static reset() {
88
- globalThis.localStorage?.removeItem(ColorScheme.KEY);
89
- globalThis?.document?.documentElement.classList.remove(ColorScheme.DARK, ColorScheme.LIGHT);
129
+ globalThis.localStorage?.removeItem(_key);
90
130
  _sync();
91
131
  }
92
132
  }
93
133
  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.
94
137
  _current = _compute();
95
138
  window.addEventListener("storage", (e) => {
96
- if (e.key === ColorScheme.KEY)
139
+ if (e.key === _key)
97
140
  _sync();
98
141
  });
99
- window
100
- .matchMedia?.(`(prefers-color-scheme: ${ColorScheme.DARK})`)
101
- .addEventListener("change", () => _sync());
102
142
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "3.90.0",
3
+ "version": "3.91.0",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && pnpm run prepack",