@stacksjs/stx 0.2.152 → 0.2.154
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/animation.js +6 -6
- package/dist/app-shell.js +2 -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/build-message.d.ts +10 -0
- package/dist/build-message.js +26 -0
- package/dist/builtins/attrs.js +6 -1
- package/dist/cli.js +1743 -1255
- package/dist/client-script-bundler.js +2 -4
- package/dist/client-script.d.ts +29 -0
- package/dist/client-script.js +142 -97
- package/dist/color-mode-boot.d.ts +46 -10
- package/dist/color-mode-boot.js +53 -13
- package/dist/component-client-factories.js +3 -2
- package/dist/component-renderer.js +10 -21
- package/dist/composables/index.d.ts +8 -1
- package/dist/composables/use-clipboard.d.ts +1 -5
- package/dist/composables/use-clipboard.js +57 -46
- package/dist/composables/use-color-mode.d.ts +19 -4
- package/dist/composables/use-color-mode.js +49 -28
- package/dist/composables/use-router.d.ts +28 -11
- package/dist/composables/use-router.js +63 -9
- package/dist/composables/use-storage.d.ts +14 -15
- package/dist/composables/use-storage.js +63 -2
- package/dist/config.js +22 -7
- package/dist/craft.js +1319 -841
- package/dist/dev-server/crosswind.js +8 -6
- package/dist/dev-server/serve-app.js +13 -7
- package/dist/dev-server/serve-markdown.js +4 -1
- package/dist/dev-server/serve-multi.js +32 -20
- package/dist/dev-server/types.d.ts +8 -1
- package/dist/document-shell.d.ts +120 -0
- package/dist/document-shell.js +161 -14
- package/dist/dynamic-components.js +123 -4
- package/dist/events.d.ts +26 -49
- package/dist/events.js +23 -9
- package/dist/expressions.d.ts +1 -1
- package/dist/expressions.js +3 -3
- package/dist/framework-composables.d.ts +41 -0
- package/dist/framework-composables.js +115 -0
- package/dist/head.js +5 -1
- package/dist/hot-reload.js +8 -2
- package/dist/includes.js +4 -4
- package/dist/index.d.ts +17 -0
- package/dist/index.js +9 -0
- package/dist/isolated-build.d.ts +12 -0
- package/dist/isolated-build.js +18 -0
- package/dist/plugin-system.d.ts +1 -1
- package/dist/process.js +46 -12
- package/dist/production-builder.js +3 -1
- package/dist/production-server.js +4 -1
- package/dist/prop-sentinels.d.ts +31 -0
- package/dist/prop-sentinels.js +7 -0
- package/dist/pwa.js +1258 -780
- package/dist/reactive.js +1 -1
- package/dist/reactivity.d.ts +0 -15
- package/dist/reactivity.js +15 -6
- package/dist/runtime-globals.d.ts +40 -2
- package/dist/runtime-globals.js +90 -0
- package/dist/runtime-injection.d.ts +51 -0
- package/dist/runtime-injection.js +13 -0
- package/dist/script-emit.d.ts +20 -0
- package/dist/script-emit.js +11 -0
- package/dist/script-validation.js +12 -7
- package/dist/signal-processing.js +25 -40
- package/dist/signals-api.d.ts +30 -0
- package/dist/signals-api.js +18 -0
- package/dist/signals.js +532 -85
- package/dist/site-builder/build.js +21 -7
- package/dist/site-builder/seo.js +19 -10
- package/dist/site-builder/theme.js +5 -4
- package/dist/site-builder/types.d.ts +1 -0
- package/dist/ssg.js +1255 -777
- package/dist/state-management.js +4 -1
- package/dist/store-loader.d.ts +0 -11
- package/dist/store-loader.js +27 -3
- package/dist/strict-lint.d.ts +9 -6
- package/dist/strict-lint.js +2 -21
- package/dist/stx.d.ts +125 -22
- package/dist/types/config-types.d.ts +1 -0
- package/dist/unresolved-identifiers.d.ts +50 -0
- package/dist/unresolved-identifiers.js +146 -0
- package/dist/utils.js +3 -3
- package/dist/variable-extractor.js +17 -1
- package/dist/x-element.js +1 -1
- package/package.json +5 -5
- package/stx.d.ts +125 -22
|
@@ -19,11 +19,6 @@ export declare function useClipboard(options?: { timeout?: number }): ClipboardR
|
|
|
19
19
|
* Copy text to clipboard (one-shot helper)
|
|
20
20
|
*/
|
|
21
21
|
export declare function copyToClipboard(text: string): Promise<boolean>;
|
|
22
|
-
/**
|
|
23
|
-
* useClipboard - Reactive clipboard composable
|
|
24
|
-
*
|
|
25
|
-
* Provides easy access to the Clipboard API with fallbacks.
|
|
26
|
-
*/
|
|
27
22
|
export declare interface ClipboardRef {
|
|
28
23
|
text: string
|
|
29
24
|
isSupported: boolean
|
|
@@ -31,4 +26,5 @@ export declare interface ClipboardRef {
|
|
|
31
26
|
copy: (text: string) => Promise<boolean>
|
|
32
27
|
read: () => Promise<string>
|
|
33
28
|
subscribe: (callback: (text: string) => void) => () => void
|
|
29
|
+
stop: () => void
|
|
34
30
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { onDestroy } from "../signals-api.js";
|
|
1
2
|
export function useClipboard(options = {}) {
|
|
2
3
|
const { timeout = 1500 } = options, subscribers = new Set;
|
|
3
4
|
let currentText = "", copied = !1, copiedTimeout = null;
|
|
@@ -8,42 +9,17 @@ export function useClipboard(options = {}) {
|
|
|
8
9
|
} catch (e) {
|
|
9
10
|
console.error("[useClipboard] Subscriber error:", e);
|
|
10
11
|
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
get text() {
|
|
14
|
-
return currentText;
|
|
15
|
-
},
|
|
16
|
-
get isSupported() {
|
|
17
|
-
return isSupported;
|
|
18
|
-
},
|
|
19
|
-
get copied() {
|
|
20
|
-
return copied;
|
|
21
|
-
},
|
|
22
|
-
copy: async (text) => {
|
|
23
|
-
if (!isSupported)
|
|
24
|
-
try {
|
|
25
|
-
const textarea = document.createElement("textarea");
|
|
26
|
-
textarea.value = text;
|
|
27
|
-
textarea.style.position = "fixed";
|
|
28
|
-
textarea.style.opacity = "0";
|
|
29
|
-
document.body.appendChild(textarea);
|
|
30
|
-
textarea.select();
|
|
31
|
-
document.execCommand("copy");
|
|
32
|
-
document.body.removeChild(textarea);
|
|
33
|
-
currentText = text;
|
|
34
|
-
copied = !0;
|
|
35
|
-
notify(text);
|
|
36
|
-
if (copiedTimeout)
|
|
37
|
-
clearTimeout(copiedTimeout);
|
|
38
|
-
copiedTimeout = setTimeout(() => {
|
|
39
|
-
copied = !1;
|
|
40
|
-
}, timeout);
|
|
41
|
-
return !0;
|
|
42
|
-
} catch {
|
|
43
|
-
return !1;
|
|
44
|
-
}
|
|
12
|
+
}, copy = async (text) => {
|
|
13
|
+
if (!isSupported)
|
|
45
14
|
try {
|
|
46
|
-
|
|
15
|
+
const textarea = document.createElement("textarea");
|
|
16
|
+
textarea.value = text;
|
|
17
|
+
textarea.style.position = "fixed";
|
|
18
|
+
textarea.style.opacity = "0";
|
|
19
|
+
document.body.appendChild(textarea);
|
|
20
|
+
textarea.select();
|
|
21
|
+
document.execCommand("copy");
|
|
22
|
+
document.body.removeChild(textarea);
|
|
47
23
|
currentText = text;
|
|
48
24
|
copied = !0;
|
|
49
25
|
notify(text);
|
|
@@ -51,27 +27,62 @@ export function useClipboard(options = {}) {
|
|
|
51
27
|
clearTimeout(copiedTimeout);
|
|
52
28
|
copiedTimeout = setTimeout(() => {
|
|
53
29
|
copied = !1;
|
|
30
|
+
notify(currentText);
|
|
54
31
|
}, timeout);
|
|
55
32
|
return !0;
|
|
56
33
|
} catch {
|
|
57
34
|
return !1;
|
|
58
35
|
}
|
|
36
|
+
try {
|
|
37
|
+
await navigator.clipboard.writeText(text);
|
|
38
|
+
currentText = text;
|
|
39
|
+
copied = !0;
|
|
40
|
+
notify(text);
|
|
41
|
+
if (copiedTimeout)
|
|
42
|
+
clearTimeout(copiedTimeout);
|
|
43
|
+
copiedTimeout = setTimeout(() => {
|
|
44
|
+
copied = !1;
|
|
45
|
+
notify(currentText);
|
|
46
|
+
}, timeout);
|
|
47
|
+
return !0;
|
|
48
|
+
} catch {
|
|
49
|
+
return !1;
|
|
50
|
+
}
|
|
51
|
+
}, read = async () => {
|
|
52
|
+
if (!isSupported)
|
|
53
|
+
return "";
|
|
54
|
+
try {
|
|
55
|
+
const text = await navigator.clipboard.readText();
|
|
56
|
+
currentText = text;
|
|
57
|
+
return text;
|
|
58
|
+
} catch {
|
|
59
|
+
return "";
|
|
60
|
+
}
|
|
61
|
+
}, stop = () => {
|
|
62
|
+
if (copiedTimeout) {
|
|
63
|
+
clearTimeout(copiedTimeout);
|
|
64
|
+
copiedTimeout = null;
|
|
65
|
+
}
|
|
66
|
+
subscribers.clear();
|
|
67
|
+
};
|
|
68
|
+
onDestroy(stop);
|
|
69
|
+
return {
|
|
70
|
+
get text() {
|
|
71
|
+
return currentText;
|
|
59
72
|
},
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return "";
|
|
63
|
-
try {
|
|
64
|
-
const text = await navigator.clipboard.readText();
|
|
65
|
-
currentText = text;
|
|
66
|
-
return text;
|
|
67
|
-
} catch {
|
|
68
|
-
return "";
|
|
69
|
-
}
|
|
73
|
+
get isSupported() {
|
|
74
|
+
return isSupported;
|
|
70
75
|
},
|
|
76
|
+
get copied() {
|
|
77
|
+
return copied;
|
|
78
|
+
},
|
|
79
|
+
copy,
|
|
80
|
+
read,
|
|
71
81
|
subscribe: (callback) => {
|
|
72
82
|
subscribers.add(callback);
|
|
73
83
|
return () => subscribers.delete(callback);
|
|
74
|
-
}
|
|
84
|
+
},
|
|
85
|
+
stop
|
|
75
86
|
};
|
|
76
87
|
}
|
|
77
88
|
export async function copyToClipboard(text) {
|
|
@@ -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,29 @@
|
|
|
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
|
-
const boot = typeof window < "u" ? window.__STX_COLOR_MODE__ : null
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
10
|
+
const boot = typeof window < "u" ? window.__STX_COLOR_MODE__ : null;
|
|
11
|
+
function pick(fromOptions, fromBoot, fallback) {
|
|
12
|
+
if (fromOptions !== void 0)
|
|
13
|
+
return fromOptions;
|
|
14
|
+
if (boot && typeof boot === "object" && fromBoot !== void 0)
|
|
15
|
+
return fromBoot;
|
|
16
|
+
return fallback;
|
|
17
|
+
}
|
|
18
|
+
const {
|
|
19
|
+
storageKey = pick(void 0, boot?.storageKey, "stx-color-mode"),
|
|
20
|
+
initialMode = pick(void 0, boot?.initialMode, "auto"),
|
|
21
|
+
darkClass = pick(void 0, boot?.darkClass, "dark"),
|
|
22
|
+
attribute = pick(void 0, boot?.attribute, null),
|
|
23
|
+
autoValue: autoValueOption = boot?.autoValue,
|
|
7
24
|
disableTransitions = !0
|
|
8
25
|
} = options;
|
|
9
|
-
let preference = initialMode, resolved = "light";
|
|
26
|
+
let preference = normalizeColorMode(initialMode) ?? "auto", resolved = "light";
|
|
10
27
|
const listeners = new Set, cleanups = [], isBrowser = typeof window < "u";
|
|
11
28
|
function getSystemPreference() {
|
|
12
29
|
if (!isBrowser)
|
|
@@ -24,41 +41,43 @@ export function useColorMode(options = {}) {
|
|
|
24
41
|
el.style.setProperty("transition", "none", "important");
|
|
25
42
|
if (attribute)
|
|
26
43
|
el.setAttribute(attribute, mode);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
44
|
+
if (darkClass)
|
|
45
|
+
if (mode === "dark")
|
|
46
|
+
el.classList.add(darkClass);
|
|
47
|
+
else
|
|
48
|
+
el.classList.remove(darkClass);
|
|
31
49
|
if (disableTransitions) {
|
|
32
50
|
el.offsetHeight;
|
|
33
51
|
el.style.removeProperty("transition");
|
|
34
52
|
}
|
|
35
53
|
}
|
|
36
|
-
function
|
|
54
|
+
function readRaw() {
|
|
37
55
|
if (!isBrowser)
|
|
38
|
-
return;
|
|
56
|
+
return null;
|
|
39
57
|
try {
|
|
40
|
-
localStorage.
|
|
41
|
-
} catch {
|
|
58
|
+
return localStorage.getItem(storageKey);
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
42
62
|
}
|
|
43
|
-
|
|
63
|
+
const storedRaw = readRaw();
|
|
64
|
+
let autoValue = autoValueOption ?? (storedRaw !== null && AUTO_VALUES.includes(storedRaw) ? storedRaw : "auto");
|
|
65
|
+
function persist(pref) {
|
|
44
66
|
if (!isBrowser)
|
|
45
|
-
return
|
|
67
|
+
return;
|
|
46
68
|
try {
|
|
47
|
-
|
|
48
|
-
if (v === "light" || v === "dark" || v === "auto")
|
|
49
|
-
return v;
|
|
69
|
+
localStorage.setItem(storageKey, pref === "auto" ? autoValue : pref);
|
|
50
70
|
} catch {}
|
|
51
|
-
return null;
|
|
52
71
|
}
|
|
53
|
-
function update(pref) {
|
|
54
|
-
preference = pref;
|
|
55
|
-
resolved = resolve(
|
|
72
|
+
function update(pref, persistChoice = !0) {
|
|
73
|
+
preference = normalizeColorMode(pref) ?? "auto";
|
|
74
|
+
resolved = resolve(preference);
|
|
56
75
|
applyToDOM(resolved);
|
|
57
|
-
|
|
76
|
+
if (persistChoice)
|
|
77
|
+
persist(preference);
|
|
58
78
|
listeners.forEach((fn) => fn(resolved, preference));
|
|
59
79
|
}
|
|
60
|
-
|
|
61
|
-
update(persisted ?? initialMode);
|
|
80
|
+
update(normalizeColorMode(storedRaw) ?? initialMode, !1);
|
|
62
81
|
if (isBrowser) {
|
|
63
82
|
const mql = window.matchMedia("(prefers-color-scheme: dark)"), onSystemChange = () => {
|
|
64
83
|
if (preference === "auto") {
|
|
@@ -72,8 +91,10 @@ export function useColorMode(options = {}) {
|
|
|
72
91
|
const onStorage = (e) => {
|
|
73
92
|
if (e.key !== storageKey)
|
|
74
93
|
return;
|
|
75
|
-
const v = e.newValue;
|
|
76
|
-
if (v
|
|
94
|
+
const v = normalizeColorMode(e.newValue);
|
|
95
|
+
if (v) {
|
|
96
|
+
if (v === "auto" && e.newValue && AUTO_VALUES.includes(e.newValue))
|
|
97
|
+
autoValue = e.newValue;
|
|
77
98
|
preference = v;
|
|
78
99
|
resolved = resolve(v);
|
|
79
100
|
applyToDOM(resolved);
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Navigate to a URL. Uses stxRouter if available, otherwise falls back to
|
|
2
|
+
* Navigate to a URL. Uses stxRouter if available, otherwise falls back to a
|
|
3
|
+
* full document load.
|
|
4
|
+
*
|
|
5
|
+
* Kept in step with the client runtime's `navigate` (#1807), which used to take
|
|
6
|
+
* a bare `forceReload` boolean while the shipped declaration promised
|
|
7
|
+
* `{ replace }` — so `navigate(url, { replace: true })` type-checked and then
|
|
8
|
+
* did a full load that PUSHED a history entry.
|
|
3
9
|
*
|
|
4
10
|
* @example
|
|
5
11
|
* ```ts
|
|
6
12
|
* navigate('/about')
|
|
7
|
-
* navigate('
|
|
13
|
+
* navigate('/dashboard', { replace: true })
|
|
14
|
+
* navigate('/legacy', { reload: true })
|
|
8
15
|
* ```
|
|
9
16
|
*/
|
|
10
|
-
export declare function navigate(url: string): void;
|
|
17
|
+
export declare function navigate(url: string, options?: NavigateOptions | boolean): void;
|
|
11
18
|
/**
|
|
12
19
|
* Go back in browser history.
|
|
13
20
|
*/
|
|
@@ -42,12 +49,6 @@ export declare function setRouteParams(params: Record<string, string>): void;
|
|
|
42
49
|
* ```
|
|
43
50
|
*/
|
|
44
51
|
export declare function useSearchParams(): SearchParamsRef;
|
|
45
|
-
/**
|
|
46
|
-
* useRouter - Navigation composable for STX applications
|
|
47
|
-
*
|
|
48
|
-
* Provides clean APIs for navigation without raw `window.location` or `window.history`.
|
|
49
|
-
* These are server-safe stubs; the actual runtime implementations live in signals.ts.
|
|
50
|
-
*/
|
|
51
52
|
export declare interface RouteInfo {
|
|
52
53
|
path: string
|
|
53
54
|
fullPath: string
|
|
@@ -55,9 +56,25 @@ export declare interface RouteInfo {
|
|
|
55
56
|
query: Record<string, string>
|
|
56
57
|
params: Record<string, string>
|
|
57
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* How a search-param mutation is written to session history.
|
|
61
|
+
*
|
|
62
|
+
* Spelled `{ replace }` to match `navigate()`, which already takes it — before
|
|
63
|
+
* this the two halves of the routing API disagreed on whether replacing was
|
|
64
|
+
* expressible at all.
|
|
65
|
+
*/
|
|
66
|
+
export declare interface SearchParamsCommitOptions {
|
|
67
|
+
replace?: boolean
|
|
68
|
+
}
|
|
58
69
|
export declare interface SearchParamsRef {
|
|
59
70
|
data: { (): Record<string, string>, set: (v: Record<string, string>) => void }
|
|
60
71
|
get: (key: string) => string | undefined
|
|
61
|
-
|
|
62
|
-
|
|
72
|
+
has: (key: string) => boolean
|
|
73
|
+
set: (key: string, value: string, options?: SearchParamsCommitOptions) => void
|
|
74
|
+
delete: (key: string, options?: SearchParamsCommitOptions) => void
|
|
75
|
+
setAll: (obj: Record<string, string>, options?: SearchParamsCommitOptions) => void
|
|
76
|
+
}
|
|
77
|
+
export declare interface NavigateOptions {
|
|
78
|
+
replace?: boolean
|
|
79
|
+
reload?: boolean
|
|
63
80
|
}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { state } from "../signals-api.js";
|
|
2
|
+
export function navigate(url, options) {
|
|
3
|
+
if (typeof window > "u")
|
|
4
|
+
return;
|
|
5
|
+
const opts = options && typeof options === "object" ? options : { reload: !!options }, router = window.stxRouter;
|
|
6
|
+
if (!opts.reload && router?.navigate) {
|
|
7
|
+
router.navigate(url, { replace: !!opts.replace });
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
if (opts.replace)
|
|
11
|
+
window.location.replace(url);
|
|
12
|
+
else
|
|
13
|
+
window.location.href = url;
|
|
7
14
|
}
|
|
8
15
|
export function goBack() {
|
|
9
16
|
if (typeof window < "u")
|
|
@@ -45,7 +52,54 @@ export function setRouteParams(params) {
|
|
|
45
52
|
window.stx.setRouteParams(params);
|
|
46
53
|
}
|
|
47
54
|
export function useSearchParams() {
|
|
48
|
-
|
|
49
|
-
return;
|
|
50
|
-
|
|
55
|
+
if (typeof window < "u" && window.stx?.useSearchParams)
|
|
56
|
+
return window.stx.useSearchParams();
|
|
57
|
+
if (typeof window > "u")
|
|
58
|
+
return {
|
|
59
|
+
data: Object.assign(() => ({}), { set: () => {} }),
|
|
60
|
+
get: () => {
|
|
61
|
+
return;
|
|
62
|
+
},
|
|
63
|
+
has: () => !1,
|
|
64
|
+
set: () => {},
|
|
65
|
+
delete: () => {},
|
|
66
|
+
setAll: () => {}
|
|
67
|
+
};
|
|
68
|
+
const read = () => {
|
|
69
|
+
const out = {};
|
|
70
|
+
new URLSearchParams(window.location.search).forEach((v, k) => {
|
|
71
|
+
out[k] = v;
|
|
72
|
+
});
|
|
73
|
+
return out;
|
|
74
|
+
}, data = state(read()), sync = () => data.set(read());
|
|
75
|
+
window.addEventListener("popstate", sync);
|
|
76
|
+
window.addEventListener("stx:navigate", sync);
|
|
77
|
+
const commit = (url, options) => {
|
|
78
|
+
if (!!(options && typeof options === "object" && options.replace))
|
|
79
|
+
window.history.replaceState({}, "", url);
|
|
80
|
+
else
|
|
81
|
+
window.history.pushState({}, "", url);
|
|
82
|
+
sync();
|
|
83
|
+
}, has = (key) => Object.prototype.hasOwnProperty.call(data(), key);
|
|
84
|
+
return {
|
|
85
|
+
data,
|
|
86
|
+
get: (key) => has(key) ? data()[key] : void 0,
|
|
87
|
+
has,
|
|
88
|
+
set: (key, value, options) => {
|
|
89
|
+
const url = new URL(window.location.href);
|
|
90
|
+
url.searchParams.set(key, value);
|
|
91
|
+
commit(url, options);
|
|
92
|
+
},
|
|
93
|
+
delete: (key, options) => {
|
|
94
|
+
const url = new URL(window.location.href);
|
|
95
|
+
url.searchParams.delete(key);
|
|
96
|
+
commit(url, options);
|
|
97
|
+
},
|
|
98
|
+
setAll: (obj, options) => {
|
|
99
|
+
const url = new URL(window.location.href);
|
|
100
|
+
for (const [k, v] of Object.entries(obj))
|
|
101
|
+
url.searchParams.set(k, v);
|
|
102
|
+
commit(url, options);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
51
105
|
}
|
|
@@ -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")
|
package/dist/config.js
CHANGED
|
@@ -103,7 +103,7 @@ export const defaultConfig = {
|
|
|
103
103
|
},
|
|
104
104
|
router: {
|
|
105
105
|
enabled: !0,
|
|
106
|
-
container: "
|
|
106
|
+
container: "main",
|
|
107
107
|
linkSelector: "a[href]",
|
|
108
108
|
viewTransitions: !0,
|
|
109
109
|
scrollToTop: !0,
|
|
@@ -360,18 +360,31 @@ export const defaultConfig = {
|
|
|
360
360
|
verbose: !1
|
|
361
361
|
}
|
|
362
362
|
};
|
|
363
|
-
function resolveStxRoot(configRoot, configPagesDir) {
|
|
363
|
+
function resolveStxRoot(configRoot, configPagesDir, cwd) {
|
|
364
364
|
const defaultPagesDir = configPagesDir || "pages";
|
|
365
365
|
if (configRoot)
|
|
366
366
|
return { root: configRoot, pagesDir: defaultPagesDir };
|
|
367
|
-
const
|
|
367
|
+
const base = cwd || process.cwd(), resourcesViews = path.join(base, "resources", "views"), resourcesLayouts = path.join(base, "resources", "layouts");
|
|
368
368
|
if (fs.existsSync(resourcesViews) && fs.existsSync(resourcesLayouts))
|
|
369
369
|
return { root: "resources", pagesDir: "views" };
|
|
370
|
-
const resourcesViewsPages = path.join(
|
|
370
|
+
const resourcesViewsPages = path.join(base, "resources", "views", "pages");
|
|
371
371
|
if (fs.existsSync(resourcesViewsPages))
|
|
372
372
|
return { root: "resources/views", pagesDir: defaultPagesDir };
|
|
373
373
|
return { root: ".", pagesDir: defaultPagesDir };
|
|
374
374
|
}
|
|
375
|
+
function warnMissingDirs(loaded, cwd) {
|
|
376
|
+
const keys = ["componentsDir", "layoutsDir", "partialsDir"];
|
|
377
|
+
for (const key of keys) {
|
|
378
|
+
const value = loaded[key];
|
|
379
|
+
if (typeof value !== "string" || !value)
|
|
380
|
+
continue;
|
|
381
|
+
const abs = path.isAbsolute(value) ? value : path.join(cwd, value);
|
|
382
|
+
if (fs.existsSync(abs))
|
|
383
|
+
continue;
|
|
384
|
+
const prefixed = !!loaded.root && loaded.root !== ".";
|
|
385
|
+
console.warn(`[stx] ${String(key)} resolves to "${abs}", which does not exist. Configured as "${value}"` + (prefixed ? ` and prefixed with root "${loaded.root}" \u2014 a value that already includes the root gets it twice.` : "."));
|
|
386
|
+
}
|
|
387
|
+
}
|
|
375
388
|
function applyStateDir(loaded) {
|
|
376
389
|
setStateDir(loaded.stateDir);
|
|
377
390
|
loaded.stateDir = stateDirName();
|
|
@@ -405,7 +418,7 @@ export async function loadStxConfig(cwd) {
|
|
|
405
418
|
verbose: !1
|
|
406
419
|
})).config;
|
|
407
420
|
applyStateDir(loaded);
|
|
408
|
-
const resolved = resolveStxRoot(loaded.root, loaded.pagesDir);
|
|
421
|
+
const resolved = resolveStxRoot(loaded.root, loaded.pagesDir, effectiveCwd);
|
|
409
422
|
loaded.root = resolved.root;
|
|
410
423
|
loaded.pagesDir = resolved.pagesDir;
|
|
411
424
|
if (loaded.root && loaded.root !== ".") {
|
|
@@ -417,6 +430,7 @@ export async function loadStxConfig(cwd) {
|
|
|
417
430
|
if (loaded.layoutsDir && !path.isAbsolute(loaded.layoutsDir))
|
|
418
431
|
loaded.layoutsDir = path.join(rootPrefix, loaded.layoutsDir);
|
|
419
432
|
}
|
|
433
|
+
warnMissingDirs(loaded, effectiveCwd);
|
|
420
434
|
if (loaded.plugins && loaded.plugins.length > 0) {
|
|
421
435
|
const { pluginManager } = await import("./plugin-system.js"), pluginComponentDirs = [], pluginPageDirs = [];
|
|
422
436
|
for (const pluginEntry of loaded.plugins) {
|
|
@@ -434,8 +448,9 @@ export async function loadStxConfig(cwd) {
|
|
|
434
448
|
const plugin = pluginModule.default || pluginModule;
|
|
435
449
|
pluginManager.register(plugin, loaded);
|
|
436
450
|
if (plugin.components) {
|
|
437
|
-
const
|
|
438
|
-
|
|
451
|
+
const dirs = Array.isArray(plugin.components) ? plugin.components : [plugin.components];
|
|
452
|
+
for (const entry of dirs)
|
|
453
|
+
pluginComponentDirs.push(path.resolve(pluginDir, entry));
|
|
439
454
|
}
|
|
440
455
|
if (plugin.pages) {
|
|
441
456
|
const dir = path.resolve(pluginDir, plugin.pages), prefix = pluginOptions.path || "/";
|