@stacksjs/stx 0.2.152 → 0.2.153
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/app-shell.js +28 -1
- package/dist/browser-composables.d.ts +8 -0
- package/dist/build-id.d.ts +49 -0
- package/dist/build-id.js +20 -0
- package/dist/cli.js +886 -648
- package/dist/client-script.js +2 -2
- package/dist/color-mode-boot.d.ts +15 -10
- package/dist/color-mode-boot.js +12 -8
- package/dist/component-client-factories.js +3 -2
- package/dist/composables/index.d.ts +6 -1
- package/dist/composables/use-color-mode.d.ts +19 -4
- package/dist/composables/use-color-mode.js +37 -24
- package/dist/composables/use-storage.d.ts +14 -15
- package/dist/composables/use-storage.js +63 -2
- package/dist/craft.js +622 -384
- package/dist/dev-server/serve-app.js +9 -5
- package/dist/dev-server/types.d.ts +8 -1
- package/dist/document-shell.d.ts +55 -0
- package/dist/document-shell.js +103 -3
- package/dist/head.js +5 -1
- package/dist/includes.js +2 -2
- package/dist/index.d.ts +17 -0
- package/dist/index.js +9 -0
- package/dist/process.js +7 -3
- package/dist/production-builder.js +3 -1
- package/dist/pwa.js +581 -343
- package/dist/signals.js +280 -44
- package/dist/ssg.js +571 -333
- package/dist/stx.d.ts +14 -6
- package/dist/types/config-types.d.ts +1 -0
- package/package.json +5 -5
- package/stx.d.ts +14 -6
package/dist/client-script.js
CHANGED
|
@@ -918,7 +918,7 @@ ${eventCode}
|
|
|
918
918
|
function wrapInMount(declarations) {
|
|
919
919
|
const returnStmt = declarations.length > 0 ? `
|
|
920
920
|
return { ${declarations.join(", ")} };` : "";
|
|
921
|
-
return `${vendorStyleTags}<script data-stx-scoped>
|
|
921
|
+
return `${vendorStyleTags}<script data-stx-scoped data-stx-run="always">
|
|
922
922
|
window.stx.mount(function() {
|
|
923
923
|
'use strict';
|
|
924
924
|
${autoImportCode}${code}
|
|
@@ -937,7 +937,7 @@ ${eventCode}${returnStmt}
|
|
|
937
937
|
}
|
|
938
938
|
}
|
|
939
939
|
}
|
|
940
|
-
return `${vendorStyleTags}<script data-stx-scoped>
|
|
940
|
+
return `${vendorStyleTags}<script data-stx-scoped data-stx-run="always">
|
|
941
941
|
;(function() {
|
|
942
942
|
'use strict';
|
|
943
943
|
${autoImportCode}${code}
|
|
@@ -53,15 +53,17 @@ export declare function injectColorModeBootScript(html: string, options?: ColorM
|
|
|
53
53
|
* hydration. If they disagree the flash comes back — the boot script would set
|
|
54
54
|
* one thing and hydration would immediately undo it, which is worse than not
|
|
55
55
|
* having a boot script at all. So this mirrors the runtime's `applyDOM` and
|
|
56
|
-
*
|
|
56
|
+
* preference reading precisely:
|
|
57
57
|
*
|
|
58
|
-
* - `attribute` and `darkClass`
|
|
59
|
-
*
|
|
60
|
-
*
|
|
58
|
+
* - `attribute` and `darkClass` both apply when both are configured, and
|
|
59
|
+
* `darkClass: null` opts out of the class (stacksjs/stx#1788)
|
|
60
|
+
* - the accepted vocabulary is `light | dark | auto | system`, with
|
|
61
|
+
* `'system'` an alias for `'auto'` (also #1788)
|
|
61
62
|
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
63
|
+
* `test/directives/color-mode-boot.test.ts` pins both sides to the same
|
|
64
|
+
* expectations so drift fails the suite rather than shipping a flash. This
|
|
65
|
+
* script never writes to storage — reading is all it needs, and a write here
|
|
66
|
+
* would race the composable's own persistence.
|
|
65
67
|
*
|
|
66
68
|
* The script publishes its resolved configuration on `window.__STX_COLOR_MODE__`
|
|
67
69
|
* so a bare `useColorMode()` call picks up the app's configured storage key and
|
|
@@ -82,7 +84,10 @@ export declare const COLOR_MODE_BOOT_GLOBAL: '__STX_COLOR_MODE__';
|
|
|
82
84
|
export declare interface ColorModeBootConfig {
|
|
83
85
|
storageKey?: string
|
|
84
86
|
initialMode?: ColorModeBootMode
|
|
85
|
-
darkClass?: string
|
|
86
|
-
attribute?: string
|
|
87
|
+
darkClass?: string | null
|
|
88
|
+
attribute?: string | null
|
|
89
|
+
autoValue?: ColorModeBootAutoValue
|
|
87
90
|
}
|
|
88
|
-
export type ColorModeBootMode = 'light' | 'dark' | 'auto';
|
|
91
|
+
export type ColorModeBootMode = 'light' | 'dark' | 'auto' | 'system';
|
|
92
|
+
/** Spelling written to storage for "follow the system". */
|
|
93
|
+
export type ColorModeBootAutoValue = 'auto' | 'system';
|
package/dist/color-mode-boot.js
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
export const COLOR_MODE_BOOT_MARKER = "data-stx-color-mode-boot", COLOR_MODE_BOOT_GLOBAL = "__STX_COLOR_MODE__";
|
|
2
|
-
const MODES = new Set(["light", "dark", "auto"]), ATTRIBUTE_PATTERN = /^[a-z][\w:-]*$/i, CLASS_PATTERN = /^[\w-]+$/;
|
|
2
|
+
const MODES = new Set(["light", "dark", "auto", "system"]), AUTO_VALUES = new Set(["auto", "system"]), ATTRIBUTE_PATTERN = /^[a-z][\w:-]*$/i, CLASS_PATTERN = /^[\w-]+$/;
|
|
3
3
|
function resolveConfig(options) {
|
|
4
|
-
const storageKey = options.storageKey ?? "stx-color-mode",
|
|
4
|
+
const storageKey = options.storageKey ?? "stx-color-mode", rawInitial = options.initialMode ?? "auto", darkClass = options.darkClass === void 0 ? "dark" : options.darkClass, attribute = options.attribute ?? null, autoValue = options.autoValue ?? null;
|
|
5
5
|
if (typeof storageKey !== "string" || storageKey.length === 0)
|
|
6
6
|
throw Error("colorMode.storageKey must be a non-empty string");
|
|
7
|
-
if (!MODES.has(
|
|
8
|
-
throw Error(`colorMode.initialMode must be one of light, dark, auto \u2014 got ${JSON.stringify(
|
|
9
|
-
if (typeof darkClass !== "string" || !CLASS_PATTERN.test(darkClass))
|
|
10
|
-
throw Error(`colorMode.darkClass must be a single CSS class name \u2014 got ${JSON.stringify(darkClass)}`);
|
|
7
|
+
if (!MODES.has(rawInitial))
|
|
8
|
+
throw Error(`colorMode.initialMode must be one of light, dark, auto, system \u2014 got ${JSON.stringify(rawInitial)}`);
|
|
9
|
+
if (darkClass !== null && (typeof darkClass !== "string" || !CLASS_PATTERN.test(darkClass)))
|
|
10
|
+
throw Error(`colorMode.darkClass must be a single CSS class name, or null to opt out \u2014 got ${JSON.stringify(darkClass)}`);
|
|
11
11
|
if (attribute !== null && (typeof attribute !== "string" || !ATTRIBUTE_PATTERN.test(attribute)))
|
|
12
12
|
throw Error(`colorMode.attribute must be a valid HTML attribute name \u2014 got ${JSON.stringify(attribute)}`);
|
|
13
|
-
|
|
13
|
+
if (autoValue !== null && !AUTO_VALUES.has(autoValue))
|
|
14
|
+
throw Error(`colorMode.autoValue must be 'auto' or 'system' \u2014 got ${JSON.stringify(autoValue)}`);
|
|
15
|
+
if (darkClass === null && attribute === null)
|
|
16
|
+
throw Error("colorMode needs at least one of darkClass or attribute \u2014 with both null nothing would mark the mode on <html>");
|
|
17
|
+
return { storageKey, initialMode: rawInitial === "system" ? "auto" : rawInitial, darkClass, attribute, autoValue };
|
|
14
18
|
}
|
|
15
19
|
function serializeForScript(value) {
|
|
16
20
|
return JSON.stringify(value).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
17
21
|
}
|
|
18
22
|
export function generateColorModeBootScript(options = {}) {
|
|
19
|
-
const config = resolveConfig(options), body = `(function(){"use strict";var c=${serializeForScript(config)};var p=c.initialMode;try{var v=localStorage.getItem(c.storageKey);if(v==="light"||v==="dark"
|
|
23
|
+
const config = resolveConfig(options), body = `(function(){"use strict";var c=${serializeForScript(config)};var p=c.initialMode;var a=c.autoValue;try{var v=localStorage.getItem(c.storageKey);if(v==="light"||v==="dark"){p=v}else if(v==="auto"||v==="system"){p="auto";if(!a)a=v}}catch(e){}var m=p;if(p==="auto"){try{m=matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}catch(e){m="light"}}var r=document.documentElement;if(c.attribute)r.setAttribute(c.attribute,m);if(c.darkClass){if(m==="dark")r.classList.add(c.darkClass);else r.classList.remove(c.darkClass)}try{window.${COLOR_MODE_BOOT_GLOBAL}={storageKey:c.storageKey,initialMode:c.initialMode,darkClass:c.darkClass,attribute:c.attribute,autoValue:a||"auto",preference:p,mode:m}}catch(e){}}());`;
|
|
20
24
|
return `<script ${COLOR_MODE_BOOT_MARKER}>${body}</script>`;
|
|
21
25
|
}
|
|
22
26
|
export function injectColorModeBootScript(html, options = {}) {
|
|
@@ -32,10 +32,11 @@ export function injectComponentClientFactories(html, context) {
|
|
|
32
32
|
const repeatedFactories = [...registry].filter(([, factory]) => factory.instances > 1);
|
|
33
33
|
if (repeatedFactories.length === 0)
|
|
34
34
|
return output;
|
|
35
|
-
const prelude = `<script data-stx-scoped data-stx-component-factories>
|
|
35
|
+
const prelude = `<script data-stx-scoped data-stx-run="always" data-stx-component-factories>
|
|
36
36
|
(function() {
|
|
37
37
|
const factories = window.__stxComponentFactories = window.__stxComponentFactories || {};
|
|
38
|
-
${repeatedFactories.map(([id, factory]) => ` factories[${JSON.stringify(id)}] = factories[${JSON.stringify(id)}] || ${factory.body}
|
|
38
|
+
${repeatedFactories.map(([id, factory]) => ` try { factories[${JSON.stringify(id)}] = factories[${JSON.stringify(id)}] || ${factory.body}; }
|
|
39
|
+
catch (e) { console.error('[stx] component factory ' + ${JSON.stringify(id)} + ' failed to register; instances of it will not hydrate.', e); }`).join(`
|
|
39
40
|
`)}
|
|
40
41
|
})();
|
|
41
42
|
</script>`, firstScopedScript = output.search(/<script\b(?=[^>]*\bdata-stx-scoped\b)/i);
|
|
@@ -16,7 +16,12 @@
|
|
|
16
16
|
* } from '@stacksjs/stx/composables'
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
|
-
// Storage
|
|
19
|
+
// Storage. `useLocalStorage` / `useSessionStorage` return a Signal<T> matching
|
|
20
|
+
// the runtime contract and the ambient stx.d.ts; the old StorageRef shape was
|
|
21
|
+
// removed in #1797 as part of the dual-impl unification. Migrate `.value` reads
|
|
22
|
+
// to `s()` and `.value = x` writes to `s.set(x)` (`.set()` is unchanged); for
|
|
23
|
+
// `.remove()` / `.subscribe()` / custom serializers use `useStorage`, which
|
|
24
|
+
// still returns a StorageRef.
|
|
20
25
|
export {
|
|
21
26
|
useStorage,
|
|
22
27
|
useLocalStorage,
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map any accepted spelling onto the canonical vocabulary, or null if the value
|
|
3
|
+
* is not a color-mode preference at all.
|
|
4
|
+
*/
|
|
5
|
+
export declare function normalizeColorMode(value: unknown): ColorMode | null;
|
|
1
6
|
/**
|
|
2
7
|
* Full-featured color-mode manager.
|
|
3
8
|
*
|
|
@@ -21,16 +26,17 @@ export declare function useColorMode(options?: ColorModeOptions): ColorModeRef;
|
|
|
21
26
|
export declare function useDark(options?: ColorModeOptions): DarkRef;
|
|
22
27
|
export declare interface ColorModeOptions {
|
|
23
28
|
storageKey?: string
|
|
24
|
-
initialMode?:
|
|
25
|
-
darkClass?: string
|
|
26
|
-
attribute?: string
|
|
29
|
+
initialMode?: ColorModeInput
|
|
30
|
+
darkClass?: string | null
|
|
31
|
+
attribute?: string | null
|
|
32
|
+
autoValue?: AutoValue
|
|
27
33
|
disableTransitions?: boolean
|
|
28
34
|
}
|
|
29
35
|
export declare interface ColorModeRef {
|
|
30
36
|
readonly mode: 'light' | 'dark'
|
|
31
37
|
readonly preference: ColorMode
|
|
32
38
|
readonly isDark: boolean
|
|
33
|
-
set: (mode:
|
|
39
|
+
set: (mode: ColorModeInput) => void
|
|
34
40
|
toggle: () => void
|
|
35
41
|
subscribe: (fn: (mode: 'light' | 'dark', preference: ColorMode) => void) => () => void
|
|
36
42
|
}
|
|
@@ -50,3 +56,12 @@ export declare interface DarkRef {
|
|
|
50
56
|
// Types
|
|
51
57
|
// =============================================================================
|
|
52
58
|
export type ColorMode = 'light' | 'dark' | 'auto';
|
|
59
|
+
/**
|
|
60
|
+
* What may be *written* into storage for "follow the system".
|
|
61
|
+
*
|
|
62
|
+
* `'system'` is a common spelling for the same concept, and an app that already
|
|
63
|
+
* persists it must not have that overwritten (stacksjs/stx#1788).
|
|
64
|
+
*/
|
|
65
|
+
export type AutoValue = 'auto' | 'system';
|
|
66
|
+
/** Accepted on input anywhere a ColorMode is; normalised to ColorMode. */
|
|
67
|
+
export type ColorModeInput = ColorMode | 'system';
|
|
@@ -1,12 +1,21 @@
|
|
|
1
|
+
const AUTO_VALUES = ["auto", "system"];
|
|
2
|
+
export function normalizeColorMode(value) {
|
|
3
|
+
if (value === "light" || value === "dark")
|
|
4
|
+
return value;
|
|
5
|
+
if (typeof value === "string" && AUTO_VALUES.includes(value))
|
|
6
|
+
return "auto";
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
1
9
|
export function useColorMode(options = {}) {
|
|
2
10
|
const boot = typeof window < "u" ? window.__STX_COLOR_MODE__ : null, {
|
|
3
11
|
storageKey = boot?.storageKey ?? "stx-color-mode",
|
|
4
12
|
initialMode = boot?.initialMode ?? "auto",
|
|
5
13
|
darkClass = boot?.darkClass ?? "dark",
|
|
6
|
-
attribute = boot?.attribute,
|
|
14
|
+
attribute = boot?.attribute ?? null,
|
|
15
|
+
autoValue: autoValueOption = boot?.autoValue,
|
|
7
16
|
disableTransitions = !0
|
|
8
17
|
} = options;
|
|
9
|
-
let preference = initialMode, resolved = "light";
|
|
18
|
+
let preference = normalizeColorMode(initialMode) ?? "auto", resolved = "light";
|
|
10
19
|
const listeners = new Set, cleanups = [], isBrowser = typeof window < "u";
|
|
11
20
|
function getSystemPreference() {
|
|
12
21
|
if (!isBrowser)
|
|
@@ -24,41 +33,43 @@ export function useColorMode(options = {}) {
|
|
|
24
33
|
el.style.setProperty("transition", "none", "important");
|
|
25
34
|
if (attribute)
|
|
26
35
|
el.setAttribute(attribute, mode);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
if (darkClass)
|
|
37
|
+
if (mode === "dark")
|
|
38
|
+
el.classList.add(darkClass);
|
|
39
|
+
else
|
|
40
|
+
el.classList.remove(darkClass);
|
|
31
41
|
if (disableTransitions) {
|
|
32
42
|
el.offsetHeight;
|
|
33
43
|
el.style.removeProperty("transition");
|
|
34
44
|
}
|
|
35
45
|
}
|
|
36
|
-
function
|
|
46
|
+
function readRaw() {
|
|
37
47
|
if (!isBrowser)
|
|
38
|
-
return;
|
|
48
|
+
return null;
|
|
39
49
|
try {
|
|
40
|
-
localStorage.
|
|
41
|
-
} catch {
|
|
50
|
+
return localStorage.getItem(storageKey);
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
42
54
|
}
|
|
43
|
-
|
|
55
|
+
const storedRaw = readRaw();
|
|
56
|
+
let autoValue = autoValueOption ?? (storedRaw !== null && AUTO_VALUES.includes(storedRaw) ? storedRaw : "auto");
|
|
57
|
+
function persist(pref) {
|
|
44
58
|
if (!isBrowser)
|
|
45
|
-
return
|
|
59
|
+
return;
|
|
46
60
|
try {
|
|
47
|
-
|
|
48
|
-
if (v === "light" || v === "dark" || v === "auto")
|
|
49
|
-
return v;
|
|
61
|
+
localStorage.setItem(storageKey, pref === "auto" ? autoValue : pref);
|
|
50
62
|
} catch {}
|
|
51
|
-
return null;
|
|
52
63
|
}
|
|
53
|
-
function update(pref) {
|
|
54
|
-
preference = pref;
|
|
55
|
-
resolved = resolve(
|
|
64
|
+
function update(pref, persistChoice = !0) {
|
|
65
|
+
preference = normalizeColorMode(pref) ?? "auto";
|
|
66
|
+
resolved = resolve(preference);
|
|
56
67
|
applyToDOM(resolved);
|
|
57
|
-
|
|
68
|
+
if (persistChoice)
|
|
69
|
+
persist(preference);
|
|
58
70
|
listeners.forEach((fn) => fn(resolved, preference));
|
|
59
71
|
}
|
|
60
|
-
|
|
61
|
-
update(persisted ?? initialMode);
|
|
72
|
+
update(normalizeColorMode(storedRaw) ?? initialMode, !1);
|
|
62
73
|
if (isBrowser) {
|
|
63
74
|
const mql = window.matchMedia("(prefers-color-scheme: dark)"), onSystemChange = () => {
|
|
64
75
|
if (preference === "auto") {
|
|
@@ -72,8 +83,10 @@ export function useColorMode(options = {}) {
|
|
|
72
83
|
const onStorage = (e) => {
|
|
73
84
|
if (e.key !== storageKey)
|
|
74
85
|
return;
|
|
75
|
-
const v = e.newValue;
|
|
76
|
-
if (v
|
|
86
|
+
const v = normalizeColorMode(e.newValue);
|
|
87
|
+
if (v) {
|
|
88
|
+
if (v === "auto" && e.newValue && AUTO_VALUES.includes(e.newValue))
|
|
89
|
+
autoValue = e.newValue;
|
|
77
90
|
preference = v;
|
|
78
91
|
resolved = resolve(v);
|
|
79
92
|
applyToDOM(resolved);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Signal } from '../signals-api';
|
|
1
2
|
/**
|
|
2
3
|
* Create a reactive storage reference
|
|
3
4
|
*
|
|
@@ -19,19 +20,23 @@
|
|
|
19
20
|
*/
|
|
20
21
|
export declare function useStorage<T>(key: string, defaultValue: T, options?: UseStorageOptions<T>): StorageRef<T>;
|
|
21
22
|
/**
|
|
22
|
-
*
|
|
23
|
+
* Reactive localStorage binding. Returns a Signal: `s()` reads, `s.set(v)`
|
|
24
|
+
* writes and persists.
|
|
23
25
|
*
|
|
24
|
-
* The
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
26
|
+
* The same shape the ambient `stx.d.ts` declares and the same shape a
|
|
27
|
+
* `<script client>` block gets from `window.stx` — one identifier, one
|
|
28
|
+
* contract, whichever way it's imported (stacksjs/stx#1797). It used to return
|
|
29
|
+
* a `StorageRef` here and a signal there, so `.value` worked or silently
|
|
30
|
+
* yielded `undefined` depending on which entry point resolved.
|
|
31
|
+
*
|
|
32
|
+
* For the richer object API — `.value`, `.remove()`, `.subscribe()`, custom
|
|
33
|
+
* serializers — use {@link useStorage}, which is unchanged.
|
|
29
34
|
*/
|
|
30
|
-
export declare function useLocalStorage<T>(key: string, defaultValue: T, options?: Omit<UseStorageOptions<T>, 'storage'>):
|
|
35
|
+
export declare function useLocalStorage<T>(key: string, defaultValue: T, options?: Omit<UseStorageOptions<T>, 'storage'>): Signal<T>;
|
|
31
36
|
/**
|
|
32
|
-
*
|
|
37
|
+
* Reactive sessionStorage binding. Returns a Signal — see {@link useLocalStorage}.
|
|
33
38
|
*/
|
|
34
|
-
export declare function useSessionStorage<T>(key: string, defaultValue: T, options?: Omit<UseStorageOptions<T>, 'storage'>):
|
|
39
|
+
export declare function useSessionStorage<T>(key: string, defaultValue: T, options?: Omit<UseStorageOptions<T>, 'storage'>): Signal<T>;
|
|
35
40
|
/**
|
|
36
41
|
* Clear all items from storage
|
|
37
42
|
*/
|
|
@@ -61,10 +66,4 @@ export declare interface StorageRef<T> {
|
|
|
61
66
|
remove: () => void
|
|
62
67
|
subscribe: (callback: (value: T, prev: T | undefined) => void) => () => void
|
|
63
68
|
}
|
|
64
|
-
/**
|
|
65
|
-
* useStorage - Reactive localStorage/sessionStorage composable
|
|
66
|
-
*
|
|
67
|
-
* Similar to Nuxt's useStorage but for STX applications.
|
|
68
|
-
* Provides a reactive, type-safe wrapper around Web Storage APIs.
|
|
69
|
-
*/
|
|
70
69
|
export type StorageType = 'local' | 'session';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { effect, state } from "../signals-api.js";
|
|
1
2
|
const defaultSerializer = {
|
|
2
3
|
read: (value) => {
|
|
3
4
|
try {
|
|
@@ -106,11 +107,71 @@ export function useStorage(key, defaultValue, options = {}) {
|
|
|
106
107
|
};
|
|
107
108
|
return ref;
|
|
108
109
|
}
|
|
110
|
+
function storageSignal(storageType, label, key, defaultValue, options = {}) {
|
|
111
|
+
const {
|
|
112
|
+
serializer = defaultSerializer,
|
|
113
|
+
mergeDefaults = !1,
|
|
114
|
+
listenToStorageChanges = !0
|
|
115
|
+
} = options, isClient = typeof window < "u", getStorage = () => {
|
|
116
|
+
if (!isClient)
|
|
117
|
+
return null;
|
|
118
|
+
return storageType === "session" ? sessionStorage : localStorage;
|
|
119
|
+
}, parse = (raw) => {
|
|
120
|
+
if (raw === null || raw === void 0)
|
|
121
|
+
return defaultValue;
|
|
122
|
+
try {
|
|
123
|
+
const parsed = serializer.read(raw);
|
|
124
|
+
if (mergeDefaults && typeof defaultValue === "object" && defaultValue !== null)
|
|
125
|
+
return { ...defaultValue, ...parsed };
|
|
126
|
+
return parsed;
|
|
127
|
+
} catch (e) {
|
|
128
|
+
console.warn(`[${label}] Failed to read key "${key}":`, e);
|
|
129
|
+
return defaultValue;
|
|
130
|
+
}
|
|
131
|
+
}, signal = state((() => {
|
|
132
|
+
const store = getStorage();
|
|
133
|
+
if (!store)
|
|
134
|
+
return defaultValue;
|
|
135
|
+
try {
|
|
136
|
+
return parse(store.getItem(key));
|
|
137
|
+
} catch (e) {
|
|
138
|
+
console.warn(`[${label}] Cannot read "${key}":`, e);
|
|
139
|
+
return defaultValue;
|
|
140
|
+
}
|
|
141
|
+
})());
|
|
142
|
+
effect(() => {
|
|
143
|
+
const value = signal(), store = getStorage();
|
|
144
|
+
if (!store)
|
|
145
|
+
return;
|
|
146
|
+
try {
|
|
147
|
+
if (value === null || value === void 0)
|
|
148
|
+
store.removeItem(key);
|
|
149
|
+
else
|
|
150
|
+
store.setItem(key, serializer.write(value));
|
|
151
|
+
} catch (e) {
|
|
152
|
+
console.warn(`[${label}] Cannot persist "${key}":`, e);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
if (isClient && listenToStorageChanges) {
|
|
156
|
+
const onStorage = (event) => {
|
|
157
|
+
if (event.key !== key)
|
|
158
|
+
return;
|
|
159
|
+
if (event.storageArea && event.storageArea !== getStorage())
|
|
160
|
+
return;
|
|
161
|
+
signal.set(parse(event.newValue));
|
|
162
|
+
};
|
|
163
|
+
window.addEventListener("storage", onStorage);
|
|
164
|
+
const maybeOnDestroy = globalThis.onDestroy;
|
|
165
|
+
if (typeof maybeOnDestroy === "function")
|
|
166
|
+
maybeOnDestroy(() => window.removeEventListener("storage", onStorage));
|
|
167
|
+
}
|
|
168
|
+
return signal;
|
|
169
|
+
}
|
|
109
170
|
export function useLocalStorage(key, defaultValue, options) {
|
|
110
|
-
return
|
|
171
|
+
return storageSignal("local", "useLocalStorage", key, defaultValue, options);
|
|
111
172
|
}
|
|
112
173
|
export function useSessionStorage(key, defaultValue, options) {
|
|
113
|
-
return
|
|
174
|
+
return storageSignal("session", "useSessionStorage", key, defaultValue, options);
|
|
114
175
|
}
|
|
115
176
|
export function clearStorage(type = "local") {
|
|
116
177
|
if (typeof window > "u")
|