@orz/ui 0.0.0-stage → 0.1.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 +29 -0
- package/README.md +541 -2
- package/dist/components/avatar.d.ts +14 -0
- package/dist/components/avatar.d.ts.map +1 -0
- package/dist/components/avatar.js +23 -0
- package/dist/components/avatar.js.map +1 -0
- package/dist/components/badge.d.ts +10 -0
- package/dist/components/badge.d.ts.map +1 -0
- package/dist/components/badge.js +26 -0
- package/dist/components/badge.js.map +1 -0
- package/dist/components/button.d.ts +11 -0
- package/dist/components/button.d.ts.map +1 -0
- package/dist/components/button.js +40 -0
- package/dist/components/button.js.map +1 -0
- package/dist/components/card.d.ts +12 -0
- package/dist/components/card.d.ts.map +1 -0
- package/dist/components/card.js +25 -0
- package/dist/components/card.js.map +1 -0
- package/dist/components/color-picker.d.ts +58 -0
- package/dist/components/color-picker.d.ts.map +1 -0
- package/dist/components/color-picker.js +950 -0
- package/dist/components/color-picker.js.map +1 -0
- package/dist/components/confirm-dialog.d.ts +26 -0
- package/dist/components/confirm-dialog.d.ts.map +1 -0
- package/dist/components/confirm-dialog.js +18 -0
- package/dist/components/confirm-dialog.js.map +1 -0
- package/dist/components/dialog.d.ts +26 -0
- package/dist/components/dialog.d.ts.map +1 -0
- package/dist/components/dialog.js +149 -0
- package/dist/components/dialog.js.map +1 -0
- package/dist/components/dropdown-menu.d.ts +30 -0
- package/dist/components/dropdown-menu.d.ts.map +1 -0
- package/dist/components/dropdown-menu.js +100 -0
- package/dist/components/dropdown-menu.js.map +1 -0
- package/dist/components/filter-menu.d.ts +98 -0
- package/dist/components/filter-menu.d.ts.map +1 -0
- package/dist/components/filter-menu.js +745 -0
- package/dist/components/filter-menu.js.map +1 -0
- package/dist/components/highlighted-input.d.ts +40 -0
- package/dist/components/highlighted-input.d.ts.map +1 -0
- package/dist/components/highlighted-input.js +95 -0
- package/dist/components/highlighted-input.js.map +1 -0
- package/dist/components/hover-card.d.ts +7 -0
- package/dist/components/hover-card.d.ts.map +1 -0
- package/dist/components/hover-card.js +22 -0
- package/dist/components/hover-card.js.map +1 -0
- package/dist/components/input.d.ts +4 -0
- package/dist/components/input.d.ts.map +1 -0
- package/dist/components/input.js +7 -0
- package/dist/components/input.js.map +1 -0
- package/dist/components/label.d.ts +5 -0
- package/dist/components/label.d.ts.map +1 -0
- package/dist/components/label.js +8 -0
- package/dist/components/label.js.map +1 -0
- package/dist/components/link-chip.d.ts +67 -0
- package/dist/components/link-chip.d.ts.map +1 -0
- package/dist/components/link-chip.js +71 -0
- package/dist/components/link-chip.js.map +1 -0
- package/dist/components/load-failure.d.ts +24 -0
- package/dist/components/load-failure.d.ts.map +1 -0
- package/dist/components/load-failure.js +27 -0
- package/dist/components/load-failure.js.map +1 -0
- package/dist/components/load-more.d.ts +15 -0
- package/dist/components/load-more.d.ts.map +1 -0
- package/dist/components/load-more.js +25 -0
- package/dist/components/load-more.js.map +1 -0
- package/dist/components/media-controls.d.ts +50 -0
- package/dist/components/media-controls.d.ts.map +1 -0
- package/dist/components/media-controls.js +71 -0
- package/dist/components/media-controls.js.map +1 -0
- package/dist/components/popover.d.ts +9 -0
- package/dist/components/popover.d.ts.map +1 -0
- package/dist/components/popover.js +82 -0
- package/dist/components/popover.js.map +1 -0
- package/dist/components/query-box.d.ts +78 -0
- package/dist/components/query-box.d.ts.map +1 -0
- package/dist/components/query-box.js +332 -0
- package/dist/components/query-box.js.map +1 -0
- package/dist/components/query-highlight.d.ts +14 -0
- package/dist/components/query-highlight.d.ts.map +1 -0
- package/dist/components/query-highlight.js +132 -0
- package/dist/components/query-highlight.js.map +1 -0
- package/dist/components/select.d.ts +19 -0
- package/dist/components/select.d.ts.map +1 -0
- package/dist/components/select.js +38 -0
- package/dist/components/select.js.map +1 -0
- package/dist/components/separator.d.ts +5 -0
- package/dist/components/separator.d.ts.map +1 -0
- package/dist/components/separator.js +9 -0
- package/dist/components/separator.js.map +1 -0
- package/dist/components/sheet.d.ts +16 -0
- package/dist/components/sheet.d.ts.map +1 -0
- package/dist/components/sheet.js +41 -0
- package/dist/components/sheet.js.map +1 -0
- package/dist/components/skeleton.d.ts +3 -0
- package/dist/components/skeleton.d.ts.map +1 -0
- package/dist/components/skeleton.js +7 -0
- package/dist/components/skeleton.js.map +1 -0
- package/dist/components/slider.d.ts +8 -0
- package/dist/components/slider.d.ts.map +1 -0
- package/dist/components/slider.js +14 -0
- package/dist/components/slider.js.map +1 -0
- package/dist/components/sonner.d.ts +4 -0
- package/dist/components/sonner.d.ts.map +1 -0
- package/dist/components/sonner.js +26 -0
- package/dist/components/sonner.js.map +1 -0
- package/dist/components/switch.d.ts +5 -0
- package/dist/components/switch.d.ts.map +1 -0
- package/dist/components/switch.js +8 -0
- package/dist/components/switch.js.map +1 -0
- package/dist/components/table.d.ts +11 -0
- package/dist/components/table.d.ts.map +1 -0
- package/dist/components/table.js +28 -0
- package/dist/components/table.js.map +1 -0
- package/dist/components/tabs.d.ts +8 -0
- package/dist/components/tabs.d.ts.map +1 -0
- package/dist/components/tabs.js +18 -0
- package/dist/components/tabs.js.map +1 -0
- package/dist/components/textarea.d.ts +4 -0
- package/dist/components/textarea.d.ts.map +1 -0
- package/dist/components/textarea.js +7 -0
- package/dist/components/textarea.js.map +1 -0
- package/dist/components/theme-editor/draft.d.ts +235 -0
- package/dist/components/theme-editor/draft.d.ts.map +1 -0
- package/dist/components/theme-editor/draft.js +638 -0
- package/dist/components/theme-editor/draft.js.map +1 -0
- package/dist/components/theme-editor/export-panel.d.ts +14 -0
- package/dist/components/theme-editor/export-panel.d.ts.map +1 -0
- package/dist/components/theme-editor/export-panel.js +39 -0
- package/dist/components/theme-editor/export-panel.js.map +1 -0
- package/dist/components/theme-editor/form.d.ts +69 -0
- package/dist/components/theme-editor/form.d.ts.map +1 -0
- package/dist/components/theme-editor/form.js +350 -0
- package/dist/components/theme-editor/form.js.map +1 -0
- package/dist/components/theme-editor/import-panel.d.ts +21 -0
- package/dist/components/theme-editor/import-panel.d.ts.map +1 -0
- package/dist/components/theme-editor/import-panel.js +117 -0
- package/dist/components/theme-editor/import-panel.js.map +1 -0
- package/dist/components/theme-editor/theme-list.d.ts +23 -0
- package/dist/components/theme-editor/theme-list.d.ts.map +1 -0
- package/dist/components/theme-editor/theme-list.js +42 -0
- package/dist/components/theme-editor/theme-list.js.map +1 -0
- package/dist/components/theme-editor/token-list.d.ts +30 -0
- package/dist/components/theme-editor/token-list.d.ts.map +1 -0
- package/dist/components/theme-editor/token-list.js +124 -0
- package/dist/components/theme-editor/token-list.js.map +1 -0
- package/dist/components/theme-editor/tokens.d.ts +37 -0
- package/dist/components/theme-editor/tokens.d.ts.map +1 -0
- package/dist/components/theme-editor/tokens.js +155 -0
- package/dist/components/theme-editor/tokens.js.map +1 -0
- package/dist/components/theme-editor.d.ts +26 -0
- package/dist/components/theme-editor.d.ts.map +1 -0
- package/dist/components/theme-editor.js +467 -0
- package/dist/components/theme-editor.js.map +1 -0
- package/dist/components/theme-menu.d.ts +28 -0
- package/dist/components/theme-menu.d.ts.map +1 -0
- package/dist/components/theme-menu.js +308 -0
- package/dist/components/theme-menu.js.map +1 -0
- package/dist/components/tooltip.d.ts +11 -0
- package/dist/components/tooltip.d.ts.map +1 -0
- package/dist/components/tooltip.js +18 -0
- package/dist/components/tooltip.js.map +1 -0
- package/dist/components/unsaved-changes-dialog.d.ts +39 -0
- package/dist/components/unsaved-changes-dialog.d.ts.map +1 -0
- package/dist/components/unsaved-changes-dialog.js +46 -0
- package/dist/components/unsaved-changes-dialog.js.map +1 -0
- package/dist/hooks/scroll-insets.d.ts +89 -0
- package/dist/hooks/scroll-insets.d.ts.map +1 -0
- package/dist/hooks/scroll-insets.js +265 -0
- package/dist/hooks/scroll-insets.js.map +1 -0
- package/dist/hooks/use-element-height.d.ts +11 -0
- package/dist/hooks/use-element-height.d.ts.map +1 -0
- package/dist/hooks/use-element-height.js +33 -0
- package/dist/hooks/use-element-height.js.map +1 -0
- package/dist/hooks/use-media-query.d.ts +22 -0
- package/dist/hooks/use-media-query.d.ts.map +1 -0
- package/dist/hooks/use-media-query.js +38 -0
- package/dist/hooks/use-media-query.js.map +1 -0
- package/dist/hooks/use-overlay-scrollbars.d.ts +31 -0
- package/dist/hooks/use-overlay-scrollbars.d.ts.map +1 -0
- package/dist/hooks/use-overlay-scrollbars.js +80 -0
- package/dist/hooks/use-overlay-scrollbars.js.map +1 -0
- package/dist/hooks/use-paged-append.d.ts +7 -0
- package/dist/hooks/use-paged-append.d.ts.map +1 -0
- package/dist/hooks/use-paged-append.js +53 -0
- package/dist/hooks/use-paged-append.js.map +1 -0
- package/dist/hooks/use-query-completion.d.ts +27 -0
- package/dist/hooks/use-query-completion.d.ts.map +1 -0
- package/dist/hooks/use-query-completion.js +100 -0
- package/dist/hooks/use-query-completion.js.map +1 -0
- package/dist/hooks/use-slash-shortcut.d.ts +9 -0
- package/dist/hooks/use-slash-shortcut.d.ts.map +1 -0
- package/dist/hooks/use-slash-shortcut.js +38 -0
- package/dist/hooks/use-slash-shortcut.js.map +1 -0
- package/dist/hooks/viewport-chrome.d.ts +50 -0
- package/dist/hooks/viewport-chrome.d.ts.map +1 -0
- package/dist/hooks/viewport-chrome.js +170 -0
- package/dist/hooks/viewport-chrome.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/cell-width-table.d.ts +3 -0
- package/dist/lib/cell-width-table.d.ts.map +1 -0
- package/dist/lib/cell-width-table.js +6 -0
- package/dist/lib/cell-width-table.js.map +1 -0
- package/dist/lib/cell-width.d.ts +7 -0
- package/dist/lib/cell-width.d.ts.map +1 -0
- package/dist/lib/cell-width.js +50 -0
- package/dist/lib/cell-width.js.map +1 -0
- package/dist/lib/clipboard.d.ts +8 -0
- package/dist/lib/clipboard.d.ts.map +1 -0
- package/dist/lib/clipboard.js +19 -0
- package/dist/lib/clipboard.js.map +1 -0
- package/dist/lib/color.d.ts +40 -0
- package/dist/lib/color.d.ts.map +1 -0
- package/dist/lib/color.js +177 -0
- package/dist/lib/color.js.map +1 -0
- package/dist/lib/scroll-map.d.ts +20 -0
- package/dist/lib/scroll-map.d.ts.map +1 -0
- package/dist/lib/scroll-map.js +46 -0
- package/dist/lib/scroll-map.js.map +1 -0
- package/dist/lib/text-metrics.d.ts +33 -0
- package/dist/lib/text-metrics.d.ts.map +1 -0
- package/dist/lib/text-metrics.js +89 -0
- package/dist/lib/text-metrics.js.map +1 -0
- package/dist/lib/theme-file.d.ts +51 -0
- package/dist/lib/theme-file.d.ts.map +1 -0
- package/dist/lib/theme-file.js +511 -0
- package/dist/lib/theme-file.js.map +1 -0
- package/dist/lib/unsaved-guard.d.ts +33 -0
- package/dist/lib/unsaved-guard.d.ts.map +1 -0
- package/dist/lib/unsaved-guard.js +37 -0
- package/dist/lib/unsaved-guard.js.map +1 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +6 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/theme/custom.d.ts +8 -0
- package/dist/theme/custom.d.ts.map +1 -0
- package/dist/theme/custom.js +56 -0
- package/dist/theme/custom.js.map +1 -0
- package/dist/theme/index.d.ts +6 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/theme/index.js +5 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/theme/paint-guard.d.ts +20 -0
- package/dist/theme/paint-guard.d.ts.map +1 -0
- package/dist/theme/paint-guard.js +120 -0
- package/dist/theme/paint-guard.js.map +1 -0
- package/dist/theme/picks.d.ts +22 -0
- package/dist/theme/picks.d.ts.map +1 -0
- package/dist/theme/picks.js +43 -0
- package/dist/theme/picks.js.map +1 -0
- package/dist/theme/pref.d.ts +39 -0
- package/dist/theme/pref.d.ts.map +1 -0
- package/dist/theme/pref.js +129 -0
- package/dist/theme/pref.js.map +1 -0
- package/dist/theme/provider.d.ts +41 -0
- package/dist/theme/provider.d.ts.map +1 -0
- package/dist/theme/provider.js +103 -0
- package/dist/theme/provider.js.map +1 -0
- package/dist/theme/store.d.ts +104 -0
- package/dist/theme/store.d.ts.map +1 -0
- package/dist/theme/store.js +509 -0
- package/dist/theme/store.js.map +1 -0
- package/package.json +94 -3
- package/styles.css +79 -0
- package/theme.css +186 -0
- package/vendor/shadcn-tailwind.css +664 -0
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
import { composeTerminalTheme, composeTheme, DEFAULT_VARIANT, isCustomTerminalTheme, schemesOf, } from "@orz/themes";
|
|
2
|
+
import { useMemo, useReducer } from "react";
|
|
3
|
+
import { uniqueLabel } from "../../lib/theme-file.js";
|
|
4
|
+
const SCHEMES = ["light", "dark"];
|
|
5
|
+
const other = (scheme) => scheme === "light" ? "dark" : "light";
|
|
6
|
+
const isBased = (side) => "base" in side;
|
|
7
|
+
const isBasedTerminal = (terminal) => "base" in terminal;
|
|
8
|
+
function baseRef(ref) {
|
|
9
|
+
const out = { ...ref };
|
|
10
|
+
if (out.variant === undefined || out.variant === DEFAULT_VARIANT) {
|
|
11
|
+
delete out.variant;
|
|
12
|
+
}
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
15
|
+
function withoutKey(record, key) {
|
|
16
|
+
if (record === undefined || !Object.hasOwn(record, key))
|
|
17
|
+
return record;
|
|
18
|
+
const out = { ...record };
|
|
19
|
+
delete out[key];
|
|
20
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
21
|
+
}
|
|
22
|
+
/** `{ ...object, [key]: value }`, the key left out when the value is undefined. */
|
|
23
|
+
function setOptional(object, key, value) {
|
|
24
|
+
const out = { ...object };
|
|
25
|
+
if (value === undefined)
|
|
26
|
+
delete out[key];
|
|
27
|
+
else
|
|
28
|
+
out[key] = value;
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
/** On a terminal theme, the mode of the side's terminal palette. */
|
|
32
|
+
export function sideMode(draft, scheme) {
|
|
33
|
+
const side = draft[scheme];
|
|
34
|
+
if (side === undefined)
|
|
35
|
+
return "none";
|
|
36
|
+
return "base" in side ? "based" : "detached";
|
|
37
|
+
}
|
|
38
|
+
/** The terminal palette a side carries: on a terminal theme, the side itself. */
|
|
39
|
+
export function terminalOf(draft, scheme) {
|
|
40
|
+
return isCustomTerminalTheme(draft) ? draft[scheme] : draft[scheme]?.terminal;
|
|
41
|
+
}
|
|
42
|
+
/** null when the side is None. A terminal theme's side never follows. */
|
|
43
|
+
export function terminalMode(draft, scheme) {
|
|
44
|
+
if (isCustomTerminalTheme(draft)) {
|
|
45
|
+
const side = draft[scheme];
|
|
46
|
+
if (side === undefined)
|
|
47
|
+
return null;
|
|
48
|
+
return isBasedTerminal(side) ? "based" : "detached";
|
|
49
|
+
}
|
|
50
|
+
const side = draft[scheme];
|
|
51
|
+
return side === undefined ? null : terminalModeOf(side);
|
|
52
|
+
}
|
|
53
|
+
function terminalModeOf(side) {
|
|
54
|
+
if (side.terminal === undefined)
|
|
55
|
+
return "follow";
|
|
56
|
+
return isBasedTerminal(side.terminal) ? "based" : "detached";
|
|
57
|
+
}
|
|
58
|
+
/** True only on a based side that pins `name`. */
|
|
59
|
+
export function isOverridden(draft, scheme, name) {
|
|
60
|
+
const side = draft[scheme];
|
|
61
|
+
return (side !== undefined &&
|
|
62
|
+
isBased(side) &&
|
|
63
|
+
side.tokens !== undefined &&
|
|
64
|
+
Object.hasOwn(side.tokens, name));
|
|
65
|
+
}
|
|
66
|
+
/** True only on a based terminal that pins `name`. */
|
|
67
|
+
export function isTerminalOverridden(draft, scheme, name) {
|
|
68
|
+
const terminal = terminalOf(draft, scheme);
|
|
69
|
+
return (terminal !== undefined &&
|
|
70
|
+
isBasedTerminal(terminal) &&
|
|
71
|
+
terminal.tokens !== undefined &&
|
|
72
|
+
Object.hasOwn(terminal.tokens, name));
|
|
73
|
+
}
|
|
74
|
+
/** Overrides on the side, among `names` when given (a group's tokens). */
|
|
75
|
+
export function overrideCount(draft, scheme, names) {
|
|
76
|
+
const side = draft[scheme];
|
|
77
|
+
if (side === undefined || !isBased(side) || side.tokens === undefined) {
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
const keys = Object.keys(side.tokens);
|
|
81
|
+
return names ? keys.filter((k) => names.includes(k)).length : keys.length;
|
|
82
|
+
}
|
|
83
|
+
export function terminalOverrideCount(draft, scheme, names) {
|
|
84
|
+
const terminal = terminalOf(draft, scheme);
|
|
85
|
+
if (terminal === undefined ||
|
|
86
|
+
!isBasedTerminal(terminal) ||
|
|
87
|
+
terminal.tokens === undefined) {
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
const keys = Object.keys(terminal.tokens);
|
|
91
|
+
return names ? keys.filter((k) => names.includes(k)).length : keys.length;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Both sides exist and carry equal terminals. Two following terminals count
|
|
95
|
+
* only on the same base and variant: following different bases shows
|
|
96
|
+
* different colours, and a pin would give each side a different reference.
|
|
97
|
+
*/
|
|
98
|
+
export function sameTerminal(draft) {
|
|
99
|
+
const { light, dark } = draft;
|
|
100
|
+
if (light === undefined || dark === undefined)
|
|
101
|
+
return false;
|
|
102
|
+
if (!deepEqual(light.terminal, dark.terminal))
|
|
103
|
+
return false;
|
|
104
|
+
return light.terminal !== undefined || followSameBase(light, dark);
|
|
105
|
+
}
|
|
106
|
+
function followSameBase(a, b) {
|
|
107
|
+
return (isBased(a) && isBased(b) && deepEqual(baseRef(a.base), baseRef(b.base)));
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Only a based UI side can leave its terminal to its base; both sides at
|
|
111
|
+
* once only when they are based on the same base.
|
|
112
|
+
*/
|
|
113
|
+
export function canFollow(draft, target) {
|
|
114
|
+
if (target !== "both")
|
|
115
|
+
return sideMode(draft, target) === "based";
|
|
116
|
+
const { light, dark } = draft;
|
|
117
|
+
return light !== undefined && dark !== undefined
|
|
118
|
+
? followSameBase(light, dark)
|
|
119
|
+
: SCHEMES.some((s) => sideMode(draft, s) === "based");
|
|
120
|
+
}
|
|
121
|
+
/** Structural equality on JSON-like values; an undefined property counts as absent. */
|
|
122
|
+
export function deepEqual(a, b) {
|
|
123
|
+
if (a === b)
|
|
124
|
+
return true;
|
|
125
|
+
if (typeof a !== "object" || typeof b !== "object" || !a || !b)
|
|
126
|
+
return false;
|
|
127
|
+
if (Array.isArray(a) !== Array.isArray(b))
|
|
128
|
+
return false;
|
|
129
|
+
const left = a;
|
|
130
|
+
const right = b;
|
|
131
|
+
const keys = new Set([...Object.keys(left), ...Object.keys(right)].filter((k) => left[k] !== undefined || right[k] !== undefined));
|
|
132
|
+
for (const key of keys) {
|
|
133
|
+
if (!deepEqual(left[key], right[key]))
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
export function isDirty(state) {
|
|
139
|
+
return state.saved === null
|
|
140
|
+
? state.touched
|
|
141
|
+
: !deepEqual(state.draft, state.saved);
|
|
142
|
+
}
|
|
143
|
+
export function initDraftState(initial) {
|
|
144
|
+
return {
|
|
145
|
+
saved: initial.saved,
|
|
146
|
+
draft: initial.draft,
|
|
147
|
+
memory: {},
|
|
148
|
+
touched: false,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function remember(memory, side) {
|
|
152
|
+
if (side === undefined)
|
|
153
|
+
return memory;
|
|
154
|
+
return isBased(side)
|
|
155
|
+
? { ...memory, based: side }
|
|
156
|
+
: { ...memory, detached: side };
|
|
157
|
+
}
|
|
158
|
+
function rememberTerminal(memory, side) {
|
|
159
|
+
const terminal = side.terminal;
|
|
160
|
+
if (terminal === undefined)
|
|
161
|
+
return memory;
|
|
162
|
+
return isBasedTerminal(terminal)
|
|
163
|
+
? { ...memory, terminalBased: terminal }
|
|
164
|
+
: { ...memory, terminalDetached: terminal };
|
|
165
|
+
}
|
|
166
|
+
function detach(based, resolved) {
|
|
167
|
+
// A palette the user named is kept; a terminal following the base or
|
|
168
|
+
// already detached becomes the colours it shows.
|
|
169
|
+
const terminal = based?.terminal && isBasedTerminal(based.terminal)
|
|
170
|
+
? based.terminal
|
|
171
|
+
: { tokens: { ...resolved.terminal } };
|
|
172
|
+
return { tokens: { ...resolved.tokens }, terminal };
|
|
173
|
+
}
|
|
174
|
+
function setSideMode(state, action) {
|
|
175
|
+
const { scheme, mode } = action;
|
|
176
|
+
const current = state.draft[scheme];
|
|
177
|
+
if (sideMode(state.draft, scheme) === mode)
|
|
178
|
+
return null;
|
|
179
|
+
const memory = remember(state.memory[scheme] ?? {}, current);
|
|
180
|
+
let next;
|
|
181
|
+
if (mode === "based") {
|
|
182
|
+
next =
|
|
183
|
+
memory.based ??
|
|
184
|
+
(action.base ? { base: baseRef(action.base) } : undefined);
|
|
185
|
+
if (next === undefined)
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
else if (mode === "detached") {
|
|
189
|
+
if (memory.detached)
|
|
190
|
+
next = memory.detached;
|
|
191
|
+
else if (action.resolved) {
|
|
192
|
+
next = detach(current && isBased(current) ? current : memory.based, action.resolved);
|
|
193
|
+
}
|
|
194
|
+
else
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
draft: setOptional(state.draft, scheme, next),
|
|
199
|
+
memory: { ...state.memory, [scheme]: memory },
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
/** Applies `edit` to the side(s) `target` names that exist; an edit returning undefined leaves a side as is. */
|
|
203
|
+
function editSides(state, target, edit) {
|
|
204
|
+
let draft = state.draft;
|
|
205
|
+
let memory = state.memory;
|
|
206
|
+
for (const scheme of target === "both" ? SCHEMES : [target]) {
|
|
207
|
+
const side = draft[scheme];
|
|
208
|
+
if (side === undefined)
|
|
209
|
+
continue;
|
|
210
|
+
const result = edit(side, memory[scheme] ?? {});
|
|
211
|
+
if (result === undefined || deepEqual(result.side, side))
|
|
212
|
+
continue;
|
|
213
|
+
draft = { ...draft, [scheme]: result.side };
|
|
214
|
+
if (result.memory)
|
|
215
|
+
memory = { ...memory, [scheme]: result.memory };
|
|
216
|
+
}
|
|
217
|
+
return draft === state.draft ? null : { draft, memory };
|
|
218
|
+
}
|
|
219
|
+
function editBased(state, scheme, edit) {
|
|
220
|
+
return editSides(state, scheme, (side) => isBased(side) ? { side: edit(side) } : undefined);
|
|
221
|
+
}
|
|
222
|
+
function setTerminal(side, terminal) {
|
|
223
|
+
return setOptional(side, "terminal", terminal);
|
|
224
|
+
}
|
|
225
|
+
function editBasedTerminal(state, target, edit) {
|
|
226
|
+
return editSides(state, target, (side) => side.terminal && isBasedTerminal(side.terminal)
|
|
227
|
+
? { side: setTerminal(side, edit(side.terminal)) }
|
|
228
|
+
: undefined);
|
|
229
|
+
}
|
|
230
|
+
function setTerminalMode(state, action) {
|
|
231
|
+
const { target, mode } = action;
|
|
232
|
+
if (mode === "none")
|
|
233
|
+
return null;
|
|
234
|
+
// For both sides, the first side's choice is written to each, so a shared
|
|
235
|
+
// terminal stays one terminal even when the sides remember different ones.
|
|
236
|
+
const source = target === "both" ? SCHEMES.find((s) => state.draft[s]) : target;
|
|
237
|
+
const side = source && state.draft[source];
|
|
238
|
+
if (source === undefined || side === undefined)
|
|
239
|
+
return null;
|
|
240
|
+
if (terminalModeOf(side) === mode)
|
|
241
|
+
return null;
|
|
242
|
+
const memory = rememberTerminal(state.memory[source] ?? {}, side);
|
|
243
|
+
let terminal;
|
|
244
|
+
if (mode === "follow") {
|
|
245
|
+
if (!canFollow(state.draft, target))
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
else if (mode === "based") {
|
|
249
|
+
terminal =
|
|
250
|
+
memory.terminalBased ??
|
|
251
|
+
(action.base ? { base: baseRef(action.base) } : undefined);
|
|
252
|
+
if (terminal === undefined)
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
terminal =
|
|
257
|
+
memory.terminalDetached ??
|
|
258
|
+
(action.resolved ? { tokens: { ...action.resolved } } : undefined);
|
|
259
|
+
if (terminal === undefined)
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
return editSides(state, target, (each, eachMemory) => ({
|
|
263
|
+
side: setTerminal(each, terminal),
|
|
264
|
+
memory: rememberTerminal(eachMemory, each),
|
|
265
|
+
}));
|
|
266
|
+
}
|
|
267
|
+
function shareTerminal(state, from, resolved) {
|
|
268
|
+
const source = state.draft[from];
|
|
269
|
+
const target = state.draft[other(from)];
|
|
270
|
+
if (source === undefined || target === undefined)
|
|
271
|
+
return null;
|
|
272
|
+
if (sameTerminal(state.draft))
|
|
273
|
+
return null;
|
|
274
|
+
if (source.terminal === undefined &&
|
|
275
|
+
isBased(source) &&
|
|
276
|
+
isBased(target) &&
|
|
277
|
+
!followSameBase(source, target)) {
|
|
278
|
+
// Following two different bases is not one terminal; both name the
|
|
279
|
+
// source's base, which shows what following it did.
|
|
280
|
+
const terminal = { base: baseRef({ ...source.base }) };
|
|
281
|
+
return {
|
|
282
|
+
draft: {
|
|
283
|
+
...state.draft,
|
|
284
|
+
[from]: setTerminal(source, terminal),
|
|
285
|
+
[other(from)]: setTerminal(target, terminal),
|
|
286
|
+
},
|
|
287
|
+
memory: state.memory,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
if (source.terminal === undefined && !isBased(target)) {
|
|
291
|
+
if (resolved === undefined)
|
|
292
|
+
return null;
|
|
293
|
+
const terminal = { tokens: { ...resolved } };
|
|
294
|
+
return {
|
|
295
|
+
draft: {
|
|
296
|
+
...state.draft,
|
|
297
|
+
[from]: setTerminal(source, terminal),
|
|
298
|
+
[other(from)]: setTerminal(target, terminal),
|
|
299
|
+
},
|
|
300
|
+
memory: state.memory,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
draft: {
|
|
305
|
+
...state.draft,
|
|
306
|
+
[other(from)]: setTerminal(target, source.terminal),
|
|
307
|
+
},
|
|
308
|
+
memory: state.memory,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
/** Applies `edit` to a terminal theme's side, when it exists. */
|
|
312
|
+
function editTerminalSide(state, target, edit) {
|
|
313
|
+
if (target === "both")
|
|
314
|
+
return null;
|
|
315
|
+
const side = state.draft[target];
|
|
316
|
+
const next = side && edit(side);
|
|
317
|
+
if (next === undefined || deepEqual(next, side))
|
|
318
|
+
return null;
|
|
319
|
+
return { draft: { ...state.draft, [target]: next }, memory: state.memory };
|
|
320
|
+
}
|
|
321
|
+
function editBasedTerminalSide(state, target, edit) {
|
|
322
|
+
return editTerminalSide(state, target, (side) => isBasedTerminal(side) ? edit(side) : undefined);
|
|
323
|
+
}
|
|
324
|
+
/** None · Based on a palette · Fully custom for one side of a terminal theme. */
|
|
325
|
+
function setTerminalSideMode(state, action) {
|
|
326
|
+
const { target, mode } = action;
|
|
327
|
+
if (target === "both" || mode === "follow")
|
|
328
|
+
return null;
|
|
329
|
+
if (sideMode(state.draft, target) === mode)
|
|
330
|
+
return null;
|
|
331
|
+
const current = state.draft[target];
|
|
332
|
+
let memory = state.memory[target] ?? {};
|
|
333
|
+
if (current !== undefined) {
|
|
334
|
+
memory = isBasedTerminal(current)
|
|
335
|
+
? { ...memory, terminalBased: current }
|
|
336
|
+
: { ...memory, terminalDetached: current };
|
|
337
|
+
}
|
|
338
|
+
let next;
|
|
339
|
+
if (mode === "based") {
|
|
340
|
+
next =
|
|
341
|
+
memory.terminalBased ??
|
|
342
|
+
(action.base ? { base: baseRef(action.base) } : undefined);
|
|
343
|
+
if (next === undefined)
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
else if (mode === "detached") {
|
|
347
|
+
next =
|
|
348
|
+
memory.terminalDetached ??
|
|
349
|
+
(action.resolved ? { tokens: { ...action.resolved } } : undefined);
|
|
350
|
+
if (next === undefined)
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
draft: setOptional(state.draft, target, next),
|
|
355
|
+
memory: { ...state.memory, [target]: memory },
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
/** The terminal actions, on a terminal theme's sides; UI actions do nothing. */
|
|
359
|
+
function applyTerminal(state, action) {
|
|
360
|
+
switch (action.type) {
|
|
361
|
+
case "setTerminalMode":
|
|
362
|
+
return setTerminalSideMode(state, action);
|
|
363
|
+
case "setTerminalBase":
|
|
364
|
+
return editTerminalSide(state, action.target, (side) => {
|
|
365
|
+
const next = { base: baseRef(action.base) };
|
|
366
|
+
if (isBasedTerminal(side) && side.tokens)
|
|
367
|
+
next.tokens = side.tokens;
|
|
368
|
+
return next;
|
|
369
|
+
});
|
|
370
|
+
case "setTerminalVariant":
|
|
371
|
+
return editBasedTerminalSide(state, action.target, (terminal) => ({
|
|
372
|
+
...terminal,
|
|
373
|
+
base: baseRef({ ...terminal.base, variant: action.variant }),
|
|
374
|
+
}));
|
|
375
|
+
case "setTerminalToken":
|
|
376
|
+
return editTerminalSide(state, action.target, (side) => {
|
|
377
|
+
const pinned = { [action.name]: action.value };
|
|
378
|
+
// A based side pins the value even when it equals the base's: only ↺ unpins.
|
|
379
|
+
return isBasedTerminal(side)
|
|
380
|
+
? { ...side, tokens: { ...side.tokens, ...pinned } }
|
|
381
|
+
: { tokens: { ...side.tokens, ...pinned } };
|
|
382
|
+
});
|
|
383
|
+
case "resetTerminalToken":
|
|
384
|
+
return editBasedTerminalSide(state, action.target, (terminal) => setOptional(terminal, "tokens", withoutKey(terminal.tokens, action.name)));
|
|
385
|
+
case "resetAllTerminal":
|
|
386
|
+
return editBasedTerminalSide(state, action.target, (terminal) => setOptional(terminal, "tokens", undefined));
|
|
387
|
+
default:
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function apply(state, action) {
|
|
392
|
+
if (action.type === "load" || action.type === "markSaved")
|
|
393
|
+
return null;
|
|
394
|
+
if (action.type === "setLabel") {
|
|
395
|
+
return action.label === state.draft.label
|
|
396
|
+
? null
|
|
397
|
+
: {
|
|
398
|
+
draft: { ...state.draft, label: action.label },
|
|
399
|
+
memory: state.memory,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
if (isCustomTerminalTheme(state.draft)) {
|
|
403
|
+
return applyTerminal(state, action);
|
|
404
|
+
}
|
|
405
|
+
return applyUi(state, action);
|
|
406
|
+
}
|
|
407
|
+
function applyUi(state, action) {
|
|
408
|
+
switch (action.type) {
|
|
409
|
+
case "load":
|
|
410
|
+
case "markSaved":
|
|
411
|
+
case "setLabel":
|
|
412
|
+
return null;
|
|
413
|
+
case "setSideMode":
|
|
414
|
+
return setSideMode(state, action);
|
|
415
|
+
case "setBase":
|
|
416
|
+
return editBased(state, action.scheme, (side) => ({
|
|
417
|
+
...side,
|
|
418
|
+
base: baseRef(action.base),
|
|
419
|
+
}));
|
|
420
|
+
case "setVariant":
|
|
421
|
+
return editBased(state, action.scheme, (side) => ({
|
|
422
|
+
...side,
|
|
423
|
+
base: baseRef({ ...side.base, variant: action.variant }),
|
|
424
|
+
}));
|
|
425
|
+
case "setToken":
|
|
426
|
+
// A based side pins the value even when it equals the base's: only ↺ unpins.
|
|
427
|
+
return editSides(state, action.scheme, (side) => ({
|
|
428
|
+
side: {
|
|
429
|
+
...side,
|
|
430
|
+
tokens: { ...side.tokens, [action.name]: action.value },
|
|
431
|
+
},
|
|
432
|
+
}));
|
|
433
|
+
case "resetToken":
|
|
434
|
+
return editBased(state, action.scheme, (side) => setOptional(side, "tokens", withoutKey(side.tokens, action.name)));
|
|
435
|
+
case "resetAll":
|
|
436
|
+
return editBased(state, action.scheme, (side) => setOptional(side, "tokens", undefined));
|
|
437
|
+
case "setTerminalMode":
|
|
438
|
+
return setTerminalMode(state, action);
|
|
439
|
+
case "setTerminalBase":
|
|
440
|
+
return editSides(state, action.target, (side) => {
|
|
441
|
+
const terminal = side.terminal;
|
|
442
|
+
const tokens = terminal && isBasedTerminal(terminal) ? terminal.tokens : undefined;
|
|
443
|
+
const next = { base: baseRef(action.base) };
|
|
444
|
+
if (tokens)
|
|
445
|
+
next.tokens = tokens;
|
|
446
|
+
return { side: setTerminal(side, next) };
|
|
447
|
+
});
|
|
448
|
+
case "setTerminalVariant":
|
|
449
|
+
return editBasedTerminal(state, action.target, (terminal) => ({
|
|
450
|
+
...terminal,
|
|
451
|
+
base: baseRef({ ...terminal.base, variant: action.variant }),
|
|
452
|
+
}));
|
|
453
|
+
case "setTerminalToken":
|
|
454
|
+
return editSides(state, action.target, (side) => {
|
|
455
|
+
const terminal = side.terminal;
|
|
456
|
+
const pinned = { [action.name]: action.value };
|
|
457
|
+
let next;
|
|
458
|
+
if (terminal === undefined) {
|
|
459
|
+
// Editing a following terminal pins the colour on the UI base's own
|
|
460
|
+
// terminal, which shows exactly what following did.
|
|
461
|
+
if (!isBased(side))
|
|
462
|
+
return undefined;
|
|
463
|
+
next = { base: baseRef({ ...side.base }), tokens: pinned };
|
|
464
|
+
}
|
|
465
|
+
else if (isBasedTerminal(terminal)) {
|
|
466
|
+
next = { ...terminal, tokens: { ...terminal.tokens, ...pinned } };
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
next = { tokens: { ...terminal.tokens, ...pinned } };
|
|
470
|
+
}
|
|
471
|
+
return { side: setTerminal(side, next) };
|
|
472
|
+
});
|
|
473
|
+
case "resetTerminalToken":
|
|
474
|
+
return editBasedTerminal(state, action.target, (terminal) => setOptional(terminal, "tokens", withoutKey(terminal.tokens, action.name)));
|
|
475
|
+
case "resetAllTerminal":
|
|
476
|
+
return editBasedTerminal(state, action.target, (terminal) => setOptional(terminal, "tokens", undefined));
|
|
477
|
+
case "shareTerminal":
|
|
478
|
+
return shareTerminal(state, action.from, action.resolved);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
// A load or markSaved may change the kind; only the editor sends those, and
|
|
482
|
+
// it keeps the union.
|
|
483
|
+
export function draftReducer(state, action) {
|
|
484
|
+
if (action.type === "load")
|
|
485
|
+
return initDraftState(action);
|
|
486
|
+
if (action.type === "markSaved") {
|
|
487
|
+
return {
|
|
488
|
+
...state,
|
|
489
|
+
saved: action.saved,
|
|
490
|
+
draft: action.saved,
|
|
491
|
+
touched: false,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
const patch = apply(state, action);
|
|
495
|
+
return patch === null
|
|
496
|
+
? state
|
|
497
|
+
: { ...state, ...patch, touched: true };
|
|
498
|
+
}
|
|
499
|
+
export function composeDraft(draft, themes) {
|
|
500
|
+
const label = draft.label.trim() || "Untitled theme";
|
|
501
|
+
return isCustomTerminalTheme(draft)
|
|
502
|
+
? composeTerminalTheme({ ...draft, label }, themes)
|
|
503
|
+
: composeTheme({ ...draft, label }, themes);
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* The draft for one editing session. `initial` is read on mount only; switch
|
|
507
|
+
* themes with a `load` action. `themes`: the UI and terminal lists together,
|
|
508
|
+
* as the store composes against.
|
|
509
|
+
*/
|
|
510
|
+
export function useThemeDraft(initial, themes) {
|
|
511
|
+
const [state, dispatch] = useReducer(draftReducer, initial, initDraftState);
|
|
512
|
+
const composed = useMemo(() => composeDraft(state.draft, themes), [state.draft, themes]);
|
|
513
|
+
return {
|
|
514
|
+
state,
|
|
515
|
+
draft: state.draft,
|
|
516
|
+
saved: state.saved,
|
|
517
|
+
dirty: isDirty(state),
|
|
518
|
+
composed,
|
|
519
|
+
dispatch,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
const ID_LENGTH = 8;
|
|
523
|
+
/** `custom-` and 8 random base-36 characters, regenerated while it is in `taken`. */
|
|
524
|
+
export function newThemeId(taken) {
|
|
525
|
+
for (;;) {
|
|
526
|
+
const bytes = crypto.getRandomValues(new Uint8Array(ID_LENGTH));
|
|
527
|
+
// 252 is the largest multiple of 36 below 256; rejecting above it keeps
|
|
528
|
+
// every character equally likely.
|
|
529
|
+
if (bytes.some((b) => b >= 252))
|
|
530
|
+
continue;
|
|
531
|
+
const id = `custom-${Array.from(bytes, (b) => (b % 36).toString(36)).join("")}`;
|
|
532
|
+
if (!taken.includes(id))
|
|
533
|
+
return id;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
const takenIds = (ctx) => [
|
|
537
|
+
...ctx.customs.map((c) => c.id),
|
|
538
|
+
...ctx.baseThemes.map((t) => t.id),
|
|
539
|
+
];
|
|
540
|
+
function copySides(custom) {
|
|
541
|
+
const out = {};
|
|
542
|
+
for (const scheme of SCHEMES) {
|
|
543
|
+
const side = custom[scheme];
|
|
544
|
+
if (side !== undefined)
|
|
545
|
+
out[scheme] = structuredClone(side);
|
|
546
|
+
}
|
|
547
|
+
return out;
|
|
548
|
+
}
|
|
549
|
+
/** "*Label* copy", numbered until free, as an unsaved theme of the same kind with a new id. */
|
|
550
|
+
export function duplicate(custom, ctx) {
|
|
551
|
+
return {
|
|
552
|
+
id: newThemeId(takenIds(ctx)),
|
|
553
|
+
...(isCustomTerminalTheme(custom) ? { kind: "terminal" } : {}),
|
|
554
|
+
label: uniqueLabel(`${custom.label} copy`, ctx.labels),
|
|
555
|
+
...copySides(custom),
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
export function newTheme(ctx) {
|
|
559
|
+
if ("kind" in ctx && ctx.kind === "terminal")
|
|
560
|
+
return newTerminalTheme(ctx);
|
|
561
|
+
return newUiTheme(ctx);
|
|
562
|
+
}
|
|
563
|
+
function newUiTheme(ctx) {
|
|
564
|
+
const id = newThemeId(takenIds(ctx));
|
|
565
|
+
const label = uniqueLabel("Custom theme", ctx.labels);
|
|
566
|
+
const shown = ctx.resolved.theme.id;
|
|
567
|
+
const custom = ctx.customs.find((c) => c.id === shown);
|
|
568
|
+
if (custom !== undefined && !isCustomTerminalTheme(custom)) {
|
|
569
|
+
return { id, label, ...copySides(custom) };
|
|
570
|
+
}
|
|
571
|
+
const base = ctx.baseThemes.find((t) => t.id === shown) ??
|
|
572
|
+
ctx.baseThemes[0] ??
|
|
573
|
+
ctx.resolved.theme;
|
|
574
|
+
const variant = ctx.pref.ui?.theme === base.id ? ctx.pref.ui.variant : ctx.resolved.variant;
|
|
575
|
+
const out = { id, label };
|
|
576
|
+
for (const scheme of schemesOf(base)) {
|
|
577
|
+
const variants = base[scheme]?.variants ?? {};
|
|
578
|
+
out[scheme] = {
|
|
579
|
+
base: baseRef({
|
|
580
|
+
theme: base.id,
|
|
581
|
+
variant: variant !== undefined && Object.hasOwn(variants, variant)
|
|
582
|
+
? variant
|
|
583
|
+
: undefined,
|
|
584
|
+
}),
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
return out;
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* A copy of what the terminal shows. A base theme (UI or terminal-only): each
|
|
591
|
+
* scheme it has becomes a based side on it, with the current variant where
|
|
592
|
+
* that palette defines it. A terminal custom theme: its sides. A UI custom
|
|
593
|
+
* theme cannot be a base, so each of its sides becomes Fully custom from the
|
|
594
|
+
* terminal colours it shows.
|
|
595
|
+
*/
|
|
596
|
+
function newTerminalTheme(ctx) {
|
|
597
|
+
const id = newThemeId(takenIds(ctx));
|
|
598
|
+
const label = uniqueLabel("Terminal theme", ctx.labels);
|
|
599
|
+
const { theme, following } = ctx.terminal;
|
|
600
|
+
const custom = ctx.customs.find((c) => c.id === theme.id);
|
|
601
|
+
if (custom !== undefined && isCustomTerminalTheme(custom)) {
|
|
602
|
+
return { id, kind: "terminal", label, ...copySides(custom) };
|
|
603
|
+
}
|
|
604
|
+
const out = { id, kind: "terminal", label };
|
|
605
|
+
const pick = following ? ctx.pref.ui : ctx.pref.terminal;
|
|
606
|
+
const variant = pick?.theme === theme.id && pick.variant !== undefined
|
|
607
|
+
? pick.variant
|
|
608
|
+
: ctx.terminal.variant;
|
|
609
|
+
for (const scheme of schemesOf(theme)) {
|
|
610
|
+
const palette = theme[scheme];
|
|
611
|
+
if (palette === undefined)
|
|
612
|
+
continue;
|
|
613
|
+
if (custom !== undefined) {
|
|
614
|
+
out[scheme] = { tokens: { ...palette.terminal } };
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
out[scheme] = {
|
|
618
|
+
base: baseRef({
|
|
619
|
+
theme: theme.id,
|
|
620
|
+
variant: Object.hasOwn(palette.variants ?? {}, variant)
|
|
621
|
+
? variant
|
|
622
|
+
: undefined,
|
|
623
|
+
}),
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
return out;
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* The base a side takes when it turns based with nothing remembered: the
|
|
630
|
+
* committed UI theme if it is a base theme with `scheme`, else the first base
|
|
631
|
+
* theme that has it.
|
|
632
|
+
*/
|
|
633
|
+
export function fallbackBase(scheme, committedTheme, baseThemes) {
|
|
634
|
+
const theme = baseThemes.find((t) => t.id === committedTheme && t[scheme]) ??
|
|
635
|
+
baseThemes.find((t) => t[scheme] !== undefined);
|
|
636
|
+
return theme && { theme: theme.id };
|
|
637
|
+
}
|
|
638
|
+
//# sourceMappingURL=draft.js.map
|