@fluixi/core 1.0.0-alpha.91 → 1.0.0-alpha.93
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/cdn/core.global.js +2 -2
- package/dist/src/cdn/{chunk-MDJLC3PE.mjs → chunk-7XM7Q5VR.mjs} +271 -229
- package/dist/src/cdn/core.cjs +360 -253
- package/dist/src/cdn/core.mjs +92 -20
- package/dist/src/cdn/{router-JYFZP3EZ.mjs → router-VOY43YNM.mjs} +7 -3
- package/dist/src/index.cjs +547 -232
- package/dist/src/index.mjs +547 -223
- package/dist/src/lib/client/chunk-QT3RFKH6.mjs +53 -0
- package/dist/src/lib/client/index.cjs +310 -176
- package/dist/src/lib/client/index.d.ts.map +1 -1
- package/dist/src/lib/client/index.js +33 -18
- package/dist/src/lib/client/index.mjs +86 -15
- package/dist/src/lib/client/{router-I66EJO2Y.mjs → router-ADTS55OP.mjs} +244 -99
- package/dist/src/lib/context/context.cjs +35 -72
- package/dist/src/lib/context/context.d.ts +45 -291
- package/dist/src/lib/context/context.d.ts.map +1 -1
- package/dist/src/lib/context/context.js +65 -397
- package/dist/src/lib/context/context.mjs +38 -73
- package/dist/src/lib/context/index.cjs +35 -72
- package/dist/src/lib/context/index.d.ts +2 -2
- package/dist/src/lib/context/index.d.ts.map +1 -1
- package/dist/src/lib/context/index.js +1 -5
- package/dist/src/lib/context/index.mjs +38 -73
- package/dist/src/lib/core.cjs +546 -231
- package/dist/src/lib/core.d.ts +3 -1
- package/dist/src/lib/core.d.ts.map +1 -1
- package/dist/src/lib/core.js +5 -3
- package/dist/src/lib/core.mjs +546 -222
- package/dist/src/lib/env/index.cjs +1 -17
- package/dist/src/lib/env/index.d.ts +2 -14
- package/dist/src/lib/env/index.d.ts.map +1 -1
- package/dist/src/lib/env/index.js +2 -26
- package/dist/src/lib/env/index.mjs +1 -17
- package/dist/src/lib/i18n/index.cjs +17 -2
- package/dist/src/lib/i18n/index.d.ts +10 -0
- package/dist/src/lib/i18n/index.d.ts.map +1 -1
- package/dist/src/lib/i18n/index.js +17 -1
- package/dist/src/lib/i18n/index.mjs +17 -2
- package/dist/src/lib/index.cjs +64 -99
- package/dist/src/lib/index.mjs +43 -76
- package/dist/src/lib/isomorphic.cjs +9 -4
- package/dist/src/lib/isomorphic.d.ts +9 -2
- package/dist/src/lib/isomorphic.d.ts.map +1 -1
- package/dist/src/lib/isomorphic.js +11 -4
- package/dist/src/lib/isomorphic.mjs +9 -4
- package/dist/src/lib/jsx-runtime/index.cjs +1 -1
- package/dist/src/lib/jsx-runtime/index.mjs +1 -1
- package/dist/src/lib/jsx-runtime/jsx-dev-runtime.cjs +1 -1
- package/dist/src/lib/jsx-runtime/jsx-dev-runtime.mjs +1 -1
- package/dist/src/lib/plugins/fluixi-routes-plugin.cjs +2 -2
- package/dist/src/lib/plugins/fluixi-routes-plugin.mjs +2 -2
- package/dist/src/lib/plugins/index.cjs +3 -3
- package/dist/src/lib/plugins/index.mjs +3 -3
- package/dist/src/lib/plugins/route-codegen.cjs +2 -2
- package/dist/src/lib/plugins/route-codegen.js +1 -1
- package/dist/src/lib/plugins/route-codegen.mjs +2 -2
- package/dist/src/lib/render/index.cjs +1 -1
- package/dist/src/lib/render/index.mjs +1 -1
- package/dist/src/lib/render/versions.cjs +1 -1
- package/dist/src/lib/render/versions.mjs +1 -1
- package/dist/src/lib/router/data.cjs +113 -115
- package/dist/src/lib/router/data.d.ts +63 -10
- package/dist/src/lib/router/data.d.ts.map +1 -1
- package/dist/src/lib/router/data.js +158 -68
- package/dist/src/lib/router/data.mjs +112 -112
- package/dist/src/lib/router/index.cjs +260 -163
- package/dist/src/lib/router/index.d.ts +22 -2
- package/dist/src/lib/router/index.d.ts.map +1 -1
- package/dist/src/lib/router/index.js +165 -22
- package/dist/src/lib/router/index.mjs +244 -143
- package/dist/src/lib/server/index.cjs +1 -1
- package/dist/src/lib/server/index.mjs +1 -1
- package/dist/src/lib/storage/cache.cjs +51 -0
- package/dist/src/lib/storage/cache.d.ts +18 -0
- package/dist/src/lib/storage/cache.d.ts.map +1 -0
- package/dist/src/lib/storage/cache.js +42 -0
- package/dist/src/lib/storage/cache.mjs +30 -0
- package/dist/src/lib/storage/index.cjs +246 -0
- package/dist/src/lib/storage/index.d.ts +83 -0
- package/dist/src/lib/storage/index.d.ts.map +1 -0
- package/dist/src/lib/storage/index.js +179 -0
- package/dist/src/lib/storage/index.mjs +231 -0
- package/dist/src/lib/storage/persisted.cjs +91 -0
- package/dist/src/lib/storage/persisted.d.ts +53 -0
- package/dist/src/lib/storage/persisted.d.ts.map +1 -0
- package/dist/src/lib/storage/persisted.js +75 -0
- package/dist/src/lib/storage/persisted.mjs +70 -0
- package/dist/src/lib/theme/color-scheme.cjs +281 -0
- package/dist/src/lib/theme/color-scheme.d.ts +61 -0
- package/dist/src/lib/theme/color-scheme.d.ts.map +1 -0
- package/dist/src/lib/theme/color-scheme.js +120 -0
- package/dist/src/lib/theme/color-scheme.mjs +265 -0
- package/dist/src/lib/theme/index.cjs +194 -37
- package/dist/src/lib/theme/index.d.ts +23 -11
- package/dist/src/lib/theme/index.d.ts.map +1 -1
- package/dist/src/lib/theme/index.js +73 -32
- package/dist/src/lib/theme/index.mjs +197 -35
- package/dist/src/lib/theme/provider.cjs +346 -0
- package/dist/src/lib/theme/provider.d.ts +34 -0
- package/dist/src/lib/theme/provider.d.ts.map +1 -0
- package/dist/src/lib/theme/provider.js +60 -0
- package/dist/src/lib/theme/provider.mjs +332 -0
- package/dist/src/routes.cjs +1 -1
- package/dist/src/routes.mjs +1 -1
- package/dist/src/utils.cjs +1 -1
- package/dist/src/utils.mjs +1 -1
- package/dist/src/version.generated.cjs +1 -1
- package/dist/src/version.generated.d.ts +1 -1
- package/dist/src/version.generated.js +1 -1
- package/dist/src/version.generated.mjs +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +18 -11
- package/dist/src/lib/client/chunk-SKXDXBGV.mjs +0 -133
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// src/lib/theme/color-scheme.ts
|
|
2
|
+
import { getActiveHead as getActiveHead2, useHead as useHead2 } from "@fluixi/head";
|
|
3
|
+
import { createEffect, createSignal as createSignal2, getOwner as getOwner2, onCleanup as onCleanup2 } from "@fluixi/reactive/signal";
|
|
4
|
+
|
|
5
|
+
// src/lib/env/index.ts
|
|
6
|
+
import { isServer } from "@fluixi/dom";
|
|
7
|
+
import { isServer as onServer } from "@fluixi/dom";
|
|
8
|
+
var env = {
|
|
9
|
+
get window() {
|
|
10
|
+
return typeof window !== "undefined" ? window : void 0;
|
|
11
|
+
},
|
|
12
|
+
get document() {
|
|
13
|
+
return typeof document !== "undefined" ? document : void 0;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// src/lib/storage/persisted.ts
|
|
18
|
+
import { getOwner, onCleanup, signal, untrack } from "@fluixi/reactive/signal";
|
|
19
|
+
var isAsync = (storage) => "encryption" in storage;
|
|
20
|
+
function persisted(storage, key, options = {}) {
|
|
21
|
+
const fallback = options.fallback ?? null;
|
|
22
|
+
const name = options.name ?? `storage:${storage.keyPrefix}${key}`;
|
|
23
|
+
const state = signal(isAsync(storage) ? fallback : storage.get(key) ?? fallback, { name });
|
|
24
|
+
const put = (value) => state.set(() => value);
|
|
25
|
+
const resolve = (next) => typeof next === "function" ? next(untrack(state)) : next;
|
|
26
|
+
const writeOptions = (extra) => ({
|
|
27
|
+
...options.ttl !== void 0 ? { ttl: options.ttl } : {},
|
|
28
|
+
...options.encrypt ? { encrypt: true } : {},
|
|
29
|
+
...extra
|
|
30
|
+
});
|
|
31
|
+
if (!isAsync(storage)) {
|
|
32
|
+
const off2 = storage.subscribe(key, (event) => {
|
|
33
|
+
if (event.type !== "set") return put(fallback);
|
|
34
|
+
if (event.value !== void 0) put(event.value);
|
|
35
|
+
});
|
|
36
|
+
if (getOwner()) onCleanup(off2);
|
|
37
|
+
return Object.assign(() => state(), {
|
|
38
|
+
set(next, extra) {
|
|
39
|
+
const value = resolve(next);
|
|
40
|
+
storage.set(key, value, writeOptions(extra));
|
|
41
|
+
put(value);
|
|
42
|
+
},
|
|
43
|
+
remove() {
|
|
44
|
+
storage.remove(key);
|
|
45
|
+
put(fallback);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
let written = false;
|
|
50
|
+
const reread = () => storage.get(key).then(
|
|
51
|
+
(value) => put(value ?? fallback),
|
|
52
|
+
() => void 0
|
|
53
|
+
);
|
|
54
|
+
const off = storage.subscribe(key, (event) => {
|
|
55
|
+
if (event.type !== "set") return put(fallback);
|
|
56
|
+
if (event.value !== void 0) put(event.value);
|
|
57
|
+
else void reread();
|
|
58
|
+
});
|
|
59
|
+
if (getOwner()) onCleanup(off);
|
|
60
|
+
const ready = storage.get(key).then((value) => {
|
|
61
|
+
if (!written) put(value ?? fallback);
|
|
62
|
+
});
|
|
63
|
+
return Object.assign(() => state(), {
|
|
64
|
+
ready,
|
|
65
|
+
async set(next, extra) {
|
|
66
|
+
const previous = untrack(state);
|
|
67
|
+
const value = resolve(next);
|
|
68
|
+
written = true;
|
|
69
|
+
put(value);
|
|
70
|
+
try {
|
|
71
|
+
await storage.set(key, value, writeOptions(extra));
|
|
72
|
+
} catch (error) {
|
|
73
|
+
put(previous);
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
async remove() {
|
|
78
|
+
written = true;
|
|
79
|
+
await storage.remove(key);
|
|
80
|
+
put(fallback);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/lib/theme/index.ts
|
|
86
|
+
import { getActiveHead, useHead } from "@fluixi/head";
|
|
87
|
+
import { createSignal } from "@fluixi/reactive/signal";
|
|
88
|
+
import { StorageUnavailableError as StorageUnavailableError2 } from "@fluixi/storage";
|
|
89
|
+
|
|
90
|
+
// src/lib/storage/index.ts
|
|
91
|
+
import { appendResponseHeader, getRequestContext, getServerData } from "@fluixi/dom";
|
|
92
|
+
import { createContext, useContext } from "@fluixi/reactive/signal";
|
|
93
|
+
import {
|
|
94
|
+
createAsyncStorage,
|
|
95
|
+
createSyncStorage,
|
|
96
|
+
memoryStorage,
|
|
97
|
+
StorageUnavailableError
|
|
98
|
+
} from "@fluixi/storage";
|
|
99
|
+
import { documentCookieAdapter, localStorageAdapter, sessionStorageAdapter } from "@fluixi/storage/browser";
|
|
100
|
+
import { cookieStorage, requestStorage, toStorageRequest } from "@fluixi/storage/server";
|
|
101
|
+
var StorageContext = createContext(void 0, "storage");
|
|
102
|
+
var AsyncStorageContext = createContext(void 0, "asyncStorage");
|
|
103
|
+
var isBrowser = () => typeof window !== "undefined" && typeof document !== "undefined";
|
|
104
|
+
var perRequest = /* @__PURE__ */ new WeakMap();
|
|
105
|
+
var perPage = /* @__PURE__ */ new Map();
|
|
106
|
+
function cached(scope, id, make) {
|
|
107
|
+
let adapter = scope.get(id);
|
|
108
|
+
if (!adapter) scope.set(id, adapter = make());
|
|
109
|
+
return adapter;
|
|
110
|
+
}
|
|
111
|
+
function requestRequired(scope) {
|
|
112
|
+
throw new StorageUnavailableError(
|
|
113
|
+
`useStorage({ scope: '${scope}' }) on the server needs a request: call it during a render, a loader or a server function, or provide a storage with <StorageProvider>.`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
function adapterFor(scope, cookie = {}) {
|
|
117
|
+
const ctx = getRequestContext();
|
|
118
|
+
const options = { httpOnly: false, ...cookie };
|
|
119
|
+
switch (scope) {
|
|
120
|
+
case "local":
|
|
121
|
+
return localStorageAdapter();
|
|
122
|
+
case "session":
|
|
123
|
+
return sessionStorageAdapter();
|
|
124
|
+
case "request":
|
|
125
|
+
if (isBrowser()) return cached(perPage, "request", memoryStorage);
|
|
126
|
+
if (!ctx) requestRequired(scope);
|
|
127
|
+
return cached(perRequest.get(ctx) ?? perRequest.set(ctx, /* @__PURE__ */ new Map()).get(ctx), "request", requestStorage);
|
|
128
|
+
case "cookie": {
|
|
129
|
+
const id = `cookie:${JSON.stringify(options)}`;
|
|
130
|
+
if (isBrowser()) {
|
|
131
|
+
const { httpOnly: _ignored, ...browser } = options;
|
|
132
|
+
return cached(perPage, id, () => documentCookieAdapter(browser));
|
|
133
|
+
}
|
|
134
|
+
if (!ctx) requestRequired(scope);
|
|
135
|
+
const raw = ctx.event.request;
|
|
136
|
+
const target = raw && typeof raw === "object" ? raw : void 0;
|
|
137
|
+
return cached(
|
|
138
|
+
perRequest.get(ctx) ?? perRequest.set(ctx, /* @__PURE__ */ new Map()).get(ctx),
|
|
139
|
+
id,
|
|
140
|
+
() => cookieStorage({
|
|
141
|
+
...options,
|
|
142
|
+
request: raw && typeof raw === "object" && ("headers" in raw || "getHeader" in raw) ? toStorageRequest(raw) : void 0,
|
|
143
|
+
response: {
|
|
144
|
+
appendHeader(name, value) {
|
|
145
|
+
if (!appendResponseHeader(name, value, target)) {
|
|
146
|
+
throw new StorageUnavailableError("The response headers were already sent, so this cookie cannot reach the browser. Write it before the page head goes out: in middleware, a loader, a server function or the render before its first chunk.");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
})
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function useStorage(options = {}) {
|
|
156
|
+
const { scope = "auto", cookie, ...storageOptions } = options;
|
|
157
|
+
if (scope === "auto") {
|
|
158
|
+
const provided = useContext(StorageContext);
|
|
159
|
+
if (provided) return options.namespace ? provided.namespace(options.namespace) : provided;
|
|
160
|
+
}
|
|
161
|
+
return createSyncStorage({ ...storageOptions, adapter: resolveAdapter(scope, cookie) });
|
|
162
|
+
}
|
|
163
|
+
function resolveAdapter(scope, cookie) {
|
|
164
|
+
if (scope !== "auto") return adapterFor(scope, cookie);
|
|
165
|
+
if (isBrowser()) return adapterFor("local");
|
|
166
|
+
if (!getRequestContext()) requestRequired(scope);
|
|
167
|
+
return adapterFor("request");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/lib/theme/index.ts
|
|
171
|
+
var YEAR = 60 * 60 * 24 * 365;
|
|
172
|
+
function defaultStorage() {
|
|
173
|
+
try {
|
|
174
|
+
return useStorage({ scope: "cookie", cookie: { maxAge: YEAR, sameSite: "lax" } });
|
|
175
|
+
} catch (error) {
|
|
176
|
+
if (error instanceof StorageUnavailableError2) return null;
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function storedLookup(storage, key) {
|
|
181
|
+
if (!storage) return null;
|
|
182
|
+
const full = storage.keyPrefix + key;
|
|
183
|
+
if (storage.scope === "cookie") {
|
|
184
|
+
return { from: "cookie", name: encodeURIComponent(full).replace(/[()]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`) };
|
|
185
|
+
}
|
|
186
|
+
if (storage.scope === "local") return { from: "local", name: full };
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/lib/theme/color-scheme.ts
|
|
191
|
+
var CHOICES = ["system", "light", "dark"];
|
|
192
|
+
var DARK = "(prefers-color-scheme: dark)";
|
|
193
|
+
function createColorScheme(options = {}) {
|
|
194
|
+
const storageKey = options.storageKey ?? "color-scheme";
|
|
195
|
+
const attribute = options.attribute ?? "data-theme";
|
|
196
|
+
const systemAs = options.systemAs ?? "resolved";
|
|
197
|
+
const storage = options.storage === void 0 ? defaultStorage() : options.storage;
|
|
198
|
+
const saved = storage ? persisted(storage, storageKey) : null;
|
|
199
|
+
const [unsaved, setUnsaved] = createSignal2(null);
|
|
200
|
+
const value = () => {
|
|
201
|
+
const local = unsaved();
|
|
202
|
+
if (local) return local;
|
|
203
|
+
const stored = saved?.();
|
|
204
|
+
return CHOICES.includes(stored) ? stored : "system";
|
|
205
|
+
};
|
|
206
|
+
const media = env.window?.matchMedia?.(DARK);
|
|
207
|
+
const [osDark, setOsDark] = createSignal2(media?.matches ?? false);
|
|
208
|
+
if (media) {
|
|
209
|
+
const onChange = () => setOsDark(media.matches);
|
|
210
|
+
media.addEventListener?.("change", onChange);
|
|
211
|
+
if (getOwner2()) onCleanup2(() => media.removeEventListener?.("change", onChange));
|
|
212
|
+
}
|
|
213
|
+
const resolved = () => {
|
|
214
|
+
const choice = value();
|
|
215
|
+
return choice === "system" ? osDark() ? "dark" : "light" : choice;
|
|
216
|
+
};
|
|
217
|
+
const shown = () => systemAs === "system" && value() === "system" ? "system" : resolved();
|
|
218
|
+
const apply = () => {
|
|
219
|
+
env.document?.documentElement.setAttribute(attribute, shown());
|
|
220
|
+
};
|
|
221
|
+
const set = (choice) => {
|
|
222
|
+
if (!CHOICES.includes(choice)) return;
|
|
223
|
+
if (!saved) setUnsaved(choice);
|
|
224
|
+
else {
|
|
225
|
+
try {
|
|
226
|
+
saved.set(choice);
|
|
227
|
+
setUnsaved(null);
|
|
228
|
+
} catch {
|
|
229
|
+
setUnsaved(choice);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
apply();
|
|
233
|
+
options.onChange?.(choice);
|
|
234
|
+
};
|
|
235
|
+
const scriptBody = () => {
|
|
236
|
+
const cfg = JSON.stringify({ s: storedLookup(storage, storageKey), a: attribute, sys: systemAs === "system", tc: options.themeColor ?? null, cs: options.colorSchemeMeta !== false }).replace(/</g, "\\u003c");
|
|
237
|
+
return `(function(){try{var c=${cfg},t=null,s=c.s,r=null;if(s&&s.from==='local')r=localStorage.getItem(s.name);if(s&&s.from==='cookie')document.cookie.split('; ').forEach(function(p){var i=p.indexOf('=');if(p.slice(0,i)===s.name)r=decodeURIComponent(p.slice(i+1));});if(r){var e=JSON.parse(r);t=e&&e.v===1?e.value:e;}var chosen=t==='light'||t==='dark'?t:null;if(!chosen)t=c.sys?'system':(matchMedia('${DARK}').matches?'dark':'light');document.documentElement.setAttribute(c.a,t);if(chosen&&c.tc)document.querySelectorAll('meta[name="theme-color"]').forEach(function(m){m.setAttribute('content',c.tc[chosen]);m.removeAttribute('media');});if(chosen&&c.cs){var m=document.querySelector('meta[name="color-scheme"]');if(m)m.setAttribute('content',chosen);}}catch(e){}})();`;
|
|
238
|
+
};
|
|
239
|
+
if (getActiveHead2()) {
|
|
240
|
+
const colors = options.themeColor;
|
|
241
|
+
useHead2({
|
|
242
|
+
htmlAttrs: { [attribute]: shown },
|
|
243
|
+
...colors ? {
|
|
244
|
+
themeColor: () => value() === "system" ? [
|
|
245
|
+
{ color: colors.light, media: "(prefers-color-scheme: light)" },
|
|
246
|
+
{ color: colors.dark, media: DARK }
|
|
247
|
+
] : colors[resolved()]
|
|
248
|
+
} : {},
|
|
249
|
+
...options.colorSchemeMeta === false ? {} : { colorScheme: () => value() === "system" ? "light dark" : resolved() },
|
|
250
|
+
...options.script === false ? {} : { script: [{ children: scriptBody() }] }
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
if (env.document && getOwner2()) createEffect(apply);
|
|
254
|
+
return {
|
|
255
|
+
value,
|
|
256
|
+
resolved,
|
|
257
|
+
set,
|
|
258
|
+
toggle: () => set(resolved() === "dark" ? "light" : "dark"),
|
|
259
|
+
init: apply,
|
|
260
|
+
headScript: () => `<script>${scriptBody()}<\/script>`
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
export {
|
|
264
|
+
createColorScheme
|
|
265
|
+
};
|
|
@@ -20,28 +20,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/lib/theme/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
createTheme: () => createTheme
|
|
23
|
+
createTheme: () => createTheme,
|
|
24
|
+
defaultStorage: () => defaultStorage,
|
|
25
|
+
storedLookup: () => storedLookup
|
|
24
26
|
});
|
|
25
27
|
module.exports = __toCommonJS(index_exports);
|
|
26
|
-
var
|
|
28
|
+
var import_head = require("@fluixi/head");
|
|
29
|
+
var import_signal3 = require("@fluixi/reactive/signal");
|
|
30
|
+
var import_storage2 = require("@fluixi/storage");
|
|
27
31
|
|
|
28
32
|
// src/lib/env/index.ts
|
|
29
33
|
var import_dom = require("@fluixi/dom");
|
|
30
34
|
var import_dom2 = require("@fluixi/dom");
|
|
31
|
-
function createMemoryStorage() {
|
|
32
|
-
const mem = /* @__PURE__ */ new Map();
|
|
33
|
-
return {
|
|
34
|
-
getItem: (k) => mem.has(k) ? mem.get(k) : null,
|
|
35
|
-
setItem: (k, v) => void mem.set(k, String(v)),
|
|
36
|
-
removeItem: (k) => void mem.delete(k),
|
|
37
|
-
clear: () => mem.clear(),
|
|
38
|
-
key: (i) => Array.from(mem.keys())[i] ?? null,
|
|
39
|
-
get length() {
|
|
40
|
-
return mem.size;
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
var storage = typeof localStorage !== "undefined" ? localStorage : createMemoryStorage();
|
|
45
35
|
var env = {
|
|
46
36
|
get window() {
|
|
47
37
|
return typeof window !== "undefined" ? window : void 0;
|
|
@@ -51,36 +41,201 @@ var env = {
|
|
|
51
41
|
}
|
|
52
42
|
};
|
|
53
43
|
|
|
44
|
+
// src/lib/storage/persisted.ts
|
|
45
|
+
var import_signal = require("@fluixi/reactive/signal");
|
|
46
|
+
var isAsync = (storage) => "encryption" in storage;
|
|
47
|
+
function persisted(storage, key, options = {}) {
|
|
48
|
+
const fallback = options.fallback ?? null;
|
|
49
|
+
const name = options.name ?? `storage:${storage.keyPrefix}${key}`;
|
|
50
|
+
const state = (0, import_signal.signal)(isAsync(storage) ? fallback : storage.get(key) ?? fallback, { name });
|
|
51
|
+
const put = (value) => state.set(() => value);
|
|
52
|
+
const resolve = (next) => typeof next === "function" ? next((0, import_signal.untrack)(state)) : next;
|
|
53
|
+
const writeOptions = (extra) => ({
|
|
54
|
+
...options.ttl !== void 0 ? { ttl: options.ttl } : {},
|
|
55
|
+
...options.encrypt ? { encrypt: true } : {},
|
|
56
|
+
...extra
|
|
57
|
+
});
|
|
58
|
+
if (!isAsync(storage)) {
|
|
59
|
+
const off2 = storage.subscribe(key, (event) => {
|
|
60
|
+
if (event.type !== "set") return put(fallback);
|
|
61
|
+
if (event.value !== void 0) put(event.value);
|
|
62
|
+
});
|
|
63
|
+
if ((0, import_signal.getOwner)()) (0, import_signal.onCleanup)(off2);
|
|
64
|
+
return Object.assign(() => state(), {
|
|
65
|
+
set(next, extra) {
|
|
66
|
+
const value = resolve(next);
|
|
67
|
+
storage.set(key, value, writeOptions(extra));
|
|
68
|
+
put(value);
|
|
69
|
+
},
|
|
70
|
+
remove() {
|
|
71
|
+
storage.remove(key);
|
|
72
|
+
put(fallback);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
let written = false;
|
|
77
|
+
const reread = () => storage.get(key).then(
|
|
78
|
+
(value) => put(value ?? fallback),
|
|
79
|
+
() => void 0
|
|
80
|
+
);
|
|
81
|
+
const off = storage.subscribe(key, (event) => {
|
|
82
|
+
if (event.type !== "set") return put(fallback);
|
|
83
|
+
if (event.value !== void 0) put(event.value);
|
|
84
|
+
else void reread();
|
|
85
|
+
});
|
|
86
|
+
if ((0, import_signal.getOwner)()) (0, import_signal.onCleanup)(off);
|
|
87
|
+
const ready = storage.get(key).then((value) => {
|
|
88
|
+
if (!written) put(value ?? fallback);
|
|
89
|
+
});
|
|
90
|
+
return Object.assign(() => state(), {
|
|
91
|
+
ready,
|
|
92
|
+
async set(next, extra) {
|
|
93
|
+
const previous = (0, import_signal.untrack)(state);
|
|
94
|
+
const value = resolve(next);
|
|
95
|
+
written = true;
|
|
96
|
+
put(value);
|
|
97
|
+
try {
|
|
98
|
+
await storage.set(key, value, writeOptions(extra));
|
|
99
|
+
} catch (error) {
|
|
100
|
+
put(previous);
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
async remove() {
|
|
105
|
+
written = true;
|
|
106
|
+
await storage.remove(key);
|
|
107
|
+
put(fallback);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/lib/storage/index.ts
|
|
113
|
+
var import_dom3 = require("@fluixi/dom");
|
|
114
|
+
var import_signal2 = require("@fluixi/reactive/signal");
|
|
115
|
+
var import_storage = require("@fluixi/storage");
|
|
116
|
+
var import_browser = require("@fluixi/storage/browser");
|
|
117
|
+
var import_server = require("@fluixi/storage/server");
|
|
118
|
+
var StorageContext = (0, import_signal2.createContext)(void 0, "storage");
|
|
119
|
+
var AsyncStorageContext = (0, import_signal2.createContext)(void 0, "asyncStorage");
|
|
120
|
+
var isBrowser = () => typeof window !== "undefined" && typeof document !== "undefined";
|
|
121
|
+
var perRequest = /* @__PURE__ */ new WeakMap();
|
|
122
|
+
var perPage = /* @__PURE__ */ new Map();
|
|
123
|
+
function cached(scope, id, make) {
|
|
124
|
+
let adapter = scope.get(id);
|
|
125
|
+
if (!adapter) scope.set(id, adapter = make());
|
|
126
|
+
return adapter;
|
|
127
|
+
}
|
|
128
|
+
function requestRequired(scope) {
|
|
129
|
+
throw new import_storage.StorageUnavailableError(
|
|
130
|
+
`useStorage({ scope: '${scope}' }) on the server needs a request: call it during a render, a loader or a server function, or provide a storage with <StorageProvider>.`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
function adapterFor(scope, cookie = {}) {
|
|
134
|
+
const ctx = (0, import_dom3.getRequestContext)();
|
|
135
|
+
const options = { httpOnly: false, ...cookie };
|
|
136
|
+
switch (scope) {
|
|
137
|
+
case "local":
|
|
138
|
+
return (0, import_browser.localStorageAdapter)();
|
|
139
|
+
case "session":
|
|
140
|
+
return (0, import_browser.sessionStorageAdapter)();
|
|
141
|
+
case "request":
|
|
142
|
+
if (isBrowser()) return cached(perPage, "request", import_storage.memoryStorage);
|
|
143
|
+
if (!ctx) requestRequired(scope);
|
|
144
|
+
return cached(perRequest.get(ctx) ?? perRequest.set(ctx, /* @__PURE__ */ new Map()).get(ctx), "request", import_server.requestStorage);
|
|
145
|
+
case "cookie": {
|
|
146
|
+
const id = `cookie:${JSON.stringify(options)}`;
|
|
147
|
+
if (isBrowser()) {
|
|
148
|
+
const { httpOnly: _ignored, ...browser } = options;
|
|
149
|
+
return cached(perPage, id, () => (0, import_browser.documentCookieAdapter)(browser));
|
|
150
|
+
}
|
|
151
|
+
if (!ctx) requestRequired(scope);
|
|
152
|
+
const raw = ctx.event.request;
|
|
153
|
+
const target = raw && typeof raw === "object" ? raw : void 0;
|
|
154
|
+
return cached(
|
|
155
|
+
perRequest.get(ctx) ?? perRequest.set(ctx, /* @__PURE__ */ new Map()).get(ctx),
|
|
156
|
+
id,
|
|
157
|
+
() => (0, import_server.cookieStorage)({
|
|
158
|
+
...options,
|
|
159
|
+
request: raw && typeof raw === "object" && ("headers" in raw || "getHeader" in raw) ? (0, import_server.toStorageRequest)(raw) : void 0,
|
|
160
|
+
response: {
|
|
161
|
+
appendHeader(name, value) {
|
|
162
|
+
if (!(0, import_dom3.appendResponseHeader)(name, value, target)) {
|
|
163
|
+
throw new import_storage.StorageUnavailableError("The response headers were already sent, so this cookie cannot reach the browser. Write it before the page head goes out: in middleware, a loader, a server function or the render before its first chunk.");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function useStorage(options = {}) {
|
|
173
|
+
const { scope = "auto", cookie, ...storageOptions } = options;
|
|
174
|
+
if (scope === "auto") {
|
|
175
|
+
const provided = (0, import_signal2.useContext)(StorageContext);
|
|
176
|
+
if (provided) return options.namespace ? provided.namespace(options.namespace) : provided;
|
|
177
|
+
}
|
|
178
|
+
return (0, import_storage.createSyncStorage)({ ...storageOptions, adapter: resolveAdapter(scope, cookie) });
|
|
179
|
+
}
|
|
180
|
+
function resolveAdapter(scope, cookie) {
|
|
181
|
+
if (scope !== "auto") return adapterFor(scope, cookie);
|
|
182
|
+
if (isBrowser()) return adapterFor("local");
|
|
183
|
+
if (!(0, import_dom3.getRequestContext)()) requestRequired(scope);
|
|
184
|
+
return adapterFor("request");
|
|
185
|
+
}
|
|
186
|
+
|
|
54
187
|
// src/lib/theme/index.ts
|
|
55
188
|
function systemMap(system) {
|
|
56
189
|
if (!system) return null;
|
|
57
190
|
if (system === true) return { dark: "dark", light: "light" };
|
|
58
191
|
return system;
|
|
59
192
|
}
|
|
193
|
+
var YEAR = 60 * 60 * 24 * 365;
|
|
194
|
+
function defaultStorage() {
|
|
195
|
+
try {
|
|
196
|
+
return useStorage({ scope: "cookie", cookie: { maxAge: YEAR, sameSite: "lax" } });
|
|
197
|
+
} catch (error) {
|
|
198
|
+
if (error instanceof import_storage2.StorageUnavailableError) return null;
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function storedLookup(storage, key) {
|
|
203
|
+
if (!storage) return null;
|
|
204
|
+
const full = storage.keyPrefix + key;
|
|
205
|
+
if (storage.scope === "cookie") {
|
|
206
|
+
return { from: "cookie", name: encodeURIComponent(full).replace(/[()]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`) };
|
|
207
|
+
}
|
|
208
|
+
if (storage.scope === "local") return { from: "local", name: full };
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
60
211
|
function createTheme(options) {
|
|
61
212
|
const storageKey = options.storageKey ?? "theme";
|
|
62
213
|
const attribute = options.attribute ?? "data-theme";
|
|
63
214
|
const sys = systemMap(options.system);
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
215
|
+
const storage = options.storage === void 0 ? defaultStorage() : options.storage;
|
|
216
|
+
const valid = (t) => typeof t === "string" && options.themes.includes(t);
|
|
217
|
+
const fallback = sys && env.window?.matchMedia?.("(prefers-color-scheme: dark)")?.matches ? sys.dark : sys ? sys.light : options.default;
|
|
218
|
+
const stored = storage ? persisted(storage, storageKey) : null;
|
|
219
|
+
const [unsaved, setUnsaved] = (0, import_signal3.createSignal)(null);
|
|
220
|
+
const value = () => {
|
|
221
|
+
const local = unsaved();
|
|
222
|
+
if (local !== null) return local;
|
|
223
|
+
const saved = stored?.();
|
|
224
|
+
return valid(saved) ? saved : fallback;
|
|
70
225
|
};
|
|
71
|
-
const [
|
|
72
|
-
const [isLoading, setLoading] = (0, import_signal.createSignal)(false);
|
|
226
|
+
const [isLoading, setLoading] = (0, import_signal3.createSignal)(false);
|
|
73
227
|
const apply = (t) => {
|
|
74
228
|
env.document?.documentElement.setAttribute(attribute, t);
|
|
75
|
-
try {
|
|
76
|
-
storage.setItem(storageKey, t);
|
|
77
|
-
} catch {
|
|
78
|
-
}
|
|
79
229
|
};
|
|
80
230
|
const set = (t) => {
|
|
81
231
|
if (!options.themes.includes(t)) return;
|
|
82
232
|
const prev = value();
|
|
83
|
-
|
|
233
|
+
try {
|
|
234
|
+
stored?.set(t);
|
|
235
|
+
setUnsaved(stored ? null : t);
|
|
236
|
+
} catch {
|
|
237
|
+
setUnsaved(() => t);
|
|
238
|
+
}
|
|
84
239
|
apply(t);
|
|
85
240
|
options.onChange?.(t, prev);
|
|
86
241
|
if (options.load) {
|
|
@@ -97,14 +252,16 @@ function createTheme(options) {
|
|
|
97
252
|
set(options.themes[(i + 1) % options.themes.length]);
|
|
98
253
|
};
|
|
99
254
|
const init = () => apply(value());
|
|
100
|
-
const
|
|
101
|
-
const cfg = JSON.stringify({
|
|
102
|
-
|
|
103
|
-
a: attribute,
|
|
104
|
-
d: options.default,
|
|
105
|
-
sys
|
|
106
|
-
});
|
|
107
|
-
return `<script>(function(){try{var c=${cfg},t=localStorage.getItem(c.k);if(!t&&c.sys)t=matchMedia('(prefers-color-scheme: dark)').matches?c.sys.dark:c.sys.light;if(!t)t=c.d;document.documentElement.setAttribute(c.a,t);}catch(e){}})();<\/script>`;
|
|
255
|
+
const scriptBody = () => {
|
|
256
|
+
const cfg = JSON.stringify({ s: storedLookup(storage, storageKey), a: attribute, d: options.default, sys, ts: options.themes }).replace(/</g, "\\u003c");
|
|
257
|
+
return `(function(){try{var c=${cfg},t=null,s=c.s,r=null;if(s&&s.from==='local')r=localStorage.getItem(s.name);if(s&&s.from==='cookie')document.cookie.split('; ').forEach(function(p){var i=p.indexOf('=');if(p.slice(0,i)===s.name)r=decodeURIComponent(p.slice(i+1));});if(r){var e=JSON.parse(r);t=e&&e.v===1?e.value:e;}if(c.ts.indexOf(t)<0)t=null;if(!t&&c.sys)t=matchMedia('(prefers-color-scheme: dark)').matches?c.sys.dark:c.sys.light;if(!t)t=c.d;document.documentElement.setAttribute(c.a,t);}catch(e){}})();`;
|
|
108
258
|
};
|
|
259
|
+
const headScript = () => `<script>${scriptBody()}<\/script>`;
|
|
260
|
+
if ((0, import_head.getActiveHead)()) {
|
|
261
|
+
(0, import_head.useHead)({
|
|
262
|
+
htmlAttrs: { [attribute]: () => value() },
|
|
263
|
+
...options.script === false ? {} : { script: [{ children: scriptBody() }] }
|
|
264
|
+
});
|
|
265
|
+
}
|
|
109
266
|
return { value, isLoading, set, toggle, init, headScript };
|
|
110
267
|
}
|
|
@@ -1,20 +1,18 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* createTheme, a reusable, SSR-safe theme store so every Fluixi app gets the whole
|
|
3
|
-
* theme workflow (detect -> persist -> apply -> toggle, + the no-flash head script)
|
|
4
|
-
* without rewriting it. Generic over an arbitrary theme list (not just light/dark),
|
|
5
|
-
* with optional async theme loading and lifecycle hooks.
|
|
6
|
-
*
|
|
7
|
-
* Built on the universal `storage` + `env` utilities, so reading the stored theme at
|
|
8
|
-
* module-eval and touching `document`/`matchMedia` are safe on the server.
|
|
9
|
-
*/
|
|
10
1
|
import { type Accessor } from '@fluixi/reactive/signal';
|
|
2
|
+
import { type TypedStorage } from '@fluixi/storage';
|
|
11
3
|
export interface ThemeOptions<T extends string> {
|
|
12
4
|
/** The full set of themes. `toggle()` cycles through them in order. */
|
|
13
5
|
themes: readonly T[];
|
|
14
6
|
/** Theme used when nothing is stored and system preference doesn't apply. */
|
|
15
7
|
default: T;
|
|
16
|
-
/**
|
|
8
|
+
/** Key the choice is stored under (default `"theme"`). */
|
|
17
9
|
storageKey?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Where the choice is kept. Defaults to a cookie for a year (`useStorage({ scope: 'cookie' })`),
|
|
12
|
+
* which the server render can read. `null` keeps it in memory only. Created outside a
|
|
13
|
+
* request on the server, there is no cookie to read, so the default falls back to memory.
|
|
14
|
+
*/
|
|
15
|
+
storage?: TypedStorage<any> | null;
|
|
18
16
|
/** Attribute set on `<html>` to drive CSS (default `"data-theme"`). */
|
|
19
17
|
attribute?: string;
|
|
20
18
|
/**
|
|
@@ -29,6 +27,12 @@ export interface ThemeOptions<T extends string> {
|
|
|
29
27
|
load?: (theme: T) => void | Promise<void>;
|
|
30
28
|
/** Fired after a theme is applied. */
|
|
31
29
|
onChange?: (next: T, prev: T | undefined) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Send the before-paint script with the page, through the head, during a render. It applies a
|
|
32
|
+
* stored choice the server could not read (a prerendered page) and the OS preference before a
|
|
33
|
+
* first choice. Default true; `headScript()` returns the same script for a page you write.
|
|
34
|
+
*/
|
|
35
|
+
script?: boolean;
|
|
32
36
|
/** Fired when an async `load` starts / finishes. */
|
|
33
37
|
onLoading?: (theme: T) => void;
|
|
34
38
|
onLoaded?: (theme: T) => void;
|
|
@@ -47,6 +51,14 @@ export interface Theme<T extends string> {
|
|
|
47
51
|
/** Inline `<script>` for `<head>` that sets the attribute before first paint (no flash). */
|
|
48
52
|
headScript: () => string;
|
|
49
53
|
}
|
|
54
|
+
/** @internal: a cookie for a year, or memory outside a request on the server. */
|
|
55
|
+
export declare function defaultStorage(): TypedStorage<any> | null;
|
|
56
|
+
/** How the head script finds the stored choice before any module has loaded. */
|
|
57
|
+
/** @internal */
|
|
58
|
+
export declare function storedLookup(storage: TypedStorage<any> | null, key: string): {
|
|
59
|
+
from: 'cookie' | 'local';
|
|
60
|
+
name: string;
|
|
61
|
+
} | null;
|
|
50
62
|
/**
|
|
51
63
|
* A theme controller: a reactive current theme, written to an attribute on the document.
|
|
52
64
|
*
|
|
@@ -59,7 +71,7 @@ export interface Theme<T extends string> {
|
|
|
59
71
|
* ```ts
|
|
60
72
|
* const theme = createTheme({ themes: ['light', 'dark'], system: { dark: 'dark' } });
|
|
61
73
|
* theme.set('dark');
|
|
62
|
-
* theme.
|
|
74
|
+
* theme.value(); // 'dark', and the document now carries data-theme="dark"
|
|
63
75
|
* ```
|
|
64
76
|
*/
|
|
65
77
|
export declare function createTheme<T extends string>(options: ThemeOptions<T>): Theme<T>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/theme/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/theme/index.ts"],"names":[],"mappings":"AAUA,OAAO,EAAgB,KAAK,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAA2B,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAK7E,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,MAAM;IAC5C,uEAAuE;IACvE,MAAM,EAAE,SAAS,CAAC,EAAE,CAAC;IACrB,6EAA6E;IAC7E,OAAO,EAAE,CAAC,CAAC;IACX,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,OAAO,CAAC,EAAE,YAAY,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;IACnC,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG;QAAE,IAAI,EAAE,CAAC,CAAC;QAAC,KAAK,EAAE,CAAC,CAAA;KAAE,CAAC;IACzC,2FAA2F;IAC3F,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,sCAAsC;IACtC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,SAAS,KAAK,IAAI,CAAC;IAClD;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,oDAAoD;IACpD,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,KAAK,CAAC,CAAC,SAAS,MAAM;IACrC,8BAA8B;IAC9B,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IACnB,uDAAuD;IACvD,SAAS,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC7B,mEAAmE;IACnE,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IACxB,2CAA2C;IAC3C,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,sEAAsE;IACtE,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,4FAA4F;IAC5F,UAAU,EAAE,MAAM,MAAM,CAAC;CAC1B;AAYD,iFAAiF;AACjF,wBAAgB,cAAc,IAAI,YAAY,CAAC,GAAG,CAAC,GAAG,IAAI,CAOzD;AAED,gFAAgF;AAChF,gBAAgB;AAChB,wBAAgB,YAAY,CAAC,OAAO,EAAE,YAAY,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAQ9H;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CA+EhF"}
|