@weasel-js/labkit 1.5.0 → 1.5.2

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 (206) hide show
  1. package/README.md +41 -4
  2. package/dist/_dts/{CanvasStackContext-CqkxkFJW.d.ts → CanvasStackContext-CMMo180l.d.ts} +1 -1
  3. package/dist/_dts/{PrefsForm.d-DDHFANkc.d.ts → PrefsForm.d-DPy9I8It.d.ts} +4 -0
  4. package/dist/_dts/Select.d-ZTiwzYyT.d.ts +479 -0
  5. package/dist/_dts/{frac-8R6UmlvN.d.ts → frac-B4hFJMiN.d.ts} +54 -6
  6. package/dist/_dts/{index-retnAut7.d.ts → index-8LiKe3A-.d.ts} +77 -10
  7. package/dist/_dts/{index-B8uu2ba7.d.ts → index-DlKolEIZ.d.ts} +20 -8
  8. package/dist/_dts/{types-Ca2LCnPq.d.ts → types-Db-3NNk1.d.ts} +28 -4
  9. package/dist/_dts/{usePanZoom-nS798rOF.d.ts → usePanZoom-SSTZe410.d.ts} +4 -47
  10. package/dist/_dts/{useTrialState-jcx_TdU6.d.ts → useTrialState-pnAE-UBD.d.ts} +1 -1
  11. package/dist/canvas/index.d.ts +5 -5
  12. package/dist/canvas/index.js +1 -1
  13. package/dist/chrome/index.d.ts +4 -4
  14. package/dist/chrome/index.js +7 -7
  15. package/dist/chunk-6BUQ77ZV.js +18 -0
  16. package/dist/chunk-6BUQ77ZV.js.map +1 -0
  17. package/dist/{chunk-KJALCDWE.js → chunk-753Y6HE6.js} +9 -9
  18. package/dist/chunk-753Y6HE6.js.map +1 -0
  19. package/dist/{chunk-6ZDGOZQV.js → chunk-A2ACAGW6.js} +72 -20
  20. package/dist/chunk-A2ACAGW6.js.map +1 -0
  21. package/dist/{chunk-NS64DXMN.js → chunk-CKUKF72R.js} +44 -21
  22. package/dist/chunk-CKUKF72R.js.map +1 -0
  23. package/dist/{chunk-MUKOW3TC.js → chunk-D35PFTS7.js} +4 -4
  24. package/dist/{chunk-MUKOW3TC.js.map → chunk-D35PFTS7.js.map} +1 -1
  25. package/dist/{chunk-UU3NJO6N.js → chunk-EMXAIRUJ.js} +3 -3
  26. package/dist/{chunk-UU3NJO6N.js.map → chunk-EMXAIRUJ.js.map} +1 -1
  27. package/dist/{chunk-ZPH5WTWF.js → chunk-ERFMC6WU.js} +142 -66
  28. package/dist/chunk-ERFMC6WU.js.map +1 -0
  29. package/dist/chunk-FKLT3ITC.js +590 -0
  30. package/dist/chunk-FKLT3ITC.js.map +1 -0
  31. package/dist/{chunk-QLSV2N3G.js → chunk-IJJUK4FU.js} +25 -13
  32. package/dist/chunk-IJJUK4FU.js.map +1 -0
  33. package/dist/{chunk-EKIICY6X.js → chunk-KJCUERQF.js} +37 -25
  34. package/dist/chunk-KJCUERQF.js.map +1 -0
  35. package/dist/chunk-LASCBWW7.js +13456 -0
  36. package/dist/chunk-LASCBWW7.js.map +1 -0
  37. package/dist/{chunk-67SJLMC7.js → chunk-LRI3MBGR.js} +45 -12
  38. package/dist/chunk-LRI3MBGR.js.map +1 -0
  39. package/dist/chunk-REOBGYMC.js +150 -0
  40. package/dist/chunk-REOBGYMC.js.map +1 -0
  41. package/dist/{chunk-PGETSEDK.js → chunk-TT5C5S6R.js} +11 -90
  42. package/dist/chunk-TT5C5S6R.js.map +1 -0
  43. package/dist/{chunk-NDRYLVOW.js → chunk-Z76KEZAX.js} +2 -2
  44. package/dist/chunk-Z76KEZAX.js.map +1 -0
  45. package/dist/config/index.d.ts +62 -19
  46. package/dist/config/index.js +6 -4
  47. package/dist/controls/index.d.ts +3 -3
  48. package/dist/controls/index.js +4 -4
  49. package/dist/dragdrop/index.d.ts +3 -3
  50. package/dist/index.d.ts +230 -62
  51. package/dist/index.js +1367 -1698
  52. package/dist/index.js.map +1 -1
  53. package/dist/layers/index.d.ts +4 -4
  54. package/dist/layers/index.js +2 -2
  55. package/dist/loupe/index.d.ts +67 -16
  56. package/dist/loupe/index.js +1 -1
  57. package/dist/passthrough/weasel-ui.d.ts +57 -230
  58. package/dist/passthrough/weasel-ui.js +1 -1
  59. package/dist/primitives/index.d.ts +3 -1
  60. package/dist/primitives/index.js +3 -3
  61. package/dist/state/index.d.ts +5 -5
  62. package/dist/state/index.js +5 -3
  63. package/dist/state/index.js.map +1 -1
  64. package/dist/styles.css +266 -57
  65. package/dist/surface/index.js +1 -1
  66. package/dist/ui/layers/index.js +1 -1
  67. package/dist/undo/index.d.ts +3 -3
  68. package/package.json +9 -9
  69. package/src/annotations/AnnotationOverlay.tsx +3 -0
  70. package/src/annotations/Annotations.less +4 -0
  71. package/src/annotations/Annotations.overlay.test.tsx +20 -0
  72. package/src/annotations/svgNodes.ts +1 -0
  73. package/src/annotations/toolMap.test.ts +9 -5
  74. package/src/annotations/toolMap.ts +3 -0
  75. package/src/canvas/CanvasStack.stories.tsx +1 -1
  76. package/src/canvas/index.ts +0 -9
  77. package/src/canvas/usePanZoom.test.ts +28 -0
  78. package/src/canvas/usePanZoom.ts +11 -4
  79. package/src/chrome/ChromeRegions.stories.tsx +1 -1
  80. package/src/chrome/LabChrome.tsx +21 -5
  81. package/src/chrome/builtins.test.ts +1 -0
  82. package/src/chrome/builtins.tsx +1 -0
  83. package/src/chrome/index.ts +1 -0
  84. package/src/chrome/labTypes.ts +3 -3
  85. package/src/chrome/regions/PaletteRegion.test.tsx +18 -0
  86. package/src/chrome/regions/PaletteRegion.tsx +6 -10
  87. package/src/chrome/regions/SidebarRegion.stories.tsx +44 -0
  88. package/src/chrome/regions/TitleBarRegion.tsx +1 -1
  89. package/src/chrome/regions/ToolbarRegion.tsx +11 -7
  90. package/src/chrome/types.ts +11 -2
  91. package/src/config/auto.test.ts +12 -0
  92. package/src/config/auto.ts +15 -0
  93. package/src/config/autoConfig.test.ts +107 -0
  94. package/src/config/autoConfig.ts +153 -0
  95. package/src/config/builder.test.ts +38 -0
  96. package/src/config/builder.ts +55 -11
  97. package/src/config/index.ts +3 -0
  98. package/src/config/path.ts +7 -2
  99. package/src/config/resolve.test.ts +25 -0
  100. package/src/config/resolve.ts +17 -1
  101. package/src/config/types.ts +32 -2
  102. package/src/config/useResolvedConfig.ts +26 -0
  103. package/src/controls/AutoControls.stories.tsx +88 -0
  104. package/src/controls/ControlPanel.less +23 -0
  105. package/src/controls/ControlPanel.stories.tsx +83 -1
  106. package/src/controls/ControlPanel.test.tsx +457 -4
  107. package/src/controls/ControlPanel.tsx +534 -73
  108. package/src/index.test.ts +0 -2
  109. package/src/index.ts +33 -11
  110. package/src/instrument/serializers.ts +29 -1
  111. package/src/lab/Lab.chrome.test.tsx +16 -0
  112. package/src/lab/Lab.stories.tsx +1 -1
  113. package/src/lab/Lab.surface.test.tsx +7 -2
  114. package/src/lab/Lab.test.tsx +79 -1
  115. package/src/lab/Lab.tsx +124 -16
  116. package/src/lab/LabContext.ts +8 -0
  117. package/src/lab/LabFit.stories.tsx +4 -4
  118. package/src/lab/LabFullChrome.stories.tsx +1 -1
  119. package/src/lab/LabHeader.tsx +0 -1
  120. package/src/lab/LabRoot.test.tsx +56 -0
  121. package/src/lab/LabRoot.tsx +47 -0
  122. package/src/lab/LabShell.less +24 -3
  123. package/src/lab/LabShell.stories.tsx +50 -1
  124. package/src/lab/LabShell.tsx +18 -27
  125. package/src/lab/LabSwitcher.less +10 -10
  126. package/src/lab/LabSwitcher.test.tsx +14 -9
  127. package/src/lab/LabSwitcher.tsx +8 -4
  128. package/src/lab/Workspace.less +1 -1
  129. package/src/lab/Workspace.stories.tsx +1 -1
  130. package/src/lab/Workspace.surface.test.tsx +1 -1
  131. package/src/lab/index.ts +2 -0
  132. package/src/layers/LayerList.less +1 -1
  133. package/src/layers/LayerList.stories.tsx +2 -2
  134. package/src/loupe/AGENTS.md +16 -4
  135. package/src/loupe/LoupeGestures.tsx +67 -0
  136. package/src/loupe/TrialLoupe.tsx +43 -28
  137. package/src/loupe/index.ts +4 -0
  138. package/src/loupe/loupeActions.ts +98 -0
  139. package/src/loupe/useLoupe.test.tsx +59 -2
  140. package/src/loupe/useLoupe.ts +28 -48
  141. package/src/passthrough/weasel-ui.ts +3 -3
  142. package/src/primitives/FloatingPanel.stories.tsx +1 -1
  143. package/src/primitives/FpsMeter.less +6 -1
  144. package/src/primitives/FpsMeter.stories.tsx +1 -1
  145. package/src/primitives/FpsMeter.test.tsx +12 -4
  146. package/src/primitives/FpsMeter.tsx +6 -1
  147. package/src/primitives/JobProgress.stories.tsx +2 -2
  148. package/src/primitives/Legend.stories.tsx +1 -1
  149. package/src/primitives/ScaleIndicator.stories.tsx +1 -1
  150. package/src/primitives/Sidebar.less +7 -0
  151. package/src/primitives/Sidebar.stories.tsx +1 -1
  152. package/src/primitives/Split.test.tsx +25 -0
  153. package/src/primitives/Split.tsx +36 -20
  154. package/src/primitives/StatusBar.less +6 -0
  155. package/src/primitives/StatusBar.stories.tsx +1 -1
  156. package/src/primitives/Toolbar.stories.tsx +1 -1
  157. package/src/specimen/Specimen.less +95 -0
  158. package/src/specimen/Specimen.stories.tsx +13 -0
  159. package/src/specimen/Specimen.test.tsx +13 -0
  160. package/src/specimen/Specimen.tsx +888 -0
  161. package/src/state/Persistence.stories.tsx +4 -4
  162. package/src/state/store.test.ts +65 -0
  163. package/src/state/store.ts +32 -8
  164. package/src/state/types.ts +31 -2
  165. package/src/state/useTrialState.test.tsx +85 -0
  166. package/src/state/useTrialState.ts +9 -1
  167. package/src/state/view.test.ts +4 -4
  168. package/src/styles.less +1 -0
  169. package/src/surface/index.ts +1 -1
  170. package/src/surface/useSurfaceTile.test.tsx +1 -1
  171. package/src/surface/useTiledSurface.test.tsx +65 -10
  172. package/src/surface/useTiledSurface.ts +23 -11
  173. package/src/theme/Interstellar.stories.tsx +2 -2
  174. package/src/theme/interstellar.test.ts +16 -5
  175. package/src/theme/interstellar.theme.json +180 -0
  176. package/src/theme/interstellar.ts +8 -5
  177. package/src/tools/labTool.ts +3 -3
  178. package/src/trial/Trial.annotations.test.tsx +13 -4
  179. package/src/trial/Trial.canvas.test.tsx +36 -0
  180. package/src/trial/Trial.config.test.tsx +75 -2
  181. package/src/trial/Trial.less +4 -0
  182. package/src/trial/Trial.stories.tsx +4 -1
  183. package/src/trial/Trial.test.tsx +3 -0
  184. package/src/trial/Trial.tsx +21 -14
  185. package/src/trial/TrialChrome.tsx +17 -3
  186. package/src/trial/trialOps.test.ts +101 -1
  187. package/src/trial/trialOps.ts +62 -4
  188. package/dist/_dts/ToggleBar.d-DbHoWKIA.d.ts +0 -116
  189. package/dist/chunk-67SJLMC7.js.map +0 -1
  190. package/dist/chunk-6ZDGOZQV.js.map +0 -1
  191. package/dist/chunk-E44SU6XS.js +0 -50
  192. package/dist/chunk-E44SU6XS.js.map +0 -1
  193. package/dist/chunk-EKIICY6X.js.map +0 -1
  194. package/dist/chunk-KJALCDWE.js.map +0 -1
  195. package/dist/chunk-NDRYLVOW.js.map +0 -1
  196. package/dist/chunk-NS64DXMN.js.map +0 -1
  197. package/dist/chunk-PGETSEDK.js.map +0 -1
  198. package/dist/chunk-QLSV2N3G.js.map +0 -1
  199. package/dist/chunk-S2SHZRD7.js +0 -7681
  200. package/dist/chunk-S2SHZRD7.js.map +0 -1
  201. package/dist/chunk-TQBBYTAF.js +0 -280
  202. package/dist/chunk-TQBBYTAF.js.map +0 -1
  203. package/dist/chunk-ZPH5WTWF.js.map +0 -1
  204. package/src/canvas/useOrbit.test.ts +0 -167
  205. package/src/canvas/useOrbit.ts +0 -156
  206. package/src/theme/interstellar.tokens.json +0 -61
