@cueplusplus/ui 0.8.0 → 0.10.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 +457 -0
- package/README.md +131 -0
- package/dist/chat/message-list.js +2 -1
- package/dist/configurator/_export.d.ts +1 -1
- package/dist/configurator/_export.js +53 -13
- package/dist/configurator/_overrides.d.ts +25 -6
- package/dist/configurator/_overrides.js +30 -17
- package/dist/configurator/configurator.js +8 -3
- package/dist/configurator/panel-sections.js +43 -13
- package/dist/elements/command-palette.js +1 -1
- package/dist/elements/flow-graph.js +2 -2
- package/dist/elements/markdown.js +1 -1
- package/dist/elements/surfaces.js +4 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +5 -2
- package/dist/instruments/meter.d.ts +5 -1
- package/dist/layout/bento.d.ts +144 -0
- package/dist/layout/bento.js +266 -0
- package/dist/layout/index.d.ts +3 -2
- package/dist/layout/index.js +3 -2
- package/dist/midi/piano-keyboard.js +5 -1
- package/dist/primitives/button.d.ts +12 -0
- package/dist/styles.css +23 -1
- package/dist/system/density.d.ts +17 -8
- package/dist/system/density.js +39 -16
- package/dist/system/index.d.ts +5 -2
- package/dist/system/index.js +3 -1
- package/dist/system/overrides.d.ts +80 -0
- package/dist/system/overrides.js +277 -0
- package/dist/system/portal.d.ts +4 -2
- package/dist/system/portal.js +34 -3
- package/dist/system/prepaint.d.ts +58 -7
- package/dist/system/prepaint.js +72 -20
- package/dist/system/theme-provider.d.ts +133 -8
- package/dist/system/theme-provider.js +203 -72
- package/dist/system/theme-registry.d.ts +53 -0
- package/dist/system/theme-registry.js +66 -0
- package/dist/system/use-density.d.ts +13 -5
- package/dist/system/use-density.js +142 -13
- package/dist/system/use-theme.d.ts +5 -3
- package/dist/system/use-theme.js +5 -3
- package/dist/system/vocabulary.d.ts +15 -0
- package/dist/system/vocabulary.js +111 -0
- package/dist/theming/contrast.d.ts +2 -122
- package/dist/theming/contrast.js +2 -194
- package/dist/theming/create-theme.d.ts +37 -11
- package/dist/theming/create-theme.js +54 -17
- package/dist/theming/index.d.ts +3 -4
- package/dist/theming/index.js +3 -4
- package/dist/theming/serialize.d.ts +24 -11
- package/dist/theming/serialize.js +16 -18
- package/manifest/components/accordion.json +24 -6
- package/manifest/components/activity-graph.json +20 -5
- package/manifest/components/agent-card.json +37 -9
- package/manifest/components/agent-handoff.json +21 -5
- package/manifest/components/agent-mode-badge.json +22 -4
- package/manifest/components/agent-pile.json +38 -8
- package/manifest/components/agent-plan.json +9 -2
- package/manifest/components/agent-status.json +17 -3
- package/manifest/components/agent-surface.json +26 -11
- package/manifest/components/alert-dialog.json +29 -5
- package/manifest/components/animated-number.json +14 -6
- package/manifest/components/app-bar.json +66 -13
- package/manifest/components/app-shell.json +133 -31
- package/manifest/components/app-window-frame.json +28 -6
- package/manifest/components/approval-card.json +37 -7
- package/manifest/components/artifact-card.json +16 -4
- package/manifest/components/ask-box.json +99 -21
- package/manifest/components/audience-icon.json +1 -7
- package/manifest/components/autocomplete.json +80 -18
- package/manifest/components/avatar-group.json +13 -2
- package/manifest/components/avatar.json +22 -4
- package/manifest/components/background-inbox.json +9 -2
- package/manifest/components/bento.json +306 -0
- package/manifest/components/branch-picker.json +29 -7
- package/manifest/components/breadcrumb.json +13 -3
- package/manifest/components/button-group.json +21 -4
- package/manifest/components/button.json +41 -4
- package/manifest/components/canvas-split-body.json +4 -1
- package/manifest/components/canvas-split-header.json +17 -4
- package/manifest/components/canvas-split-line.json +4 -1
- package/manifest/components/canvas-split-message.json +8 -1
- package/manifest/components/card.json +14 -3
- package/manifest/components/carousel.json +54 -12
- package/manifest/components/catalogue-icon.json +1 -7
- package/manifest/components/channel-beta-icon.json +1 -7
- package/manifest/components/channel-matrix.json +80 -15
- package/manifest/components/channel-released-icon.json +1 -7
- package/manifest/components/chart-container.json +26 -10
- package/manifest/components/chart-ramp.json +13 -3
- package/manifest/components/chart-swatch.json +9 -2
- package/manifest/components/chart-tooltip-content.json +31 -7
- package/manifest/components/chart.json +30 -6
- package/manifest/components/chat-panel-composer.json +9 -2
- package/manifest/components/checkbox-group.json +4 -2
- package/manifest/components/checkbox.json +17 -4
- package/manifest/components/checkpoint-history.json +13 -3
- package/manifest/components/chip.json +35 -5
- package/manifest/components/clamp.json +30 -7
- package/manifest/components/cli-tool-icon.json +1 -7
- package/manifest/components/code-diff.json +20 -5
- package/manifest/components/code-runner.json +32 -6
- package/manifest/components/collapsible.json +16 -4
- package/manifest/components/color-area.json +4 -1
- package/manifest/components/color-field.json +13 -3
- package/manifest/components/color-picker.json +75 -17
- package/manifest/components/color-slider.json +12 -3
- package/manifest/components/color-swatch.json +13 -2
- package/manifest/components/colors-section.json +10 -7
- package/manifest/components/combobox.json +81 -18
- package/manifest/components/command-palette.json +57 -13
- package/manifest/components/compaction-row.json +35 -7
- package/manifest/components/comparison-card.json +17 -4
- package/manifest/components/composer-attachment-chip.json +9 -2
- package/manifest/components/composer-bar.json +4 -1
- package/manifest/components/composer-command-item.json +8 -2
- package/manifest/components/composer-context.json +4 -1
- package/manifest/components/composer-input.json +5 -1
- package/manifest/components/composer-menu-item.json +4 -1
- package/manifest/components/composer-menu.json +12 -2
- package/manifest/components/composer-model-item.json +8 -2
- package/manifest/components/composer-model-trigger.json +8 -2
- package/manifest/components/composer-person-item.json +8 -2
- package/manifest/components/composer-send.json +8 -2
- package/manifest/components/composer-voice-button.json +4 -1
- package/manifest/components/composer-voice.json +8 -2
- package/manifest/components/composer.json +76 -17
- package/manifest/components/computer-use.json +17 -4
- package/manifest/components/confidence-marker.json +13 -3
- package/manifest/components/connection-state.json +23 -4
- package/manifest/components/container.json +14 -2
- package/manifest/components/context-breakdown.json +8 -2
- package/manifest/components/context-menu.json +16 -4
- package/manifest/components/context-usage.json +16 -4
- package/manifest/components/conversation-search.json +22 -5
- package/manifest/components/copy-button.json +32 -7
- package/manifest/components/cost-meter.json +12 -3
- package/manifest/components/cue-logotype.json +17 -3
- package/manifest/components/cue-mark.json +17 -3
- package/manifest/components/cue-portal-frame.json +14 -4
- package/manifest/components/data-row.json +21 -7
- package/manifest/components/data-table-pagination.json +18 -4
- package/manifest/components/data-table-toolbar.json +30 -7
- package/manifest/components/data-tree.json +42 -9
- package/manifest/components/date-field.json +13 -2
- package/manifest/components/date-picker.json +81 -19
- package/manifest/components/date-range-picker.json +81 -19
- package/manifest/components/day-separator.json +4 -1
- package/manifest/components/delegation-card.json +64 -12
- package/manifest/components/density.json +25 -5
- package/manifest/components/description-list.json +8 -1
- package/manifest/components/diagram.json +33 -7
- package/manifest/components/dialog.json +29 -6
- package/manifest/components/disclosure.json +31 -7
- package/manifest/components/dmx-bar.json +16 -4
- package/manifest/components/dmx-strip.json +20 -5
- package/manifest/components/document-reference.json +21 -5
- package/manifest/components/draft-restore.json +18 -4
- package/manifest/components/drawer.json +30 -6
- package/manifest/components/dropdown-menu.json +55 -10
- package/manifest/components/edit-message.json +32 -7
- package/manifest/components/elements-command-palette.json +27 -6
- package/manifest/components/elements-data-table.json +9 -2
- package/manifest/components/elements-timeline.json +8 -2
- package/manifest/components/elicitation-form.json +31 -6
- package/manifest/components/empty-state-composer.json +9 -2
- package/manifest/components/empty-state-suggestion.json +4 -1
- package/manifest/components/empty-state.json +25 -5
- package/manifest/components/end-of-turn-summary.json +16 -4
- package/manifest/components/env-var-input.json +54 -12
- package/manifest/components/error-state.json +17 -4
- package/manifest/components/export-dialog.json +27 -11
- package/manifest/components/eyebrow.json +4 -1
- package/manifest/components/feedback-dialog.json +33 -7
- package/manifest/components/field-description.json +4 -1
- package/manifest/components/field-error.json +13 -3
- package/manifest/components/field-label.json +4 -1
- package/manifest/components/field.json +4 -1
- package/manifest/components/file-tree.json +16 -4
- package/manifest/components/file-upload.json +58 -13
- package/manifest/components/flow-graph.json +12 -3
- package/manifest/components/folder-icon.json +1 -7
- package/manifest/components/footer.json +24 -4
- package/manifest/components/frac.json +10 -2
- package/manifest/components/generation-loader.json +17 -3
- package/manifest/components/generative-ui.json +39 -7
- package/manifest/components/grid.json +56 -5
- package/manifest/components/group-bar.json +25 -6
- package/manifest/components/guardrail-notice.json +22 -5
- package/manifest/components/hover-card.json +45 -8
- package/manifest/components/icon-button.json +22 -4
- package/manifest/components/image-generation.json +8 -2
- package/manifest/components/info-tip.json +42 -7
- package/manifest/components/inline-citation.json +14 -3
- package/manifest/components/input-group.json +24 -5
- package/manifest/components/input.json +13 -3
- package/manifest/components/item.json +42 -8
- package/manifest/components/job-progress.json +25 -6
- package/manifest/components/launcher-bubble.json +32 -7
- package/manifest/components/ledger.json +87 -19
- package/manifest/components/link.json +16 -3
- package/manifest/components/live-region-announcer.json +21 -5
- package/manifest/components/log-viewer.json +42 -10
- package/manifest/components/map-answer.json +17 -4
- package/manifest/components/markdown-text.json +36 -5
- package/manifest/components/math-block.json +12 -3
- package/manifest/components/mcp-server-icon.json +1 -7
- package/manifest/components/mcp-server-panel.json +18 -4
- package/manifest/components/memory-chips.json +9 -2
- package/manifest/components/menubar.json +33 -7
- package/manifest/components/message-actions.json +32 -7
- package/manifest/components/message-attachments.json +9 -2
- package/manifest/components/message-branches.json +14 -3
- package/manifest/components/message-list.json +27 -6
- package/manifest/components/message-pair.json +25 -5
- package/manifest/components/message-queue.json +13 -3
- package/manifest/components/message-timing.json +8 -2
- package/manifest/components/message.json +41 -9
- package/manifest/components/meter.json +43 -7
- package/manifest/components/mobile-composer.json +47 -10
- package/manifest/components/model-picker.json +13 -3
- package/manifest/components/multi-select.json +78 -17
- package/manifest/components/musical-time-input.json +34 -8
- package/manifest/components/navigation-menu.json +54 -12
- package/manifest/components/node-card.json +42 -7
- package/manifest/components/node-handle.json +17 -2
- package/manifest/components/number-field.json +28 -6
- package/manifest/components/number-ticker.json +8 -2
- package/manifest/components/onboarding.json +18 -4
- package/manifest/components/otp-field.json +43 -10
- package/manifest/components/page-shell.json +35 -6
- package/manifest/components/pagination.json +21 -5
- package/manifest/components/panel-header.json +15 -3
- package/manifest/components/password-input.json +38 -8
- package/manifest/components/permission-grant.json +28 -5
- package/manifest/components/permission-scopes.json +57 -12
- package/manifest/components/piano-keyboard.json +38 -9
- package/manifest/components/plussie.json +34 -5
- package/manifest/components/popover.json +45 -8
- package/manifest/components/preset-section.json +14 -8
- package/manifest/components/progress.json +25 -6
- package/manifest/components/prompt-library.json +27 -6
- package/manifest/components/queue-dock.json +36 -8
- package/manifest/components/quota-banner.json +25 -6
- package/manifest/components/quote-reply.json +29 -7
- package/manifest/components/radio-group.json +12 -2
- package/manifest/components/radio.json +17 -3
- package/manifest/components/rating.json +60 -13
- package/manifest/components/read-aloud.json +35 -8
- package/manifest/components/reasoning-effort.json +17 -4
- package/manifest/components/reasoning-panel.json +33 -8
- package/manifest/components/recommendation-card.json +35 -10
- package/manifest/components/regenerate-menu.json +22 -5
- package/manifest/components/replay-player.json +29 -7
- package/manifest/components/research-report.json +12 -3
- package/manifest/components/resizable.json +4 -1
- package/manifest/components/retrieval-chunks.json +16 -4
- package/manifest/components/revert-dock.json +61 -14
- package/manifest/components/reviewable-diff.json +23 -5
- package/manifest/components/risk-badge.json +18 -3
- package/manifest/components/row.json +17 -4
- package/manifest/components/schedule-card.json +25 -6
- package/manifest/components/score-breakdown.json +20 -5
- package/manifest/components/scroll-anchor.json +13 -3
- package/manifest/components/scroll-area.json +31 -8
- package/manifest/components/scrollable-tabs-list.json +12 -4
- package/manifest/components/scrub-input.json +84 -18
- package/manifest/components/seam-cell.json +19 -3
- package/manifest/components/seam-grid.json +12 -3
- package/manifest/components/search-input.json +49 -10
- package/manifest/components/section-header.json +36 -8
- package/manifest/components/segmented-control.json +50 -11
- package/manifest/components/select.json +74 -16
- package/manifest/components/separator.json +4 -1
- package/manifest/components/settings-panel.json +41 -9
- package/manifest/components/shape-section.json +10 -7
- package/manifest/components/shared-conversation.json +21 -5
- package/manifest/components/sheet.json +29 -6
- package/manifest/components/shimmer-label.json +4 -2
- package/manifest/components/sidebar.json +60 -13
- package/manifest/components/skill-icon.json +1 -7
- package/manifest/components/slider.json +22 -5
- package/manifest/components/sources.json +17 -4
- package/manifest/components/sparkline.json +17 -4
- package/manifest/components/speaker-identity.json +4 -1
- package/manifest/components/spec-sheet.json +16 -4
- package/manifest/components/spectrum-visualizer.json +41 -10
- package/manifest/components/spinner.json +12 -2
- package/manifest/components/stack-icon.json +1 -7
- package/manifest/components/stack.json +54 -7
- package/manifest/components/stacks-matrix-icon.json +1 -7
- package/manifest/components/stat.json +28 -4
- package/manifest/components/status-bar.json +50 -9
- package/manifest/components/status-dot.json +23 -4
- package/manifest/components/stepper.json +22 -5
- package/manifest/components/stopped-run.json +19 -4
- package/manifest/components/streaming-text.json +12 -3
- package/manifest/components/subagent-list.json +21 -5
- package/manifest/components/suggestions.json +27 -5
- package/manifest/components/swap-label.json +18 -4
- package/manifest/components/switch.json +18 -3
- package/manifest/components/table-scroll-region.json +4 -1
- package/manifest/components/table.json +54 -11
- package/manifest/components/tabs.json +16 -5
- package/manifest/components/tags-input.json +79 -17
- package/manifest/components/tail-status.json +37 -8
- package/manifest/components/terminal-block.json +25 -5
- package/manifest/components/terminal-frame.json +34 -8
- package/manifest/components/textarea.json +8 -3
- package/manifest/components/theme-configurator.json +47 -14
- package/manifest/components/theme-provider.json +97 -18
- package/manifest/components/thinking-indicator.json +8 -2
- package/manifest/components/thread-list.json +13 -3
- package/manifest/components/thread-search.json +22 -5
- package/manifest/components/threshold-rail.json +77 -14
- package/manifest/components/time-boundary.json +8 -2
- package/manifest/components/time-field.json +13 -2
- package/manifest/components/timeline-ruler.json +51 -12
- package/manifest/components/timeline.json +19 -3
- package/manifest/components/title-bar.json +19 -4
- package/manifest/components/toast.json +12 -3
- package/manifest/components/todo-list.json +8 -2
- package/manifest/components/toggle-group.json +33 -7
- package/manifest/components/toggle.json +23 -4
- package/manifest/components/token-editor.json +26 -10
- package/manifest/components/tool-call-card.json +83 -16
- package/manifest/components/tool-call.json +37 -9
- package/manifest/components/tool-error.json +34 -8
- package/manifest/components/tool-group.json +17 -4
- package/manifest/components/tool-timeline.json +37 -9
- package/manifest/components/toolbar.json +37 -8
- package/manifest/components/tooltip.json +41 -7
- package/manifest/components/trace-waterfall.json +12 -3
- package/manifest/components/tree-visibility-toggle.json +18 -3
- package/manifest/components/tree.json +52 -11
- package/manifest/components/turn-footer.json +36 -8
- package/manifest/components/two-step-button.json +46 -9
- package/manifest/components/typing-indicator.json +8 -1
- package/manifest/components/universe-grid.json +37 -10
- package/manifest/components/unread-divider.json +4 -1
- package/manifest/components/usage-chart.json +46 -9
- package/manifest/components/verdict-row.json +20 -5
- package/manifest/components/voice-conversation.json +37 -7
- package/manifest/components/web-preview.json +23 -5
- package/manifest/components/web-search.json +20 -5
- package/manifest/components/work-collapse.json +30 -7
- package/manifest/fixtures.json +106 -0
- package/manifest/manifest.json +449 -310
- package/manifest/tokens.json +122 -12
- package/package.json +16 -6
- package/dist/theming/_presets.d.ts +0 -11
- package/dist/theming/_presets.js +0 -678
package/dist/system/prepaint.js
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { vocabulary } from "./vocabulary.js";
|
|
2
|
+
import { DEFAULT_DENSITY, DEFAULT_FONT } from "@cueplusplus/tokens";
|
|
3
|
+
import { MODES } from "@cueplusplus/theme-base";
|
|
2
4
|
//#region src/system/prepaint.ts
|
|
3
5
|
/**
|
|
4
6
|
* Pre-paint stamping. Server-safe by design: no `"use client"`, no React, no
|
|
5
7
|
* DOM access at module scope — `app/layout.tsx` calls `prepaintScript()` on the
|
|
6
8
|
* server and inlines the result before hydration.
|
|
9
|
+
*
|
|
10
|
+
* That is also why the vocabulary comes from `./vocabulary` and never from
|
|
11
|
+
* `./theme-registry`: the registry's React half carries a `"use client"`
|
|
12
|
+
* directive, and a module reached through a client boundary arrives in a server
|
|
13
|
+
* graph as a client *reference* rather than a function — calling one throws at
|
|
14
|
+
* prerender, in the app's root layout, where nothing has rendered yet.
|
|
15
|
+
* `test/system/server-safe.test.ts` pins the exact set of files this one
|
|
16
|
+
* reaches, so the next import that would break the rule fails a test instead of
|
|
17
|
+
* a deployment.
|
|
7
18
|
*/
|
|
8
19
|
/** localStorage key the ThemeProvider and the pre-paint script share by default. */
|
|
9
20
|
const DEFAULT_STORAGE_KEY = "cue-ui";
|
|
@@ -22,39 +33,80 @@ function prepaintScript(storageKeyOrOptions = DEFAULT_STORAGE_KEY, defaultTheme
|
|
|
22
33
|
defaults: {
|
|
23
34
|
theme: defaultTheme,
|
|
24
35
|
density: defaultDensity,
|
|
25
|
-
mode: "dark"
|
|
36
|
+
mode: "dark",
|
|
37
|
+
font: defaultFont
|
|
26
38
|
},
|
|
27
39
|
readStoredPreferences: true
|
|
28
40
|
} : storageKeyOrOptions;
|
|
29
|
-
if (options === null || typeof options !== "object" || options.defaults === void 0) throw new TypeError("prepaintScript options require a
|
|
41
|
+
if (options === null || typeof options !== "object" || options.defaults === void 0) throw new TypeError("prepaintScript options require a defaults object with theme and mode");
|
|
30
42
|
const defaults = options.defaults;
|
|
31
|
-
if (defaults.theme === void 0 || defaults.
|
|
32
|
-
|
|
33
|
-
if (!
|
|
34
|
-
if (!
|
|
35
|
-
if (
|
|
43
|
+
if (defaults.theme === void 0 || defaults.mode === void 0) throw new TypeError("prepaintScript options require defaults.theme and defaults.mode");
|
|
44
|
+
const vocab = vocabulary(options.themes ?? []);
|
|
45
|
+
if (!vocab.acceptsTheme(defaults.theme)) throw new TypeError(vocab.themes === null ? `prepaintScript defaults.theme ${JSON.stringify(defaults.theme)} is not a valid theme name` : `prepaintScript defaults.theme ${JSON.stringify(defaults.theme)} is not a registered theme`);
|
|
46
|
+
if (defaults.density !== void 0 && !vocab.acceptsDensity(defaults.theme, defaults.density)) throw new TypeError(`prepaintScript defaults.density ${JSON.stringify(defaults.density)} is not offered by theme ${JSON.stringify(defaults.theme)}`);
|
|
47
|
+
if (!MODES.includes(defaults.mode)) throw new TypeError(`prepaintScript defaults.mode ${JSON.stringify(defaults.mode)} is not one of ${MODES.join(", ")}`);
|
|
48
|
+
if (defaults.font !== void 0 && !vocab.acceptsFont(defaults.theme, defaults.font)) throw new TypeError(`prepaintScript defaults.font ${JSON.stringify(defaults.font)} is not offered by theme ${JSON.stringify(defaults.theme)}`);
|
|
36
49
|
if (options.readStoredPreferences !== void 0 && typeof options.readStoredPreferences !== "boolean") throw new TypeError("prepaintScript readStoredPreferences must be boolean");
|
|
37
50
|
const storageKey = options.storageKey ?? "cue-ui";
|
|
38
51
|
if (typeof storageKey !== "string") throw new TypeError("prepaintScript storageKey must be a string");
|
|
39
52
|
const readStoredPreferences = options.readStoredPreferences ?? true;
|
|
40
53
|
const key = jsStringLiteral(storageKey);
|
|
41
54
|
const fallbackTheme = jsStringLiteral(defaults.theme);
|
|
42
|
-
const fallbackDensity = jsStringLiteral(defaults.density);
|
|
55
|
+
const fallbackDensity = jsStringLiteral(defaults.density ?? vocab.preferredDensity(defaults.theme));
|
|
43
56
|
const fallbackMode = jsStringLiteral(defaults.mode);
|
|
44
|
-
const fallbackFont = jsStringLiteral(defaults.font ??
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
57
|
+
const fallbackFont = jsStringLiteral(defaults.font ?? vocab.preferredFont(defaults.theme));
|
|
58
|
+
const baseDensity = jsStringLiteral(DEFAULT_DENSITY);
|
|
59
|
+
const baseFont = jsStringLiteral(DEFAULT_FONT);
|
|
60
|
+
/**
|
|
61
|
+
* The registered names, or `null`.
|
|
62
|
+
*
|
|
63
|
+
* `null` tells the script "any well-formed name", which is the empty-registry
|
|
64
|
+
* rule (see `vocabulary`) and is what the positional overload — every app
|
|
65
|
+
* that has not migrated — gets. Anything else is the exact list the provider
|
|
66
|
+
* will validate against a moment later, inlined so the two cannot disagree.
|
|
67
|
+
*/
|
|
68
|
+
const themes = scriptSafe(JSON.stringify(vocab.themes));
|
|
48
69
|
/**
|
|
49
|
-
* The
|
|
70
|
+
* The other two axes, `supportsLight`, and each theme's own preference, **per theme**.
|
|
50
71
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
72
|
+
* Per theme, because a rung a theme adds is scoped by the cascade to that
|
|
73
|
+
* theme's `data-theme`; accepting it under another theme stamps an attribute
|
|
74
|
+
* no block will match, and the page renders at the rung it fell back from
|
|
75
|
+
* while the picker says otherwise. The keys are short (`d`, `f`, `l`, `p`,
|
|
76
|
+
* `q`) because this is inlined into every document this app serves, once per
|
|
77
|
+
* response, and with ten themes registered the long names would cost more
|
|
78
|
+
* bytes than the whole rest of the script.
|
|
79
|
+
*
|
|
80
|
+
* `p` and `q` are §5's `densities.default` / `fontPairings.default` — the
|
|
81
|
+
* rung and the pairing that theme prefers when nobody named one — read
|
|
82
|
+
* through `vocabulary()`, which is the same function `acceptedPreferences()`
|
|
83
|
+
* asks on the client. Two implementations of one rule, one of them written
|
|
84
|
+
* out as a string of JavaScript, is exactly the asymmetry the round-trip
|
|
85
|
+
* tests exist to catch; inlining the *answers* rather than re-deriving them
|
|
86
|
+
* is what makes the two sides agree by construction.
|
|
87
|
+
*
|
|
88
|
+
* They are omitted when they equal the base default, which is what every one
|
|
89
|
+
* of the ten shipped presets does — none of them declares either. The final
|
|
90
|
+
* clause already ends at the base literal, so an entry that repeats it is
|
|
91
|
+
* bytes in every response that change no answer. `a.p&&…` below is what
|
|
92
|
+
* reads an absent key as "no opinion".
|
|
93
|
+
*
|
|
94
|
+
* With nothing registered there is one entry under the empty key: the base
|
|
95
|
+
* ladder, the base pairings, and a light block, which the blank base has.
|
|
55
96
|
*/
|
|
56
|
-
const
|
|
57
|
-
|
|
97
|
+
const axes = scriptSafe(JSON.stringify(Object.fromEntries((vocab.themes ?? [null]).map((name) => {
|
|
98
|
+
const preferredDensity = vocab.preferredDensity(name);
|
|
99
|
+
const preferredFont = vocab.preferredFont(name);
|
|
100
|
+
return [name ?? "", {
|
|
101
|
+
d: vocab.densitiesFor(name),
|
|
102
|
+
f: vocab.fontsFor(name),
|
|
103
|
+
l: vocab.supportsLight(name),
|
|
104
|
+
...preferredDensity === DEFAULT_DENSITY ? {} : { p: preferredDensity },
|
|
105
|
+
...preferredFont === DEFAULT_FONT ? {} : { q: preferredFont }
|
|
106
|
+
}];
|
|
107
|
+
}))));
|
|
108
|
+
const modes = scriptSafe(JSON.stringify([...MODES]));
|
|
109
|
+
return `!function(){try{var k=${key},e=document.documentElement,s=null;` + (readStoredPreferences ? "try{var r=localStorage.getItem(k);s=r?JSON.parse(r):null}catch(_){}" : "") + `s=s&&typeof s=="object"?s:{};var T=${themes},A=${axes},P=/^[a-z][a-z0-9-]*$/i;var t=T?(T.indexOf(s.theme)>-1?s.theme:${fallbackTheme}):(typeof s.theme=="string"&&P.test(s.theme)?s.theme:${fallbackTheme});var a=A[T?t:""]||A[""]||{d:[],f:[]};var d=a.d.indexOf(s.density)>-1?s.density:(a.d.indexOf(${fallbackDensity})>-1?${fallbackDensity}:(a.p&&a.d.indexOf(a.p)>-1?a.p:${baseDensity}));var f=a.f.indexOf(s.font)>-1?s.font:(a.f.indexOf(${fallbackFont})>-1?${fallbackFont}:(a.q&&a.f.indexOf(a.q)>-1?a.q:${baseFont}));var m=${modes}.indexOf(s.mode)>-1?s.mode:${fallbackMode};e.setAttribute("data-theme",t);e.setAttribute("data-density",d);e.setAttribute("data-font",f);e.setAttribute("data-mode",m);e.style.colorScheme=a.l===false?"dark":(m==="system"?"light dark":m)}catch(_){}}()`;
|
|
58
110
|
}
|
|
59
111
|
//#endregion
|
|
60
112
|
export { DEFAULT_STORAGE_KEY, prepaintScript };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { TokenOverrides } from "./overrides.js";
|
|
1
2
|
import * as React from "react";
|
|
2
|
-
import { Density, FontName, Mode, ThemeName } from "@cueplusplus/
|
|
3
|
+
import { Density, FontName, Mode, ThemeManifest, ThemeName } from "@cueplusplus/theme-base";
|
|
3
4
|
//#region src/system/theme-provider.d.ts
|
|
4
5
|
/** A mode that has been resolved to an actual palette — `"system"` never survives this far. */
|
|
5
6
|
type ResolvedMode = "dark" | "light";
|
|
@@ -7,6 +8,12 @@ type ResolvedMode = "dark" | "light";
|
|
|
7
8
|
interface ThemeContextValue {
|
|
8
9
|
/** Active theme preset. */
|
|
9
10
|
theme: ThemeName;
|
|
11
|
+
/**
|
|
12
|
+
* The active theme's manifest, or `null` when the active name is not
|
|
13
|
+
* registered — which is every name in an app that passes no `themes`, and the
|
|
14
|
+
* one a `setTheme` for an unknown preset leaves behind.
|
|
15
|
+
*/
|
|
16
|
+
manifest: ThemeManifest | null;
|
|
10
17
|
/** Density the *provider* holds — a nested `<Density>` island does not change it. */
|
|
11
18
|
density: Density;
|
|
12
19
|
/** Mode as chosen, including the literal `"system"`. */
|
|
@@ -32,15 +39,66 @@ interface FontFamilies {
|
|
|
32
39
|
mono?: string;
|
|
33
40
|
}
|
|
34
41
|
interface ThemeProviderProps {
|
|
35
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* The registry: an ordered array of theme manifests, the first of which is
|
|
44
|
+
* the default theme.
|
|
45
|
+
*
|
|
46
|
+
* A prop rather than a side effect, so the server render and the client
|
|
47
|
+
* render are handed the same array and nothing depends on which module an app
|
|
48
|
+
* happened to import first. Each entry is a `manifest.json` from a
|
|
49
|
+
* `@cueplusplus/theme-<name>` package; this library never discovers a theme by
|
|
50
|
+
* itself, because which themes a product ships is an application decision.
|
|
51
|
+
*
|
|
52
|
+
* Optional for one release, defaulting to `[]` with a development warning. An
|
|
53
|
+
* app that registers nothing gets the base axes and no registry: any
|
|
54
|
+
* well-formed name is accepted as a theme (so a visitor's stored preference
|
|
55
|
+
* still restores on first paint), density and font validate against the base
|
|
56
|
+
* ladder and pairings, and `useTheme().manifest` is `null`. What it *paints*
|
|
57
|
+
* is a separate question with a separate answer — whichever `[data-theme]`
|
|
58
|
+
* blocks its stylesheets declare, which is the blank base only when none of
|
|
59
|
+
* them declares the stamped name.
|
|
60
|
+
*
|
|
61
|
+
* A nested provider that passes none inherits the ambient registry.
|
|
62
|
+
*
|
|
63
|
+
* One disclosure to be aware of: {@link prepaintScript} inlines the
|
|
64
|
+
* registered **names** — and each theme's `supportsLight` flag, and the names
|
|
65
|
+
* of any rung or pairing it adds — into the blocking script in every page's
|
|
66
|
+
* HTML, because first paint has to judge a stored preference before any
|
|
67
|
+
* module loads. No colours, no geometry, no package names, and only what an
|
|
68
|
+
* app passes here. But an app that registers
|
|
69
|
+
* a per-customer theme is publishing that customer's name to every visitor
|
|
70
|
+
* who views source, so register per-customer themes per response rather than
|
|
71
|
+
* globally.
|
|
72
|
+
*/
|
|
73
|
+
themes?: readonly ThemeManifest[];
|
|
74
|
+
/**
|
|
75
|
+
* Initial theme preset. Defaults to the first registered manifest's name, or
|
|
76
|
+
* `"cue"` with nothing registered. Changing it after mount adopts the new value.
|
|
77
|
+
*/
|
|
36
78
|
theme?: ThemeName;
|
|
37
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* Initial density level.
|
|
81
|
+
*
|
|
82
|
+
* Defaults to the rung the active theme's manifest names in
|
|
83
|
+
* `densities.default`, and to `"compact"` when it names none — spec §5's
|
|
84
|
+
* rule, and the same one `resolve()` applies when no rung is wanted, so a
|
|
85
|
+
* theme opens on the rung it prefers whether it is read from JS or painted
|
|
86
|
+
* by its own stylesheet. Naming a rung here overrides that for every theme:
|
|
87
|
+
* a consumer who named one named it deliberately.
|
|
88
|
+
*
|
|
89
|
+
* Changing it after mount adopts the new value; dropping it does not — a
|
|
90
|
+
* parent that stops naming a rung is not asking to move back to the theme's.
|
|
91
|
+
*/
|
|
38
92
|
density?: Density;
|
|
39
93
|
/** Initial mode. Defaults to `"dark"`; `"system"` tracks `prefers-color-scheme`. */
|
|
40
94
|
mode?: Mode;
|
|
41
95
|
/**
|
|
42
|
-
* Initial font pairing.
|
|
43
|
-
*
|
|
96
|
+
* Initial font pairing.
|
|
97
|
+
*
|
|
98
|
+
* Defaults to the pairing the active theme's manifest names in
|
|
99
|
+
* `fontPairings.default` — §5's rule, the density prop's rule one axis over
|
|
100
|
+
* — and to `"system"` when it names none: the platform's own faces, nothing
|
|
101
|
+
* downloaded, and the theme keeps whatever monospace it authored.
|
|
44
102
|
*
|
|
45
103
|
* A pairing that needs delivering is a set of *names*: this library ships no
|
|
46
104
|
* font files, and a pairing nobody delivers falls through its stack to the
|
|
@@ -58,6 +116,66 @@ interface ThemeProviderProps {
|
|
|
58
116
|
fontScale?: number;
|
|
59
117
|
/** Optional app-owned font stacks, published as inline CUE font custom properties. */
|
|
60
118
|
fontFamilies?: FontFamilies;
|
|
119
|
+
/**
|
|
120
|
+
* Typed token edits layered over the active theme: colours per mode, geometry
|
|
121
|
+
* per rung, and the three font stacks.
|
|
122
|
+
*
|
|
123
|
+
* For the band of edits that sit below "publish a theme package": one accent
|
|
124
|
+
* for a tenant, a stack the app already loads, a rung with two more pixels in
|
|
125
|
+
* a touch build. Anything larger belongs in a `@cueplusplus/theme-<name>`
|
|
126
|
+
* package, where a build measures it; anything smaller than this is a
|
|
127
|
+
* `!important` in a stray stylesheet, which outranks the token layer
|
|
128
|
+
* everywhere at once.
|
|
129
|
+
*
|
|
130
|
+
* **Colours and fonts are inline on this element**, so they inherit down the
|
|
131
|
+
* subtree, beat every stylesheet without `!important`, and are re-stamped
|
|
132
|
+
* onto portal containers — which mount on `<body>` and inherit nothing from
|
|
133
|
+
* here. They do not leak *out* of this provider: a nested provider's colour
|
|
134
|
+
* edit is scoped to its own subtree, and to portals opened from inside it.
|
|
135
|
+
*
|
|
136
|
+
* **Densities are one document-scoped `<style>` element**, because a rung is
|
|
137
|
+
* selected by attribute rather than inherited, and because
|
|
138
|
+
* `useControlHeight()`'s measuring probe hangs off `document.body`, where
|
|
139
|
+
* a subtree-scoped rule would not reach it — leaving the measured height and
|
|
140
|
+
* the painted control disagreeing by exactly the override. The consequence is
|
|
141
|
+
* worth knowing before you nest one: a nested provider's `densities` edit
|
|
142
|
+
* reaches the whole page, exactly as a theme's own rung rules do. Colours and
|
|
143
|
+
* fonts in the same object do not.
|
|
144
|
+
*
|
|
145
|
+
* And it cuts the other way as well, which is the half that surprises: two
|
|
146
|
+
* providers editing the same rung produce two document-scoped rules of equal
|
|
147
|
+
* specificity, so the **later** one in document order wins everywhere — and
|
|
148
|
+
* the later one is the outer provider's, because a nested provider renders
|
|
149
|
+
* inside it. A nested `densities` edit does not merely leak out; an ancestor
|
|
150
|
+
* that edits the same rung overrules it *inside the nested subtree too*. If
|
|
151
|
+
* an island needs geometry of its own, it needs a rung of its own — a name no
|
|
152
|
+
* ancestor is editing — not the same rung with different numbers.
|
|
153
|
+
*
|
|
154
|
+
* Its rules are `[data-density="x"][data-density="x"]` — (0,2,0), tying a
|
|
155
|
+
* theme's own `[data-theme="t"] [data-density="x"]` and winning on source
|
|
156
|
+
* order — so the precedence is: **`overrides`, then the configurator's
|
|
157
|
+
* persisted snapshot, then the theme, then base.**
|
|
158
|
+
*
|
|
159
|
+
* **A font override shadows the pairing for this subtree.** `data-font` is
|
|
160
|
+
* the document's axis and this element deliberately never restates it (see
|
|
161
|
+
* the note by `stamp` below), but `overrides.fonts` writes the resolved
|
|
162
|
+
* `--cue-font-*` properties directly, which is a stronger claim than the
|
|
163
|
+
* attribute and is the point: this is how an app says "this product's face,
|
|
164
|
+
* whatever pairing the visitor picked".
|
|
165
|
+
*
|
|
166
|
+
* In development the resulting palette is measured, and any required contrast
|
|
167
|
+
* pair the edit broke — or made worse — is named on the console.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* <ThemeProvider themes={[cue]} overrides={{
|
|
171
|
+
* colors: { dark: { accent: "#ff8800" }, light: { accent: "#b35900" } },
|
|
172
|
+
* densities: { compact: { "control-md": "1.75rem" } },
|
|
173
|
+
* fonts: { sans: '"Tenant Sans", ui-sans-serif, sans-serif' },
|
|
174
|
+
* }}>
|
|
175
|
+
* <App />
|
|
176
|
+
* </ThemeProvider>
|
|
177
|
+
*/
|
|
178
|
+
overrides?: TokenOverrides;
|
|
61
179
|
/**
|
|
62
180
|
* localStorage key holding `{ theme, density, font, mode }`. Defaults to `"cue-ui"`.
|
|
63
181
|
* Only the outermost provider reads or writes it — a nested provider is an
|
|
@@ -78,7 +196,8 @@ interface ThemeProviderProps {
|
|
|
78
196
|
/**
|
|
79
197
|
* Root of the theme system: stamps `data-theme`, `data-density` and `data-mode`
|
|
80
198
|
* so the token layer resolves, puts `data-font` on `<html>`, publishes
|
|
81
|
-
* `--cue-font-scale`,
|
|
199
|
+
* `--cue-font-scale`, owns the persisted user preference, and publishes the
|
|
200
|
+
* registry the three axis hooks read.
|
|
82
201
|
*
|
|
83
202
|
* Rendering: `<div data-cue-root data-theme data-density data-mode style={{ colorScheme, --cue-font-scale }}>`
|
|
84
203
|
* (or the single child when `asChild`). The `theme`/`density`/`font`/`mode` props are
|
|
@@ -100,10 +219,16 @@ interface ThemeProviderProps {
|
|
|
100
219
|
* font pairing — `useTheme().font` and `setFont` inside one are the root's.
|
|
101
220
|
*
|
|
102
221
|
* @example
|
|
103
|
-
*
|
|
222
|
+
* // `cue` and `terminal` are the `manifest.json` each theme package ships, read
|
|
223
|
+
* // from the `…/manifest.json` subpath of `@cueplusplus/theme-cue` and
|
|
224
|
+
* // `@cueplusplus/theme-terminal`. Written that way round on purpose: a literal
|
|
225
|
+
* // import statement in this comment reads, to every import-graph gate in this
|
|
226
|
+
* // repository, as `ui` depending on a theme package — which is the one thing
|
|
227
|
+
* // it may not do.
|
|
228
|
+
* <ThemeProvider themes={[cue, terminal]} theme="terminal" density="ultra-compact" font="plex" mode="system">
|
|
104
229
|
* <App />
|
|
105
230
|
* </ThemeProvider>
|
|
106
231
|
*/
|
|
107
|
-
declare function ThemeProvider({ theme: themeProp, density: densityProp, mode: modeProp, font: fontProp, fontScale, fontFamilies, storageKey, persistPreferences, asChild, className, style: styleProp, children }: ThemeProviderProps): React.JSX.Element;
|
|
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;
|
|
108
233
|
//#endregion
|
|
109
234
|
export { FontFamilies, ResolvedMode, ThemeContextValue, ThemeProvider, ThemeProviderProps };
|