@cueplusplus/ui 0.12.0 → 0.14.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/CHANGELOG.md +305 -0
- package/LICENSE +21 -0
- package/NOTICE.md +34 -0
- package/README.md +2 -19
- package/dist/agent-runtime/tool-call-card.js +2 -2
- package/dist/chat/ask-box.js +2 -2
- package/dist/chat/composer.js +1 -1
- package/dist/chat/message.js +1 -1
- package/dist/chrome/_band.js +54 -0
- package/dist/chrome/band.d.ts +106 -0
- package/dist/chrome/band.js +97 -0
- package/dist/chrome/empty-state.js +1 -1
- package/dist/chrome/footer.d.ts +13 -0
- package/dist/chrome/footer.js +17 -2
- package/dist/chrome/index.d.ts +2 -1
- package/dist/chrome/index.js +2 -1
- package/dist/chrome/navigation-menu.js +1 -1
- package/dist/chrome/page-shell.js +4 -4
- package/dist/configurator/export-dialog.js +1 -1
- package/dist/configurator/panel-sections.js +1 -1
- package/dist/elements/agent-card.js +1 -1
- package/dist/elements/agent-handoff.js +2 -2
- package/dist/elements/canvas-split.js +2 -2
- package/dist/elements/chat-panel.js +1 -1
- package/dist/elements/code-diff.js +1 -1
- package/dist/elements/code-runner.js +2 -2
- package/dist/elements/comparison-card.js +1 -1
- package/dist/elements/confidence-marker.js +1 -1
- package/dist/elements/conversation-search.js +1 -1
- package/dist/elements/day-separator.js +1 -1
- package/dist/elements/document-reference.js +1 -1
- package/dist/elements/edit-message.js +1 -1
- package/dist/elements/elicitation-form.js +1 -1
- package/dist/elements/guardrail-notice.js +1 -1
- package/dist/elements/image-generation.js +1 -1
- package/dist/elements/inline-citation.js +2 -2
- package/dist/elements/markdown.js +1 -1
- package/dist/elements/math-block.js +1 -1
- package/dist/elements/message-branches.js +1 -1
- package/dist/elements/message-pair.js +1 -1
- package/dist/elements/onboarding.js +2 -2
- package/dist/elements/prompt-library.js +1 -1
- package/dist/elements/quote-reply.js +2 -2
- package/dist/elements/read-aloud.js +1 -1
- package/dist/elements/reasoning-panel.js +1 -1
- package/dist/elements/recommendation-card.js +1 -1
- package/dist/elements/research-report.js +1 -1
- package/dist/elements/retrieval-chunks.js +1 -1
- package/dist/elements/reviewable-diff.js +1 -1
- package/dist/elements/score-breakdown.js +1 -1
- package/dist/elements/scroll-anchor.js +1 -1
- package/dist/elements/settings-panel.js +1 -1
- package/dist/elements/shared-conversation.js +1 -1
- package/dist/elements/speaker-identity.js +1 -1
- package/dist/elements/stopped-run.js +1 -1
- package/dist/elements/streaming-text.js +1 -1
- package/dist/elements/timeline.js +1 -1
- package/dist/elements/tool-error.js +1 -1
- package/dist/elements/voice-conversation.js +1 -1
- package/dist/forms/textarea.js +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +4 -2
- package/dist/instruments/code-block.js +1 -1
- package/dist/instruments/progress.d.ts +19 -3
- package/dist/instruments/progress.js +19 -1
- package/dist/instruments/terminal-frame.js +1 -1
- package/dist/layout/_collapse.js +1 -1
- package/dist/layout/card.js +1 -1
- package/dist/layout/container.js +3 -3
- package/dist/layout/frames.d.ts +19 -1
- package/dist/layout/frames.js +21 -7
- package/dist/layout/index.d.ts +2 -2
- package/dist/layout/preview.d.ts +29 -9
- package/dist/layout/preview.js +38 -17
- package/dist/overlays/_surface.d.ts +1 -1
- package/dist/overlays/_surface.js +1 -1
- package/dist/overlays/toast.js +1 -1
- package/dist/overlays/tooltip.js +1 -1
- package/dist/primitives/chip.d.ts +1 -1
- package/dist/system/density.d.ts +34 -6
- package/dist/system/density.js +41 -10
- package/dist/system/index.d.ts +2 -1
- package/dist/system/index.js +2 -1
- package/dist/system/portal.d.ts +8 -7
- package/dist/system/portal.js +16 -9
- package/dist/system/prepaint.d.ts +25 -5
- package/dist/system/prepaint.js +10 -2
- package/dist/system/theme-provider.d.ts +86 -7
- package/dist/system/theme-provider.js +62 -20
- package/dist/system/use-theme.d.ts +9 -2
- package/dist/system/use-theme.js +9 -2
- package/dist/system/vocabulary.d.ts +2 -1
- package/dist/system/vocabulary.js +16 -2
- package/manifest/components/accordion.json +1 -0
- package/manifest/components/agent-card.json +1 -0
- package/manifest/components/agent-handoff.json +1 -0
- package/manifest/components/alert-dialog.json +1 -0
- package/manifest/components/ask-box.json +1 -0
- package/manifest/components/autocomplete.json +1 -0
- package/manifest/components/band.json +104 -0
- package/manifest/components/canvas-split-body.json +1 -0
- package/manifest/components/canvas-split-document.json +1 -0
- package/manifest/components/canvas-split-header.json +1 -0
- package/manifest/components/canvas-split-line.json +1 -0
- package/manifest/components/canvas-split-message.json +1 -0
- package/manifest/components/canvas-split-thread.json +1 -0
- package/manifest/components/canvas-split.json +1 -0
- package/manifest/components/card.json +1 -0
- package/manifest/components/channel-matrix.json +1 -0
- package/manifest/components/chart-tooltip-content.json +1 -0
- package/manifest/components/chat-panel-assistant-message.json +1 -0
- package/manifest/components/chat-panel-composer.json +1 -0
- package/manifest/components/chat-panel-messages.json +1 -0
- package/manifest/components/chat-panel-typing.json +1 -0
- package/manifest/components/chat-panel-user-message.json +1 -0
- package/manifest/components/chat-panel.json +1 -0
- package/manifest/components/code-block.json +1 -0
- package/manifest/components/code-diff.json +1 -0
- package/manifest/components/code-runner.json +1 -0
- package/manifest/components/collapsible.json +1 -0
- package/manifest/components/colors-section.json +1 -0
- package/manifest/components/combobox.json +1 -0
- package/manifest/components/command-palette.json +1 -0
- package/manifest/components/comparison-card.json +1 -0
- package/manifest/components/composer.json +1 -0
- package/manifest/components/confidence-marker.json +1 -0
- package/manifest/components/container.json +3 -0
- package/manifest/components/context-menu.json +1 -0
- package/manifest/components/conversation-search.json +1 -0
- package/manifest/components/cue-portal-frame.json +1 -1
- package/manifest/components/day-separator.json +1 -0
- package/manifest/components/density.json +16 -1
- package/manifest/components/dialog.json +1 -0
- package/manifest/components/disclosure.json +1 -0
- package/manifest/components/dmx-bar.json +1 -0
- package/manifest/components/dmx-strip.json +1 -0
- package/manifest/components/document-reference.json +1 -0
- package/manifest/components/drawer.json +1 -0
- package/manifest/components/dropdown-menu.json +1 -0
- package/manifest/components/edit-message.json +1 -0
- package/manifest/components/elements-timeline.json +1 -0
- package/manifest/components/elicitation-form.json +1 -0
- package/manifest/components/empty-state.json +1 -0
- package/manifest/components/export-dialog.json +1 -0
- package/manifest/components/footer.json +3 -2
- package/manifest/components/frac.json +1 -0
- package/manifest/components/frames.json +14 -0
- package/manifest/components/guardrail-notice.json +1 -0
- package/manifest/components/hover-card.json +1 -0
- package/manifest/components/inline-citation.json +1 -0
- package/manifest/components/markdown-text.json +1 -0
- package/manifest/components/math-block.json +1 -0
- package/manifest/components/menubar.json +1 -0
- package/manifest/components/message-branches.json +1 -0
- package/manifest/components/message-pair.json +1 -0
- package/manifest/components/message.json +1 -0
- package/manifest/components/multi-select.json +1 -0
- package/manifest/components/navigation-menu.json +1 -0
- package/manifest/components/onboarding.json +1 -0
- package/manifest/components/page-shell.json +4 -0
- package/manifest/components/popover.json +1 -0
- package/manifest/components/preset-section.json +1 -0
- package/manifest/components/preview.json +17 -2
- package/manifest/components/progress.json +2 -2
- package/manifest/components/prompt-library.json +1 -0
- package/manifest/components/quote-reply.json +1 -0
- package/manifest/components/read-aloud.json +1 -0
- package/manifest/components/reasoning-panel.json +1 -0
- package/manifest/components/recommendation-card.json +1 -0
- package/manifest/components/research-report.json +1 -0
- package/manifest/components/retrieval-chunks.json +1 -0
- package/manifest/components/reviewable-diff.json +1 -0
- package/manifest/components/score-breakdown.json +1 -0
- package/manifest/components/scroll-anchor.json +1 -0
- package/manifest/components/settings-panel.json +1 -0
- package/manifest/components/shape-section.json +1 -0
- package/manifest/components/shared-conversation.json +1 -0
- package/manifest/components/sheet.json +1 -0
- package/manifest/components/speaker-identity.json +1 -0
- package/manifest/components/stopped-run.json +1 -0
- package/manifest/components/streaming-text.json +1 -0
- package/manifest/components/sub.json +1 -0
- package/manifest/components/sup.json +1 -0
- package/manifest/components/tags-input.json +1 -0
- package/manifest/components/terminal-frame.json +1 -0
- package/manifest/components/textarea.json +1 -0
- package/manifest/components/theme-configurator.json +1 -0
- package/manifest/components/theme-provider.json +26 -1
- package/manifest/components/toast.json +1 -0
- package/manifest/components/tool-call-card.json +1 -0
- package/manifest/components/tool-error.json +1 -0
- package/manifest/components/tooltip.json +1 -0
- package/manifest/components/voice-conversation.json +1 -0
- package/manifest/manifest.json +133 -105
- package/manifest/tokens.json +20 -1
- package/package.json +11 -9
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TokenOverrides } from "./overrides.js";
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { StyleSet } from "@cueplusplus/tokens";
|
|
4
|
+
import { Density as Density$1, FontName, Mode, ThemeManifest, ThemeName } from "@cueplusplus/theme-base";
|
|
4
5
|
//#region src/system/theme-provider.d.ts
|
|
5
6
|
/** A mode that has been resolved to an actual palette — `"system"` never survives this far. */
|
|
6
7
|
type ResolvedMode = "dark" | "light";
|
|
@@ -15,21 +16,48 @@ interface ThemeContextValue {
|
|
|
15
16
|
*/
|
|
16
17
|
manifest: ThemeManifest | null;
|
|
17
18
|
/** Density the *provider* holds — a nested `<Density>` island does not change it. */
|
|
18
|
-
density: Density;
|
|
19
|
+
density: Density$1;
|
|
19
20
|
/** Mode as chosen, including the literal `"system"`. */
|
|
20
21
|
mode: Mode;
|
|
21
22
|
/** Active font pairing. */
|
|
22
23
|
font: FontName;
|
|
24
|
+
/**
|
|
25
|
+
* The style set in force, or `null` when nothing has chosen one — which is
|
|
26
|
+
* every application that has not asked for page composition. `null` rather
|
|
27
|
+
* than the default set's name, because the two are different facts: no
|
|
28
|
+
* attribute is stamped, and `console` is the set a subtree stamps to say it is
|
|
29
|
+
* the ordinary thing inside a page that is not.
|
|
30
|
+
*
|
|
31
|
+
* *In force*, not *named here*: a provider that names no set reports the one
|
|
32
|
+
* stamped around it, exactly as the DOM resolves it, so this and the
|
|
33
|
+
* `StyleSetContext` the portal and stage stamps read are one value.
|
|
34
|
+
*/
|
|
35
|
+
styleSet: StyleSet | null;
|
|
23
36
|
/** Mode after resolving `"system"` against `prefers-color-scheme`. */
|
|
24
37
|
resolvedMode: ResolvedMode;
|
|
25
38
|
/** Switch theme and persist the new preference triple. */
|
|
26
39
|
setTheme: (theme: ThemeName) => void;
|
|
27
40
|
/** Switch the provider-level density and persist the new preference triple. */
|
|
28
|
-
setDensity: (density: Density) => void;
|
|
41
|
+
setDensity: (density: Density$1) => void;
|
|
29
42
|
/** Switch mode (`"system"` included) and persist the new preference triple. */
|
|
30
43
|
setMode: (mode: Mode) => void;
|
|
31
44
|
/** Switch the font pairing and persist the new preference. */
|
|
32
45
|
setFont: (font: FontName) => void;
|
|
46
|
+
/**
|
|
47
|
+
* Switch the style set this provider names, and persist the preference.
|
|
48
|
+
*
|
|
49
|
+
* `null` means *this provider names none* — not *no set applies*. The
|
|
50
|
+
* provider stops stamping `data-style`, so a nested one under an outer
|
|
51
|
+
* `editorial` page goes back to inheriting `editorial`, and `styleSet`
|
|
52
|
+
* above reports it, because that is what the DOM resolves to. At the root,
|
|
53
|
+
* where nothing is stamped around it, `null` and "no set" coincide.
|
|
54
|
+
*
|
|
55
|
+
* To positively leave an inherited set rather than stop naming one, pass
|
|
56
|
+
* {@link DEFAULT_STYLE_SET}: it stamps an attribute whose block restates the
|
|
57
|
+
* `:root` defaults token for token, which is the only thing that can
|
|
58
|
+
* out-specify an ancestor's in a cascade.
|
|
59
|
+
*/
|
|
60
|
+
setStyleSet: (next: StyleSet | null) => void;
|
|
33
61
|
}
|
|
34
62
|
/** Provider-owned font-family overrides that detached portals must republish. */
|
|
35
63
|
interface FontFamilies {
|
|
@@ -89,7 +117,7 @@ interface ThemeProviderProps {
|
|
|
89
117
|
* Changing it after mount adopts the new value; dropping it does not — a
|
|
90
118
|
* parent that stops naming a rung is not asking to move back to the theme's.
|
|
91
119
|
*/
|
|
92
|
-
density?: Density;
|
|
120
|
+
density?: Density$1;
|
|
93
121
|
/** Initial mode. Defaults to `"dark"`; `"system"` tracks `prefers-color-scheme`. */
|
|
94
122
|
mode?: Mode;
|
|
95
123
|
/**
|
|
@@ -112,6 +140,30 @@ interface ThemeProviderProps {
|
|
|
112
140
|
* needs nothing from this library but `data-font` on a `<div>`.
|
|
113
141
|
*/
|
|
114
142
|
font?: FontName;
|
|
143
|
+
/**
|
|
144
|
+
* Page composition for this subtree: `console`, `editorial` or `marketing`.
|
|
145
|
+
*
|
|
146
|
+
* **`styleSet`, not `style`.** This interface already has a
|
|
147
|
+
* `style?: React.CSSProperties` a few lines below, applied to the provider
|
|
148
|
+
* root, and a second meaning for that name would be the one collision every
|
|
149
|
+
* consumer of this component would have to think about. The DOM attribute is
|
|
150
|
+
* `data-style`, matching the four public axes — `data-cue-*` is this tree's
|
|
151
|
+
* prefix for *internal* attributes (`data-cue-theme`, `data-cue-fidelity`), and
|
|
152
|
+
* a public axis wearing the internal prefix would be the more confusing of the
|
|
153
|
+
* two mistakes.
|
|
154
|
+
*
|
|
155
|
+
* Unset, **no attribute is stamped at all**, and that is the promise: an
|
|
156
|
+
* application that has never heard of this axis renders the markup it rendered
|
|
157
|
+
* before it existed and stores the preference object it stored before it
|
|
158
|
+
* existed. A style set is opt-in page composition, not a fourth thing every
|
|
159
|
+
* page has.
|
|
160
|
+
*
|
|
161
|
+
* Like the other three axes this is an *initial* value the provider then holds
|
|
162
|
+
* as state, so `setStyleSet` can drive it; a changed prop is adopted after
|
|
163
|
+
* mount. Unlike the font pairing it has an island form, so a nested provider
|
|
164
|
+
* holds its own rather than handing the axis up to the root.
|
|
165
|
+
*/
|
|
166
|
+
styleSet?: StyleSet;
|
|
115
167
|
/** Multiplier on every text token, published as `--cue-font-scale`. Defaults to `1`. */
|
|
116
168
|
fontScale?: number;
|
|
117
169
|
/** Optional app-owned font stacks, published as inline CUE font custom properties. */
|
|
@@ -184,6 +236,29 @@ interface ThemeProviderProps {
|
|
|
184
236
|
storageKey?: string;
|
|
185
237
|
/** Restore and write root preferences in localStorage. Defaults to `true`. */
|
|
186
238
|
persistPreferences?: boolean;
|
|
239
|
+
/**
|
|
240
|
+
* The Content-Security-Policy nonce for the density override layer.
|
|
241
|
+
*
|
|
242
|
+
* When `overrides.densities` is set this provider emits one `<style>`
|
|
243
|
+
* element (see {@link ThemeProviderProps.overrides}). Under a policy of
|
|
244
|
+
* `style-src-elem 'self' 'nonce-<per-request>'` the browser refuses that
|
|
245
|
+
* element on every page load, because an element React renders on the client
|
|
246
|
+
* cannot pick up a server nonce by itself. Pass the request's nonce here and
|
|
247
|
+
* it is set on the element.
|
|
248
|
+
*
|
|
249
|
+
* Nothing else in this library emits a `<style>` element, and
|
|
250
|
+
* `prepaintScript()` returns a *string* the application renders in a
|
|
251
|
+
* `<script>` of its own — so the application already controls that element's
|
|
252
|
+
* nonce, and this prop is the whole of what a strict policy needs from here.
|
|
253
|
+
*
|
|
254
|
+
* **A nonce cannot rescue a style ATTRIBUTE**, and this provider always sets
|
|
255
|
+
* one on its root: `color-scheme` and `--cue-font-scale`, plus the font
|
|
256
|
+
* families and overrides where they are given. CSP has no nonce form for
|
|
257
|
+
* `style-src-attr` — it is `'unsafe-inline'` or `'none'` — so an application
|
|
258
|
+
* serving `style-src-attr 'none'` loses those declarations, silently. That is
|
|
259
|
+
* a policy decision rather than something this prop can change.
|
|
260
|
+
*/
|
|
261
|
+
nonce?: string;
|
|
187
262
|
/** Stamp the single child element instead of rendering a wrapper `<div>`. */
|
|
188
263
|
asChild?: boolean;
|
|
189
264
|
/** Extra classes for the provider root. */
|
|
@@ -200,7 +275,8 @@ interface ThemeProviderProps {
|
|
|
200
275
|
* registry the three axis hooks read.
|
|
201
276
|
*
|
|
202
277
|
* Rendering: `<div data-cue-root data-theme data-density data-mode style={{ colorScheme, --cue-font-scale }}>`
|
|
203
|
-
* (or the single child when `asChild`)
|
|
278
|
+
* (or the single child when `asChild`), plus `data-style` when — and only when —
|
|
279
|
+
* a style set is in force. The `theme`/`density`/`font`/`mode`/`styleSet` props are
|
|
204
280
|
* *initial* values — the provider holds the state so `setTheme` and friends can
|
|
205
281
|
* drive it — but a changed prop is adopted after mount, so a controlling parent
|
|
206
282
|
* still works.
|
|
@@ -211,7 +287,10 @@ interface ThemeProviderProps {
|
|
|
211
287
|
* below.
|
|
212
288
|
*
|
|
213
289
|
* Persistence and the pre-paint contract: setters write
|
|
214
|
-
* `{ theme, density, font, mode }` to `localStorage[storageKey]`,
|
|
290
|
+
* `{ theme, density, font, mode }` to `localStorage[storageKey]`, plus a `style`
|
|
291
|
+
* key holding the style set when there is one — omitted entirely when there is
|
|
292
|
+
* not, so an application that never touches that axis writes the same four-key
|
|
293
|
+
* object it always wrote. That is exactly what
|
|
215
294
|
* {@link prepaintScript} reads to stamp `<html>` before the first paint. The
|
|
216
295
|
* outermost provider keeps `<html>` in sync while the app runs (and restores the
|
|
217
296
|
* previous stamp on unmount) so the page ground, UA scrollbars and form controls
|
|
@@ -229,6 +308,6 @@ interface ThemeProviderProps {
|
|
|
229
308
|
* <App />
|
|
230
309
|
* </ThemeProvider>
|
|
231
310
|
*/
|
|
232
|
-
declare function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityProp, mode: modeProp, font: fontProp, fontScale, fontFamilies, overrides, storageKey, persistPreferences, asChild, className, style: styleProp, children }: ThemeProviderProps): React.JSX.Element;
|
|
311
|
+
declare function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityProp, mode: modeProp, font: fontProp, styleSet: styleSetProp, fontScale, fontFamilies, overrides, storageKey, persistPreferences, nonce, asChild, className, style: styleProp, children }: ThemeProviderProps): React.JSX.Element;
|
|
233
312
|
//#endregion
|
|
234
313
|
export { FontFamilies, ResolvedMode, ThemeContextValue, ThemeProvider, ThemeProviderProps };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/cn.js";
|
|
3
3
|
import { useIsomorphicInsertionEffect, useIsomorphicLayoutEffect } from "./use-isomorphic-layout-effect.js";
|
|
4
|
-
import { AmbientThemeContext, DensityContext } from "./density.js";
|
|
5
|
-
import { buildRegistry, vocabulary, warnOnce } from "./vocabulary.js";
|
|
4
|
+
import { AmbientThemeContext, DensityContext, StyleSetContext } from "./density.js";
|
|
5
|
+
import { buildRegistry, isStyleSet, vocabulary, warnOnce } from "./vocabulary.js";
|
|
6
6
|
import { OverridesContext, inlineOverrides, overridesCss, reportOverrides } from "./overrides.js";
|
|
7
7
|
import { DEFAULT_STORAGE_KEY } from "./prepaint.js";
|
|
8
8
|
import { ThemeRegistryContext } from "./theme-registry.js";
|
|
@@ -115,13 +115,19 @@ function acceptedPreferences(vocab, stored, fallback) {
|
|
|
115
115
|
theme,
|
|
116
116
|
density: vocab.acceptsDensity(theme, stored.density) ? stored.density : vocab.acceptsDensity(theme, fallback.density) ? fallback.density : vocab.preferredDensity(theme),
|
|
117
117
|
font: vocab.acceptsFont(theme, stored.font) ? stored.font : vocab.acceptsFont(theme, fallback.font) ? fallback.font : vocab.preferredFont(theme),
|
|
118
|
-
mode: isOneOf(MODES, stored.mode) ? stored.mode : fallback.mode
|
|
118
|
+
mode: isOneOf(MODES, stored.mode) ? stored.mode : fallback.mode,
|
|
119
|
+
styleSet: isStyleSet(stored.style) ? stored.style : fallback.styleSet
|
|
119
120
|
};
|
|
120
121
|
}
|
|
121
122
|
function writePreferences(storageKey, preferences) {
|
|
122
123
|
if (typeof localStorage === "undefined") return;
|
|
124
|
+
const { styleSet, ...axes } = preferences;
|
|
125
|
+
const payload = styleSet === null ? axes : {
|
|
126
|
+
...axes,
|
|
127
|
+
style: styleSet
|
|
128
|
+
};
|
|
123
129
|
try {
|
|
124
|
-
localStorage.setItem(storageKey, JSON.stringify(
|
|
130
|
+
localStorage.setItem(storageKey, JSON.stringify(payload));
|
|
125
131
|
} catch {}
|
|
126
132
|
}
|
|
127
133
|
/**
|
|
@@ -131,7 +137,8 @@ function writePreferences(storageKey, preferences) {
|
|
|
131
137
|
* registry the three axis hooks read.
|
|
132
138
|
*
|
|
133
139
|
* Rendering: `<div data-cue-root data-theme data-density data-mode style={{ colorScheme, --cue-font-scale }}>`
|
|
134
|
-
* (or the single child when `asChild`)
|
|
140
|
+
* (or the single child when `asChild`), plus `data-style` when — and only when —
|
|
141
|
+
* a style set is in force. The `theme`/`density`/`font`/`mode`/`styleSet` props are
|
|
135
142
|
* *initial* values — the provider holds the state so `setTheme` and friends can
|
|
136
143
|
* drive it — but a changed prop is adopted after mount, so a controlling parent
|
|
137
144
|
* still works.
|
|
@@ -142,7 +149,10 @@ function writePreferences(storageKey, preferences) {
|
|
|
142
149
|
* below.
|
|
143
150
|
*
|
|
144
151
|
* Persistence and the pre-paint contract: setters write
|
|
145
|
-
* `{ theme, density, font, mode }` to `localStorage[storageKey]`,
|
|
152
|
+
* `{ theme, density, font, mode }` to `localStorage[storageKey]`, plus a `style`
|
|
153
|
+
* key holding the style set when there is one — omitted entirely when there is
|
|
154
|
+
* not, so an application that never touches that axis writes the same four-key
|
|
155
|
+
* object it always wrote. That is exactly what
|
|
146
156
|
* {@link prepaintScript} reads to stamp `<html>` before the first paint. The
|
|
147
157
|
* outermost provider keeps `<html>` in sync while the app runs (and restores the
|
|
148
158
|
* previous stamp on unmount) so the page ground, UA scrollbars and form controls
|
|
@@ -160,9 +170,10 @@ function writePreferences(storageKey, preferences) {
|
|
|
160
170
|
* <App />
|
|
161
171
|
* </ThemeProvider>
|
|
162
172
|
*/
|
|
163
|
-
function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityProp, mode: modeProp = DEFAULT_MODE, font: fontProp, fontScale = 1, fontFamilies, overrides, storageKey = DEFAULT_STORAGE_KEY, persistPreferences = true, asChild = false, className, style: styleProp, children }) {
|
|
173
|
+
function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityProp, mode: modeProp = DEFAULT_MODE, font: fontProp, styleSet: styleSetProp, fontScale = 1, fontFamilies, overrides, storageKey = DEFAULT_STORAGE_KEY, persistPreferences = true, nonce, asChild = false, className, style: styleProp, children }) {
|
|
164
174
|
const ambient = React.useContext(ThemeContext);
|
|
165
175
|
const isRoot = ambient === null;
|
|
176
|
+
const outerStyleSet = React.useContext(StyleSetContext);
|
|
166
177
|
const ambientRegistry = React.useContext(ThemeRegistryContext);
|
|
167
178
|
const themes = themesProp ?? ambientRegistry?.themes ?? EMPTY;
|
|
168
179
|
const vocab = React.useMemo(() => vocabulary(themes), [themes]);
|
|
@@ -187,9 +198,11 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
|
|
|
187
198
|
const initialTheme = themeProp ?? (isRoot ? themes[0]?.name : ambient?.theme) ?? DEFAULT_THEME;
|
|
188
199
|
const initialDensity = densityProp ?? vocab.preferredDensity(initialTheme);
|
|
189
200
|
const initialFont = fontProp ?? vocab.preferredFont(initialTheme);
|
|
201
|
+
const initialStyleSet = styleSetProp ?? (isRoot ? null : outerStyleSet);
|
|
190
202
|
const [theme, setThemeState] = React.useState(initialTheme);
|
|
191
203
|
const [densityState, setDensityState] = React.useState(initialDensity);
|
|
192
204
|
const [mode, setModeState] = React.useState(modeProp);
|
|
205
|
+
const [styleSet, setStyleSetState] = React.useState(initialStyleSet);
|
|
193
206
|
const [storedSeed] = React.useState(() => {
|
|
194
207
|
if (!isRoot || !persistPreferences) return {
|
|
195
208
|
theme: initialTheme,
|
|
@@ -230,13 +243,15 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
|
|
|
230
243
|
theme,
|
|
231
244
|
density,
|
|
232
245
|
font,
|
|
233
|
-
mode
|
|
246
|
+
mode,
|
|
247
|
+
styleSet
|
|
234
248
|
});
|
|
235
249
|
setThemeState(next.theme);
|
|
236
250
|
setDensityState(next.density);
|
|
237
251
|
setRootFontState(next.font);
|
|
238
252
|
setModeState(next.mode);
|
|
239
|
-
|
|
253
|
+
setStyleSetState(next.styleSet);
|
|
254
|
+
if (stored.theme !== void 0 && stored.theme !== next.theme || stored.density !== void 0 && stored.density !== next.density || stored.font !== void 0 && stored.font !== next.font || stored.mode !== void 0 && stored.mode !== next.mode || stored.style !== void 0 && !isStyleSet(stored.style)) writePreferences(storageKey, next);
|
|
240
255
|
}, [
|
|
241
256
|
isRoot,
|
|
242
257
|
persistPreferences,
|
|
@@ -246,7 +261,8 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
|
|
|
246
261
|
theme: themeProp,
|
|
247
262
|
density: densityProp,
|
|
248
263
|
mode: modeProp,
|
|
249
|
-
font: fontProp
|
|
264
|
+
font: fontProp,
|
|
265
|
+
styleSet: styleSetProp
|
|
250
266
|
});
|
|
251
267
|
React.useEffect(() => {
|
|
252
268
|
const previous = lastProps.current;
|
|
@@ -254,17 +270,20 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
|
|
|
254
270
|
theme: themeProp,
|
|
255
271
|
density: densityProp,
|
|
256
272
|
mode: modeProp,
|
|
257
|
-
font: fontProp
|
|
273
|
+
font: fontProp,
|
|
274
|
+
styleSet: styleSetProp
|
|
258
275
|
};
|
|
259
276
|
if (previous.theme !== themeProp && themeProp !== void 0) setThemeState(themeProp);
|
|
260
277
|
if (previous.density !== densityProp && densityProp !== void 0) setDensityState(densityProp);
|
|
261
278
|
if (previous.mode !== modeProp) setModeState(modeProp);
|
|
262
279
|
if (previous.font !== fontProp && fontProp !== void 0) setRootFontState(fontProp);
|
|
280
|
+
if (previous.styleSet !== styleSetProp && styleSetProp !== void 0) setStyleSetState(styleSetProp);
|
|
263
281
|
}, [
|
|
264
282
|
themeProp,
|
|
265
283
|
densityProp,
|
|
266
284
|
modeProp,
|
|
267
|
-
fontProp
|
|
285
|
+
fontProp,
|
|
286
|
+
styleSetProp
|
|
268
287
|
]);
|
|
269
288
|
useIsomorphicLayoutEffect(() => {
|
|
270
289
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
@@ -278,14 +297,16 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
|
|
|
278
297
|
theme,
|
|
279
298
|
density: densityState,
|
|
280
299
|
mode,
|
|
281
|
-
font: rawFont
|
|
300
|
+
font: rawFont,
|
|
301
|
+
styleSet
|
|
282
302
|
});
|
|
283
303
|
useIsomorphicLayoutEffect(() => {
|
|
284
304
|
stateRef.current = {
|
|
285
305
|
theme,
|
|
286
306
|
density: densityState,
|
|
287
307
|
mode,
|
|
288
|
-
font: rawFont
|
|
308
|
+
font: rawFont,
|
|
309
|
+
styleSet
|
|
289
310
|
};
|
|
290
311
|
});
|
|
291
312
|
const persist = React.useCallback((patch) => {
|
|
@@ -319,6 +340,10 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
|
|
|
319
340
|
}, [persist]);
|
|
320
341
|
/** Nested providers hand the axis straight up: one document, one pairing. */
|
|
321
342
|
const setFont = isRoot ? rootSetFont : ambient.setFont;
|
|
343
|
+
const setStyleSet = React.useCallback((next) => {
|
|
344
|
+
setStyleSetState(next);
|
|
345
|
+
persist({ styleSet: next });
|
|
346
|
+
}, [persist]);
|
|
322
347
|
const originalStamp = React.useRef(null);
|
|
323
348
|
useIsomorphicInsertionEffect(() => {
|
|
324
349
|
if (!isRoot || typeof document === "undefined") return;
|
|
@@ -327,7 +352,8 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
|
|
|
327
352
|
theme,
|
|
328
353
|
density,
|
|
329
354
|
font,
|
|
330
|
-
mode
|
|
355
|
+
mode,
|
|
356
|
+
styleSet
|
|
331
357
|
};
|
|
332
358
|
const next = restored || !persistPreferences ? current : acceptedPreferences(vocab, readPreferences(storageKey), current);
|
|
333
359
|
originalStamp.current ??= {
|
|
@@ -335,6 +361,7 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
|
|
|
335
361
|
density: root.getAttribute("data-density"),
|
|
336
362
|
font: root.getAttribute("data-font"),
|
|
337
363
|
mode: root.getAttribute("data-mode"),
|
|
364
|
+
style: root.getAttribute("data-style"),
|
|
338
365
|
colorScheme: root.style.colorScheme
|
|
339
366
|
};
|
|
340
367
|
const write = (name, value) => {
|
|
@@ -344,6 +371,9 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
|
|
|
344
371
|
write("data-density", next.density);
|
|
345
372
|
write("data-font", next.font);
|
|
346
373
|
write("data-mode", next.mode);
|
|
374
|
+
if (next.styleSet === null) {
|
|
375
|
+
if (root.hasAttribute("data-style")) root.removeAttribute("data-style");
|
|
376
|
+
} else write("data-style", next.styleSet);
|
|
347
377
|
const scheme = (themes.find((m) => m.name === next.theme) ?? null)?.supportsLight === false ? "dark" : next.mode === "system" ? "light dark" : next.mode;
|
|
348
378
|
if (root.style.colorScheme !== scheme) root.style.colorScheme = scheme;
|
|
349
379
|
}, [
|
|
@@ -355,6 +385,7 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
|
|
|
355
385
|
density,
|
|
356
386
|
font,
|
|
357
387
|
mode,
|
|
388
|
+
styleSet,
|
|
358
389
|
themes,
|
|
359
390
|
vocab
|
|
360
391
|
]);
|
|
@@ -371,30 +402,36 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
|
|
|
371
402
|
restore("data-density", original.density);
|
|
372
403
|
restore("data-font", original.font);
|
|
373
404
|
restore("data-mode", original.mode);
|
|
405
|
+
restore("data-style", original.style);
|
|
374
406
|
root.style.colorScheme = original.colorScheme;
|
|
375
407
|
}, []);
|
|
408
|
+
const effectiveStyleSet = styleSet ?? outerStyleSet;
|
|
376
409
|
const contextValue = React.useMemo(() => ({
|
|
377
410
|
theme,
|
|
378
411
|
manifest,
|
|
379
412
|
density,
|
|
380
413
|
mode,
|
|
381
414
|
font,
|
|
415
|
+
styleSet: effectiveStyleSet,
|
|
382
416
|
resolvedMode,
|
|
383
417
|
setTheme,
|
|
384
418
|
setDensity,
|
|
385
419
|
setMode,
|
|
386
|
-
setFont
|
|
420
|
+
setFont,
|
|
421
|
+
setStyleSet
|
|
387
422
|
}), [
|
|
388
423
|
theme,
|
|
389
424
|
manifest,
|
|
390
425
|
density,
|
|
391
426
|
mode,
|
|
392
427
|
font,
|
|
428
|
+
effectiveStyleSet,
|
|
393
429
|
resolvedMode,
|
|
394
430
|
setTheme,
|
|
395
431
|
setDensity,
|
|
396
432
|
setMode,
|
|
397
|
-
setFont
|
|
433
|
+
setFont,
|
|
434
|
+
setStyleSet
|
|
398
435
|
]);
|
|
399
436
|
/** The axes on offer under the theme that is actually active. */
|
|
400
437
|
const registry = React.useMemo(() => buildRegistry(themes, theme), [themes, theme]);
|
|
@@ -452,7 +489,8 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
|
|
|
452
489
|
"data-cue-root": "",
|
|
453
490
|
"data-theme": theme,
|
|
454
491
|
"data-density": density,
|
|
455
|
-
"data-mode": mode
|
|
492
|
+
"data-mode": mode,
|
|
493
|
+
...styleSet === null ? null : { "data-style": styleSet }
|
|
456
494
|
};
|
|
457
495
|
let root;
|
|
458
496
|
if (asChild) {
|
|
@@ -472,6 +510,7 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
|
|
|
472
510
|
children
|
|
473
511
|
});
|
|
474
512
|
const densityLayer = overridesStyle === "" ? null : /* @__PURE__ */ jsx("style", {
|
|
513
|
+
...nonce === void 0 ? null : { nonce },
|
|
475
514
|
"data-cue-overrides": "",
|
|
476
515
|
children: overridesStyle
|
|
477
516
|
});
|
|
@@ -487,9 +526,12 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
|
|
|
487
526
|
value: effectiveFontFamilies,
|
|
488
527
|
children: /* @__PURE__ */ jsx(OverridesContext.Provider, {
|
|
489
528
|
value: effectiveOverrides,
|
|
490
|
-
children: /* @__PURE__ */
|
|
529
|
+
children: /* @__PURE__ */ jsx(DensityContext.Provider, {
|
|
491
530
|
value: density,
|
|
492
|
-
children:
|
|
531
|
+
children: /* @__PURE__ */ jsxs(StyleSetContext.Provider, {
|
|
532
|
+
value: effectiveStyleSet,
|
|
533
|
+
children: [root, densityLayer]
|
|
534
|
+
})
|
|
493
535
|
})
|
|
494
536
|
})
|
|
495
537
|
})
|
|
@@ -3,8 +3,8 @@ import { ThemeContextValue } from "./theme-provider.js";
|
|
|
3
3
|
/**
|
|
4
4
|
* Read and drive the ambient theme state.
|
|
5
5
|
*
|
|
6
|
-
* Returns `{ theme, manifest, density, mode, font,
|
|
7
|
-
* setDensity, setMode, setFont }`.
|
|
6
|
+
* Returns `{ theme, manifest, density, mode, font, styleSet, resolvedMode,
|
|
7
|
+
* setTheme, setDensity, setMode, setFont, setStyleSet }`.
|
|
8
8
|
* `density` here is the *provider's* level — a nested `<Density>` island does not
|
|
9
9
|
* change it; use `useDensity()` when you want the level that actually applies
|
|
10
10
|
* where you are. `font` is the pairing the provider holds; it has no island, so
|
|
@@ -12,6 +12,13 @@ import { ThemeContextValue } from "./theme-provider.js";
|
|
|
12
12
|
* when that name is not in the provider's `themes` — which is every name in an
|
|
13
13
|
* app that has registered none; `useThemes()` is what was registered.
|
|
14
14
|
*
|
|
15
|
+
* `styleSet` is the page-composition set in force, or `null` when nothing has
|
|
16
|
+
* chosen one — which is every application that has not asked for it. *In
|
|
17
|
+
* force*, not *named here*: a provider that names no set of its own reports the
|
|
18
|
+
* one stamped around it — a `<Density styleSet>` island between it and an outer
|
|
19
|
+
* provider included — exactly as the DOM resolves it, so this and the set a
|
|
20
|
+
* portal or a `<Preview>` stage carries are one value.
|
|
21
|
+
*
|
|
15
22
|
* @throws If called outside a `<ThemeProvider>`. Theme state is never guessed:
|
|
16
23
|
* a missing provider is a wiring bug, not a fallback situation.
|
|
17
24
|
* @example
|
package/dist/system/use-theme.js
CHANGED
|
@@ -5,8 +5,8 @@ import * as React from "react";
|
|
|
5
5
|
/**
|
|
6
6
|
* Read and drive the ambient theme state.
|
|
7
7
|
*
|
|
8
|
-
* Returns `{ theme, manifest, density, mode, font,
|
|
9
|
-
* setDensity, setMode, setFont }`.
|
|
8
|
+
* Returns `{ theme, manifest, density, mode, font, styleSet, resolvedMode,
|
|
9
|
+
* setTheme, setDensity, setMode, setFont, setStyleSet }`.
|
|
10
10
|
* `density` here is the *provider's* level — a nested `<Density>` island does not
|
|
11
11
|
* change it; use `useDensity()` when you want the level that actually applies
|
|
12
12
|
* where you are. `font` is the pairing the provider holds; it has no island, so
|
|
@@ -14,6 +14,13 @@ import * as React from "react";
|
|
|
14
14
|
* when that name is not in the provider's `themes` — which is every name in an
|
|
15
15
|
* app that has registered none; `useThemes()` is what was registered.
|
|
16
16
|
*
|
|
17
|
+
* `styleSet` is the page-composition set in force, or `null` when nothing has
|
|
18
|
+
* chosen one — which is every application that has not asked for it. *In
|
|
19
|
+
* force*, not *named here*: a provider that names no set of its own reports the
|
|
20
|
+
* one stamped around it — a `<Density styleSet>` island between it and an outer
|
|
21
|
+
* provider included — exactly as the DOM resolves it, so this and the set a
|
|
22
|
+
* portal or a `<Preview>` stage carries are one value.
|
|
23
|
+
*
|
|
17
24
|
* @throws If called outside a `<ThemeProvider>`. Theme state is never guessed:
|
|
18
25
|
* a missing provider is a wiring bug, not a fallback situation.
|
|
19
26
|
* @example
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { StyleSet } from "@cueplusplus/tokens";
|
|
2
|
+
import { Density as Density$1, DensityEntry, FontEntry, FontName, ThemeManifest } from "@cueplusplus/theme-base";
|
|
2
3
|
//#region src/system/vocabulary.d.ts
|
|
3
4
|
/** What the three registry hooks read: the manifests, and the axes the ACTIVE theme offers. */
|
|
4
5
|
interface ThemeRegistry {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_DENSITY, DEFAULT_FONT } from "@cueplusplus/tokens";
|
|
1
|
+
import { DEFAULT_DENSITY, DEFAULT_FONT, STYLE_SETS } from "@cueplusplus/tokens";
|
|
2
2
|
import { DENSITIES, FONTS, THEME_NAME_PATTERN, resolveDensities, resolveFonts } from "@cueplusplus/theme-base";
|
|
3
3
|
//#region src/system/vocabulary.ts
|
|
4
4
|
/**
|
|
@@ -68,6 +68,20 @@ function vocabulary(themes) {
|
|
|
68
68
|
supportsLight: (theme) => theme === null ? true : byName.get(theme)?.supportsLight ?? true
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Whether a value is one of the three style sets.
|
|
73
|
+
*
|
|
74
|
+
* A standalone function rather than a member of {@link Vocabulary}, and the
|
|
75
|
+
* asymmetry is the point: every other axis is asked *under a theme*, because a
|
|
76
|
+
* theme may add a rung or a pairing and a name accepted under the wrong theme
|
|
77
|
+
* stamps an attribute no block matches. No theme can add a style set — the axis
|
|
78
|
+
* is emitted by `@cueplusplus/tokens` and no manifest field describes it — so
|
|
79
|
+
* there is nothing per-theme to ask and a per-theme signature would be three
|
|
80
|
+
* parameters pretending to be a rule.
|
|
81
|
+
*/
|
|
82
|
+
function isStyleSet(value) {
|
|
83
|
+
return typeof value === "string" && STYLE_SETS.includes(value);
|
|
84
|
+
}
|
|
71
85
|
/** Derive the context value. Pure; the provider memoises it on `themes` and the active name. */
|
|
72
86
|
function buildRegistry(themes, active) {
|
|
73
87
|
return {
|
|
@@ -108,4 +122,4 @@ function noteOnce(message) {
|
|
|
108
122
|
console.info(`[cue] ${message}`);
|
|
109
123
|
}
|
|
110
124
|
//#endregion
|
|
111
|
-
export { buildRegistry, noteOnce, vocabulary, warnOnce };
|
|
125
|
+
export { buildRegistry, isStyleSet, noteOnce, vocabulary, warnOnce };
|