@@ -0,0 +1,590 @@
1
+ import { fromConfigFields, isLeafVisible } from './chunk-EMXAIRUJ.js';
2
+ import { resolveAutoConfig, schemaNodeAtPath, valueAtPath, auto } from './chunk-REOBGYMC.js';
3
+ import { O, X, n2, j2, z2, G2, W, V, B, I2, U, r5, b2, a3, H, e } from './chunk-LASCBWW7.js';
4
+ import { isBuiltinToolPref } from '@weasel-js/core';
5
+ import { useMemo, useState, Fragment as Fragment$1, useRef, useEffect } from 'react';
6
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
+
8
+ var gridOf = (pack) => pack === "one-up" ? "auto-color" : "pairs";
9
+ function sectionRows(section, rows) {
10
+ return {
11
+ pack: section.pack ?? rows.pack,
12
+ layout: section.layout ?? rows.layout,
13
+ grid: section.pack ? gridOf(section.pack) : rows.grid
14
+ };
15
+ }
16
+ var NO_AUTO = /* @__PURE__ */ new Set();
17
+ function ControlPanel({
18
+ schema,
19
+ fields,
20
+ config,
21
+ setConfig,
22
+ renderers,
23
+ pack = "pairs",
24
+ layout,
25
+ density,
26
+ align,
27
+ collapse,
28
+ collapsed,
29
+ onCollapse,
30
+ auto: given,
31
+ showHidden = false,
32
+ className
33
+ }) {
34
+ const resolved = useMemo(() => schema ?? fromConfigFields(fields ?? []), [schema, fields]);
35
+ const [ownAuto, setOwnAuto] = useState(NO_AUTO);
36
+ const auto2 = given ?? ownAuto;
37
+ const setRowAuto = (path, next, value) => {
38
+ if (given) {
39
+ setConfig(path, next ? auto : value);
40
+ return;
41
+ }
42
+ setOwnAuto((prev) => {
43
+ const now = new Set(prev);
44
+ if (next) now.add(path);
45
+ else now.delete(path);
46
+ return now;
47
+ });
48
+ if (!next) setConfig(path, value);
49
+ };
50
+ const shown = useMemo(
51
+ () => auto2 && auto2.size > 0 ? resolveAutoConfig(resolved, config, auto2) : config,
52
+ [resolved, config, auto2]
53
+ );
54
+ const gridPack = gridOf(pack);
55
+ const folds = collapse !== void 0 || collapsed !== void 0 || onCollapse !== void 0 || resolved.sections.some((s) => s.collapsed !== void 0);
56
+ const startsFolded = collapse === "closed";
57
+ const fold = (key, declared, grid = gridPack) => ({
58
+ pack: grid,
59
+ collapsible: folds,
60
+ defaultCollapsed: declared ?? startsFolded,
61
+ collapsed: collapsed ? collapsed[key] ?? declared ?? startsFolded : void 0,
62
+ onCollapsedChange: onCollapse ? (next) => onCollapse(key, next) : void 0
63
+ });
64
+ const node = (path, rows) => {
65
+ const found = schemaNodeAtPath(resolved.group, path);
66
+ if (!found) return null;
67
+ if (!isLeafVisible(resolved, path, config, showHidden)) return null;
68
+ if (n2(found)) {
69
+ return /* @__PURE__ */ jsx(
70
+ ControlRow,
71
+ {
72
+ path,
73
+ leaf: found,
74
+ resolved,
75
+ config,
76
+ shown,
77
+ setConfig,
78
+ renderers,
79
+ pack: rows.pack,
80
+ layout: rows.layout,
81
+ auto: auto2,
82
+ setRowAuto
83
+ },
84
+ path
85
+ );
86
+ }
87
+ if (found.name === "") return /* @__PURE__ */ jsx(Fragment$1, { children: body(found, path, rows) }, path);
88
+ return /* @__PURE__ */ jsx(
89
+ X,
90
+ {
91
+ title: found.name,
92
+ description: found.description,
93
+ span: true,
94
+ ...fold(path, void 0, rows.grid),
95
+ children: body(found, path, rows)
96
+ },
97
+ path
98
+ );
99
+ };
100
+ const pairable = (path) => {
101
+ const found = schemaNodeAtPath(resolved.group, path);
102
+ if (!found || !n2(found)) return void 0;
103
+ const pair = extra(found, "pair");
104
+ if (pair === void 0) return void 0;
105
+ if (!isLeafVisible(resolved, path, config, showHidden))
106
+ return void 0;
107
+ if (renderers?.[path] ?? resolved.renderers[path] ?? renderers?.[found.kind]) return void 0;
108
+ if (!isBuiltinToolPref(found)) return void 0;
109
+ if (found.kind === "paint" || found.kind === "object" || isSliderLeaf(found)) return void 0;
110
+ return { leaf: found, pair };
111
+ };
112
+ const rowsFor = (paths, rows) => {
113
+ const out = [];
114
+ for (let i = 0; i < paths.length; ) {
115
+ const head = pairable(paths[i]);
116
+ if (!head) {
117
+ out.push(node(paths[i], rows));
118
+ i += 1;
119
+ continue;
120
+ }
121
+ const { pair } = head;
122
+ const cells = [{ path: paths[i], leaf: head.leaf }];
123
+ let j2 = i + 1;
124
+ for (; j2 < paths.length; j2 += 1) {
125
+ const next = pairable(paths[j2]);
126
+ if (!next || next.pair !== pair) break;
127
+ cells.push({ path: paths[j2], leaf: next.leaf });
128
+ }
129
+ out.push(
130
+ /* @__PURE__ */ jsx(
131
+ PairedRow,
132
+ {
133
+ label: pair,
134
+ cells,
135
+ shown,
136
+ setConfig,
137
+ layout: rows.layout,
138
+ span: cells.some(({ leaf }) => extra(leaf, "control") === "radio")
139
+ },
140
+ paths[i]
141
+ )
142
+ );
143
+ i = j2;
144
+ }
145
+ return out;
146
+ };
147
+ const body = (group, at, rows) => {
148
+ const sections = resolved.sections.filter((s) => s.at === at);
149
+ const sectioned = new Set(sections.flatMap((s) => s.paths));
150
+ const paths = Object.keys(group.children).map((key) => at === "" ? key : `${at}.${key}`);
151
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
152
+ rowsFor(
153
+ paths.filter((p) => !sectioned.has(p)),
154
+ rows
155
+ ),
156
+ sections.map((section) => {
157
+ const inner = sectionRows(section, rows);
158
+ return /* @__PURE__ */ jsx(
159
+ X,
160
+ {
161
+ title: section.label,
162
+ ...fold(sectionKey(section), section.collapsed, inner.grid),
163
+ children: rowsFor(section.paths, inner)
164
+ },
165
+ sectionKey(section)
166
+ );
167
+ })
168
+ ] });
169
+ };
170
+ return /* @__PURE__ */ jsx(
171
+ O,
172
+ {
173
+ pack: gridPack,
174
+ density,
175
+ align,
176
+ className: className ? `lk-control-panel ${className}` : "lk-control-panel",
177
+ children: body(resolved.group, "", { pack, layout, grid: gridPack })
178
+ }
179
+ );
180
+ }
181
+ function sectionKey(section) {
182
+ return section.at === "" ? section.label : `${section.at}.${section.label}`;
183
+ }
184
+ function isSliderLeaf(leaf) {
185
+ return leaf.kind === "number" && extra(leaf, "control") === "slider" && extra(leaf, "min") !== void 0 && extra(leaf, "max") !== void 0;
186
+ }
187
+ function extra(leaf, key) {
188
+ return leaf[key];
189
+ }
190
+ function ControlRow({
191
+ path,
192
+ leaf,
193
+ resolved,
194
+ config,
195
+ shown,
196
+ setConfig,
197
+ renderers,
198
+ pack,
199
+ layout,
200
+ auto: auto2,
201
+ setRowAuto
202
+ }) {
203
+ const write = (value2) => setConfig(path, value2);
204
+ const fallback = extra(leaf, "default");
205
+ const pinned = valueAtPath(config, path) ?? fallback;
206
+ const isAutoRow = auto2?.has(path) ?? false;
207
+ const canAuto = !extra(leaf, "manual");
208
+ const resolvedValue = isAutoRow ? valueAtPath(shown, path) : void 0;
209
+ const value = resolvedValue ?? pinned;
210
+ const setAuto = (next) => setRowAuto(path, next, value);
211
+ const onAutoChange = canAuto ? setAuto : void 0;
212
+ const autoReadout = isAutoRow ? "auto" : void 0;
213
+ const autoProps = { auto: isAutoRow, onAutoChange };
214
+ const custom = renderers?.[path] ?? resolved.renderers[path] ?? renderers?.[leaf.kind];
215
+ if (custom) return custom({ path, pref: leaf, value, setValue: write, auto: isAutoRow, setAuto });
216
+ const label = leaf.name;
217
+ const description = leaf.description;
218
+ const read = () => value;
219
+ const wide = pack === "auto";
220
+ if (!isBuiltinToolPref(leaf))
221
+ return /* @__PURE__ */ jsx(UnwiredRow, { label, kind: leaf.kind, description });
222
+ switch (leaf.kind) {
223
+ case "number": {
224
+ const field = numberField(leaf, read());
225
+ const suffix = extra(leaf, "suffix") ?? field.unit?.suffix;
226
+ const notation = extra(leaf, "format");
227
+ if (isSliderLeaf(leaf)) {
228
+ return /* @__PURE__ */ jsx(
229
+ I2,
230
+ {
231
+ label,
232
+ value: field.shown,
233
+ min: field.min,
234
+ max: field.max,
235
+ step: field.step,
236
+ notation,
237
+ unit: suffix,
238
+ onChange: (n2) => write(field.store(n2)),
239
+ readout: autoReadout,
240
+ layout,
241
+ span: wide,
242
+ description,
243
+ ...autoProps
244
+ }
245
+ );
246
+ }
247
+ return /* @__PURE__ */ jsx(
248
+ U,
249
+ {
250
+ label,
251
+ value: field.shown,
252
+ min: field.min,
253
+ max: field.max,
254
+ step: field.step,
255
+ unit: suffix,
256
+ onChange: (n2) => write(field.store(n2)),
257
+ readout: autoReadout,
258
+ layout,
259
+ description,
260
+ ...autoProps
261
+ }
262
+ );
263
+ }
264
+ case "boolean": {
265
+ const Row = extra(leaf, "control") === "switch" ? V : B;
266
+ return /* @__PURE__ */ jsx(
267
+ Row,
268
+ {
269
+ label,
270
+ value: read(),
271
+ onChange: write,
272
+ readout: autoReadout,
273
+ layout,
274
+ description,
275
+ ...autoProps
276
+ }
277
+ );
278
+ }
279
+ case "enum": {
280
+ const options = extra(leaf, "options") ?? [];
281
+ const segmented = extra(leaf, "control") === "radio";
282
+ const Row = segmented ? G2 : W;
283
+ return /* @__PURE__ */ jsx(
284
+ Row,
285
+ {
286
+ label,
287
+ value: read(),
288
+ options,
289
+ onChange: write,
290
+ readout: autoReadout,
291
+ layout,
292
+ span: segmented && wide,
293
+ description,
294
+ ...autoProps
295
+ }
296
+ );
297
+ }
298
+ case "string":
299
+ return /* @__PURE__ */ jsx(
300
+ DebouncedTextRow,
301
+ {
302
+ leaf,
303
+ label,
304
+ value: read(),
305
+ write,
306
+ readout: autoReadout,
307
+ layout,
308
+ span: wide,
309
+ description,
310
+ ...autoProps
311
+ }
312
+ );
313
+ case "color": {
314
+ const common = {
315
+ label,
316
+ readout: autoReadout,
317
+ layout,
318
+ description,
319
+ ...autoProps
320
+ };
321
+ if (extra(leaf, "alpha") !== true)
322
+ return /* @__PURE__ */ jsx(z2, { ...common, value: read(), onChange: write });
323
+ const { rgb, alpha } = splitHexAlpha(read());
324
+ return /* @__PURE__ */ jsx(
325
+ z2,
326
+ {
327
+ ...common,
328
+ value: rgb,
329
+ onChange: (next) => write(joinHexAlpha(next, alpha)),
330
+ alpha,
331
+ onAlphaChange: (next) => write(joinHexAlpha(rgb, next))
332
+ }
333
+ );
334
+ }
335
+ case "paint":
336
+ case "object":
337
+ return /* @__PURE__ */ jsx(UnwiredRow, { label, kind: leaf.kind, description });
338
+ default: {
339
+ const _exhaustive = leaf;
340
+ throw new Error(
341
+ `ControlPanel: no control for built-in pref kind "${_exhaustive.kind}"`
342
+ );
343
+ }
344
+ }
345
+ }
346
+ function UnwiredRow({
347
+ label,
348
+ kind,
349
+ description
350
+ }) {
351
+ return /* @__PURE__ */ jsx(j2, { label, description, children: /* @__PURE__ */ jsxs("span", { className: "lk-control-panel__unknown", children: [
352
+ "no control for \u201C",
353
+ kind,
354
+ "\u201D"
355
+ ] }) });
356
+ }
357
+ function DebouncedTextRow({
358
+ leaf,
359
+ label,
360
+ value,
361
+ write,
362
+ layout,
363
+ span,
364
+ description,
365
+ readout,
366
+ auto: auto2,
367
+ onAutoChange
368
+ }) {
369
+ const text = useDebouncedText(
370
+ value,
371
+ write,
372
+ extra(leaf, "debounceMs") ?? 150
373
+ );
374
+ return /* @__PURE__ */ jsx(
375
+ H,
376
+ {
377
+ label,
378
+ readout,
379
+ layout,
380
+ span,
381
+ description,
382
+ auto: auto2,
383
+ onAutoChange,
384
+ value: text.local,
385
+ placeholder: extra(leaf, "placeholder"),
386
+ maxLength: extra(leaf, "maxLength"),
387
+ onChange: text.type
388
+ }
389
+ );
390
+ }
391
+ function numberField(leaf, value) {
392
+ const unit = extra(leaf, "unit");
393
+ const bounds = unit ? e(leaf) : {
394
+ min: extra(leaf, "min"),
395
+ max: extra(leaf, "max"),
396
+ step: extra(leaf, "step")
397
+ };
398
+ const lo = bounds.min ?? Number.NEGATIVE_INFINITY;
399
+ const hi = bounds.max ?? Number.POSITIVE_INFINITY;
400
+ return {
401
+ unit,
402
+ min: bounds.min,
403
+ max: bounds.max,
404
+ step: bounds.step,
405
+ shown: unit ? unit.toDisplay(value) : value,
406
+ store: (shown) => {
407
+ const clamped = Math.min(hi, Math.max(lo, shown));
408
+ return unit ? unit.fromDisplay(clamped) : clamped;
409
+ }
410
+ };
411
+ }
412
+ function splitHexAlpha(value) {
413
+ const digits = /^#([0-9a-f]{3,8})$/i.exec(value?.trim() ?? "")?.[1];
414
+ const twice = (s) => s.split("").map((c) => c + c).join("");
415
+ const byte = (s) => Number.parseInt(s, 16) / 255;
416
+ switch (digits?.length) {
417
+ case 3:
418
+ return { rgb: `#${twice(digits)}`, alpha: 1 };
419
+ case 4:
420
+ return { rgb: `#${twice(digits.slice(0, 3))}`, alpha: byte(twice(digits.slice(3))) };
421
+ case 6:
422
+ return { rgb: `#${digits}`, alpha: 1 };
423
+ case 8:
424
+ return { rgb: `#${digits.slice(0, 6)}`, alpha: byte(digits.slice(6)) };
425
+ default:
426
+ return { rgb: "#000000", alpha: 1 };
427
+ }
428
+ }
429
+ function joinHexAlpha(rgb, alpha) {
430
+ const byte = Math.max(0, Math.min(255, Math.round(alpha * 255)));
431
+ return `${rgb}${byte.toString(16).padStart(2, "0")}`;
432
+ }
433
+ function PairedRow({
434
+ label,
435
+ cells,
436
+ shown,
437
+ setConfig,
438
+ layout,
439
+ span
440
+ }) {
441
+ return /* @__PURE__ */ jsx(j2, { group: true, span, label, layout, children: cells.map(({ path, leaf }) => (
442
+ // The row is named by the pair, so a cell that shares it says which
443
+ // knob it is — without a caption a paired switch reads only to a
444
+ // screen reader. A cell whose leaf is deliberately unnamed keeps none.
445
+ /* @__PURE__ */ jsxs("span", { className: "lk-pair-cell", children: [
446
+ leaf.name ? /* @__PURE__ */ jsx("span", { className: "lk-pair-label", children: leaf.name }) : null,
447
+ /* @__PURE__ */ jsx(
448
+ PairCell,
449
+ {
450
+ leaf,
451
+ value: valueAtPath(shown, path) ?? extra(leaf, "default"),
452
+ write: (value) => setConfig(path, value)
453
+ }
454
+ )
455
+ ] }, path)
456
+ )) });
457
+ }
458
+ function PairCell({
459
+ leaf,
460
+ value,
461
+ write
462
+ }) {
463
+ const name = leaf.name;
464
+ const title = leaf.description;
465
+ const text = useDebouncedText(
466
+ typeof value === "string" ? value : "",
467
+ write,
468
+ extra(leaf, "debounceMs") ?? 150
469
+ );
470
+ switch (leaf.kind) {
471
+ case "number": {
472
+ const field = numberField(leaf, typeof value === "number" ? value : 0);
473
+ return /* @__PURE__ */ jsx(
474
+ "input",
475
+ {
476
+ type: "number",
477
+ "aria-label": name,
478
+ title,
479
+ value: field.shown,
480
+ min: field.min,
481
+ max: field.max,
482
+ step: field.step,
483
+ onChange: (e2) => {
484
+ const n2 = Number(e2.target.value);
485
+ if (e2.target.value !== "" && Number.isFinite(n2)) write(field.store(n2));
486
+ }
487
+ }
488
+ );
489
+ }
490
+ case "boolean":
491
+ return extra(leaf, "control") === "switch" ? /* @__PURE__ */ jsx(a3, { "aria-label": name, isSelected: value === true, onChange: write }) : /* @__PURE__ */ jsx(
492
+ "input",
493
+ {
494
+ type: "checkbox",
495
+ "aria-label": name,
496
+ title,
497
+ checked: value === true,
498
+ onChange: (e2) => write(e2.target.checked)
499
+ }
500
+ );
501
+ case "string":
502
+ return /* @__PURE__ */ jsx(
503
+ "input",
504
+ {
505
+ type: "text",
506
+ "aria-label": name,
507
+ title,
508
+ value: text.local,
509
+ placeholder: extra(leaf, "placeholder"),
510
+ maxLength: extra(leaf, "maxLength"),
511
+ onChange: (e2) => text.type(e2.target.value)
512
+ }
513
+ );
514
+ case "color": {
515
+ const { rgb, alpha } = splitHexAlpha(typeof value === "string" ? value : "");
516
+ const carriesAlpha = extra(leaf, "alpha") === true;
517
+ return /* @__PURE__ */ jsx(
518
+ "input",
519
+ {
520
+ type: "color",
521
+ "aria-label": name,
522
+ title,
523
+ value: rgb,
524
+ onChange: (e2) => write(carriesAlpha ? joinHexAlpha(e2.target.value, alpha) : e2.target.value)
525
+ }
526
+ );
527
+ }
528
+ case "enum": {
529
+ const options = extra(leaf, "options") ?? [];
530
+ if (extra(leaf, "control") === "radio") {
531
+ return /* @__PURE__ */ jsx("span", { title, children: /* @__PURE__ */ jsx(
532
+ r5,
533
+ {
534
+ ariaLabel: name,
535
+ size: "sm",
536
+ value: typeof value === "string" ? value : null,
537
+ onChange: (next) => next !== null && write(next),
538
+ items: options.map((o) => ({ value: o.value, label: o.label }))
539
+ }
540
+ ) });
541
+ }
542
+ return /* @__PURE__ */ jsx("span", { title, children: /* @__PURE__ */ jsx(
543
+ b2,
544
+ {
545
+ variant: "bare",
546
+ "aria-label": name,
547
+ options,
548
+ selectedKey: typeof value === "string" ? value : null,
549
+ onSelectionChange: write
550
+ }
551
+ ) });
552
+ }
553
+ default:
554
+ return null;
555
+ }
556
+ }
557
+ function useDebouncedText(value, write, debounceMs) {
558
+ const [local, setLocal] = useState(value);
559
+ const timer = useRef(null);
560
+ const lastExternal = useRef(value);
561
+ useEffect(() => {
562
+ if (value !== lastExternal.current) {
563
+ lastExternal.current = value;
564
+ setLocal(value);
565
+ }
566
+ }, [value]);
567
+ useEffect(
568
+ () => () => {
569
+ if (timer.current) clearTimeout(timer.current);
570
+ },
571
+ []
572
+ );
573
+ return {
574
+ local,
575
+ type: (next) => {
576
+ setLocal(next);
577
+ if (timer.current) clearTimeout(timer.current);
578
+ const commit = () => {
579
+ lastExternal.current = next;
580
+ write(next);
581
+ };
582
+ if (debounceMs === 0) commit();
583
+ else timer.current = setTimeout(commit, debounceMs);
584
+ }
585
+ };
586
+ }
587
+
588
+ export { ControlPanel };
589
+ //# sourceMappingURL=chunk-FKLT3ITC.js.map
590
+ //# sourceMappingURL=chunk-FKLT3ITC.js.map