@orz/ui 0.0.0-stage → 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +541 -2
  3. package/dist/components/avatar.d.ts +14 -0
  4. package/dist/components/avatar.d.ts.map +1 -0
  5. package/dist/components/avatar.js +23 -0
  6. package/dist/components/avatar.js.map +1 -0
  7. package/dist/components/badge.d.ts +10 -0
  8. package/dist/components/badge.d.ts.map +1 -0
  9. package/dist/components/badge.js +26 -0
  10. package/dist/components/badge.js.map +1 -0
  11. package/dist/components/button.d.ts +11 -0
  12. package/dist/components/button.d.ts.map +1 -0
  13. package/dist/components/button.js +40 -0
  14. package/dist/components/button.js.map +1 -0
  15. package/dist/components/card.d.ts +12 -0
  16. package/dist/components/card.d.ts.map +1 -0
  17. package/dist/components/card.js +25 -0
  18. package/dist/components/card.js.map +1 -0
  19. package/dist/components/color-picker.d.ts +58 -0
  20. package/dist/components/color-picker.d.ts.map +1 -0
  21. package/dist/components/color-picker.js +950 -0
  22. package/dist/components/color-picker.js.map +1 -0
  23. package/dist/components/confirm-dialog.d.ts +26 -0
  24. package/dist/components/confirm-dialog.d.ts.map +1 -0
  25. package/dist/components/confirm-dialog.js +18 -0
  26. package/dist/components/confirm-dialog.js.map +1 -0
  27. package/dist/components/dialog.d.ts +26 -0
  28. package/dist/components/dialog.d.ts.map +1 -0
  29. package/dist/components/dialog.js +149 -0
  30. package/dist/components/dialog.js.map +1 -0
  31. package/dist/components/dropdown-menu.d.ts +30 -0
  32. package/dist/components/dropdown-menu.d.ts.map +1 -0
  33. package/dist/components/dropdown-menu.js +100 -0
  34. package/dist/components/dropdown-menu.js.map +1 -0
  35. package/dist/components/filter-menu.d.ts +98 -0
  36. package/dist/components/filter-menu.d.ts.map +1 -0
  37. package/dist/components/filter-menu.js +745 -0
  38. package/dist/components/filter-menu.js.map +1 -0
  39. package/dist/components/highlighted-input.d.ts +40 -0
  40. package/dist/components/highlighted-input.d.ts.map +1 -0
  41. package/dist/components/highlighted-input.js +95 -0
  42. package/dist/components/highlighted-input.js.map +1 -0
  43. package/dist/components/hover-card.d.ts +7 -0
  44. package/dist/components/hover-card.d.ts.map +1 -0
  45. package/dist/components/hover-card.js +22 -0
  46. package/dist/components/hover-card.js.map +1 -0
  47. package/dist/components/input.d.ts +4 -0
  48. package/dist/components/input.d.ts.map +1 -0
  49. package/dist/components/input.js +7 -0
  50. package/dist/components/input.js.map +1 -0
  51. package/dist/components/label.d.ts +5 -0
  52. package/dist/components/label.d.ts.map +1 -0
  53. package/dist/components/label.js +8 -0
  54. package/dist/components/label.js.map +1 -0
  55. package/dist/components/link-chip.d.ts +67 -0
  56. package/dist/components/link-chip.d.ts.map +1 -0
  57. package/dist/components/link-chip.js +71 -0
  58. package/dist/components/link-chip.js.map +1 -0
  59. package/dist/components/load-failure.d.ts +24 -0
  60. package/dist/components/load-failure.d.ts.map +1 -0
  61. package/dist/components/load-failure.js +27 -0
  62. package/dist/components/load-failure.js.map +1 -0
  63. package/dist/components/load-more.d.ts +15 -0
  64. package/dist/components/load-more.d.ts.map +1 -0
  65. package/dist/components/load-more.js +25 -0
  66. package/dist/components/load-more.js.map +1 -0
  67. package/dist/components/media-controls.d.ts +50 -0
  68. package/dist/components/media-controls.d.ts.map +1 -0
  69. package/dist/components/media-controls.js +71 -0
  70. package/dist/components/media-controls.js.map +1 -0
  71. package/dist/components/popover.d.ts +9 -0
  72. package/dist/components/popover.d.ts.map +1 -0
  73. package/dist/components/popover.js +82 -0
  74. package/dist/components/popover.js.map +1 -0
  75. package/dist/components/query-box.d.ts +78 -0
  76. package/dist/components/query-box.d.ts.map +1 -0
  77. package/dist/components/query-box.js +332 -0
  78. package/dist/components/query-box.js.map +1 -0
  79. package/dist/components/query-highlight.d.ts +14 -0
  80. package/dist/components/query-highlight.d.ts.map +1 -0
  81. package/dist/components/query-highlight.js +132 -0
  82. package/dist/components/query-highlight.js.map +1 -0
  83. package/dist/components/select.d.ts +19 -0
  84. package/dist/components/select.d.ts.map +1 -0
  85. package/dist/components/select.js +38 -0
  86. package/dist/components/select.js.map +1 -0
  87. package/dist/components/separator.d.ts +5 -0
  88. package/dist/components/separator.d.ts.map +1 -0
  89. package/dist/components/separator.js +9 -0
  90. package/dist/components/separator.js.map +1 -0
  91. package/dist/components/sheet.d.ts +16 -0
  92. package/dist/components/sheet.d.ts.map +1 -0
  93. package/dist/components/sheet.js +41 -0
  94. package/dist/components/sheet.js.map +1 -0
  95. package/dist/components/skeleton.d.ts +3 -0
  96. package/dist/components/skeleton.d.ts.map +1 -0
  97. package/dist/components/skeleton.js +7 -0
  98. package/dist/components/skeleton.js.map +1 -0
  99. package/dist/components/slider.d.ts +8 -0
  100. package/dist/components/slider.d.ts.map +1 -0
  101. package/dist/components/slider.js +14 -0
  102. package/dist/components/slider.js.map +1 -0
  103. package/dist/components/sonner.d.ts +4 -0
  104. package/dist/components/sonner.d.ts.map +1 -0
  105. package/dist/components/sonner.js +26 -0
  106. package/dist/components/sonner.js.map +1 -0
  107. package/dist/components/switch.d.ts +5 -0
  108. package/dist/components/switch.d.ts.map +1 -0
  109. package/dist/components/switch.js +8 -0
  110. package/dist/components/switch.js.map +1 -0
  111. package/dist/components/table.d.ts +11 -0
  112. package/dist/components/table.d.ts.map +1 -0
  113. package/dist/components/table.js +28 -0
  114. package/dist/components/table.js.map +1 -0
  115. package/dist/components/tabs.d.ts +8 -0
  116. package/dist/components/tabs.d.ts.map +1 -0
  117. package/dist/components/tabs.js +18 -0
  118. package/dist/components/tabs.js.map +1 -0
  119. package/dist/components/textarea.d.ts +4 -0
  120. package/dist/components/textarea.d.ts.map +1 -0
  121. package/dist/components/textarea.js +7 -0
  122. package/dist/components/textarea.js.map +1 -0
  123. package/dist/components/theme-editor/draft.d.ts +235 -0
  124. package/dist/components/theme-editor/draft.d.ts.map +1 -0
  125. package/dist/components/theme-editor/draft.js +638 -0
  126. package/dist/components/theme-editor/draft.js.map +1 -0
  127. package/dist/components/theme-editor/export-panel.d.ts +14 -0
  128. package/dist/components/theme-editor/export-panel.d.ts.map +1 -0
  129. package/dist/components/theme-editor/export-panel.js +39 -0
  130. package/dist/components/theme-editor/export-panel.js.map +1 -0
  131. package/dist/components/theme-editor/form.d.ts +69 -0
  132. package/dist/components/theme-editor/form.d.ts.map +1 -0
  133. package/dist/components/theme-editor/form.js +350 -0
  134. package/dist/components/theme-editor/form.js.map +1 -0
  135. package/dist/components/theme-editor/import-panel.d.ts +21 -0
  136. package/dist/components/theme-editor/import-panel.d.ts.map +1 -0
  137. package/dist/components/theme-editor/import-panel.js +117 -0
  138. package/dist/components/theme-editor/import-panel.js.map +1 -0
  139. package/dist/components/theme-editor/theme-list.d.ts +23 -0
  140. package/dist/components/theme-editor/theme-list.d.ts.map +1 -0
  141. package/dist/components/theme-editor/theme-list.js +42 -0
  142. package/dist/components/theme-editor/theme-list.js.map +1 -0
  143. package/dist/components/theme-editor/token-list.d.ts +30 -0
  144. package/dist/components/theme-editor/token-list.d.ts.map +1 -0
  145. package/dist/components/theme-editor/token-list.js +124 -0
  146. package/dist/components/theme-editor/token-list.js.map +1 -0
  147. package/dist/components/theme-editor/tokens.d.ts +37 -0
  148. package/dist/components/theme-editor/tokens.d.ts.map +1 -0
  149. package/dist/components/theme-editor/tokens.js +155 -0
  150. package/dist/components/theme-editor/tokens.js.map +1 -0
  151. package/dist/components/theme-editor.d.ts +26 -0
  152. package/dist/components/theme-editor.d.ts.map +1 -0
  153. package/dist/components/theme-editor.js +467 -0
  154. package/dist/components/theme-editor.js.map +1 -0
  155. package/dist/components/theme-menu.d.ts +28 -0
  156. package/dist/components/theme-menu.d.ts.map +1 -0
  157. package/dist/components/theme-menu.js +308 -0
  158. package/dist/components/theme-menu.js.map +1 -0
  159. package/dist/components/tooltip.d.ts +11 -0
  160. package/dist/components/tooltip.d.ts.map +1 -0
  161. package/dist/components/tooltip.js +18 -0
  162. package/dist/components/tooltip.js.map +1 -0
  163. package/dist/components/unsaved-changes-dialog.d.ts +39 -0
  164. package/dist/components/unsaved-changes-dialog.d.ts.map +1 -0
  165. package/dist/components/unsaved-changes-dialog.js +46 -0
  166. package/dist/components/unsaved-changes-dialog.js.map +1 -0
  167. package/dist/hooks/scroll-insets.d.ts +89 -0
  168. package/dist/hooks/scroll-insets.d.ts.map +1 -0
  169. package/dist/hooks/scroll-insets.js +265 -0
  170. package/dist/hooks/scroll-insets.js.map +1 -0
  171. package/dist/hooks/use-element-height.d.ts +11 -0
  172. package/dist/hooks/use-element-height.d.ts.map +1 -0
  173. package/dist/hooks/use-element-height.js +33 -0
  174. package/dist/hooks/use-element-height.js.map +1 -0
  175. package/dist/hooks/use-media-query.d.ts +22 -0
  176. package/dist/hooks/use-media-query.d.ts.map +1 -0
  177. package/dist/hooks/use-media-query.js +38 -0
  178. package/dist/hooks/use-media-query.js.map +1 -0
  179. package/dist/hooks/use-overlay-scrollbars.d.ts +31 -0
  180. package/dist/hooks/use-overlay-scrollbars.d.ts.map +1 -0
  181. package/dist/hooks/use-overlay-scrollbars.js +80 -0
  182. package/dist/hooks/use-overlay-scrollbars.js.map +1 -0
  183. package/dist/hooks/use-paged-append.d.ts +7 -0
  184. package/dist/hooks/use-paged-append.d.ts.map +1 -0
  185. package/dist/hooks/use-paged-append.js +53 -0
  186. package/dist/hooks/use-paged-append.js.map +1 -0
  187. package/dist/hooks/use-query-completion.d.ts +27 -0
  188. package/dist/hooks/use-query-completion.d.ts.map +1 -0
  189. package/dist/hooks/use-query-completion.js +100 -0
  190. package/dist/hooks/use-query-completion.js.map +1 -0
  191. package/dist/hooks/use-slash-shortcut.d.ts +9 -0
  192. package/dist/hooks/use-slash-shortcut.d.ts.map +1 -0
  193. package/dist/hooks/use-slash-shortcut.js +38 -0
  194. package/dist/hooks/use-slash-shortcut.js.map +1 -0
  195. package/dist/hooks/viewport-chrome.d.ts +50 -0
  196. package/dist/hooks/viewport-chrome.d.ts.map +1 -0
  197. package/dist/hooks/viewport-chrome.js +170 -0
  198. package/dist/hooks/viewport-chrome.js.map +1 -0
  199. package/dist/index.d.ts +2 -0
  200. package/dist/index.d.ts.map +1 -0
  201. package/dist/index.js +2 -0
  202. package/dist/index.js.map +1 -0
  203. package/dist/lib/cell-width-table.d.ts +3 -0
  204. package/dist/lib/cell-width-table.d.ts.map +1 -0
  205. package/dist/lib/cell-width-table.js +6 -0
  206. package/dist/lib/cell-width-table.js.map +1 -0
  207. package/dist/lib/cell-width.d.ts +7 -0
  208. package/dist/lib/cell-width.d.ts.map +1 -0
  209. package/dist/lib/cell-width.js +50 -0
  210. package/dist/lib/cell-width.js.map +1 -0
  211. package/dist/lib/clipboard.d.ts +8 -0
  212. package/dist/lib/clipboard.d.ts.map +1 -0
  213. package/dist/lib/clipboard.js +19 -0
  214. package/dist/lib/clipboard.js.map +1 -0
  215. package/dist/lib/color.d.ts +40 -0
  216. package/dist/lib/color.d.ts.map +1 -0
  217. package/dist/lib/color.js +177 -0
  218. package/dist/lib/color.js.map +1 -0
  219. package/dist/lib/scroll-map.d.ts +20 -0
  220. package/dist/lib/scroll-map.d.ts.map +1 -0
  221. package/dist/lib/scroll-map.js +46 -0
  222. package/dist/lib/scroll-map.js.map +1 -0
  223. package/dist/lib/text-metrics.d.ts +33 -0
  224. package/dist/lib/text-metrics.d.ts.map +1 -0
  225. package/dist/lib/text-metrics.js +89 -0
  226. package/dist/lib/text-metrics.js.map +1 -0
  227. package/dist/lib/theme-file.d.ts +51 -0
  228. package/dist/lib/theme-file.d.ts.map +1 -0
  229. package/dist/lib/theme-file.js +511 -0
  230. package/dist/lib/theme-file.js.map +1 -0
  231. package/dist/lib/unsaved-guard.d.ts +33 -0
  232. package/dist/lib/unsaved-guard.d.ts.map +1 -0
  233. package/dist/lib/unsaved-guard.js +37 -0
  234. package/dist/lib/unsaved-guard.js.map +1 -0
  235. package/dist/lib/utils.d.ts +3 -0
  236. package/dist/lib/utils.d.ts.map +1 -0
  237. package/dist/lib/utils.js +6 -0
  238. package/dist/lib/utils.js.map +1 -0
  239. package/dist/theme/custom.d.ts +8 -0
  240. package/dist/theme/custom.d.ts.map +1 -0
  241. package/dist/theme/custom.js +56 -0
  242. package/dist/theme/custom.js.map +1 -0
  243. package/dist/theme/index.d.ts +6 -0
  244. package/dist/theme/index.d.ts.map +1 -0
  245. package/dist/theme/index.js +5 -0
  246. package/dist/theme/index.js.map +1 -0
  247. package/dist/theme/paint-guard.d.ts +20 -0
  248. package/dist/theme/paint-guard.d.ts.map +1 -0
  249. package/dist/theme/paint-guard.js +120 -0
  250. package/dist/theme/paint-guard.js.map +1 -0
  251. package/dist/theme/picks.d.ts +22 -0
  252. package/dist/theme/picks.d.ts.map +1 -0
  253. package/dist/theme/picks.js +43 -0
  254. package/dist/theme/picks.js.map +1 -0
  255. package/dist/theme/pref.d.ts +39 -0
  256. package/dist/theme/pref.d.ts.map +1 -0
  257. package/dist/theme/pref.js +129 -0
  258. package/dist/theme/pref.js.map +1 -0
  259. package/dist/theme/provider.d.ts +41 -0
  260. package/dist/theme/provider.d.ts.map +1 -0
  261. package/dist/theme/provider.js +103 -0
  262. package/dist/theme/provider.js.map +1 -0
  263. package/dist/theme/store.d.ts +104 -0
  264. package/dist/theme/store.d.ts.map +1 -0
  265. package/dist/theme/store.js +509 -0
  266. package/dist/theme/store.js.map +1 -0
  267. package/package.json +94 -3
  268. package/styles.css +79 -0
  269. package/theme.css +186 -0
  270. 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