@vuecs/design 1.0.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.
- package/LICENSE +201 -0
- package/README.md +57 -0
- package/assets/animations.css +506 -0
- package/assets/index.css +197 -0
- package/assets/palettes.css +303 -0
- package/assets/standalone.css +37 -0
- package/dist/core/color-mode/bind.d.ts +12 -0
- package/dist/core/color-mode/bind.d.ts.map +1 -0
- package/dist/core/color-mode/composable.d.ts +13 -0
- package/dist/core/color-mode/composable.d.ts.map +1 -0
- package/dist/core/color-mode/index.d.ts +4 -0
- package/dist/core/color-mode/index.d.ts.map +1 -0
- package/dist/core/color-mode/types.d.ts +36 -0
- package/dist/core/color-mode/types.d.ts.map +1 -0
- package/dist/core/color-palette/apply.d.ts +34 -0
- package/dist/core/color-palette/apply.d.ts.map +1 -0
- package/dist/core/color-palette/bind.d.ts +14 -0
- package/dist/core/color-palette/bind.d.ts.map +1 -0
- package/dist/core/color-palette/catalog.d.ts +78 -0
- package/dist/core/color-palette/catalog.d.ts.map +1 -0
- package/dist/core/color-palette/composable.d.ts +34 -0
- package/dist/core/color-palette/composable.d.ts.map +1 -0
- package/dist/core/color-palette/index.d.ts +7 -0
- package/dist/core/color-palette/index.d.ts.map +1 -0
- package/dist/core/color-palette/render.d.ts +16 -0
- package/dist/core/color-palette/render.d.ts.map +1 -0
- package/dist/core/color-palette/types.d.ts +107 -0
- package/dist/core/color-palette/types.d.ts.map +1 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/theme-runtime/capture.d.ts +32 -0
- package/dist/core/theme-runtime/capture.d.ts.map +1 -0
- package/dist/core/theme-runtime/composable.d.ts +34 -0
- package/dist/core/theme-runtime/composable.d.ts.map +1 -0
- package/dist/core/theme-runtime/index.d.ts +4 -0
- package/dist/core/theme-runtime/index.d.ts.map +1 -0
- package/dist/core/theme-runtime/types.d.ts +42 -0
- package/dist/core/theme-runtime/types.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +467 -0
- package/dist/index.mjs.map +1 -0
- package/dist/utils/object.d.ts +2 -0
- package/dist/utils/object.d.ts.map +1 -0
- package/package.json +83 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import { createSharedComposable, usePreferredDark, useStorage } from "@vueuse/core";
|
|
2
|
+
import { computed, inject, ref, watch, watchEffect } from "vue";
|
|
3
|
+
//#region src/core/theme-runtime/composable.ts
|
|
4
|
+
/**
|
|
5
|
+
* Globally-shared `InjectionKey` for the ThemeManager. SSR plugins
|
|
6
|
+
* (Nuxt, future frameworks) that need to look up the manager from
|
|
7
|
+
* outside Vue's component context use this with `app.runWithContext`
|
|
8
|
+
* (or equivalent) + `inject`.
|
|
9
|
+
*
|
|
10
|
+
* Bridge between `@vuecs/design` and `@vuecs/core`'s `ThemeManager`
|
|
11
|
+
* without a hard runtime dep: both packages reference the same
|
|
12
|
+
* `Symbol.for('VCThemeManager')` registry key, so the ThemeManager
|
|
13
|
+
* provided by `installThemeManager(app)` (in `@vuecs/core`) is
|
|
14
|
+
* reachable here. `Symbol.for(...)` is reference-equal across module
|
|
15
|
+
* boundaries, and the `InjectionKey<ThemeRuntimeManager>` cast
|
|
16
|
+
* surfaces the manager type to `inject()` callers without leaking
|
|
17
|
+
* `@vuecs/core` internals.
|
|
18
|
+
*
|
|
19
|
+
* `inject()` returns `undefined` if no ThemeManager is installed —
|
|
20
|
+
* design composables use that as the "no theme dispatch" fallback so
|
|
21
|
+
* they keep working in standalone apps that import `@vuecs/design`
|
|
22
|
+
* without `@vuecs/core`.
|
|
23
|
+
*/
|
|
24
|
+
const THEME_RUNTIME_MANAGER_SYMBOL = Symbol.for("VCThemeManager");
|
|
25
|
+
/**
|
|
26
|
+
* Look up the ThemeManager installed by `app.use(vuecs)` (or
|
|
27
|
+
* `installThemeManager(app)`). Returns `undefined` if no manager is
|
|
28
|
+
* provided in the current setup context — design composables fall back
|
|
29
|
+
* to no-dispatch behaviour in that case.
|
|
30
|
+
*
|
|
31
|
+
* Must be called during `setup()` or another composable's setup phase
|
|
32
|
+
* (Vue's `inject()` requirement).
|
|
33
|
+
*/
|
|
34
|
+
function useThemeRuntimeManager() {
|
|
35
|
+
return inject(THEME_RUNTIME_MANAGER_SYMBOL, void 0);
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/core/color-mode/bind.ts
|
|
39
|
+
/**
|
|
40
|
+
* Wire any reactive `Ref<ColorMode>` into the design system: track
|
|
41
|
+
* system preference, expose the resolved light/dark value, and
|
|
42
|
+
* (optionally) sync the `.dark` / `.light` class on `<html>`.
|
|
43
|
+
*
|
|
44
|
+
* The `syncClass` watcher mirrors what `@vuecs/nuxt`'s SSR plugin
|
|
45
|
+
* writes server-side, so client hydration leaves the class in place.
|
|
46
|
+
*/
|
|
47
|
+
function bindColorMode(source, options = {}) {
|
|
48
|
+
const { syncClass = true } = options;
|
|
49
|
+
const preferredDark = usePreferredDark();
|
|
50
|
+
const resolved = computed(() => {
|
|
51
|
+
if (source.value === "dark") return "dark";
|
|
52
|
+
if (source.value === "light") return "light";
|
|
53
|
+
return preferredDark.value ? "dark" : "light";
|
|
54
|
+
});
|
|
55
|
+
const mode = computed({
|
|
56
|
+
get: () => source.value,
|
|
57
|
+
set: (value) => {
|
|
58
|
+
source.value = value;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
const isDark = computed({
|
|
62
|
+
get: () => resolved.value === "dark",
|
|
63
|
+
set: (value) => {
|
|
64
|
+
source.value = value ? "dark" : "light";
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
if (syncClass && typeof document !== "undefined") watch(resolved, (value) => {
|
|
68
|
+
document.documentElement.classList.toggle("dark", value === "dark");
|
|
69
|
+
document.documentElement.classList.toggle("light", value === "light");
|
|
70
|
+
}, { immediate: true });
|
|
71
|
+
const manager = useThemeRuntimeManager();
|
|
72
|
+
if (typeof document !== "undefined") watch([resolved, () => manager?.themes], ([value, themes]) => {
|
|
73
|
+
if (!themes) return;
|
|
74
|
+
for (const theme of themes) theme.colorMode?.handle(document, value);
|
|
75
|
+
}, { immediate: true });
|
|
76
|
+
function toggle() {
|
|
77
|
+
source.value = resolved.value === "dark" ? "light" : "dark";
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
mode,
|
|
81
|
+
resolved,
|
|
82
|
+
isDark,
|
|
83
|
+
toggle
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/core/color-mode/composable.ts
|
|
88
|
+
const DEFAULT_STORAGE_KEY$1 = "vc-color-mode";
|
|
89
|
+
const COLOR_MODE_SET = new Set([
|
|
90
|
+
"light",
|
|
91
|
+
"dark",
|
|
92
|
+
"system"
|
|
93
|
+
]);
|
|
94
|
+
const sanitize = (value, fallback) => typeof value === "string" && COLOR_MODE_SET.has(value) ? value : fallback;
|
|
95
|
+
/**
|
|
96
|
+
* Reactive color-mode state with localStorage persistence. Shared
|
|
97
|
+
* across all call sites via `createSharedComposable` so toggling in
|
|
98
|
+
* one component updates every consumer (and the `<html>` class) in
|
|
99
|
+
* lockstep.
|
|
100
|
+
*
|
|
101
|
+
* For SSR-aware cookie-backed storage (Nuxt), the `@vuecs/nuxt` module
|
|
102
|
+
* ships its own `useColorMode()` that calls `bindColorMode()` directly
|
|
103
|
+
* with a cookie-backed ref. Both expose the same return shape.
|
|
104
|
+
*/
|
|
105
|
+
const useColorMode = createSharedComposable((options = {}) => {
|
|
106
|
+
const { initial = "system", persist = true, storageKey = DEFAULT_STORAGE_KEY$1, syncClass = true } = options;
|
|
107
|
+
return bindColorMode(persist ? useStorage(storageKey, initial, void 0, { serializer: {
|
|
108
|
+
read: (raw) => sanitize(raw, initial),
|
|
109
|
+
write: (value) => value
|
|
110
|
+
} }) : ref(initial), { syncClass });
|
|
111
|
+
});
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/core/color-palette/apply.ts
|
|
114
|
+
/**
|
|
115
|
+
* DOM id used for the runtime palette `<style>` block. Themes that
|
|
116
|
+
* implement palette switching should write into a `<style>` element
|
|
117
|
+
* with this id (via `applyColorPaletteCss()`); SSR plugins use the same
|
|
118
|
+
* id so client hydration replaces the server-rendered block atomically.
|
|
119
|
+
*/
|
|
120
|
+
const COLOR_PALETTE_STYLE_ELEMENT_ID = "vc-color-palette";
|
|
121
|
+
/**
|
|
122
|
+
* Apply an arbitrary CSS string as a `<style id="vc-color-palette">` block
|
|
123
|
+
* (client-side only). Idempotent — subsequent calls replace the
|
|
124
|
+
* element's content.
|
|
125
|
+
*
|
|
126
|
+
* Theme-agnostic: accepts whatever CSS string the caller wants. Themes
|
|
127
|
+
* that ship palette switching compose this with their own renderer
|
|
128
|
+
* (e.g. `@vuecs/theme-tailwind`'s `renderColorPaletteStyles()`); other
|
|
129
|
+
* tooling can call it directly with custom CSS.
|
|
130
|
+
*
|
|
131
|
+
* The optional `nonce` parameter wires CSP nonce attribution: when set,
|
|
132
|
+
* the created `<style>` element carries `nonce="..."` so it survives a
|
|
133
|
+
* strict Content-Security-Policy. Subsequent calls update the
|
|
134
|
+
* attribute when the value changes, and clear it when the new value is
|
|
135
|
+
* undefined (so consumers can revoke a stale nonce on policy update).
|
|
136
|
+
* Consumers typically read this via
|
|
137
|
+
* `useConfig('nonce')` (from `@vuecs/core`, augmented by
|
|
138
|
+
* `@vuecs/theme-tailwind`); the per-theme `useColorPalette` wrappers
|
|
139
|
+
* already do this.
|
|
140
|
+
*
|
|
141
|
+
* On the server (`document` undefined) this is a no-op; SSR pre-render
|
|
142
|
+
* paths should serialize the renderer's output into the response head
|
|
143
|
+
* directly (with the nonce wired through framework-specific head
|
|
144
|
+
* APIs), then let the client take over on hydration.
|
|
145
|
+
*/
|
|
146
|
+
function applyColorPaletteCss(css, doc = globalThis.document, nonce) {
|
|
147
|
+
if (!doc) return;
|
|
148
|
+
let style = doc.getElementById(COLOR_PALETTE_STYLE_ELEMENT_ID);
|
|
149
|
+
if (!style) {
|
|
150
|
+
style = doc.createElement("style");
|
|
151
|
+
style.id = COLOR_PALETTE_STYLE_ELEMENT_ID;
|
|
152
|
+
if (nonce) style.setAttribute("nonce", nonce);
|
|
153
|
+
doc.head.appendChild(style);
|
|
154
|
+
} else if (nonce) {
|
|
155
|
+
if (style.getAttribute("nonce") !== nonce) style.setAttribute("nonce", nonce);
|
|
156
|
+
} else if (style.hasAttribute("nonce")) style.removeAttribute("nonce");
|
|
157
|
+
style.textContent = css;
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region src/core/color-palette/bind.ts
|
|
161
|
+
/**
|
|
162
|
+
* Wire any reactive `Ref<T>` into the palette runtime: render the
|
|
163
|
+
* current value via the theme-supplied `render` function, apply it via
|
|
164
|
+
* `applyColorPaletteCss`, and re-apply on every change.
|
|
165
|
+
*
|
|
166
|
+
* Generic: each theme defines its own palette shape `T` and renderer.
|
|
167
|
+
* `@vuecs/theme-tailwind` wraps this with its `ColorPaletteConfig` and
|
|
168
|
+
* `renderColorPaletteStyles`; community themes can do the same with their
|
|
169
|
+
* own shapes — including custom merge semantics via `options.extend`.
|
|
170
|
+
*/
|
|
171
|
+
function bindColorPalette(source, options) {
|
|
172
|
+
const { render, extend, document = globalThis.document, nonce } = options;
|
|
173
|
+
const resolveNonce = typeof nonce === "function" ? nonce : () => nonce;
|
|
174
|
+
if (document) applyColorPaletteCss(render(source.value), document, resolveNonce());
|
|
175
|
+
watch([source, () => resolveNonce()], () => {
|
|
176
|
+
applyColorPaletteCss(render(source.value), document, resolveNonce());
|
|
177
|
+
}, { deep: true });
|
|
178
|
+
return {
|
|
179
|
+
current: computed(() => source.value),
|
|
180
|
+
set(palette) {
|
|
181
|
+
source.value = palette;
|
|
182
|
+
},
|
|
183
|
+
extend(partial) {
|
|
184
|
+
source.value = extend(source.value, partial);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region src/core/color-palette/catalog.ts
|
|
190
|
+
/**
|
|
191
|
+
* Canonical palette catalog for the vuecs design system.
|
|
192
|
+
*
|
|
193
|
+
* The names originated in Tailwind v4 (see `@vuecs/design/standalone`'s
|
|
194
|
+
* `palettes.css`, generated from `tailwindcss/theme.css`), but the
|
|
195
|
+
* catalog is now considered design-owned: every supported palette
|
|
196
|
+
* source — Tailwind via `@import "tailwindcss"`, or the
|
|
197
|
+
* standalone subpath — provides the matching `--color-<palette>-<shade>`
|
|
198
|
+
* literals so `setColorPalette()` resolves correctly regardless of
|
|
199
|
+
* whether Tailwind is loaded.
|
|
200
|
+
*
|
|
201
|
+
* Themes typically reuse this catalog verbatim (theme-tailwind and
|
|
202
|
+
* theme-bulma both declare `palette.names: COLOR_PALETTES`). A theme
|
|
203
|
+
* with extra palette names just defines its own local union:
|
|
204
|
+
*
|
|
205
|
+
* type AcmePaletteName = ColorPaletteName | 'acme-blue';
|
|
206
|
+
*
|
|
207
|
+
* — and ships its own `palette.names` array.
|
|
208
|
+
*/
|
|
209
|
+
/**
|
|
210
|
+
* The six semantic scales every vuecs theme exposes through the
|
|
211
|
+
* `--vc-color-<scale>-*` variable family. A `setColorPalette({
|
|
212
|
+
* primary: 'green' })` call binds one scale to one palette catalog
|
|
213
|
+
* entry at runtime.
|
|
214
|
+
*/
|
|
215
|
+
const SEMANTIC_SCALES = [
|
|
216
|
+
"primary",
|
|
217
|
+
"neutral",
|
|
218
|
+
"success",
|
|
219
|
+
"warning",
|
|
220
|
+
"error",
|
|
221
|
+
"info"
|
|
222
|
+
];
|
|
223
|
+
/**
|
|
224
|
+
* The 22 catalog palettes shipped with `@vuecs/design` (sourced
|
|
225
|
+
* verbatim from Tailwind v4). Any of these can be assigned to a
|
|
226
|
+
* `SemanticScaleName` via `setColorPalette()`.
|
|
227
|
+
*/
|
|
228
|
+
const COLOR_PALETTES = [
|
|
229
|
+
"slate",
|
|
230
|
+
"gray",
|
|
231
|
+
"zinc",
|
|
232
|
+
"neutral",
|
|
233
|
+
"stone",
|
|
234
|
+
"red",
|
|
235
|
+
"orange",
|
|
236
|
+
"amber",
|
|
237
|
+
"yellow",
|
|
238
|
+
"lime",
|
|
239
|
+
"green",
|
|
240
|
+
"emerald",
|
|
241
|
+
"teal",
|
|
242
|
+
"cyan",
|
|
243
|
+
"sky",
|
|
244
|
+
"blue",
|
|
245
|
+
"indigo",
|
|
246
|
+
"violet",
|
|
247
|
+
"purple",
|
|
248
|
+
"fuchsia",
|
|
249
|
+
"pink",
|
|
250
|
+
"rose"
|
|
251
|
+
];
|
|
252
|
+
/**
|
|
253
|
+
* Tailwind-style 11-stop shade ladder. The same ladder appears in
|
|
254
|
+
* every catalog entry (the standalone subpath's `palettes.css` and
|
|
255
|
+
* Tailwind's own `theme.css` both emit `--color-<palette>-<shade>`
|
|
256
|
+
* for each stop).
|
|
257
|
+
*/
|
|
258
|
+
const COLOR_PALETTE_SHADES = [
|
|
259
|
+
"50",
|
|
260
|
+
"100",
|
|
261
|
+
"200",
|
|
262
|
+
"300",
|
|
263
|
+
"400",
|
|
264
|
+
"500",
|
|
265
|
+
"600",
|
|
266
|
+
"700",
|
|
267
|
+
"800",
|
|
268
|
+
"900",
|
|
269
|
+
"950"
|
|
270
|
+
];
|
|
271
|
+
//#endregion
|
|
272
|
+
//#region src/utils/object.ts
|
|
273
|
+
function isObject(input) {
|
|
274
|
+
return typeof input === "object" && input !== null && !Array.isArray(input);
|
|
275
|
+
}
|
|
276
|
+
//#endregion
|
|
277
|
+
//#region src/core/color-palette/render.ts
|
|
278
|
+
/**
|
|
279
|
+
* Concatenate every installed theme's `palette.handle` output into a
|
|
280
|
+
* single CSS string. SSR plugins emit the result as the
|
|
281
|
+
* `<style id="vc-color-palette">` block so palette-aware themes flow
|
|
282
|
+
* on first paint.
|
|
283
|
+
*
|
|
284
|
+
* Mirrors the client-side concat semantics in `useColorPalette()`:
|
|
285
|
+
* non-overlapping rules from different themes coexist; CSS cascade
|
|
286
|
+
* resolves any incidental overlap with later-rule-wins.
|
|
287
|
+
*
|
|
288
|
+
* Errors thrown by a theme's handler are caught + logged so one
|
|
289
|
+
* broken theme can't crash SSR; other themes still emit.
|
|
290
|
+
*/
|
|
291
|
+
function renderColorPaletteFromThemes(themes, palette) {
|
|
292
|
+
const parts = [];
|
|
293
|
+
for (const theme of themes) {
|
|
294
|
+
if (!theme.palette?.handle) continue;
|
|
295
|
+
const handle = theme.palette.handle.bind(theme.palette);
|
|
296
|
+
const input = applyScaleAliases(palette, theme.palette.scaleAliases);
|
|
297
|
+
try {
|
|
298
|
+
const out = handle(input);
|
|
299
|
+
if (out) parts.push(out);
|
|
300
|
+
} catch (e) {
|
|
301
|
+
if (typeof console !== "undefined") console.warn("[vuecs] theme palette.handle failed; skipping:", e);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return parts.join("\n");
|
|
305
|
+
}
|
|
306
|
+
const FORBIDDEN_KEYS = new Set([
|
|
307
|
+
"__proto__",
|
|
308
|
+
"constructor",
|
|
309
|
+
"prototype"
|
|
310
|
+
]);
|
|
311
|
+
function applyScaleAliases(palette, aliases) {
|
|
312
|
+
if (!aliases) return palette;
|
|
313
|
+
const out = Object.create(null);
|
|
314
|
+
for (const [k, v] of Object.entries(palette)) {
|
|
315
|
+
const target = aliases[k] ?? k;
|
|
316
|
+
if (FORBIDDEN_KEYS.has(target)) continue;
|
|
317
|
+
out[target] = v;
|
|
318
|
+
}
|
|
319
|
+
return out;
|
|
320
|
+
}
|
|
321
|
+
//#endregion
|
|
322
|
+
//#region src/core/color-palette/composable.ts
|
|
323
|
+
const DEFAULT_STORAGE_KEY = "vc-color-palette";
|
|
324
|
+
const SEMANTIC_SCALE_SET = new Set(SEMANTIC_SCALES);
|
|
325
|
+
const PALETTE_NAME_SET = new Set(COLOR_PALETTES);
|
|
326
|
+
function defaultSanitize(value) {
|
|
327
|
+
if (!isObject(value)) return {};
|
|
328
|
+
const out = {};
|
|
329
|
+
for (const [k, v] of Object.entries(value)) if (SEMANTIC_SCALE_SET.has(k) && typeof v === "string" && PALETTE_NAME_SET.has(v)) out[k] = v;
|
|
330
|
+
return out;
|
|
331
|
+
}
|
|
332
|
+
const shallowMerge = (current, partial) => ({
|
|
333
|
+
...current,
|
|
334
|
+
...partial
|
|
335
|
+
});
|
|
336
|
+
/**
|
|
337
|
+
* Theme-aware reactive palette state — un-shared variant.
|
|
338
|
+
*
|
|
339
|
+
* Concatenates every installed theme's `palette.handle` output into the
|
|
340
|
+
* `<style id="vc-color-palette">` element. Walking the installed themes
|
|
341
|
+
* each render means runtime theme swaps via `setThemes()` automatically
|
|
342
|
+
* pick up the new renderer chain.
|
|
343
|
+
*
|
|
344
|
+
* Concat (rather than last-wins) is the doctrinal semantic: when an app
|
|
345
|
+
* stacks multiple palette-aware themes (the docs-site case where
|
|
346
|
+
* Tailwind components and Bulma components share the same picker UI),
|
|
347
|
+
* each theme's renderer emits its own non-overlapping CSS rules —
|
|
348
|
+
* Tailwind rebinds `--vc-color-*`, Bulma writes per-variant HSL channel
|
|
349
|
+
* vars. The CSS cascade resolves any incidental overlap with
|
|
350
|
+
* later-rule-wins semantics, so concat behaves like last-wins for
|
|
351
|
+
* overlapping properties AND emits both themes' unique properties.
|
|
352
|
+
*
|
|
353
|
+
* Production callers should use `useColorPalette` (the shared variant
|
|
354
|
+
* below). This un-shared form is exposed primarily for testing — every
|
|
355
|
+
* call creates a fresh `watchEffect` and palette state.
|
|
356
|
+
*/
|
|
357
|
+
function useColorPaletteUnshared(options = {}) {
|
|
358
|
+
const { initial = {}, source, persist = true, storageKey = DEFAULT_STORAGE_KEY, sanitize = defaultSanitize, extend = shallowMerge, nonce } = options;
|
|
359
|
+
const resolveNonce = typeof nonce === "function" ? nonce : () => nonce;
|
|
360
|
+
const manager = useThemeRuntimeManager();
|
|
361
|
+
const storage = source ?? (persist ? useStorage(storageKey, sanitize(initial), void 0, { serializer: {
|
|
362
|
+
read: (raw) => {
|
|
363
|
+
try {
|
|
364
|
+
return sanitize(JSON.parse(raw));
|
|
365
|
+
} catch {
|
|
366
|
+
return sanitize({});
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
write: (value) => JSON.stringify(value)
|
|
370
|
+
} }) : ref(sanitize(initial)));
|
|
371
|
+
const renderConcatenated = (palette) => {
|
|
372
|
+
const themes = manager?.themes;
|
|
373
|
+
if (!themes || themes.length === 0) return "";
|
|
374
|
+
return renderColorPaletteFromThemes(themes, sanitize(palette));
|
|
375
|
+
};
|
|
376
|
+
if (typeof document !== "undefined") watchEffect(() => {
|
|
377
|
+
applyColorPaletteCss(renderConcatenated(storage.value), void 0, resolveNonce());
|
|
378
|
+
});
|
|
379
|
+
return {
|
|
380
|
+
current: computed(() => storage.value),
|
|
381
|
+
set(palette) {
|
|
382
|
+
storage.value = palette;
|
|
383
|
+
},
|
|
384
|
+
extend(partial) {
|
|
385
|
+
storage.value = extend(storage.value, partial);
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Theme-aware reactive palette state with localStorage persistence
|
|
391
|
+
* (plan 021 slice 2).
|
|
392
|
+
*
|
|
393
|
+
* Wrapped with `createSharedComposable` so every call site shares the
|
|
394
|
+
* same ref + watcher. For SSR-aware cookie-backed storage (Nuxt), the
|
|
395
|
+
* matching Nuxt module ships its own composable that calls
|
|
396
|
+
* `bindColorPalette()` directly with a cookie-backed ref.
|
|
397
|
+
*/
|
|
398
|
+
const useColorPalette = createSharedComposable(useColorPaletteUnshared);
|
|
399
|
+
//#endregion
|
|
400
|
+
//#region src/core/theme-runtime/capture.ts
|
|
401
|
+
/**
|
|
402
|
+
* Build a synthetic Document-like whose `documentElement` records
|
|
403
|
+
* `setAttribute` / `removeAttribute` calls into the supplied target
|
|
404
|
+
* record, plus a no-op `classList`. **No other Document or Element
|
|
405
|
+
* APIs are stubbed** — themes that call `doc.createElement`,
|
|
406
|
+
* `doc.head.appendChild`, etc. WILL throw at SSR runtime. Themes
|
|
407
|
+
* needing richer DOM access from `colorMode.handle` should guard their
|
|
408
|
+
* CSR-only logic with `if (typeof window === 'undefined') return;`
|
|
409
|
+
* and split the SSR-flowing bits into declarative `setAttribute` calls.
|
|
410
|
+
*
|
|
411
|
+
* Exposed for advanced consumers; the higher-level
|
|
412
|
+
* `captureColorModeAttrs()` covers the common case and catches errors
|
|
413
|
+
* per theme so a single broken theme can't crash SSR.
|
|
414
|
+
*/
|
|
415
|
+
function createCaptureDocument(target) {
|
|
416
|
+
return { documentElement: {
|
|
417
|
+
setAttribute(name, value) {
|
|
418
|
+
target[name] = value;
|
|
419
|
+
},
|
|
420
|
+
removeAttribute(name) {
|
|
421
|
+
delete target[name];
|
|
422
|
+
},
|
|
423
|
+
classList: {
|
|
424
|
+
add() {},
|
|
425
|
+
remove() {},
|
|
426
|
+
toggle() {
|
|
427
|
+
return false;
|
|
428
|
+
},
|
|
429
|
+
contains() {
|
|
430
|
+
return false;
|
|
431
|
+
},
|
|
432
|
+
replace() {}
|
|
433
|
+
}
|
|
434
|
+
} };
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Walk every installed theme's `colorMode.handle` against a synthetic
|
|
438
|
+
* Document and capture the resulting attribute mutations. SSR plugins
|
|
439
|
+
* use this to flow `data-bs-theme` / `data-theme` (or any other
|
|
440
|
+
* attribute a theme declares) into `useHead({ htmlAttrs })` before
|
|
441
|
+
* first paint.
|
|
442
|
+
*
|
|
443
|
+
* Each theme's handler runs in install order. If multiple themes set
|
|
444
|
+
* the same attribute, the last one wins — same semantic as the live
|
|
445
|
+
* `document.documentElement.setAttribute` chain on the client.
|
|
446
|
+
*
|
|
447
|
+
* Errors thrown by a theme's handler are caught + logged as a warning
|
|
448
|
+
* so one malformed theme can't crash SSR; other themes still run.
|
|
449
|
+
*/
|
|
450
|
+
function captureColorModeAttrs(themes, mode) {
|
|
451
|
+
const attrs = Object.create(null);
|
|
452
|
+
const fakeDoc = createCaptureDocument(attrs);
|
|
453
|
+
for (const theme of themes) {
|
|
454
|
+
if (!theme.colorMode?.handle) continue;
|
|
455
|
+
const handle = theme.colorMode?.handle.bind(theme.colorMode);
|
|
456
|
+
try {
|
|
457
|
+
handle(fakeDoc, mode);
|
|
458
|
+
} catch (e) {
|
|
459
|
+
if (typeof console !== "undefined") console.warn("[vuecs] theme colorMode.handle failed; skipping:", e);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return attrs;
|
|
463
|
+
}
|
|
464
|
+
//#endregion
|
|
465
|
+
export { COLOR_PALETTES, COLOR_PALETTE_SHADES, COLOR_PALETTE_STYLE_ELEMENT_ID, SEMANTIC_SCALES, THEME_RUNTIME_MANAGER_SYMBOL, applyColorPaletteCss, bindColorMode, bindColorPalette, captureColorModeAttrs, createCaptureDocument, renderColorPaletteFromThemes, useColorMode, useColorPalette, useColorPaletteUnshared, useThemeRuntimeManager };
|
|
466
|
+
|
|
467
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["DEFAULT_STORAGE_KEY"],"sources":["../src/core/theme-runtime/composable.ts","../src/core/color-mode/bind.ts","../src/core/color-mode/composable.ts","../src/core/color-palette/apply.ts","../src/core/color-palette/bind.ts","../src/core/color-palette/catalog.ts","../src/utils/object.ts","../src/core/color-palette/render.ts","../src/core/color-palette/composable.ts","../src/core/theme-runtime/capture.ts"],"sourcesContent":["import type { InjectionKey } from 'vue';\nimport { inject } from 'vue';\nimport type { ThemeRuntimeManager } from './types';\n\n/**\n * Globally-shared `InjectionKey` for the ThemeManager. SSR plugins\n * (Nuxt, future frameworks) that need to look up the manager from\n * outside Vue's component context use this with `app.runWithContext`\n * (or equivalent) + `inject`.\n *\n * Bridge between `@vuecs/design` and `@vuecs/core`'s `ThemeManager`\n * without a hard runtime dep: both packages reference the same\n * `Symbol.for('VCThemeManager')` registry key, so the ThemeManager\n * provided by `installThemeManager(app)` (in `@vuecs/core`) is\n * reachable here. `Symbol.for(...)` is reference-equal across module\n * boundaries, and the `InjectionKey<ThemeRuntimeManager>` cast\n * surfaces the manager type to `inject()` callers without leaking\n * `@vuecs/core` internals.\n *\n * `inject()` returns `undefined` if no ThemeManager is installed —\n * design composables use that as the \"no theme dispatch\" fallback so\n * they keep working in standalone apps that import `@vuecs/design`\n * without `@vuecs/core`.\n */\nexport const THEME_RUNTIME_MANAGER_SYMBOL = Symbol.for('VCThemeManager') as InjectionKey<ThemeRuntimeManager>;\n\n/**\n * Look up the ThemeManager installed by `app.use(vuecs)` (or\n * `installThemeManager(app)`). Returns `undefined` if no manager is\n * provided in the current setup context — design composables fall back\n * to no-dispatch behaviour in that case.\n *\n * Must be called during `setup()` or another composable's setup phase\n * (Vue's `inject()` requirement).\n */\nexport function useThemeRuntimeManager(): ThemeRuntimeManager | undefined {\n return inject(THEME_RUNTIME_MANAGER_SYMBOL, undefined);\n}\n","import { usePreferredDark } from '@vueuse/core';\nimport { computed, watch } from 'vue';\nimport type { Ref } from 'vue';\nimport { useThemeRuntimeManager } from '../theme-runtime/composable';\nimport type { ColorMode, UseColorModeOptions, UseColorModeReturn } from './types';\n\n/**\n * Wire any reactive `Ref<ColorMode>` into the design system: track\n * system preference, expose the resolved light/dark value, and\n * (optionally) sync the `.dark` / `.light` class on `<html>`.\n *\n * The `syncClass` watcher mirrors what `@vuecs/nuxt`'s SSR plugin\n * writes server-side, so client hydration leaves the class in place.\n */\nexport function bindColorMode(\n source: Ref<ColorMode>,\n options: Pick<UseColorModeOptions, 'syncClass'> = {},\n): UseColorModeReturn {\n const { syncClass = true } = options;\n const preferredDark = usePreferredDark();\n\n const resolved = computed<'light' | 'dark'>(() => {\n if (source.value === 'dark') return 'dark';\n if (source.value === 'light') return 'light';\n return preferredDark.value ? 'dark' : 'light';\n });\n\n const mode = computed<ColorMode>({\n get: () => source.value,\n set: (value) => {\n source.value = value;\n },\n });\n\n const isDark = computed<boolean>({\n get: () => resolved.value === 'dark',\n set: (value) => {\n source.value = value ? 'dark' : 'light';\n },\n });\n\n if (syncClass && typeof document !== 'undefined') {\n watch(\n resolved,\n (value) => {\n document.documentElement.classList.toggle('dark', value === 'dark');\n document.documentElement.classList.toggle('light', value === 'light');\n },\n { immediate: true },\n );\n }\n\n /*\n * Theme-configurable dispatch (plan 021): each installed theme that\n * declares a `colorMode.handle` hook gets called with the resolved\n * mode. Themes use this to mirror framework-specific dark-mode\n * markers (theme-bootstrap → `data-bs-theme`, theme-bulma →\n * `data-theme`) so framework chrome flips alongside vuecs's own\n * `.dark` class without per-app `watchEffect` mirrors.\n *\n * The watch source is a tuple `[resolved, () => manager?.themes]`\n * because Vue's `watch(source, callback)` only tracks reactive\n * dependencies accessed inside the source — callback reads do NOT\n * subscribe. Including the themes getter in the source means\n * `ThemeManager.setThemes()` (which mutates the underlying\n * `shallowRef`) re-fires the dispatch with the new theme list.\n */\n const manager = useThemeRuntimeManager();\n if (typeof document !== 'undefined') {\n watch(\n [resolved, () => manager?.themes],\n ([value, themes]) => {\n if (!themes) return;\n for (const theme of themes) {\n theme.colorMode?.handle(document, value);\n }\n },\n { immediate: true },\n );\n }\n\n function toggle(): void {\n source.value = resolved.value === 'dark' ? 'light' : 'dark';\n }\n\n return {\n mode,\n resolved,\n isDark,\n toggle,\n };\n}\n","import { createSharedComposable, useStorage } from '@vueuse/core';\nimport { ref } from 'vue';\nimport type { Ref } from 'vue';\nimport { bindColorMode } from './bind';\nimport type { ColorMode, UseColorModeOptions, UseColorModeReturn } from './types';\n\nconst DEFAULT_STORAGE_KEY = 'vc-color-mode';\nconst COLOR_MODES: readonly ColorMode[] = ['light', 'dark', 'system'];\nconst COLOR_MODE_SET = new Set<string>(COLOR_MODES);\n\nconst sanitize = (value: unknown, fallback: ColorMode): ColorMode => (\n typeof value === 'string' && COLOR_MODE_SET.has(value) ?\n (value as ColorMode) :\n fallback\n);\n\n/**\n * Reactive color-mode state with localStorage persistence. Shared\n * across all call sites via `createSharedComposable` so toggling in\n * one component updates every consumer (and the `<html>` class) in\n * lockstep.\n *\n * For SSR-aware cookie-backed storage (Nuxt), the `@vuecs/nuxt` module\n * ships its own `useColorMode()` that calls `bindColorMode()` directly\n * with a cookie-backed ref. Both expose the same return shape.\n */\nexport const useColorMode = createSharedComposable(\n (options: UseColorModeOptions = {}): UseColorModeReturn => {\n const {\n initial = 'system',\n persist = true,\n storageKey = DEFAULT_STORAGE_KEY,\n syncClass = true,\n } = options;\n\n const storage: Ref<ColorMode> = persist ?\n useStorage<ColorMode>(storageKey, initial, undefined, {\n serializer: {\n read: (raw) => sanitize(raw, initial),\n write: (value) => value,\n },\n }) :\n ref<ColorMode>(initial);\n\n return bindColorMode(storage, { syncClass });\n },\n);\n","/**\n * DOM id used for the runtime palette `<style>` block. Themes that\n * implement palette switching should write into a `<style>` element\n * with this id (via `applyColorPaletteCss()`); SSR plugins use the same\n * id so client hydration replaces the server-rendered block atomically.\n */\nexport const COLOR_PALETTE_STYLE_ELEMENT_ID = 'vc-color-palette';\n\n/**\n * Apply an arbitrary CSS string as a `<style id=\"vc-color-palette\">` block\n * (client-side only). Idempotent — subsequent calls replace the\n * element's content.\n *\n * Theme-agnostic: accepts whatever CSS string the caller wants. Themes\n * that ship palette switching compose this with their own renderer\n * (e.g. `@vuecs/theme-tailwind`'s `renderColorPaletteStyles()`); other\n * tooling can call it directly with custom CSS.\n *\n * The optional `nonce` parameter wires CSP nonce attribution: when set,\n * the created `<style>` element carries `nonce=\"...\"` so it survives a\n * strict Content-Security-Policy. Subsequent calls update the\n * attribute when the value changes, and clear it when the new value is\n * undefined (so consumers can revoke a stale nonce on policy update).\n * Consumers typically read this via\n * `useConfig('nonce')` (from `@vuecs/core`, augmented by\n * `@vuecs/theme-tailwind`); the per-theme `useColorPalette` wrappers\n * already do this.\n *\n * On the server (`document` undefined) this is a no-op; SSR pre-render\n * paths should serialize the renderer's output into the response head\n * directly (with the nonce wired through framework-specific head\n * APIs), then let the client take over on hydration.\n */\nexport function applyColorPaletteCss(\n css: string,\n doc: Document | undefined = globalThis.document,\n nonce?: string,\n): void {\n if (!doc) return;\n\n let style = doc.getElementById(COLOR_PALETTE_STYLE_ELEMENT_ID) as HTMLStyleElement | null;\n if (!style) {\n style = doc.createElement('style');\n style.id = COLOR_PALETTE_STYLE_ELEMENT_ID;\n if (nonce) style.setAttribute('nonce', nonce);\n doc.head.appendChild(style);\n } else if (nonce) {\n if (style.getAttribute('nonce') !== nonce) {\n style.setAttribute('nonce', nonce);\n }\n } else if (style.hasAttribute('nonce')) {\n style.removeAttribute('nonce');\n }\n style.textContent = css;\n}\n","import { computed, watch } from 'vue';\nimport type { Ref } from 'vue';\nimport { applyColorPaletteCss } from './apply';\nimport type { BindColorPaletteOptions, UseColorPaletteReturn } from './types';\n\n/**\n * Wire any reactive `Ref<T>` into the palette runtime: render the\n * current value via the theme-supplied `render` function, apply it via\n * `applyColorPaletteCss`, and re-apply on every change.\n *\n * Generic: each theme defines its own palette shape `T` and renderer.\n * `@vuecs/theme-tailwind` wraps this with its `ColorPaletteConfig` and\n * `renderColorPaletteStyles`; community themes can do the same with their\n * own shapes — including custom merge semantics via `options.extend`.\n */\nexport function bindColorPalette<T>(\n source: Ref<T>,\n options: BindColorPaletteOptions<T>,\n): UseColorPaletteReturn<T> {\n const {\n render,\n extend,\n document = globalThis.document,\n nonce,\n } = options;\n\n const resolveNonce: () => string | undefined = typeof nonce === 'function' ?\n nonce :\n () => nonce;\n\n if (document) {\n applyColorPaletteCss(render(source.value), document, resolveNonce());\n }\n /*\n * Watch both the palette source AND the resolved nonce. The nonce\n * getter form (e.g. `() => useConfig('nonce').value`) reads a\n * reactive ref, so a nonce-only rotation (CSP policy update via\n * `setConfig({ nonce })`) re-applies the `<style>` element's\n * attribute without needing a palette mutation. Static nonce\n * strings return the same primitive on every call → the nonce\n * lane of the watcher is silently inert.\n */\n watch(\n [source, () => resolveNonce()] as const,\n () => {\n applyColorPaletteCss(render(source.value), document, resolveNonce());\n },\n { deep: true },\n );\n\n return {\n current: computed(() => source.value),\n set(palette) {\n source.value = palette;\n },\n extend(partial) {\n source.value = extend(source.value, partial);\n },\n };\n}\n","/**\n * Canonical palette catalog for the vuecs design system.\n *\n * The names originated in Tailwind v4 (see `@vuecs/design/standalone`'s\n * `palettes.css`, generated from `tailwindcss/theme.css`), but the\n * catalog is now considered design-owned: every supported palette\n * source — Tailwind via `@import \"tailwindcss\"`, or the\n * standalone subpath — provides the matching `--color-<palette>-<shade>`\n * literals so `setColorPalette()` resolves correctly regardless of\n * whether Tailwind is loaded.\n *\n * Themes typically reuse this catalog verbatim (theme-tailwind and\n * theme-bulma both declare `palette.names: COLOR_PALETTES`). A theme\n * with extra palette names just defines its own local union:\n *\n * type AcmePaletteName = ColorPaletteName | 'acme-blue';\n *\n * — and ships its own `palette.names` array.\n */\n\n/**\n * The six semantic scales every vuecs theme exposes through the\n * `--vc-color-<scale>-*` variable family. A `setColorPalette({\n * primary: 'green' })` call binds one scale to one palette catalog\n * entry at runtime.\n */\nexport const SEMANTIC_SCALES = [\n 'primary',\n 'neutral',\n 'success',\n 'warning',\n 'error',\n 'info',\n] as const;\n\nexport type SemanticScaleName = typeof SEMANTIC_SCALES[number];\n\n/**\n * The 22 catalog palettes shipped with `@vuecs/design` (sourced\n * verbatim from Tailwind v4). Any of these can be assigned to a\n * `SemanticScaleName` via `setColorPalette()`.\n */\nexport const COLOR_PALETTES = [\n 'slate',\n 'gray',\n 'zinc',\n 'neutral',\n 'stone',\n 'red',\n 'orange',\n 'amber',\n 'yellow',\n 'lime',\n 'green',\n 'emerald',\n 'teal',\n 'cyan',\n 'sky',\n 'blue',\n 'indigo',\n 'violet',\n 'purple',\n 'fuchsia',\n 'pink',\n 'rose',\n] as const;\n\n/**\n * Augmentation hook for community themes that extend the catalog with\n * their own palette names. Defaults to empty — `ColorPaletteName` is just\n * the 22-name Tailwind-derived catalog. A theme that ships extra palettes\n * widens the union via declaration merging:\n *\n * declare module '@vuecs/design' {\n * interface ExtraColorPaletteNames {\n * 'acme-blue': true;\n * 'acme-orange': true;\n * }\n * }\n *\n * Both the SPA composables (`@vuecs/theme-tailwind`'s `useColorPalette`,\n * etc.) and `@vuecs/nuxt`'s `colorPalette.value` option pick up the\n * extension automatically because both type against `ColorPaletteName`.\n */\nexport interface ExtraColorPaletteNames {}\n\nexport type ColorPaletteName = typeof COLOR_PALETTES[number] | keyof ExtraColorPaletteNames;\n\n/**\n * Tailwind-style 11-stop shade ladder. The same ladder appears in\n * every catalog entry (the standalone subpath's `palettes.css` and\n * Tailwind's own `theme.css` both emit `--color-<palette>-<shade>`\n * for each stop).\n */\nexport const COLOR_PALETTE_SHADES = [\n '50',\n '100',\n '200',\n '300',\n '400',\n '500',\n '600',\n '700',\n '800',\n '900',\n '950',\n] as const;\n\nexport type ColorPaletteShade = typeof COLOR_PALETTE_SHADES[number];\n\n/**\n * Canonical runtime palette config — a partial mapping of every\n * semantic scale to a catalog palette name. Used by every theme that\n * opts into runtime palette switching (`theme-tailwind`, `theme-bulma`,\n * and any future palette-aware theme).\n *\n * Both keys (`SemanticScaleName`) and values (`ColorPaletteName`)\n * widen automatically via declaration merging: `ExtraColorPaletteNames`\n * adds value-side names; the canonical scale list is fixed at six.\n *\n * Themes whose internal scale names diverge from the canonical six\n * declare a `scaleAliases` map on their `Theme.palette` slot — the\n * dispatcher translates input keys before calling `palette.handle`,\n * so the public-facing config still uses canonical names.\n */\nexport type ColorPaletteConfig = Partial<Record<SemanticScaleName, ColorPaletteName>>;\n","/*\n * Local mirror of `@vuecs/core`'s `isObject` helper. Duplicated (not\n * imported from core) so `@vuecs/design` keeps Layer-0 standing — no\n * internal runtime deps, works standalone with BS / Bulma / no theme.\n *\n * Keep this in sync with `packages/core/src/utils/object.ts` if the\n * semantics ever change.\n */\nexport function isObject(input: unknown): input is Record<string, unknown> {\n return typeof input === 'object' &&\n input !== null &&\n !Array.isArray(input);\n}\n","import type { ThemeRuntimeEntry } from '../theme-runtime/types';\n\n/**\n * Concatenate every installed theme's `palette.handle` output into a\n * single CSS string. SSR plugins emit the result as the\n * `<style id=\"vc-color-palette\">` block so palette-aware themes flow\n * on first paint.\n *\n * Mirrors the client-side concat semantics in `useColorPalette()`:\n * non-overlapping rules from different themes coexist; CSS cascade\n * resolves any incidental overlap with later-rule-wins.\n *\n * Errors thrown by a theme's handler are caught + logged so one\n * broken theme can't crash SSR; other themes still emit.\n */\nexport function renderColorPaletteFromThemes(\n themes: readonly ThemeRuntimeEntry[],\n palette: Record<string, string>,\n): string {\n const parts: string[] = [];\n for (const theme of themes) {\n if (!theme.palette?.handle) {\n continue;\n }\n\n // Preserve `this` so themes whose handle method reads from\n // `theme.palette` state (e.g. caches a renderer instance) keep\n // working when the function is extracted before call.\n const handle = theme.palette.handle.bind(theme.palette);\n\n // Apply per-theme scale aliasing (plan 026). Themes whose\n // internal scale names diverge from the canonical six declare\n // a rename map; the dispatcher rewrites input keys so the\n // theme's renderer sees its own naming while the public-facing\n // palette config stays canonical.\n const input = applyScaleAliases(palette, theme.palette.scaleAliases);\n\n try {\n const out = handle(input);\n if (out) {\n parts.push(out);\n }\n } catch (e) {\n if (typeof console !== 'undefined') {\n // eslint-disable-next-line no-console\n console.warn('[vuecs] theme palette.handle failed; skipping:', e);\n }\n }\n }\n return parts.join('\\n');\n}\n\nconst FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\n\nfunction applyScaleAliases(\n palette: Record<string, string>,\n aliases: Record<string, string> | undefined,\n): Record<string, string> {\n if (!aliases) {\n return palette;\n }\n // Use a prototype-free object + explicit deny-list so a theme-provided\n // `scaleAliases` mapping a canonical key to `__proto__` (or any other\n // prototype-touching name) can't reach Object.prototype. Defense in\n // depth: the upstream sanitize already filters input palette keys to\n // SEMANTIC_SCALES, but `aliases` itself is theme-provided and unsanitized.\n const out: Record<string, string> = Object.create(null);\n for (const [k, v] of Object.entries(palette)) {\n const target = aliases[k] ?? k;\n if (FORBIDDEN_KEYS.has(target)) continue;\n out[target] = v;\n }\n return out;\n}\n","import { createSharedComposable, useStorage } from '@vueuse/core';\nimport type { ComputedRef, Ref } from 'vue';\nimport { computed, ref, watchEffect } from 'vue';\nimport { isObject } from '../../utils/object';\nimport { useThemeRuntimeManager } from '../theme-runtime/composable';\nimport { applyColorPaletteCss } from './apply';\nimport { COLOR_PALETTES, SEMANTIC_SCALES } from './catalog';\nimport { renderColorPaletteFromThemes } from './render';\nimport type { UseColorPaletteOptions, UseColorPaletteReturn } from './types';\n\nconst DEFAULT_STORAGE_KEY = 'vc-color-palette';\n\nconst SEMANTIC_SCALE_SET = new Set<string>(SEMANTIC_SCALES);\nconst PALETTE_NAME_SET = new Set<string>(COLOR_PALETTES);\n\nfunction defaultSanitize<T>(value: unknown): T {\n /*\n * Defensive default: reject primitives + arrays, then filter to the\n * canonical catalog (six semantic scales × 22 palette names). Cookies\n * and localStorage can hold anything (older library version,\n * hand-edited DevTools value, payload written under the same key by\n * a different theme). Dropping unknown keys prevents broken CSS\n * downstream — the renderers each defend their inputs as a second\n * line of defense, but applying the filter at the dispatcher means\n * theme `palette.handle` hooks never see junk on the happy path.\n *\n * Themes whose palette config widens past the canonical catalog\n * (`ExtraColorPaletteNames` or a divergent scale set via\n * `scaleAliases`) pass their own `sanitize` to override.\n */\n if (!isObject(value)) {\n return {} as T;\n }\n\n const out: Record<string, string> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (\n SEMANTIC_SCALE_SET.has(k) &&\n typeof v === 'string' &&\n PALETTE_NAME_SET.has(v)\n ) {\n out[k] = v;\n }\n }\n return out as T;\n}\n\nconst shallowMerge = <T extends Record<string, unknown>>(current: T, partial: Partial<T>): T => ({\n ...current,\n ...partial,\n} as T);\n\n/**\n * Theme-aware reactive palette state — un-shared variant.\n *\n * Concatenates every installed theme's `palette.handle` output into the\n * `<style id=\"vc-color-palette\">` element. Walking the installed themes\n * each render means runtime theme swaps via `setThemes()` automatically\n * pick up the new renderer chain.\n *\n * Concat (rather than last-wins) is the doctrinal semantic: when an app\n * stacks multiple palette-aware themes (the docs-site case where\n * Tailwind components and Bulma components share the same picker UI),\n * each theme's renderer emits its own non-overlapping CSS rules —\n * Tailwind rebinds `--vc-color-*`, Bulma writes per-variant HSL channel\n * vars. The CSS cascade resolves any incidental overlap with\n * later-rule-wins semantics, so concat behaves like last-wins for\n * overlapping properties AND emits both themes' unique properties.\n *\n * Production callers should use `useColorPalette` (the shared variant\n * below). This un-shared form is exposed primarily for testing — every\n * call creates a fresh `watchEffect` and palette state.\n */\nexport function useColorPaletteUnshared<\n T extends Record<string, unknown> = Record<string, string>,\n>(options: UseColorPaletteOptions<T> = {}): UseColorPaletteReturn<T> {\n const {\n initial = {} as T,\n source,\n persist = true,\n storageKey = DEFAULT_STORAGE_KEY,\n sanitize = defaultSanitize<T>,\n extend = shallowMerge,\n nonce,\n } = options;\n\n const resolveNonce: () => string | undefined = typeof nonce === 'function' ?\n nonce :\n () => nonce;\n\n const manager = useThemeRuntimeManager();\n\n /*\n * `source` lets external persistence layers (Nuxt's `useCookie`,\n * custom IndexedDB-backed refs, etc.) replace the default storage\n * without forking the dispatch logic. When provided, `persist` /\n * `storageKey` / `initial` are ignored — the caller is responsible\n * for the initial value and any persistence semantics.\n */\n const storage: Ref<T> = source ??\n (persist ?\n useStorage<T>(storageKey, sanitize(initial), undefined, {\n serializer: {\n read: (raw): T => {\n try {\n return sanitize(JSON.parse(raw));\n } catch {\n return sanitize({});\n }\n },\n write: (value) => JSON.stringify(value),\n },\n }) :\n ref<T>(sanitize(initial)) as Ref<T>);\n\n const renderConcatenated = (palette: T): string => {\n const themes = manager?.themes;\n if (!themes || themes.length === 0) {\n return '';\n }\n\n /*\n * Sanitize at the render boundary so the `source`-provided path\n * (Nuxt cookie, custom IndexedDB, etc.) gets the same defensive\n * filter as the default `useStorage` path (which sanitizes at\n * `serializer.read` time). Theme `palette.handle` hooks should\n * never see primitives, arrays, or other malformed payloads —\n * downstream theme renderers each filter their own input, but\n * applying `sanitize` here keeps the per-theme filter as a\n * second line of defense rather than the only one.\n */\n const sanitized = sanitize(palette);\n return renderColorPaletteFromThemes(themes, sanitized as Record<string, string>);\n };\n\n if (typeof document !== 'undefined') {\n /*\n * Reactive on both storage AND theme swaps: reading\n * `manager.themes` (a shallowRef-backed getter) inside the\n * effect subscribes to `setThemes()`. Reading `storage.value`\n * subscribes to palette changes. Either trigger re-renders the\n * `<style>` block.\n */\n watchEffect(() => {\n applyColorPaletteCss(renderConcatenated(storage.value), undefined, resolveNonce());\n });\n }\n\n return {\n current: computed(() => storage.value) as ComputedRef<T>,\n set(palette) {\n storage.value = palette;\n },\n extend(partial) {\n storage.value = extend(storage.value, partial);\n },\n };\n}\n\n/**\n * Theme-aware reactive palette state with localStorage persistence\n * (plan 021 slice 2).\n *\n * Wrapped with `createSharedComposable` so every call site shares the\n * same ref + watcher. For SSR-aware cookie-backed storage (Nuxt), the\n * matching Nuxt module ships its own composable that calls\n * `bindColorPalette()` directly with a cookie-backed ref.\n */\nexport const useColorPalette = createSharedComposable(useColorPaletteUnshared);\n","import type { ThemeRuntimeEntry } from './types';\n\n/*\n * SSR capture utilities (plan 021 slice 3).\n *\n * On the client, `bindColorMode` and `useColorPalette` walk installed\n * themes and dispatch through their hooks against the live `document`.\n * On the server there is no `document` — but Nuxt SSR plugins still\n * need to flow the same theme-specific markers (`data-bs-theme` /\n * `data-theme` for color mode, `<style id=\"vc-color-palette\">` for\n * palette) into the rendered head before first paint.\n *\n * The pattern: run themes' `colorMode.handle` against a synthetic\n * Document-like object that captures attribute mutations into a plain\n * record. The Nuxt plugin then plumbs the captured record into\n * `useHead({ htmlAttrs })`.\n *\n * Themes that need DOM operations beyond `setAttribute` /\n * `removeAttribute` / `classList` (e.g. inserting child nodes) are not\n * SSR-friendly via this helper; their CSR-only logic should guard with\n * `if (typeof window === 'undefined') return;`.\n */\n\n/**\n * Build a synthetic Document-like whose `documentElement` records\n * `setAttribute` / `removeAttribute` calls into the supplied target\n * record, plus a no-op `classList`. **No other Document or Element\n * APIs are stubbed** — themes that call `doc.createElement`,\n * `doc.head.appendChild`, etc. WILL throw at SSR runtime. Themes\n * needing richer DOM access from `colorMode.handle` should guard their\n * CSR-only logic with `if (typeof window === 'undefined') return;`\n * and split the SSR-flowing bits into declarative `setAttribute` calls.\n *\n * Exposed for advanced consumers; the higher-level\n * `captureColorModeAttrs()` covers the common case and catches errors\n * per theme so a single broken theme can't crash SSR.\n */\nexport function createCaptureDocument(target: Record<string, string>): Document {\n const noopClassList = {\n add() {},\n remove() {},\n toggle(): boolean {\n return false;\n },\n contains(): boolean {\n return false;\n },\n replace() {},\n };\n\n const documentElement = {\n setAttribute(name: string, value: string): void {\n target[name] = value;\n },\n removeAttribute(name: string): void {\n delete target[name];\n },\n classList: noopClassList,\n };\n\n return { documentElement } as unknown as Document;\n}\n\n/**\n * Walk every installed theme's `colorMode.handle` against a synthetic\n * Document and capture the resulting attribute mutations. SSR plugins\n * use this to flow `data-bs-theme` / `data-theme` (or any other\n * attribute a theme declares) into `useHead({ htmlAttrs })` before\n * first paint.\n *\n * Each theme's handler runs in install order. If multiple themes set\n * the same attribute, the last one wins — same semantic as the live\n * `document.documentElement.setAttribute` chain on the client.\n *\n * Errors thrown by a theme's handler are caught + logged as a warning\n * so one malformed theme can't crash SSR; other themes still run.\n */\nexport function captureColorModeAttrs(\n themes: readonly ThemeRuntimeEntry[],\n mode: 'light' | 'dark',\n): Record<string, string> {\n /*\n * Use a prototype-free record: attribute names come from theme\n * code (potentially third-party), and a key like `__proto__` could\n * theoretically poison the result if it later gets merged into\n * another object via `Object.assign` (which uses `[[Set]]` and\n * triggers the prototype setter). `Object.create(null)` avoids the\n * concern entirely without any functional downside.\n */\n const attrs: Record<string, string> = Object.create(null) as Record<string, string>;\n const fakeDoc = createCaptureDocument(attrs);\n\n for (const theme of themes) {\n if (!theme.colorMode?.handle) {\n continue;\n }\n\n const handle = theme.colorMode?.handle.bind(theme.colorMode);\n\n try {\n handle(fakeDoc, mode);\n } catch (e) {\n if (typeof console !== 'undefined') {\n // eslint-disable-next-line no-console\n console.warn('[vuecs] theme colorMode.handle failed; skipping:', e);\n }\n }\n }\n\n return attrs;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"object.d.ts","sourceRoot":"","sources":["../../src/utils/object.ts"],"names":[],"mappings":"AAQA,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAIzE"}
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vuecs/design",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Design tokens (CSS variables) and runtime palette switcher for vuecs components.",
|
|
6
|
+
"exports": {
|
|
7
|
+
"./package.json": "./package.json",
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"style": "./assets/index.css",
|
|
11
|
+
"import": "./dist/index.mjs"
|
|
12
|
+
},
|
|
13
|
+
"./index.css": "./assets/index.css",
|
|
14
|
+
"./assets/index.css": "./assets/index.css",
|
|
15
|
+
"./animations.css": "./assets/animations.css",
|
|
16
|
+
"./assets/animations.css": "./assets/animations.css",
|
|
17
|
+
"./palettes.css": "./assets/palettes.css",
|
|
18
|
+
"./assets/palettes.css": "./assets/palettes.css",
|
|
19
|
+
"./standalone": {
|
|
20
|
+
"style": "./assets/standalone.css",
|
|
21
|
+
"default": "./assets/standalone.css"
|
|
22
|
+
},
|
|
23
|
+
"./standalone.css": "./assets/standalone.css",
|
|
24
|
+
"./assets/standalone.css": "./assets/standalone.css"
|
|
25
|
+
},
|
|
26
|
+
"style": "./assets/index.css",
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"assets"
|
|
30
|
+
],
|
|
31
|
+
"keywords": [
|
|
32
|
+
"vuecs",
|
|
33
|
+
"design",
|
|
34
|
+
"design-system",
|
|
35
|
+
"design-tokens",
|
|
36
|
+
"css-variables",
|
|
37
|
+
"theme",
|
|
38
|
+
"tailwind",
|
|
39
|
+
"palette"
|
|
40
|
+
],
|
|
41
|
+
"author": {
|
|
42
|
+
"name": "Peter Placzek",
|
|
43
|
+
"email": "contact@tada5hi.net",
|
|
44
|
+
"url": "https://tada5hi.net"
|
|
45
|
+
},
|
|
46
|
+
"license": "Apache-2.0",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "https://github.com/tada5hi/vuecs.git",
|
|
50
|
+
"directory": "packages/design"
|
|
51
|
+
},
|
|
52
|
+
"sideEffects": [
|
|
53
|
+
"./assets/*.css",
|
|
54
|
+
"./dist/*.css"
|
|
55
|
+
],
|
|
56
|
+
"scripts": {
|
|
57
|
+
"build:js": "tsdown",
|
|
58
|
+
"build:types": "tsc --emitDeclarationOnly -p tsconfig.build.json",
|
|
59
|
+
"build": "rimraf dist && npm run build:js && npm run build:types",
|
|
60
|
+
"standalone:build": "tsx scripts/build-standalone.ts",
|
|
61
|
+
"standalone:check": "tsx scripts/build-standalone.ts --check",
|
|
62
|
+
"test": "vitest --config test/vitest.config.ts --run",
|
|
63
|
+
"test:coverage": "vitest --config test/vitest.config.ts --run --coverage"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@vuecs/core": "^3.0.0",
|
|
67
|
+
"@vueuse/core": "^14.3.0",
|
|
68
|
+
"jsdom": "^29.1.1",
|
|
69
|
+
"tailwindcss": "^4.0.0",
|
|
70
|
+
"tsx": "^4.22.0",
|
|
71
|
+
"vue": "^3.5.34"
|
|
72
|
+
},
|
|
73
|
+
"peerDependencies": {
|
|
74
|
+
"@vueuse/core": "^13.0.0 || ^14.0.0",
|
|
75
|
+
"vue": "^3.x"
|
|
76
|
+
},
|
|
77
|
+
"engines": {
|
|
78
|
+
"node": ">=22.0.0"
|
|
79
|
+
},
|
|
80
|
+
"publishConfig": {
|
|
81
|
+
"access": "public"
|
|
82
|
+
}
|
|
83
|
+
}
|