@weasel-js/labkit 1.6.1 → 1.7.1

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 (256) hide show
  1. package/README.md +3 -3
  2. package/dist/_dts/{CanvasStackContext-Wo6OwIVM.d.ts → CanvasStackContext-Cjy6I-Iv.d.ts} +4 -1
  3. package/dist/_dts/{Stage-DWBBi8IU.d.ts → Stage-D6t7o0pF.d.ts} +59 -18
  4. package/dist/_dts/{index-CKyX0ME_.d.ts → index-BTmaRy6a.d.ts} +10 -6
  5. package/dist/_dts/{types-B7yBTTF9.d.ts → index-DCMA4Fy1.d.ts} +59 -113
  6. package/dist/_dts/{types-DTuViBvc.d.ts → types-BUBSIZuV.d.ts} +18 -8
  7. package/dist/_dts/{useTrialState-CMaV6UD4.d.ts → useTrialState-Bh8bzFgE.d.ts} +1 -1
  8. package/dist/canvas/index.d.ts +8 -6
  9. package/dist/canvas/index.js +6 -6
  10. package/dist/chrome/index.d.ts +6 -4
  11. package/dist/chrome/index.js +11 -10
  12. package/dist/chunk-2ES4W47N.js +8 -0
  13. package/dist/chunk-2ES4W47N.js.map +1 -0
  14. package/dist/{chunk-WPLAANH4.js → chunk-2N7UBSS2.js} +12 -12
  15. package/dist/chunk-2N7UBSS2.js.map +1 -0
  16. package/dist/chunk-4KSC7NXL.js +15491 -0
  17. package/dist/chunk-4KSC7NXL.js.map +1 -0
  18. package/dist/{chunk-SLAQPUI4.js → chunk-CEGFXTBD.js} +10 -63
  19. package/dist/chunk-CEGFXTBD.js.map +1 -0
  20. package/dist/chunk-CZZPXUOU.js +3 -0
  21. package/dist/{chunk-SBXNQW4G.js.map → chunk-CZZPXUOU.js.map} +1 -1
  22. package/dist/{chunk-QGMOA2YE.js → chunk-E23Q6A3Y.js} +296 -104
  23. package/dist/chunk-E23Q6A3Y.js.map +1 -0
  24. package/dist/{chunk-GHGO52SO.js → chunk-E6NARGU7.js} +5 -5
  25. package/dist/{chunk-GHGO52SO.js.map → chunk-E6NARGU7.js.map} +1 -1
  26. package/dist/{chunk-IJJUK4FU.js → chunk-EESJUSYR.js} +5 -6
  27. package/dist/chunk-EESJUSYR.js.map +1 -0
  28. package/dist/{chunk-HXXTULDN.js → chunk-EFXRM6XQ.js} +8 -11
  29. package/dist/chunk-EFXRM6XQ.js.map +1 -0
  30. package/dist/chunk-F4ACIK7Z.js +210 -0
  31. package/dist/chunk-F4ACIK7Z.js.map +1 -0
  32. package/dist/{chunk-6FLXL3I7.js → chunk-FB4MIZOO.js} +77 -45
  33. package/dist/chunk-FB4MIZOO.js.map +1 -0
  34. package/dist/{chunk-AWQG7FCC.js → chunk-FG3EXZDV.js} +3 -3
  35. package/dist/{chunk-AWQG7FCC.js.map → chunk-FG3EXZDV.js.map} +1 -1
  36. package/dist/{chunk-4TT3PNPR.js → chunk-GAHELLJT.js} +95 -95
  37. package/dist/chunk-GAHELLJT.js.map +1 -0
  38. package/dist/{chunk-HKV7LRHY.js → chunk-IBVRMHW5.js} +5 -5
  39. package/dist/{chunk-HKV7LRHY.js.map → chunk-IBVRMHW5.js.map} +1 -1
  40. package/dist/{chunk-ASOK76ZH.js → chunk-JFOJESJF.js} +72 -29
  41. package/dist/chunk-JFOJESJF.js.map +1 -0
  42. package/dist/{chunk-RWNDDKOH.js → chunk-PMLZP7RT.js} +18 -15
  43. package/dist/chunk-PMLZP7RT.js.map +1 -0
  44. package/dist/{chunk-TJ77A4A3.js → chunk-READTDQX.js} +5 -9
  45. package/dist/chunk-READTDQX.js.map +1 -0
  46. package/dist/{chunk-JVBYV6TL.js → chunk-ROGJWDQA.js} +3 -3
  47. package/dist/{chunk-JVBYV6TL.js.map → chunk-ROGJWDQA.js.map} +1 -1
  48. package/dist/{chunk-CGHF6RZV.js → chunk-T6DLGEJB.js} +18 -18
  49. package/dist/chunk-T6DLGEJB.js.map +1 -0
  50. package/dist/{chunk-24W5VEJW.js → chunk-ULYYNWGP.js} +2 -2
  51. package/dist/{chunk-24W5VEJW.js.map → chunk-ULYYNWGP.js.map} +1 -1
  52. package/dist/{chunk-DWMSB4CA.js → chunk-VYVGVBGY.js} +95 -32
  53. package/dist/chunk-VYVGVBGY.js.map +1 -0
  54. package/dist/{chunk-IRTGUQ5O.js → chunk-WLMDTI2Q.js} +16 -6
  55. package/dist/chunk-WLMDTI2Q.js.map +1 -0
  56. package/dist/{chunk-NEXR37YJ.js → chunk-XE5HJOPL.js} +6 -8
  57. package/dist/chunk-XE5HJOPL.js.map +1 -0
  58. package/dist/chunk-XVUKDSYV.js +38 -0
  59. package/dist/chunk-XVUKDSYV.js.map +1 -0
  60. package/dist/{chunk-O6FFFOHS.js → chunk-YCFKW5RX.js} +16 -8
  61. package/dist/chunk-YCFKW5RX.js.map +1 -0
  62. package/dist/{chunk-CQLQPQ4P.js → chunk-ZQIHJXP4.js} +2 -2
  63. package/dist/chunk-ZQIHJXP4.js.map +1 -0
  64. package/dist/config/index.d.ts +28 -13
  65. package/dist/config/index.js +4 -4
  66. package/dist/controls/index.d.ts +4 -7
  67. package/dist/controls/index.js +6 -5
  68. package/dist/dragdrop/index.d.ts +23 -3
  69. package/dist/dragdrop/index.js +4 -3
  70. package/dist/fonts/oswald-tabular-digits.woff2 +0 -0
  71. package/dist/index.d.ts +72 -487
  72. package/dist/index.js +603 -441
  73. package/dist/index.js.map +1 -1
  74. package/dist/job/index.d.ts +8 -0
  75. package/dist/job/index.js +1 -1
  76. package/dist/layers/index.d.ts +6 -5
  77. package/dist/layers/index.js +3 -2
  78. package/dist/loupe/index.d.ts +100 -30
  79. package/dist/loupe/index.js +585 -6
  80. package/dist/loupe/index.js.map +1 -1
  81. package/dist/overview/index.d.ts +4 -2
  82. package/dist/overview/index.js +20 -18
  83. package/dist/overview/index.js.map +1 -1
  84. package/dist/passthrough/weasel-canvas.d.ts +2 -0
  85. package/dist/passthrough/weasel-ui.d.ts +2 -2701
  86. package/dist/passthrough/weasel-ui.js +4 -3
  87. package/dist/primitives/index.d.ts +37 -2
  88. package/dist/primitives/index.js +9 -8
  89. package/dist/state/index.d.ts +7 -5
  90. package/dist/state/index.js +6 -6
  91. package/dist/styles.css +150 -126
  92. package/dist/surface/index.d.ts +10 -2
  93. package/dist/surface/index.js +1 -1
  94. package/dist/undo/index.d.ts +5 -3
  95. package/package.json +14 -13
  96. package/src/annotations/AnnotationOverlay.abandon.test.tsx +143 -0
  97. package/src/annotations/AnnotationOverlay.tsx +52 -33
  98. package/src/annotations/Annotations.less +0 -10
  99. package/src/annotations/Annotations.overlay.test.tsx +31 -0
  100. package/src/annotations/MarkList.tsx +4 -8
  101. package/src/annotations/capture.browser.test.ts +41 -0
  102. package/src/annotations/capture.ts +14 -7
  103. package/src/annotations/drawOne.ts +2 -2
  104. package/src/annotations/frac.test.ts +44 -1
  105. package/src/annotations/frac.ts +46 -18
  106. package/src/annotations/index.ts +1 -0
  107. package/src/annotations/paint.test.ts +76 -46
  108. package/src/annotations/paint.ts +65 -43
  109. package/src/annotations/store.test.ts +212 -2
  110. package/src/annotations/store.ts +130 -27
  111. package/src/annotations/svgNodes.test.ts +28 -19
  112. package/src/annotations/svgNodes.ts +6 -6
  113. package/src/annotations/types.ts +29 -10
  114. package/src/canvas/AGENTS.md +1 -0
  115. package/src/canvas/CameraInput.test.tsx +30 -0
  116. package/src/canvas/CameraInput.tsx +60 -48
  117. package/src/canvas/CanvasStack.tsx +11 -6
  118. package/src/canvas/CanvasStackContext.ts +4 -1
  119. package/src/canvas/LinkedCursor.less +1 -1
  120. package/src/canvas/Stage.test.tsx +33 -4
  121. package/src/canvas/Stage.tsx +9 -4
  122. package/src/canvas/cameraRegistry.test.ts +55 -0
  123. package/src/canvas/cameraRegistry.ts +87 -0
  124. package/src/canvas/cameraZoom.test.ts +46 -0
  125. package/src/canvas/cameraZoom.ts +24 -0
  126. package/src/canvas/index.ts +14 -2
  127. package/src/canvas/sharedScope.test.tsx +94 -0
  128. package/src/canvas/worldSpec.ts +3 -1
  129. package/src/chrome/ChromeRegions.stories.tsx +1 -0
  130. package/src/chrome/builtins.test.ts +7 -6
  131. package/src/chrome/builtins.tsx +5 -1
  132. package/src/chrome/index.ts +1 -1
  133. package/src/chrome/merge.test.ts +5 -0
  134. package/src/chrome/merge.ts +4 -2
  135. package/src/chrome/regions/PaletteRegion.less +0 -7
  136. package/src/chrome/regions/PaletteRegion.test.tsx +2 -3
  137. package/src/chrome/regions/PaletteRegion.tsx +3 -4
  138. package/src/chrome/regions/SidebarRegion.less +12 -2
  139. package/src/chrome/regions/ViewportRegion.browser.test.less +6 -0
  140. package/src/chrome/regions/ViewportRegion.browser.test.tsx +38 -0
  141. package/src/chrome/regions/ViewportRegion.less +6 -0
  142. package/src/chrome/regions/ViewportRegion.tsx +10 -4
  143. package/src/chrome/types.ts +4 -2
  144. package/src/config/auto.ts +1 -0
  145. package/src/config/builder.test.ts +2 -1
  146. package/src/config/builder.ts +18 -5
  147. package/src/config/fromConfigField.test.ts +14 -0
  148. package/src/config/fromConfigField.ts +18 -4
  149. package/src/config/path.ts +2 -5
  150. package/src/config/resolve.ts +4 -0
  151. package/src/config/types.ts +14 -9
  152. package/src/controls/ControlPanel.less +1 -1
  153. package/src/controls/ControlPanel.stories.tsx +3 -2
  154. package/src/controls/ControlPanel.test.tsx +98 -11
  155. package/src/controls/ControlPanel.tsx +61 -2
  156. package/src/dragdrop/DragDropRuntime.tsx +22 -13
  157. package/src/dragdrop/DragGhost.tsx +3 -0
  158. package/src/dragdrop/Palette.tsx +2 -0
  159. package/src/index.test.ts +5 -0
  160. package/src/index.ts +16 -2
  161. package/src/instrument/types.ts +0 -6
  162. package/src/job/useJob.ts +14 -9
  163. package/src/lab/Lab.tsx +88 -73
  164. package/src/lab/{LabFit.stories.less → LabFit.browser.test.less} +1 -1
  165. package/src/lab/{LabFit.stories.tsx → LabFit.browser.test.tsx} +41 -63
  166. package/src/lab/LabFullChrome.stories.tsx +1 -0
  167. package/src/lab/LabHeader.test.tsx +24 -14
  168. package/src/lab/LabHeader.tsx +11 -12
  169. package/src/lab/LabShell.less +9 -17
  170. package/src/lab/LabSwitcher.browser.test.tsx +27 -0
  171. package/src/lab/LabSwitcher.less +8 -6
  172. package/src/lab/LabZoom.test.tsx +223 -0
  173. package/src/lab/LabZoom.tsx +170 -0
  174. package/src/lab/Workspace.tsx +48 -52
  175. package/src/lab/index.ts +1 -0
  176. package/src/loupe/AGENTS.md +28 -15
  177. package/src/loupe/CanvasLoupe.tsx +2 -3
  178. package/src/loupe/DomLoupe.tsx +1 -7
  179. package/src/loupe/Loupe.less +16 -0
  180. package/src/loupe/LoupeGestures.tsx +1 -1
  181. package/src/loupe/TrialLoupe.tsx +120 -57
  182. package/src/loupe/index.ts +1 -6
  183. package/src/loupe/types.test.ts +3 -3
  184. package/src/loupe/types.ts +24 -27
  185. package/src/loupe/useLoupe.test.tsx +29 -4
  186. package/src/loupe/useLoupe.ts +20 -17
  187. package/src/overview/OverviewMarks.tsx +12 -4
  188. package/src/overview/TrialOverview.tsx +10 -8
  189. package/src/passthrough/weasel-ui.test.ts +2 -0
  190. package/src/passthrough/weasel-ui.ts +17 -5
  191. package/src/primitives/FloatingPanel.test.tsx +11 -0
  192. package/src/primitives/FloatingPanel.tsx +8 -7
  193. package/src/primitives/JobProgress.less +2 -1
  194. package/src/primitives/Legend.stories.tsx +1 -0
  195. package/src/primitives/Readout.browser.test.tsx +137 -0
  196. package/src/primitives/Readout.less +12 -0
  197. package/src/primitives/Readout.stories.tsx +58 -0
  198. package/src/primitives/Readout.test.tsx +93 -0
  199. package/src/primitives/Readout.tsx +56 -0
  200. package/src/primitives/Split.tsx +69 -41
  201. package/src/primitives/Toolbar.tsx +3 -3
  202. package/src/primitives/ZoomControl.less +1 -2
  203. package/src/primitives/index.ts +2 -0
  204. package/src/specimen/Specimen.stories.tsx +1 -0
  205. package/src/specimen/Specimen.tsx +2 -3
  206. package/src/state/Persistence.browser.test.tsx +47 -0
  207. package/src/state/store.test.ts +5 -1
  208. package/src/state/undock.ts +3 -0
  209. package/src/state/useOpenOnce.ts +20 -16
  210. package/src/state/usePersistedState.test.tsx +1 -1
  211. package/src/state/view.ts +1 -0
  212. package/src/styles.less +1 -0
  213. package/src/surface/index.ts +1 -1
  214. package/src/surface/useTiledSurface.test.tsx +14 -0
  215. package/src/surface/useTiledSurface.ts +11 -3
  216. package/src/theme/Interstellar.stories.tsx +3 -1
  217. package/src/theme/base.less +21 -47
  218. package/src/theme/interstellar.test.ts +1 -1
  219. package/src/theme/interstellar.theme.json +10 -3
  220. package/src/trial/Trial.annotations.test.tsx +73 -0
  221. package/src/trial/Trial.less +0 -29
  222. package/src/trial/Trial.loupe.test.tsx +28 -11
  223. package/src/trial/Trial.tsx +85 -67
  224. package/src/trial/TrialChrome.tsx +27 -6
  225. package/src/trial/index.ts +2 -0
  226. package/src/trial/loupeSwitch.ts +15 -0
  227. package/dist/_dts/LayerList.d-BoELAMsM.d.ts +0 -83
  228. package/dist/_dts/PrefsRow.d-DFpPIeIC.d.ts +0 -39
  229. package/dist/_dts/PropertyField.d-D0ndfIIt.d.ts +0 -404
  230. package/dist/_dts/Select.d-Cr8-zoup.d.ts +0 -569
  231. package/dist/chunk-4TT3PNPR.js.map +0 -1
  232. package/dist/chunk-6FLXL3I7.js.map +0 -1
  233. package/dist/chunk-ASOK76ZH.js.map +0 -1
  234. package/dist/chunk-CGHF6RZV.js.map +0 -1
  235. package/dist/chunk-CQLQPQ4P.js.map +0 -1
  236. package/dist/chunk-DWMSB4CA.js.map +0 -1
  237. package/dist/chunk-HXXTULDN.js.map +0 -1
  238. package/dist/chunk-IJJUK4FU.js.map +0 -1
  239. package/dist/chunk-IRTGUQ5O.js.map +0 -1
  240. package/dist/chunk-KHK5FGER.js +0 -145
  241. package/dist/chunk-KHK5FGER.js.map +0 -1
  242. package/dist/chunk-NEXR37YJ.js.map +0 -1
  243. package/dist/chunk-O6FFFOHS.js.map +0 -1
  244. package/dist/chunk-QGMOA2YE.js.map +0 -1
  245. package/dist/chunk-RWNDDKOH.js.map +0 -1
  246. package/dist/chunk-S4NCI32V.js +0 -549
  247. package/dist/chunk-S4NCI32V.js.map +0 -1
  248. package/dist/chunk-SBXNQW4G.js +0 -3
  249. package/dist/chunk-SLAQPUI4.js.map +0 -1
  250. package/dist/chunk-TJ77A4A3.js.map +0 -1
  251. package/dist/chunk-TYN6MLXX.js +0 -13864
  252. package/dist/chunk-TYN6MLXX.js.map +0 -1
  253. package/dist/chunk-WPLAANH4.js.map +0 -1
  254. package/src/primitives/useRovingTabIndex.test.ts +0 -48
  255. package/src/primitives/useRovingTabIndex.ts +0 -88
  256. package/src/state/Persistence.stories.tsx +0 -75
@@ -1,6 +1,6 @@
1
- import { DEFAULT_FRAME } from './chunk-24W5VEJW.js';
2
- import { createContext, useContext, useRef, useMemo, useEffect } from 'react';
3
- import { normalizeZoom, usePointerContext, DepRegistryProvider, ActionsProvider, ActiveToolContextProvider, PointerContextProvider, useActionsRegistry, useDepSource, makeViewportZoomAction, viewportDragPanAction, useGestureDispatcher } from '@weasel-js/core';
1
+ import { DEFAULT_FRAME } from './chunk-ULYYNWGP.js';
2
+ import { createContext, useContext, useEffect, useRef, useInsertionEffect, useMemo } from 'react';
3
+ import { normalizeZoom, useActionsRegistry, usePointerContext, PointerContextProvider, useLatest, useDepSource, makeViewportZoomAction, viewportDragPanAction, useGestureDispatcher, InputScope } from '@weasel-js/core';
4
4
  import { jsx, Fragment } from 'react/jsx-runtime';
5
5
 
6
6
  var CameraWheelContext = createContext(null);
@@ -23,6 +23,65 @@ function normalize2DView(view) {
23
23
  function withZoom(view, zoom) {
24
24
  return normalize2DView({ zoom, pan: view.pan });
25
25
  }
26
+ function createCameraRegistry() {
27
+ const stacks = /* @__PURE__ */ new Map();
28
+ const listeners = /* @__PURE__ */ new Set();
29
+ const notify = () => {
30
+ for (const l of listeners) l();
31
+ };
32
+ return {
33
+ register(key, camera) {
34
+ const stack = stacks.get(key) ?? [];
35
+ stack.push(camera);
36
+ stacks.set(key, stack);
37
+ notify();
38
+ let released = false;
39
+ return () => {
40
+ if (released) return;
41
+ released = true;
42
+ const cur = stacks.get(key);
43
+ const i = cur?.lastIndexOf(camera) ?? -1;
44
+ if (!cur || i === -1) return;
45
+ cur.splice(i, 1);
46
+ if (cur.length === 0) stacks.delete(key);
47
+ notify();
48
+ };
49
+ },
50
+ get: (key) => stacks.get(key)?.at(-1) ?? null,
51
+ subscribe(listener) {
52
+ listeners.add(listener);
53
+ return () => {
54
+ listeners.delete(listener);
55
+ };
56
+ }
57
+ };
58
+ }
59
+ var CameraRegistryContext = createContext(null);
60
+ var CameraPublishContext = createContext(
61
+ null
62
+ );
63
+ function usePublishCamera(camera) {
64
+ const publish = useContext(CameraPublishContext);
65
+ const actions = useActionsRegistry();
66
+ useEffect(() => {
67
+ if (!publish) return;
68
+ let release = publish(camera);
69
+ if (!actions) return release;
70
+ let was = actions.isActive();
71
+ const off = actions.subscribe(() => {
72
+ const now = actions.isActive();
73
+ if (now && !was) {
74
+ release();
75
+ release = publish(camera);
76
+ }
77
+ was = now;
78
+ });
79
+ return () => {
80
+ off();
81
+ release();
82
+ };
83
+ }, [publish, camera, actions]);
84
+ }
26
85
 
27
86
  // src/canvas/cameraView.ts
28
87
  function toCameraView(view, frame = DEFAULT_FRAME) {
@@ -60,13 +119,12 @@ function clampZoomAbout(prev, next, min, max) {
60
119
  }
61
120
  var STAGE_VIEW_ID = "stage";
62
121
  var CameraContext = createContext(null);
63
- var CameraScopeContext = createContext(false);
64
- function CameraScope({ children }) {
65
- const inScope = useContext(CameraScopeContext);
122
+ function CameraScope({ yoke, children }) {
123
+ const around = useContext(CameraContext);
66
124
  const pointer = usePointerContext();
67
- if (inScope) return /* @__PURE__ */ jsx(Fragment, { children });
68
- const scoped = /* @__PURE__ */ jsx(CameraScopeContext.Provider, { value: true, children });
69
- return /* @__PURE__ */ jsx(DepRegistryProvider, { children: /* @__PURE__ */ jsx(ActionsProvider, { children: /* @__PURE__ */ jsx(ActiveToolContextProvider, { children: pointer ? scoped : /* @__PURE__ */ jsx(PointerContextProvider, { children: scoped }) }) }) });
125
+ if (around) return /* @__PURE__ */ jsx(Fragment, { children });
126
+ const scoped = /* @__PURE__ */ jsx(InputScope, { yoke, children });
127
+ return pointer ? scoped : /* @__PURE__ */ jsx(PointerContextProvider, { children: scoped });
70
128
  }
71
129
  function isPositiveFinite(n) {
72
130
  return Number.isFinite(n) && n > 0;
@@ -80,14 +138,15 @@ function useCameraView({
80
138
  maxZoom = 32
81
139
  }) {
82
140
  const live = useRef({ view: normalize2DView(view), onViewChange, frame });
83
- live.current = { view: normalize2DView(view), onViewChange, frame };
141
+ useInsertionEffect(() => {
142
+ live.current = { view: normalize2DView(view), onViewChange, frame };
143
+ });
84
144
  const initialZoom = useRef(isPositiveFinite(view.zoom) ? view.zoom : null);
85
- const bounds = useRef({ min: minZoom, max: maxZoom });
86
145
  const opening = initialZoom.current;
87
- bounds.current = {
146
+ const bounds = useLatest({
88
147
  min: opening == null ? minZoom : Math.min(minZoom, opening),
89
148
  max: opening == null ? maxZoom : Math.max(maxZoom, opening)
90
- };
149
+ });
91
150
  return useMemo(() => {
92
151
  const get = () => toCameraView(live.current.view, live.current.frame);
93
152
  return {
@@ -102,9 +161,10 @@ function useCameraView({
102
161
  hostSize: () => {
103
162
  const r = hostRef.current?.getBoundingClientRect();
104
163
  return r ? { width: r.width, height: r.height } : null;
105
- }
164
+ },
165
+ zoomRange: () => ({ ...bounds.current })
106
166
  };
107
- }, [hostRef]);
167
+ }, [bounds, hostRef]);
108
168
  }
109
169
  var TAP_ID = "camera.tap";
110
170
  var NO_TOOLS = /* @__PURE__ */ new Map();
@@ -121,10 +181,8 @@ function CameraDispatch({
121
181
  onTap,
122
182
  registry
123
183
  }) {
124
- const frameRef = useRef(frame);
125
- frameRef.current = frame;
126
- const onTapRef = useRef(onTap);
127
- onTapRef.current = onTap;
184
+ const frameRef = useLatest(frame);
185
+ const onTapRef = useLatest(onTap);
128
186
  useDepSource("view", () => camera);
129
187
  useDepSource("rootView", () => camera);
130
188
  const actions = useMemo(() => {
@@ -150,11 +208,12 @@ function CameraDispatch({
150
208
  }
151
209
  };
152
210
  return [
153
- viewportDragPanAction,
211
+ // A trial pans on a plain drag; the kit action binds none of its own.
212
+ { ...viewportDragPanAction, defaultBinding: { kind: "drag" } },
154
213
  { ...zoom, defaultBinding: wheelOnly },
155
214
  tap
156
215
  ];
157
- }, []);
216
+ }, [frameRef, onTapRef]);
158
217
  useEffect(() => {
159
218
  const offs = actions.map((a) => registry.register(a));
160
219
  return () => {
@@ -175,30 +234,34 @@ function CameraDispatch({
175
234
  useGestureDispatcher({
176
235
  canvasRef: hostRef,
177
236
  actions: registry,
178
- toolsById: NO_TOOLS,
237
+ entriesById: NO_TOOLS,
179
238
  clientToWorld,
180
239
  channels: CHANNELS
181
240
  });
182
241
  usePublishPointer(hostRef, clientToWorld, frame, STAGE_VIEW_ID);
242
+ usePublishCamera(camera);
183
243
  const wheelSlot = useContext(CameraWheelContext);
184
244
  useEffect(() => {
185
245
  if (!wheelSlot) return;
186
246
  const forward = (e) => {
187
247
  hostRef.current?.dispatchEvent(new WheelEvent("wheel", e));
188
248
  };
189
- wheelSlot.current = forward;
249
+ const claim = () => {
250
+ if (registry.isActive()) wheelSlot.current = forward;
251
+ };
252
+ claim();
253
+ const off = registry.subscribe(claim);
190
254
  return () => {
255
+ off();
191
256
  if (wheelSlot.current === forward) wheelSlot.current = null;
192
257
  };
193
- }, [wheelSlot, hostRef]);
258
+ }, [wheelSlot, hostRef, registry]);
194
259
  return null;
195
260
  }
196
261
  function usePublishPointer(hostRef, clientToLocal, frame, viewId) {
197
262
  const store = usePointerContext();
198
- const frameRef = useRef(frame);
199
- frameRef.current = frame;
200
- const toLocal = useRef(clientToLocal);
201
- toLocal.current = clientToLocal;
263
+ const frameRef = useLatest(frame);
264
+ const toLocal = useLatest(clientToLocal);
202
265
  useEffect(() => {
203
266
  const el = hostRef.current;
204
267
  if (!el || !store) return;
@@ -216,13 +279,13 @@ function usePublishPointer(hostRef, clientToLocal, frame, viewId) {
216
279
  el.removeEventListener("pointermove", onMove);
217
280
  el.removeEventListener("pointerleave", onLeave);
218
281
  };
219
- }, [hostRef, store, viewId]);
282
+ }, [frameRef, hostRef, store, toLocal, viewId]);
220
283
  }
221
284
  function pointerElsewhere(p, viewId, frame) {
222
285
  if (!p || p.viewId === viewId) return null;
223
286
  return frameLocalToWorld({ x: p.worldX, y: p.worldY }, frame);
224
287
  }
225
288
 
226
- export { CameraContext, CameraInput, CameraScope, CameraScopeContext, CameraWheelContext, DEFAULT_VIEW, STAGE_VIEW_ID, as2DView, clampZoomAbout, frameLocalToWorld, fromCameraView, normalize2DView, pointerElsewhere, toCameraView, useCameraView, usePublishPointer, withZoom };
227
- //# sourceMappingURL=chunk-DWMSB4CA.js.map
228
- //# sourceMappingURL=chunk-DWMSB4CA.js.map
289
+ export { CameraContext, CameraInput, CameraPublishContext, CameraRegistryContext, CameraScope, CameraWheelContext, DEFAULT_VIEW, STAGE_VIEW_ID, as2DView, clampZoomAbout, createCameraRegistry, frameLocalToWorld, fromCameraView, normalize2DView, pointerElsewhere, toCameraView, useCameraView, usePublishCamera, usePublishPointer, withZoom };
290
+ //# sourceMappingURL=chunk-VYVGVBGY.js.map
291
+ //# sourceMappingURL=chunk-VYVGVBGY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/canvas/CameraWheelContext.ts","../src/state/view.ts","../src/canvas/cameraRegistry.ts","../src/canvas/cameraView.ts","../src/canvas/CameraInput.tsx"],"names":["createContext","useContext","useActionsRegistry","useEffect"],"mappings":"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","file":"chunk-VYVGVBGY.js","sourcesContent":["import { createContext } from 'react';\n\n/** Where a trial's camera takes a wheel event from outside its own element. */\nexport type CameraWheelSlot = { current: ((e: WheelEvent) => void) | null };\n\n/**\n * The trial's camera, for input that lands outside it. An annotation target's\n * input box is portalled into the surface container, so a wheel over a mark\n * never bubbles through the stage or canvas stack it sits on; the box hands\n * the event here instead.\n */\nexport const CameraWheelContext = createContext<CameraWheelSlot | null>(null);\n","import { normalizeZoom } from '@weasel-js/core';\nimport type { ViewTransform } from '../instrument/types';\n\n/** The 2D view labkit has always shipped, and what a trial gets when it names no\n * other. Re-exported under its own name so a consumer can say which it means. */\nexport type ViewTransform2D = ViewTransform;\n\n/** Zoom 1, no pan. */\nexport const DEFAULT_VIEW: ViewTransform2D = { zoom: 1, pan: { x: 0, y: 0 } };\n\n/** A trial's view is opaque to labkit, so anything that needs the 2D shape — the\n * zoom chrome, `CanvasStack` — asks for it and handles not getting it. */\nexport function as2DView(view: unknown): ViewTransform2D | null {\n if (typeof view !== 'object' || view === null) return null;\n const v = view as Partial<ViewTransform2D>;\n if (typeof v.zoom !== 'number') return null;\n if (typeof v.pan !== 'object' || v.pan === null) return null;\n if (typeof v.pan.x !== 'number' || typeof v.pan.y !== 'number') return null;\n return normalize2DView({ zoom: v.zoom, pan: { x: v.pan.x, y: v.pan.y } });\n}\n\n/** `view` inside the invariant every view holds: a zoom that is not a finite\n * number of at least `ZOOM_FLOOR` becomes the floor, and a non-finite pan\n * becomes 0. Returns `view` itself when it already holds. */\nexport function normalize2DView(view: ViewTransform2D): ViewTransform2D {\n const zoom = normalizeZoom(view.zoom);\n const x = Number.isFinite(view.pan.x) ? view.pan.x : 0;\n const y = Number.isFinite(view.pan.y) ? view.pan.y : 0;\n if (zoom === view.zoom && x === view.pan.x && y === view.pan.y) return view;\n return { zoom, pan: { x, y } };\n}\n\n/** `view` at a new zoom, through the same rule. */\nexport function withZoom(view: ViewTransform2D, zoom: number): ViewTransform2D {\n return normalize2DView({ zoom, pan: view.pan });\n}\n","import { useActionsRegistry } from '@weasel-js/core';\nimport { createContext, useContext, useEffect } from 'react';\nimport type { CameraView } from './CameraInput';\n\n/**\n * Cameras by key, for chrome that acts on a camera it does not contain — the\n * lab's zoom controls, which drive whichever trial has the focus.\n */\nexport interface CameraRegistry {\n /** Publish `camera` under `key` and return its release. Registrants for one\n * key stack, newest live. */\n register(key: string, camera: CameraView): () => void;\n get(key: string): CameraView | null;\n /** Fires after any register or release. Shaped for `useSyncExternalStore`. */\n subscribe(listener: () => void): () => void;\n}\n\n/** An empty in-memory `CameraRegistry`. */\nexport function createCameraRegistry(): CameraRegistry {\n const stacks = new Map<string, CameraView[]>();\n const listeners = new Set<() => void>();\n const notify = (): void => {\n for (const l of listeners) l();\n };\n return {\n register(key, camera) {\n const stack = stacks.get(key) ?? [];\n stack.push(camera);\n stacks.set(key, stack);\n notify();\n let released = false;\n return () => {\n if (released) return;\n released = true;\n const cur = stacks.get(key);\n const i = cur?.lastIndexOf(camera) ?? -1;\n if (!cur || i === -1) return;\n cur.splice(i, 1);\n if (cur.length === 0) stacks.delete(key);\n notify();\n };\n },\n get: (key) => stacks.get(key)?.at(-1) ?? null,\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n}\n\n/** The registry a lab keeps its trials' cameras in. */\nexport const CameraRegistryContext = createContext<CameraRegistry | null>(null);\n\n/** Where a camera publishes itself. The host binds the key — a trial provides\n * one that registers under its own id — so the camera needs no name. */\nexport const CameraPublishContext = createContext<((camera: CameraView) => () => void) | null>(\n null,\n);\n\n/** Publish `camera` to the host in scope for as long as the caller is mounted,\n * and again each time its input scope becomes the active one — a registry\n * key stacks newest-live, so the camera last used is the one chrome reaches.\n * Call it inside the camera's scope. */\nexport function usePublishCamera(camera: CameraView): void {\n const publish = useContext(CameraPublishContext);\n const actions = useActionsRegistry();\n useEffect(() => {\n if (!publish) return;\n let release = publish(camera);\n if (!actions) return release;\n let was = actions.isActive();\n const off = actions.subscribe(() => {\n const now = actions.isActive();\n if (now && !was) {\n release();\n release = publish(camera);\n }\n was = now;\n });\n return () => {\n off();\n release();\n };\n }, [publish, camera, actions]);\n}\n","/**\n * labkit's camera as a weasel `View`, so core's viewport actions drive it.\n *\n * labkit places a world point on screen at\n * `originPx + pan + zoom * (x, yDir * y)`. A weasel `View` places one at\n * `(p - (x, y)) * scale`. Over *frame-local* coordinates — the world with `y`\n * multiplied by `yDir` — the two are the same affine map, which is all a pan\n * or a zoom about a screen point needs.\n */\nimport type { View } from '@weasel-js/core';\nimport type { Point, ViewTransform } from '../instrument/types';\nimport { normalize2DView } from '../state/view';\nimport { DEFAULT_FRAME, type WorldFrame } from './worldSpec';\n\n/** The weasel `View` equal to `view` under `frame`. */\nexport function toCameraView(view: ViewTransform, frame: WorldFrame = DEFAULT_FRAME): View {\n const v = normalize2DView(view);\n return {\n x: -(frame.originPx.x + v.pan.x) / v.zoom,\n y: -(frame.originPx.y + v.pan.y) / v.zoom,\n scale: { x: v.zoom, y: v.zoom },\n };\n}\n\n/** The labkit camera equal to weasel `view` under `frame`. */\nexport function fromCameraView(view: View, frame: WorldFrame = DEFAULT_FRAME): ViewTransform {\n const zoom = view.scale.x;\n return {\n zoom,\n pan: { x: -view.x * zoom - frame.originPx.x, y: -view.y * zoom - frame.originPx.y },\n };\n}\n\n/** A frame-local point in the instrument's world, and back — the map is its\n * own inverse. */\nexport function frameLocalToWorld(p: Point, frame: WorldFrame = DEFAULT_FRAME): Point {\n return { x: p.x, y: p.y * frame.yDir };\n}\n\n/**\n * `next` with its zoom held to [`min`, `max`], about the screen point the step\n * from `prev` was anchored on, so a zoom that hits a bound stays anchored where\n * the pointer is. A pan (equal scales) passes through.\n */\nexport function clampZoomAbout(prev: View, next: View, min: number, max: number): View {\n const s0 = prev.scale.x;\n const s1 = next.scale.x;\n const clamped = Math.min(max, Math.max(min, s1));\n if (clamped === s1) return next;\n if (s0 === s1) return { ...next, scale: { x: clamped, y: clamped } };\n // The screen point both views agree on: p / s0 + x0 = p / s1 + x1.\n const inv = 1 / s0 - 1 / s1;\n const px = (next.x - prev.x) / inv;\n const py = (next.y - prev.y) / inv;\n // The same point under the clamped scale.\n return {\n x: prev.x + px / s0 - px / clamped,\n y: prev.y + py / s0 - py / clamped,\n scale: { x: clamped, y: clamped },\n };\n}\n","/**\n * A labkit camera driven by weasel's dispatcher: drag pans, the wheel zooms\n * about the pointer, a tap reports the world point, and the pointer is\n * published into the store in scope. What `usePanZoom` used to hand-roll.\n */\nimport {\n type Action,\n type ActionsRegistry,\n InputScope,\n makeViewportZoomAction,\n PointerContextProvider,\n type PointerWorldPos,\n type Tool,\n useActionsRegistry,\n useDepSource,\n useGestureDispatcher,\n useLatest,\n usePointerContext,\n type View,\n type ViewApi,\n viewportDragPanAction,\n type Yoke,\n} from '@weasel-js/core';\nimport {\n createContext,\n type ReactNode,\n type RefObject,\n useContext,\n useEffect,\n useInsertionEffect,\n useMemo,\n useRef,\n} from 'react';\nimport type { Point, ViewTransform } from '../instrument/types';\nimport { normalize2DView } from '../state/view';\nimport { CameraWheelContext } from './CameraWheelContext';\nimport { usePublishCamera } from './cameraRegistry';\nimport { clampZoomAbout, frameLocalToWorld, fromCameraView, toCameraView } from './cameraView';\nimport type { ViewportSize, WorldFrame } from './worldSpec';\n\n/** The view id the stage publishes its pointer under. */\nexport const STAGE_VIEW_ID = 'stage';\n\n/**\n * A camera, as the things that sit beside it read it: an overview, a loupe, a\n * linked cursor. Points are *frame-local* — the world with `y` multiplied by\n * `frame.yDir` — which is the space `view` works in.\n */\nexport interface CameraContextValue {\n /** The camera as a weasel `ViewApi`, clamped to its zoom range. */\n view: ViewApi;\n frame: WorldFrame;\n /** The element the camera's dispatcher listens on. */\n element: () => HTMLElement | null;\n /** The content's own rect, frame-local, when it has one — a `<Stage>`'s. */\n content?: { x: number; y: number; width: number; height: number };\n}\n\n/** The camera around the caller, or `null` outside one. */\nexport const CameraContext = createContext<CameraContextValue | null>(null);\n\n/**\n * The input scope a camera routes through. Inside another camera — a loupe or\n * an overlay laid over a canvas stack — it joins that camera's scope, so its\n * actions reach the dispatcher already listening there. Anywhere else it\n * mounts a scope of its own, which keeps its own tool unless it joins `yoke`:\n * a camera beside another never takes that one's gestures. The pointer store is the exception — one\n * already in scope is kept, since sharing it is the point.\n */\nexport function CameraScope({ yoke, children }: { yoke?: Yoke; children: ReactNode }) {\n const around = useContext(CameraContext);\n const pointer = usePointerContext();\n if (around) return <>{children}</>;\n const scoped = <InputScope yoke={yoke}>{children}</InputScope>;\n return pointer ? scoped : <PointerContextProvider>{scoped}</PointerContextProvider>;\n}\n\n/** Options for {@link useCameraView}. */\nexport interface CameraViewOptions {\n view: ViewTransform;\n onViewChange: (v: ViewTransform) => void;\n frame: WorldFrame;\n hostRef: RefObject<HTMLElement | null>;\n minZoom?: number;\n maxZoom?: number;\n}\n\n/** A camera's zoom limits, after widening to admit its opening zoom. */\nexport interface ZoomRange {\n min: number;\n max: number;\n}\n\n/** A labkit camera: a weasel `ViewApi` that also says how far it zooms. */\nexport interface CameraView extends ViewApi {\n zoomRange(): ZoomRange;\n}\n\nfunction isPositiveFinite(n: number): boolean {\n return Number.isFinite(n) && n > 0;\n}\n\n/**\n * A labkit camera as a stable `ViewApi`. Its zoom range is widened to keep the\n * opening zoom reachable — captured once, so a zoom-out cannot shrink the\n * range and strand the opening view behind it.\n */\nexport function useCameraView({\n view,\n onViewChange,\n frame,\n hostRef,\n minZoom = 0.1,\n maxZoom = 32,\n}: CameraViewOptions): CameraView {\n // Written by each commit and by `set`, ahead of the render it asks for —\n // two writers, so not `useLatest`.\n const live = useRef({ view: normalize2DView(view), onViewChange, frame });\n useInsertionEffect(() => {\n live.current = { view: normalize2DView(view), onViewChange, frame };\n });\n const initialZoom = useRef(isPositiveFinite(view.zoom) ? view.zoom : null);\n const opening = initialZoom.current;\n const bounds = useLatest({\n min: opening == null ? minZoom : Math.min(minZoom, opening),\n max: opening == null ? maxZoom : Math.max(maxZoom, opening),\n });\n\n return useMemo<CameraView>(() => {\n const get = (): View => toCameraView(live.current.view, live.current.frame);\n return {\n get,\n set: (next: View) => {\n const { min, max } = bounds.current;\n const clamped = clampZoomAbout(get(), next, min, max);\n const vt = fromCameraView(clamped, live.current.frame);\n // Written ahead of the render, so a second event inside one commit\n // steps from this one.\n live.current.view = vt;\n live.current.onViewChange(vt);\n },\n hostSize: () => {\n const r = hostRef.current?.getBoundingClientRect();\n return r ? { width: r.width, height: r.height } : null;\n },\n zoomRange: () => ({ ...bounds.current }),\n };\n }, [bounds, hostRef]);\n}\n\nconst TAP_ID = 'camera.tap';\nconst NO_TOOLS: ReadonlyMap<string, Tool> = new Map();\nconst CHANNELS = { contextMenu: false, ingest: false } as const;\n\n/** Props for `<CameraInput>`. */\nexport interface CameraInputProps {\n hostRef: RefObject<HTMLElement | null>;\n /** Published to the host's camera registry while its scope is the active\n * one, so chrome acting on \"the camera\" acts on the one last used. */\n camera: CameraView;\n frame: WorldFrame;\n /** A primary-button press released without crossing the drag threshold, at\n * the world point it landed on. */\n onTap?: (world: Point) => void;\n}\n\n/**\n * Mounts the dispatcher on `hostRef` and routes the camera's gestures through\n * it. Render it inside a `<CameraScope>`; with no actions registry in scope it\n * renders nothing.\n */\nexport function CameraInput(props: CameraInputProps) {\n const registry = useActionsRegistry();\n if (!registry) return null;\n return <CameraDispatch {...props} registry={registry} />;\n}\n\nfunction CameraDispatch({\n hostRef,\n camera,\n frame,\n onTap,\n registry,\n}: CameraInputProps & { registry: ActionsRegistry }) {\n const frameRef = useLatest(frame);\n const onTapRef = useLatest(onTap);\n\n useDepSource('view', () => camera);\n useDepSource('rootView', () => camera);\n\n const actions = useMemo<Action[]>(() => {\n // Unclamped here: `camera.set` clamps, to a range widened around the\n // opening zoom, and anchors the clamp where the wheel was.\n const zoom = makeViewportZoomAction({\n wheel: 'plain',\n min: 1e-9,\n max: Number.POSITIVE_INFINITY,\n });\n // The wheel and the trackpad's two pinch forms only: the zoom keys listen\n // on the window, and every trial on a page would take them at once.\n const wheelOnly = (zoom.defaultBinding as { spec: { kind: string } }[]).filter(\n (b) => b.spec.kind === 'wheel' || b.spec.kind === 'pinch',\n );\n const tap: Action = {\n id: TAP_ID,\n label: 'Tap the canvas',\n defaultBinding: { kind: 'click' },\n invoker: {\n timing: 'immediate',\n run: (_deps, params) => {\n const p = params as { worldX?: number; worldY?: number } | undefined;\n if (p?.worldX === undefined || p.worldY === undefined) return;\n onTapRef.current?.(frameLocalToWorld({ x: p.worldX, y: p.worldY }, frameRef.current));\n },\n },\n };\n return [\n // A trial pans on a plain drag; the kit action binds none of its own.\n { ...viewportDragPanAction, defaultBinding: { kind: 'drag' } },\n { ...zoom, defaultBinding: wheelOnly as Action['defaultBinding'] },\n tap,\n ];\n }, [frameRef, onTapRef]);\n\n useEffect(() => {\n const offs = actions.map((a) => registry.register(a));\n return () => {\n for (const off of offs) off();\n };\n }, [registry, actions]);\n\n const clientToWorld = useMemo(\n () => (cx: number, cy: number) => {\n const r = hostRef.current?.getBoundingClientRect();\n const v = camera.get();\n return {\n x: (cx - (r?.left ?? 0)) / v.scale.x + v.x,\n y: (cy - (r?.top ?? 0)) / v.scale.y + v.y,\n };\n },\n [hostRef, camera],\n );\n\n useGestureDispatcher({\n canvasRef: hostRef,\n actions: registry,\n entriesById: NO_TOOLS,\n clientToWorld,\n channels: CHANNELS,\n });\n\n usePublishPointer(hostRef, clientToWorld, frame, STAGE_VIEW_ID);\n usePublishCamera(camera);\n\n // Input that lands outside the camera's element — an annotation target's\n // box, portalled into the surface — reaches the active camera's dispatcher\n // through here.\n const wheelSlot = useContext(CameraWheelContext);\n useEffect(() => {\n if (!wheelSlot) return;\n const forward = (e: WheelEvent): void => {\n hostRef.current?.dispatchEvent(new WheelEvent('wheel', e));\n };\n const claim = (): void => {\n if (registry.isActive()) wheelSlot.current = forward;\n };\n claim();\n const off = registry.subscribe(claim);\n return () => {\n off();\n if (wheelSlot.current === forward) wheelSlot.current = null;\n };\n }, [wheelSlot, hostRef, registry]);\n\n return null;\n}\n\n/**\n * Publish the pointer over `hostRef` into the store in scope, in the\n * instrument's world, as `viewId`; clear it when the pointer leaves with no\n * button held.\n */\nexport function usePublishPointer(\n hostRef: RefObject<HTMLElement | null>,\n clientToLocal: (cx: number, cy: number) => Point,\n frame: WorldFrame,\n viewId: string,\n): void {\n const store = usePointerContext();\n const frameRef = useLatest(frame);\n const toLocal = useLatest(clientToLocal);\n useEffect(() => {\n const el = hostRef.current;\n if (!el || !store) return;\n const onMove = (e: PointerEvent): void => {\n const w = frameLocalToWorld(toLocal.current(e.clientX, e.clientY), frameRef.current);\n store.set({ worldX: w.x, worldY: w.y, viewId });\n };\n const onLeave = (e: PointerEvent): void => {\n if (e.buttons !== 0) return;\n if (store.get()?.viewId === viewId) store.set(null);\n };\n el.addEventListener('pointermove', onMove);\n el.addEventListener('pointerleave', onLeave);\n return () => {\n el.removeEventListener('pointermove', onMove);\n el.removeEventListener('pointerleave', onLeave);\n };\n }, [frameRef, hostRef, store, toLocal, viewId]);\n}\n\n/** The pointer as a frame-local point, or `null` when there is none or it is\n * over `viewId` itself. */\nexport function pointerElsewhere(\n p: PointerWorldPos,\n viewId: string,\n frame: WorldFrame,\n): Point | null {\n if (!p || p.viewId === viewId) return null;\n return frameLocalToWorld({ x: p.worldX, y: p.worldY }, frame);\n}\n\nexport type { ViewportSize };\n"]}
@@ -1,4 +1,4 @@
1
- import { schemaNodeAtPath } from './chunk-TJ77A4A3.js';
1
+ import { schemaNodeAtPath } from './chunk-READTDQX.js';
2
2
 
3
3
  // src/config/fromConfigField.ts
4
4
  function toLeaf(field) {
@@ -41,7 +41,7 @@ function toLeaf(field) {
41
41
  }
42
42
  }
43
43
  function fromConfigFields(fields) {
44
- const children = {};
44
+ const root = { name: "", children: {} };
45
45
  for (const field of fields) {
46
46
  const patch = toLeaf(field);
47
47
  for (const key of Object.keys(patch)) {
@@ -49,10 +49,20 @@ function fromConfigFields(fields) {
49
49
  delete patch[key];
50
50
  }
51
51
  }
52
- children[field.key] = { ...patch, default: field.default };
52
+ const segments = field.key.split(".");
53
+ const leafKey = segments.pop();
54
+ let group = root;
55
+ for (const segment of segments) {
56
+ const existing = group.children[segment];
57
+ if (existing === void 0 || !("children" in existing) || "kind" in existing) {
58
+ group.children[segment] = { name: "", children: {} };
59
+ }
60
+ group = group.children[segment];
61
+ }
62
+ group.children[leafKey] = { ...patch, default: field.default };
53
63
  }
54
64
  return {
55
- group: { name: "", children },
65
+ group: root,
56
66
  sections: [],
57
67
  showIf: /* @__PURE__ */ new Map(),
58
68
  renderers: {},
@@ -69,5 +79,5 @@ function isLeafVisible(resolved, path, config, showHidden = false) {
69
79
  }
70
80
 
71
81
  export { fromConfigFields, isLeafVisible };
72
- //# sourceMappingURL=chunk-IRTGUQ5O.js.map
73
- //# sourceMappingURL=chunk-IRTGUQ5O.js.map
82
+ //# sourceMappingURL=chunk-WLMDTI2Q.js.map
83
+ //# sourceMappingURL=chunk-WLMDTI2Q.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config/fromConfigField.ts","../src/config/visible.ts"],"names":[],"mappings":";;;AAOA,SAAS,OAAO,KAAA,EAA+B;AAC7C,EAAA,MAAM,OAAO,EAAE,IAAA,EAAM,KAAA,CAAM,KAAA,EAAO,aAAa,EAAA,EAAG;AAClD,EAAA,QAAQ,MAAM,IAAA;AAAM,IAClB,KAAK,QAAA;AACH,MAAA,OAAO;AAAA,QACL,GAAG,IAAA;AAAA,QACH,IAAA,EAAM,QAAA;AAAA,QACN,OAAA,EAAS,QAAA;AAAA,QACT,KAAK,KAAA,CAAM,GAAA;AAAA,QACX,KAAK,KAAA,CAAM,GAAA;AAAA,QACX,MAAM,KAAA,CAAM;AAAA,OACd;AAAA,IACF,KAAK,QAAA;AACH,MAAA,OAAO;AAAA,QACL,GAAG,IAAA;AAAA,QACH,IAAA,EAAM,QAAA;AAAA,QACN,OAAA,EAAS,OAAA;AAAA,QACT,KAAK,KAAA,CAAM,GAAA;AAAA,QACX,KAAK,KAAA,CAAM,GAAA;AAAA,QACX,MAAM,KAAA,CAAM;AAAA,OACd;AAAA,IACF,KAAK,UAAA;AACH,MAAA,OAAO,EAAE,GAAG,IAAA,EAAM,IAAA,EAAM,SAAA,EAAW,SAAS,UAAA,EAAW;AAAA,IACzD,KAAK,QAAA;AACH,MAAA,OAAO,EAAE,GAAG,IAAA,EAAM,MAAM,MAAA,EAAQ,OAAA,EAAS,MAAM,OAAA,EAAQ;AAAA,IACzD,KAAK,MAAA;AACH,MAAA,OAAO;AAAA,QACL,GAAG,IAAA;AAAA,QACH,IAAA,EAAM,QAAA;AAAA,QACN,aAAa,KAAA,CAAM,WAAA;AAAA,QACnB,WAAW,KAAA,CAAM,SAAA;AAAA,QACjB,YAAY,KAAA,CAAM;AAAA,OACpB;AAAA,IACF,KAAK,OAAA;AACH,MAAA,OAAO,EAAE,GAAG,IAAA,EAAM,IAAA,EAAM,OAAA,EAAQ;AAAA,IAClC;AAGE,MAAA,OAAO,EAAE,GAAG,IAAA,EAAM,IAAA,EAAO,MAAsB,IAAA,EAAK;AAAA;AAE1D;AAaO,SAAS,iBAAiB,MAAA,EAAgD;AAC/E,EAAA,MAAM,OAAkB,EAAE,IAAA,EAAM,EAAA,EAAI,QAAA,EAAU,EAAC,EAAE;AACjD,EAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,IAAA,MAAM,KAAA,GAAQ,OAAO,KAAK,CAAA;AAC1B,IAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA,EAAG;AACpC,MAAA,IAAK,KAAA,CAAkC,GAAG,CAAA,KAAM,MAAA,EAAW;AACzD,QAAA,OAAQ,MAAkC,GAAG,CAAA;AAAA,MAC/C;AAAA,IACF;AACA,IAAA,MAAM,QAAA,GAAW,KAAA,CAAM,GAAA,CAAI,KAAA,CAAM,GAAG,CAAA;AACpC,IAAA,MAAM,OAAA,GAAU,SAAS,GAAA,EAAI;AAC7B,IAAA,IAAI,KAAA,GAAQ,IAAA;AACZ,IAAA,KAAA,MAAW,WAAW,QAAA,EAAU;AAC9B,MAAA,MAAM,QAAA,GAAW,KAAA,CAAM,QAAA,CAAS,OAAO,CAAA;AACvC,MAAA,IAAI,aAAa,MAAA,IAAa,EAAE,UAAA,IAAc,QAAA,CAAA,IAAa,UAAU,QAAA,EAAU;AAC7E,QAAA,KAAA,CAAM,QAAA,CAAS,OAAO,CAAA,GAAI,EAAE,MAAM,EAAA,EAAI,QAAA,EAAU,EAAC,EAAE;AAAA,MACrD;AACA,MAAA,KAAA,GAAQ,KAAA,CAAM,SAAS,OAAO,CAAA;AAAA,IAChC;AACA,IAAA,KAAA,CAAM,QAAA,CAAS,OAAO,CAAA,GAAI,EAAE,GAAG,KAAA,EAAO,OAAA,EAAS,MAAM,OAAA,EAAQ;AAAA,EAC/D;AACA,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,IAAA;AAAA,IACP,UAAU,EAAC;AAAA,IACX,MAAA,sBAAY,GAAA,EAAI;AAAA,IAChB,WAAW,EAAC;AAAA,IACZ,SAAS;AAAC,GACZ;AACF;;;AC7EO,SAAS,aAAA,CACd,QAAA,EACA,IAAA,EACA,MAAA,EACA,aAAa,KAAA,EACJ;AACT,EAAA,MAAM,IAAA,GAAO,gBAAA,CAAiB,QAAA,CAAS,KAAA,EAAO,IAAI,CAAA;AAClD,EAAA,IAAI,IAAA,IAAQ,YAAY,IAAA,IAAQ,IAAA,CAAK,WAAW,IAAA,IAAQ,CAAC,YAAY,OAAO,KAAA;AAC5E,EAAA,MAAM,SAAA,GAAY,QAAA,CAAS,MAAA,CAAO,GAAA,CAAI,IAAI,CAAA;AAC1C,EAAA,OAAO,SAAA,GAAY,SAAA,CAAU,MAAM,CAAA,GAAI,IAAA;AACzC","file":"chunk-WLMDTI2Q.js","sourcesContent":["import type { PrefGroup, PrefLeaf } from '@weasel-js/ui';\nimport type { ConfigField } from '../controls/types';\nimport type { LeafPatch, ResolvedConfig } from './types';\n\n/** One `ConfigField` as the `Pref*` vocabulary spells it. The two describe the\n * same thing; `ConfigField` just keys on the control where `PrefLeaf`\n * separates the value's kind from how it is presented. */\nfunction toLeaf(field: ConfigField): LeafPatch {\n const base = { name: field.label, description: '' };\n switch (field.type) {\n case 'slider':\n return {\n ...base,\n kind: 'number',\n control: 'slider',\n min: field.min,\n max: field.max,\n step: field.step,\n };\n case 'number':\n return {\n ...base,\n kind: 'number',\n control: 'input',\n min: field.min,\n max: field.max,\n step: field.step,\n };\n case 'checkbox':\n return { ...base, kind: 'boolean', control: 'checkbox' };\n case 'select':\n return { ...base, kind: 'enum', options: field.options };\n case 'text':\n return {\n ...base,\n kind: 'string',\n placeholder: field.placeholder,\n maxLength: field.maxLength,\n debounceMs: field.debounceMs,\n };\n case 'color':\n return { ...base, kind: 'color' };\n default:\n // A schema can arrive from outside TypeScript. Carry the kind through\n // so a lab-supplied renderer can still claim it.\n return { ...base, kind: (field as ConfigField).type };\n }\n}\n\n/**\n * Adapt a legacy `configSchema(): ConfigField[]` into the resolved shape, so\n * both instrument paths reach one renderer.\n *\n * Rules do not run here: a hand-written `ConfigField` already states its label\n * and control, which is everything the rule chain would have inferred.\n *\n * A dotted `key` is a config path, as it is to every read and write, so its\n * leaf is filed under headless groups at that path rather than as one child\n * whose name contains dots — which no path lookup would find.\n */\nexport function fromConfigFields(fields: readonly ConfigField[]): ResolvedConfig {\n const root: PrefGroup = { name: '', children: {} };\n for (const field of fields) {\n const patch = toLeaf(field);\n for (const key of Object.keys(patch)) {\n if ((patch as Record<string, unknown>)[key] === undefined) {\n delete (patch as Record<string, unknown>)[key];\n }\n }\n const segments = field.key.split('.');\n const leafKey = segments.pop() as string;\n let group = root;\n for (const segment of segments) {\n const existing = group.children[segment];\n if (existing === undefined || !('children' in existing) || 'kind' in existing) {\n group.children[segment] = { name: '', children: {} };\n }\n group = group.children[segment] as PrefGroup;\n }\n group.children[leafKey] = { ...patch, default: field.default } as PrefLeaf;\n }\n return {\n group: root,\n sections: [],\n showIf: new Map(),\n renderers: {},\n dialogs: {},\n };\n}\n","import { schemaNodeAtPath } from './path';\nimport type { ResolvedConfig } from './types';\n\n/**\n * Whether the row at a dotted path should be drawn right now. A group answers\n * for its whole subtree — hiding one hides everything under it.\n *\n * `hidden` is the node's own static flag; `showIf` is the schema's predicate\n * over the live config. Both are presentational — a hidden leaf keeps its\n * value and the instrument still reads it.\n */\nexport function isLeafVisible(\n resolved: ResolvedConfig,\n path: string,\n config: Record<string, unknown>,\n showHidden = false,\n): boolean {\n const node = schemaNodeAtPath(resolved.group, path);\n if (node && 'hidden' in node && node.hidden === true && !showHidden) return false;\n const predicate = resolved.showIf.get(path);\n return predicate ? predicate(config) : true;\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { usePersistedState } from './chunk-RWNDDKOH.js';
2
- import { openPointerSession } from '@weasel-js/core';
1
+ import { usePersistedState } from './chunk-PMLZP7RT.js';
2
+ import { useLatest, openPointerSession, pastDragThreshold } from '@weasel-js/core';
3
3
  import { useRef, useMemo, useState, useEffect } from 'react';
4
4
  import { floatingStrategy, DEFAULT_INSET, DEFAULT_ANCHOR, FLOATING_DRAG_PREFIX } from 'windease';
5
5
  import { jsx } from 'react/jsx-runtime';
@@ -10,7 +10,6 @@ function borderBoxOf(entry) {
10
10
  if (box) return { w: box.inlineSize, h: box.blockSize };
11
11
  return { w: entry.contentRect.width, h: entry.contentRect.height };
12
12
  }
13
- var DRAG_THRESHOLD = 3;
14
13
  function isInteractive(target) {
15
14
  if (!(target instanceof Element)) return false;
16
15
  return target.closest("input, button, a, select, textarea, [data-no-drag]") !== null;
@@ -67,8 +66,7 @@ function FloatingPanel({
67
66
  const dragging = useRef(null);
68
67
  const session = useRef(null);
69
68
  const [isDragging, setDragging] = useState(false);
70
- const layout = useRef({ container, options, item });
71
- layout.current = { container, options, item };
69
+ const layout = useLatest({ container, options, item });
72
70
  useEffect(() => () => session.current?.cancel(), []);
73
71
  const onPointerDown = (e) => {
74
72
  if (isInteractive(e.target)) return;
@@ -92,7 +90,7 @@ function FloatingPanel({
92
90
  {
93
91
  onMove: (ev) => {
94
92
  if (!dragging.current) {
95
- if (Math.hypot(ev.clientX - start.x, ev.clientY - start.y) < DRAG_THRESHOLD) return;
93
+ if (!pastDragThreshold({ clientX: start.x, clientY: start.y }, ev)) return;
96
94
  dragging.current = { ...start };
97
95
  setDragging(true);
98
96
  origin.setPointerCapture?.(pointerId);
@@ -140,5 +138,5 @@ function FloatingPanel({
140
138
  }
141
139
 
142
140
  export { FloatingPanel };
143
- //# sourceMappingURL=chunk-NEXR37YJ.js.map
144
- //# sourceMappingURL=chunk-NEXR37YJ.js.map
141
+ //# sourceMappingURL=chunk-XE5HJOPL.js.map
142
+ //# sourceMappingURL=chunk-XE5HJOPL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/primitives/FloatingPanel.tsx"],"names":[],"mappings":"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","file":"chunk-XE5HJOPL.js","sourcesContent":["import {\n openPointerSession,\n type PointerSession,\n pastDragThreshold,\n useLatest,\n} from '@weasel-js/core';\nimport type React from 'react';\nimport { type CSSProperties, type ReactNode, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n type Corner,\n DEFAULT_ANCHOR,\n DEFAULT_INSET,\n FLOATING_DRAG_PREFIX,\n type FloatingPlacement,\n floatingStrategy,\n} from 'windease';\nimport { usePersistedState } from '../state/usePersistedState';\n\n/** The strategy holds a map keyed by item id; a panel is always a lone item. */\nconst ITEM_ID = 'panel';\n\n/** Props for `<FloatingPanel>`. */\nexport interface FloatingPanelProps {\n children: ReactNode;\n /** Corner it rests in before it has ever been dragged. Default `'bottom-left'`. */\n anchor?: Corner;\n /** Corners that may capture it. Default: all four. */\n snapCorners?: readonly Corner[];\n /** Pixels in from a corner when snapped. Default 12. */\n inset?: number;\n /** Name to remember its position under, through `usePersistedState` — so it\n * is remembered only inside a lab or a `<Persistence>`. Omit to forget on\n * reload. */\n persist?: string;\n className?: string;\n}\n\n/**\n * The panel's footprint is its border box — `contentRect` excludes padding and\n * border, which would place a panel that then overflows its corner by exactly\n * that much.\n */\nfunction borderBoxOf(entry: ResizeObserverEntry): { w: number; h: number } {\n const box = entry.borderBoxSize?.[0];\n if (box) return { w: box.inlineSize, h: box.blockSize };\n return { w: entry.contentRect.width, h: entry.contentRect.height };\n}\n\n/** A pointerdown on one of these is the child's, not a drag. */\nfunction isInteractive(target: EventTarget | null): boolean {\n if (!(target instanceof Element)) return false;\n return target.closest('input, button, a, select, textarea, [data-no-drag]') !== null;\n}\n\n/**\n * A draggable box that floats over its offset parent and snaps to its corners.\n *\n * Drives windease's `floatingStrategy` as a pure function rather than mounting a\n * windease container: a lab overlay has one item and no zone tree, so the node\n * model would be all cost. Pointer handling is therefore this component's, and\n * windease's own affordance rendering never participates.\n */\nexport function FloatingPanel({\n children,\n anchor = DEFAULT_ANCHOR,\n snapCorners,\n inset = DEFAULT_INSET,\n persist,\n className,\n}: FloatingPanelProps) {\n const ref = useRef<HTMLDivElement>(null);\n const strategy = useMemo(() => floatingStrategy(), []);\n const options = useMemo(() => ({ defaultAnchor: anchor, inset }), [anchor, inset]);\n\n const [container, setContainer] = useState({ w: 0, h: 0 });\n const [size, setSize] = useState({ w: 0, h: 0 });\n const [place, setPlace] = usePersistedState<FloatingPlacement>(persist, () => ({\n x: 0,\n y: 0,\n anchor,\n }));\n\n const item = useMemo(\n () => ({\n id: ITEM_ID,\n meta: { floating: true, ...(snapCorners ? { snapCorners: [...snapCorners] } : {}) },\n natural: size,\n }),\n [size, snapCorners],\n );\n\n useEffect(() => {\n const el = ref.current;\n const parent = el?.offsetParent ?? el?.parentElement;\n if (!el || !parent) return;\n // Sizes come off the entry, not the DOM: jsdom reports 0 for every\n // clientWidth/offsetWidth, and an unmeasured item is withheld from layout.\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n if (entry.target === parent) {\n setContainer({ w: entry.contentRect.width, h: entry.contentRect.height });\n } else {\n setSize(borderBoxOf(entry));\n }\n }\n });\n observer.observe(parent);\n observer.observe(el);\n return () => observer.disconnect();\n }, []);\n\n const rect = strategy\n .layout({\n items: [item],\n container,\n state: { at: { [ITEM_ID]: place }, inner: undefined },\n options,\n })\n .placements.get(ITEM_ID);\n\n const dragging = useRef<{ x: number; y: number } | null>(null);\n const session = useRef<PointerSession | null>(null);\n const [isDragging, setDragging] = useState(false);\n // A drag outlives the render that started it, so the reducer's inputs are\n // read here and not from that render's closure — a container resized\n // mid-drag would otherwise place the panel against its old box.\n const layout = useLatest({ container, options, item });\n\n useEffect(() => () => session.current?.cancel(), []);\n\n const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {\n if (isInteractive(e.target)) return;\n // The canvas stack underneath owns pan/zoom on the same pointer events.\n e.stopPropagation();\n const origin = e.currentTarget;\n const pointerId = e.pointerId;\n const start = { x: e.clientX, y: e.clientY };\n\n const end = () => {\n session.current = null;\n if (!dragging.current) return;\n dragging.current = null;\n setDragging(false);\n try {\n origin.releasePointerCapture?.(pointerId);\n } catch {\n // Capture is already gone whenever the session ended by losing it.\n }\n };\n\n // Armed, not capturing. Capture retargets mouseup to this element, and the\n // browser then synthesizes no `click` on the child under the cursor — so\n // capturing on press makes every non-native control in the panel dead to a\n // real mouse, silently. `isInteractive` only exempts the names it lists; a\n // `role=\"button\"` span, a react-aria control or a canvas is not among them.\n session.current = openPointerSession(\n origin,\n e,\n {\n onMove: (ev) => {\n if (!dragging.current) {\n if (!pastDragThreshold({ clientX: start.x, clientY: start.y }, ev)) return;\n dragging.current = { ...start };\n setDragging(true);\n origin.setPointerCapture?.(pointerId);\n }\n const from = dragging.current;\n const dx = ev.clientX - from.x;\n const dy = ev.clientY - from.y;\n if (dx === 0 && dy === 0) return;\n dragging.current = { x: ev.clientX, y: ev.clientY };\n const { container: box, options: opts, item: only } = layout.current;\n setPlace(\n (prev) =>\n strategy.reduce?.(\n { at: { [ITEM_ID]: prev }, inner: undefined },\n {\n affordanceId: `${FLOATING_DRAG_PREFIX}${ITEM_ID}`,\n kind: 'drag',\n payload: { dx, dy },\n },\n { container: box, options: opts, items: [only] },\n ).at[ITEM_ID] ?? prev,\n );\n },\n onEnd: end,\n onCancel: end,\n },\n { capture: false },\n );\n };\n\n return (\n <div\n className={className ? `lk-floating-panel ${className}` : 'lk-floating-panel'}\n data-dragging={isDragging ? 'true' : undefined}\n data-placed={rect ? 'true' : undefined}\n onPointerDown={onPointerDown}\n ref={ref}\n style={\n {\n position: 'absolute',\n left: `${rect?.x ?? 0}px`,\n top: `${rect?.y ?? 0}px`,\n } as CSSProperties\n }\n >\n {children}\n </div>\n );\n}\n"]}
@@ -0,0 +1,38 @@
1
+ // ../geom/dist/chunk-6T5I3PR5.js
2
+ function boundsOfCoords(coords) {
3
+ if (coords.length < 2) return null;
4
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
5
+ for (let i = 0; i + 1 < coords.length; i += 2) {
6
+ const x = coords[i], y = coords[i + 1];
7
+ if (x < minX) minX = x;
8
+ if (y < minY) minY = y;
9
+ if (x > maxX) maxX = x;
10
+ if (y > maxY) maxY = y;
11
+ }
12
+ return [minX, minY, maxX, maxY];
13
+ }
14
+ function boxContainsPoint(b, x, y) {
15
+ return x >= b[0] && x <= b[2] && y >= b[1] && y <= b[3];
16
+ }
17
+ function boxContainsBox(outer, inner) {
18
+ return inner[0] >= outer[0] && inner[1] >= outer[1] && inner[2] <= outer[2] && inner[3] <= outer[3];
19
+ }
20
+
21
+ // ../geom/dist/index.js
22
+ function identity() {
23
+ return [1, 0, 0, 1, 0, 0];
24
+ }
25
+ function multiply(m, n) {
26
+ return [
27
+ m[0] * n[0] + m[2] * n[1],
28
+ m[1] * n[0] + m[3] * n[1],
29
+ m[0] * n[2] + m[2] * n[3],
30
+ m[1] * n[2] + m[3] * n[3],
31
+ m[0] * n[4] + m[2] * n[5] + m[4],
32
+ m[1] * n[4] + m[3] * n[5] + m[5]
33
+ ];
34
+ }
35
+
36
+ export { boundsOfCoords, boxContainsBox, boxContainsPoint, identity, multiply };
37
+ //# sourceMappingURL=chunk-XVUKDSYV.js.map
38
+ //# sourceMappingURL=chunk-XVUKDSYV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../geom/src/box.ts","../../geom/src/mat3.ts"],"names":[],"mappings":";AAQO,SAAS,eAAe,MAAA,EAAuC;AACpE,EAAA,IAAI,MAAA,CAAO,MAAA,GAAS,CAAA,EAAG,OAAO,IAAA;AAC9B,EAAA,IAAI,OAAO,QAAA,EAAU,IAAA,GAAO,QAAA,EAAU,IAAA,GAAO,WAAW,IAAA,GAAO,CAAA,QAAA;AAC/D,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,IAAI,MAAA,CAAO,MAAA,EAAQ,KAAK,CAAA,EAAG;AAC7C,IAAA,MAAM,IAAI,MAAA,CAAO,CAAC,GAAG,CAAA,GAAI,MAAA,CAAO,IAAI,CAAC,CAAA;AACrC,IAAA,IAAI,CAAA,GAAI,MAAM,IAAA,GAAO,CAAA;AACrB,IAAA,IAAI,CAAA,GAAI,MAAM,IAAA,GAAO,CAAA;AACrB,IAAA,IAAI,CAAA,GAAI,MAAM,IAAA,GAAO,CAAA;AACrB,IAAA,IAAI,CAAA,GAAI,MAAM,IAAA,GAAO,CAAA;AACvB,EAAA;AACA,EAAA,OAAO,CAAC,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,IAAI,CAAA;AAChC;AAaO,SAAS,gBAAA,CAAiB,CAAA,EAAQ,CAAA,EAAW,CAAA,EAAoB;AACtE,EAAA,OAAO,CAAA,IAAK,CAAA,CAAE,CAAC,CAAA,IAAK,KAAK,CAAA,CAAE,CAAC,CAAA,IAAK,CAAA,IAAK,CAAA,CAAE,CAAC,CAAA,IAAK,CAAA,IAAK,EAAE,CAAC,CAAA;AACxD;AAIO,SAAS,cAAA,CAAe,OAAY,KAAA,EAAqB;AAC9D,EAAA,OAAO,KAAA,CAAM,CAAC,CAAA,IAAK,KAAA,CAAM,CAAC,CAAA,IAAK,KAAA,CAAM,CAAC,CAAA,IAAK,KAAA,CAAM,CAAC,KAAK,KAAA,CAAM,CAAC,KAAK,KAAA,CAAM,CAAC,KAAK,KAAA,CAAM,CAAC,CAAA,IAAK,KAAA,CAAM,CAAC,CAAA;AACpG;;;ACnBO,SAAS,QAAA,GAAiB;AAC/B,EAAA,OAAO,CAAC,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,GAAG,CAAC,CAAA;AAC1B;AAsBO,SAAS,QAAA,CAAS,GAAS,CAAA,EAAe;AAC/C,EAAA,OAAO;IACL,CAAA,CAAE,CAAC,IAAI,CAAA,CAAE,CAAC,IAAI,CAAA,CAAE,CAAC,CAAA,GAAI,CAAA,CAAE,CAAC,CAAA;IACxB,CAAA,CAAE,CAAC,IAAI,CAAA,CAAE,CAAC,IAAI,CAAA,CAAE,CAAC,CAAA,GAAI,CAAA,CAAE,CAAC,CAAA;IACxB,CAAA,CAAE,CAAC,IAAI,CAAA,CAAE,CAAC,IAAI,CAAA,CAAE,CAAC,CAAA,GAAI,CAAA,CAAE,CAAC,CAAA;IACxB,CAAA,CAAE,CAAC,IAAI,CAAA,CAAE,CAAC,IAAI,CAAA,CAAE,CAAC,CAAA,GAAI,CAAA,CAAE,CAAC,CAAA;AACxB,IAAA,CAAA,CAAE,CAAC,CAAA,GAAI,CAAA,CAAE,CAAC,CAAA,GAAI,CAAA,CAAE,CAAC,CAAA,GAAI,CAAA,CAAE,CAAC,CAAA,GAAI,CAAA,CAAE,CAAC,CAAA;AAC/B,IAAA,CAAA,CAAE,CAAC,CAAA,GAAI,CAAA,CAAE,CAAC,CAAA,GAAI,CAAA,CAAE,CAAC,CAAA,GAAI,CAAA,CAAE,CAAC,CAAA,GAAI,CAAA,CAAE,CAAC;AAAA,GAAA;AAEnC","file":"chunk-XVUKDSYV.js","sourcesContent":["/** Axis-aligned box as [minX, minY, maxX, maxY]. */\nexport type Box = readonly [number, number, number, number];\n\n/** An axis-aligned rectangle in object form. `Box` is the tuple form; this is\n * the one that survives being read by a human. */\nexport interface Rect { x: number; y: number; width: number; height: number }\n\n/** Tight bounds of an interleaved coord stream, or null if empty. */\nexport function boundsOfCoords(coords: ArrayLike<number>): Box | null {\n if (coords.length < 2) return null;\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (let i = 0; i + 1 < coords.length; i += 2) {\n const x = coords[i], y = coords[i + 1];\n if (x < minX) minX = x;\n if (y < minY) minY = y;\n if (x > maxX) maxX = x;\n if (y > maxY) maxY = y;\n }\n return [minX, minY, maxX, maxY];\n}\n\n/** Smallest box containing both inputs. */\nexport function unionBox(a: Box, b: Box): Box {\n return [\n Math.min(a[0], b[0]),\n Math.min(a[1], b[1]),\n Math.max(a[2], b[2]),\n Math.max(a[3], b[3]),\n ];\n}\n\n/** Inclusive point-in-box test. */\nexport function boxContainsPoint(b: Box, x: number, y: number): boolean {\n return x >= b[0] && x <= b[2] && y >= b[1] && y <= b[3];\n}\n\n/** Whether `outer` wholly holds `inner`, edges inclusive. Two boxes that merely\n * overlap answer false. */\nexport function boxContainsBox(outer: Box, inner: Box): boolean {\n return inner[0] >= outer[0] && inner[1] >= outer[1] && inner[2] <= outer[2] && inner[3] <= outer[3];\n}\n\n/** Interleaved corner ring for a rect at (x,y,w,h), wound clockwise from the\n * origin corner. The closing edge is implicit — the same unclosed shape\n * `pointInPolygon` takes, and what a repeated first vertex would leave as a\n * zero-length segment for anything that strokes it. */\nexport function rectToContour(x: number, y: number, w: number, h: number): Float64Array {\n return Float64Array.of(x, y, x + w, y, x + w, y + h, x, y + h);\n}\n","/**\n * 2D affine transforms in canvas/DOMMatrix order: [a, b, c, d, e, f].\n * x' = a·x + c·y + e\n * y' = b·x + d·y + f\n * Represented as a readonly 6-tuple (f64). The affine tier of the kernel.\n *\n * Convention alignment: the renderer already has a `Mat3` in\n * `src/renderer/math/mat3.ts`. That one is a 9-element column-major\n * `Float32Array` (a full 3×3) shaped for `uniformMatrix3fv` — a deliberately\n * different *representation* for the WebGL upload path. Its *logical element\n * order* is identical to ours: `create(a, b, c, d, tx, ty)` maps\n * `x' = a·x + c·y + tx`, `y' = b·x + d·y + ty` (canvas/DOMMatrix a,b,c,d,e,f).\n * We keep the pure 6-tuple f64 form here (the kernel form); the 9-element f32\n * form stays a render-layer concern. No second logical convention is created.\n */\nimport { SINGULAR_RATIO } from './scalar';\n\n/** `[a, b, c, d, e, f]`: `x' = a·x + c·y + e`, `y' = b·x + d·y + f`. */\nexport type Mat3 = readonly [number, number, number, number, number, number];\n\n/** The transform that leaves a point where it is. */\nexport function identity(): Mat3 {\n return [1, 0, 0, 1, 0, 0];\n}\n\n/** A pure translation by `(tx, ty)`. */\nexport function translate(tx: number, ty: number): Mat3 {\n return [1, 0, 0, 1, tx, ty];\n}\n\n/** A pure scale about the origin. Scale about another point by composing\n * translate → scale → inverse translate with `multiply`. */\nexport function scale(sx: number, sy: number): Mat3 {\n return [sx, 0, 0, sy, 0, 0];\n}\n\n/** A pure rotation about the origin, in radians, clockwise in canvas\n * coordinates (y down). */\nexport function rotate(rad: number): Mat3 {\n const c = Math.cos(rad);\n const s = Math.sin(rad);\n return [c, s, -s, c, 0, 0];\n}\n\n/** Compose: result applies `n` first, then `m` (m·n). */\nexport function multiply(m: Mat3, n: Mat3): Mat3 {\n return [\n m[0] * n[0] + m[2] * n[1],\n m[1] * n[0] + m[3] * n[1],\n m[0] * n[2] + m[2] * n[3],\n m[1] * n[2] + m[3] * n[3],\n m[0] * n[4] + m[2] * n[5] + m[4],\n m[1] * n[4] + m[3] * n[5] + m[5],\n ];\n}\n\n/** Inverse, or null when the matrix is singular, non-finite, or too\n * ill-conditioned to invert meaningfully at its own scale. */\nexport function invert(m: Mat3): Mat3 | null {\n const det = m[0] * m[3] - m[1] * m[2];\n // The determinant is an area, so it is judged against the squared column norms.\n const norm2 = Math.max(m[0] * m[0] + m[1] * m[1], m[2] * m[2] + m[3] * m[3]);\n // Negated so a non-finite determinant falls out as singular.\n if (!(Math.abs(det) > SINGULAR_RATIO * norm2)) return null;\n const id = 1 / det;\n const a = m[3] * id;\n const b = -m[1] * id;\n const c = -m[2] * id;\n const d = m[0] * id;\n return [a, b, c, d, -(m[4] * a + m[5] * c), -(m[4] * b + m[5] * d)];\n}\n\n/** Apply to a point, returning a tuple. Cold-path use; hot loops inline. */\nexport function applyToPoint(m: Mat3, x: number, y: number): [number, number] {\n return [m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]];\n}\n\n/** Affine that maps source box (sx,sy,sw,sh) onto destination box (dx,dy,dw,dh). */\nexport function boxToBox(\n sx: number, sy: number, sw: number, sh: number,\n dx: number, dy: number, dw: number, dh: number,\n): Mat3 {\n const kx = sw === 0 ? 1 : dw / sw;\n const ky = sh === 0 ? 1 : dh / sh;\n // translate(dx,dy) · scale(kx,ky) · translate(-sx,-sy)\n return [kx, 0, 0, ky, dx - sx * kx, dy - sy * ky];\n}\n\n/** Rotation by `rad` about pivot (cx,cy): translate(c)·rotate·translate(-c). */\nexport function rotateAboutPoint(cx: number, cy: number, rad: number): Mat3 {\n return multiply(translate(cx, cy), multiply(rotate(rad), translate(-cx, -cy)));\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { isAuto, resolveAutoConfig } from './chunk-TJ77A4A3.js';
1
+ import { isAuto, resolveAutoConfig } from './chunk-READTDQX.js';
2
2
  import { useMemo } from 'react';
3
3
 
4
4
  // src/config/builder.ts
@@ -100,10 +100,10 @@ var NumberNode = class extends BaseNode {
100
100
  suffix(suffix) {
101
101
  return this.ann({ suffix });
102
102
  }
103
- /** Show the value in a named format — `'compact'` reads `2.00M`. Presentation
104
- * only: the stored value stays a plain number, and a typed `2.5m` reads back. */
105
- format(format) {
106
- return this.ann({ format });
103
+ /** Show, speak and read the value through a display — `compact()` reads
104
+ * `2.00M`. Presentation only: the stored value stays a plain number. */
105
+ display(display) {
106
+ return this.ann({ display });
107
107
  }
108
108
  /**
109
109
  * Store the value in one unit and edit it in another — radians stored,
@@ -213,6 +213,11 @@ var GroupNode = class _GroupNode {
213
213
  showIf(predicate) {
214
214
  return this.with(this.annotations, { ...this.options, showIf: predicate });
215
215
  }
216
+ /** Put a button on the group's heading that returns every value beneath it
217
+ * to its default, leaving the rest of the config alone. */
218
+ resettable() {
219
+ return this.with(this.annotations, { ...this.options, resettable: true });
220
+ }
216
221
  };
217
222
  function shapeDefaults(shape) {
218
223
  const out = {};
@@ -300,6 +305,7 @@ function resolveConfigSchema(schema, rules = []) {
300
305
  showIf: /* @__PURE__ */ new Map(),
301
306
  renderers: {},
302
307
  dialogs: {},
308
+ resettable: /* @__PURE__ */ new Set(),
303
309
  chain: [...rules, ...builtinRules]
304
310
  };
305
311
  const group = resolveShape(schema.nodes, "", "", sink);
@@ -308,7 +314,8 @@ function resolveConfigSchema(schema, rules = []) {
308
314
  sections: sink.sections,
309
315
  showIf: sink.showIf,
310
316
  renderers: sink.renderers,
311
- dialogs: sink.dialogs
317
+ dialogs: sink.dialogs,
318
+ resettable: sink.resettable
312
319
  };
313
320
  }
314
321
  function resolveShape(shape, at, name, sink) {
@@ -333,6 +340,7 @@ function resolveShape(shape, at, name, sink) {
333
340
  if (section.pack !== void 0 && spec.pack === void 0) spec.pack = section.pack;
334
341
  }
335
342
  if (predicate) sink.showIf.set(path, predicate);
343
+ if (isConfigBranch(entry) && entry.options.resettable) sink.resettable.add(path);
336
344
  if (!isConfigBranch(entry) && entry.options.render) sink.renderers[path] = entry.options.render;
337
345
  if (!isConfigBranch(entry) && entry.options.dialog) sink.dialogs[path] = entry.options.dialog;
338
346
  }
@@ -383,5 +391,5 @@ function useResolvedConfig(schema, config, autoPaths) {
383
391
  }
384
392
 
385
393
  export { BaseNode, BooleanNode, ColorNode, CustomNode, EnumNode, GroupNode, ListNode, NumberNode, StringNode, ValueNode, applyRules, builtinRules, f, isConfigBranch, resolveConfigSchema, titleCase, useResolvedConfig };
386
- //# sourceMappingURL=chunk-O6FFFOHS.js.map
387
- //# sourceMappingURL=chunk-O6FFFOHS.js.map
394
+ //# sourceMappingURL=chunk-YCFKW5RX.js.map
395
+ //# sourceMappingURL=chunk-YCFKW5RX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config/builder.ts","../src/config/rules.ts","../src/config/resolve.ts","../src/config/useResolvedConfig.ts"],"names":[],"mappings":";;;;AAqBO,SAAS,eAAe,KAAA,EAA2C;AACxE,EAAA,OAAO,UAAA,IAAc,KAAA;AACvB;AAIO,IAAe,WAAf,MAAoD;AAAA,EAOzD,YACE,YAAA,EACS,WAAA,GAAqC,EAAC,EACtC,OAAA,GAAiC,EAAC,EAC3C;AAFS,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AACA,IAAA,IAAA,CAAA,OAAA,GAAA,OAAA;AAET,IAAA,IAAA,CAAK,OAAA,GAAU,YAAA;AAAA,EACjB;AAAA,EAJW,WAAA;AAAA,EACA,OAAA;AAAA;AAAA;AAAA,EALF,OAAA;AAAA;AAAA;AAAA,EAYC,IAAA,CAAK,aAA0B,OAAA,EAA4B;AACnE,IAAA,MAAM,OAAO,IAAA,CAAK,WAAA;AAClB,IAAA,OAAO,IAAI,IAAA,CAAK,IAAA,CAAK,OAAA,EAAS,aAAa,OAAO,CAAA;AAAA,EACpD;AAAA,EAEU,IAAI,KAAA,EAA0B;AACtC,IAAA,OAAO,IAAA,CAAK,IAAA,CAAK,EAAE,GAAG,IAAA,CAAK,aAAa,GAAG,KAAA,EAAM,EAAG,IAAA,CAAK,OAAO,CAAA;AAAA,EAClE;AAAA,EAEU,IAAI,KAAA,EAA0B;AACtC,IAAA,OAAO,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,WAAA,EAAa,EAAE,GAAG,IAAA,CAAK,OAAA,EAAS,GAAG,KAAA,EAAO,CAAA;AAAA,EAClE;AAAA;AAAA,EAGA,MAAM,IAAA,EAAoB;AACxB,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,IAAA,EAAM,CAAA;AAAA,EAC1B;AAAA;AAAA,EAGA,SAAS,WAAA,EAA2B;AAClC,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,WAAA,EAAa,CAAA;AAAA,EACjC;AAAA;AAAA,EAGA,MAAA,GAAe;AACb,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,MAAA,EAAQ,MAAM,CAAA;AAAA,EAClC;AAAA;AAAA,EAGA,KAAK,IAAA,EAAoB;AACvB,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,IAAA,EAAM,CAAA;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,OAAA,CAAQ,KAAA,EAAe,IAAA,GAAqC,EAAC,EAAS;AACpE,IAAA,OAAO,IAAA,CAAK,IAAI,EAAE,OAAA,EAAS,EAAE,KAAA,EAAO,GAAG,IAAA,EAAK,EAAG,CAAA;AAAA,EACjD;AAAA;AAAA;AAAA,EAIA,OAAO,SAAA,EAA+D;AACpE,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,MAAA,EAAQ,WAAW,CAAA;AAAA,EACvC;AAAA;AAAA,EAGA,OAAO,QAAA,EAAiC;AACtC,IAAA,OAAO,KAAK,GAAA,CAAI,EAAE,QAAQ,QAAA,EAAU,MAAA,EAAQ,QAAW,CAAA;AAAA,EACzD;AAAA;AAAA;AAAA,EAIA,MAAA,CAAO,MAAuB,IAAA,EAA8B;AAC1D,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,MAAA,EAAQ,EAAE,GAAG,IAAA,EAAM,IAAA,EAAK,EAAG,MAAA,EAAQ,MAAA,EAAW,CAAA;AAAA,EAClE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,KAAK,OAAA,EAAuD;AAC1D,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,WAAA,EAAa,SAAoD,CAAA;AAAA,EACrF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAQ,KAAA,EAAmB;AACzB,IAAA,IAAI,CAAC,MAAA,CAAO,KAAK,GAAG,MAAM,IAAI,MAAM,mDAAmD,CAAA;AACvF,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,QAAA,EAAU,MAAM,CAAA;AAAA,EACpC;AAAA;AAAA,EAGA,MAAA,GAAe;AACb,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,MAAA,EAAQ,MAAM,CAAA;AAAA,EAClC;AACF;AAGO,IAAM,UAAA,GAAN,cAAyB,QAAA,CAAiB;AAAA,EACtC,IAAA,GAAO,QAAA;AAAA;AAAA,EAGhB,KAAA,CAAM,KAAa,GAAA,EAAmB;AACpC,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,GAAA,EAAK,KAAK,CAAA;AAAA,EAC9B;AAAA,EAEA,KAAK,IAAA,EAAoB;AACvB,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,IAAA,EAAM,CAAA;AAAA,EAC1B;AAAA;AAAA;AAAA,EAIA,OAAO,MAAA,EAAsB;AAC3B,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,MAAA,EAAQ,CAAA;AAAA,EAC5B;AAAA;AAAA;AAAA,EAIA,QAAQ,OAAA,EAAwB;AAC9B,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,OAAA,EAAS,CAAA;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,KAAK,IAAA,EAA4B;AAC/B,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,IAAA,EAAM,CAAA;AAAA,EAC1B;AAAA;AAAA,EAGA,MAAA,GAAe;AACb,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,OAAA,EAAS,UAAU,CAAA;AAAA,EACvC;AAAA;AAAA,EAGA,KAAA,GAAc;AACZ,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,OAAA,EAAS,SAAS,CAAA;AAAA,EACtC;AACF;AAGO,IAAM,WAAA,GAAN,cAA0B,QAAA,CAAkB;AAAA,EACxC,IAAA,GAAO,SAAA;AAAA;AAAA,EAGhB,MAAA,GAAe;AACb,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,OAAA,EAAS,UAAU,CAAA;AAAA,EACvC;AACF;AAGO,IAAM,UAAA,GAAN,cAAyB,QAAA,CAAiB;AAAA,EACtC,IAAA,GAAO,QAAA;AAAA,EAEhB,YAAY,WAAA,EAA2B;AACrC,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,WAAA,EAAa,CAAA;AAAA,EACjC;AAAA,EAEA,UAAU,SAAA,EAAyB;AACjC,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,SAAA,EAAW,CAAA;AAAA,EAC/B;AAAA;AAAA,EAGA,SAAS,UAAA,EAA0B;AACjC,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,UAAA,EAAY,CAAA;AAAA,EAChC;AACF;AAGO,IAAM,QAAA,GAAN,cAAuB,QAAA,CAAmB;AAAA,EACtC,IAAA,GAAO,MAAA;AAAA;AAAA,EAGhB,YAAY,WAAA,EAA2B;AACrC,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,WAAA,EAAa,CAAA;AAAA,EACjC;AACF;AAGO,IAAM,SAAA,GAAN,cAAwB,QAAA,CAAiB;AAAA,EACrC,IAAA,GAAO,OAAA;AAAA;AAAA;AAAA,EAIhB,KAAA,GAAc;AACZ,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,KAAA,EAAO,MAAM,CAAA;AAAA,EACjC;AACF;AAGO,IAAM,QAAA,GAAN,cAAyC,QAAA,CAAY;AAAA,EACjD,IAAA,GAAO,MAAA;AAAA;AAAA,EAGhB,KAAA,GAAc;AACZ,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,OAAA,EAAS,SAAS,CAAA;AAAA,EACtC;AAAA;AAAA;AAAA,EAIA,OAAO,OAAA,EAAwC;AAC7C,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAE,OAAA,EAAS,CAAA;AAAA,EAC7B;AACF;AAGO,IAAM,SAAA,GAAN,cAA2B,QAAA,CAAY;AAAA,EACnC,IAAA,GAAO,IAAA;AAClB;AAGO,IAAM,UAAA,GAAN,MAAM,WAAA,SAAsB,QAAA,CAAY;AAAA,EAC7C,WAAA,CACW,MACT,YAAA,EACA,WAAA,GAAqC,EAAC,EACtC,OAAA,GAAiC,EAAC,EAClC;AACA,IAAA,KAAA,CAAM,YAAA,EAAc,aAAa,OAAO,CAAA;AAL/B,IAAA,IAAA,CAAA,IAAA,GAAA,IAAA;AAAA,EAMX;AAAA,EANW,IAAA;AAAA,EAQQ,IAAA,CAAK,aAA0B,OAAA,EAA4B;AAC5E,IAAA,OAAO,IAAI,WAAA,CAAW,IAAA,CAAK,MAAM,IAAA,CAAK,OAAA,EAAS,aAAa,OAAO,CAAA;AAAA,EACrE;AACF;AAOO,IAAM,SAAA,GAAN,MAAM,UAAA,CAA4D;AAAA,EACvE,YACW,QAAA,EACA,WAAA,GAA2C,EAAC,EAC5C,OAAA,GAAmC,EAAC,EAC7C;AAHS,IAAA,IAAA,CAAA,QAAA,GAAA,QAAA;AACA,IAAA,IAAA,CAAA,WAAA,GAAA,WAAA;AACA,IAAA,IAAA,CAAA,OAAA,GAAA,OAAA;AAAA,EACR;AAAA,EAHQ,QAAA;AAAA,EACA,WAAA;AAAA,EACA,OAAA;AAAA,EAGH,IAAA,CAAK,aAAgC,OAAA,EAAsC;AACjF,IAAA,OAAO,IAAI,UAAA,CAAU,IAAA,CAAK,QAAA,EAAU,aAAa,OAAO,CAAA;AAAA,EAC1D;AAAA;AAAA,EAGA,MAAM,IAAA,EAA4B;AAChC,IAAA,OAAO,IAAA,CAAK,KAAK,EAAE,GAAG,KAAK,WAAA,EAAa,IAAA,EAAK,EAAG,IAAA,CAAK,OAAO,CAAA;AAAA,EAC9D;AAAA;AAAA;AAAA,EAIA,SAAS,WAAA,EAAmC;AAC1C,IAAA,OAAO,IAAA,CAAK,KAAK,EAAE,GAAG,KAAK,WAAA,EAAa,WAAA,EAAY,EAAG,IAAA,CAAK,OAAO,CAAA;AAAA,EACrE;AAAA;AAAA,EAGA,OAAA,CAAQ,KAAA,EAAe,IAAA,GAAqC,EAAC,EAAiB;AAC5E,IAAA,OAAO,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,WAAA,EAAa,EAAE,GAAG,IAAA,CAAK,OAAA,EAAS,OAAA,EAAS,EAAE,KAAA,EAAO,GAAG,IAAA,IAAQ,CAAA;AAAA,EACrF;AAAA;AAAA;AAAA,EAIA,OAAO,SAAA,EAAuE;AAC5E,IAAA,OAAO,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,WAAA,EAAa,EAAE,GAAG,IAAA,CAAK,OAAA,EAAS,MAAA,EAAQ,SAAA,EAAW,CAAA;AAAA,EAC3E;AAAA;AAAA;AAAA,EAIA,UAAA,GAA2B;AACzB,IAAA,OAAO,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,WAAA,EAAa,EAAE,GAAG,IAAA,CAAK,OAAA,EAAS,UAAA,EAAY,IAAA,EAAM,CAAA;AAAA,EAC1E;AACF;AAGA,SAAS,cAAc,KAAA,EAA6C;AAClE,EAAA,MAAM,MAA+B,EAAC;AACtC,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,EAAG;AAChD,IAAA,GAAA,CAAI,GAAG,IAAI,cAAA,CAAe,KAAK,IAAI,aAAA,CAAc,KAAA,CAAM,QAAQ,CAAA,GAAI,KAAA,CAAM,OAAA;AAAA,EAC3E;AACA,EAAA,OAAO,GAAA;AACT;AAEA,IAAM,SAAS,CACb,OAAA,KAEA,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,KAAO,OAAO,CAAA,KAAM,QAAA,GAAW,EAAE,KAAA,EAAO,CAAA,EAAG,KAAA,EAAO,CAAA,KAAM,CAAE,CAAA;AAelE,IAAM,CAAA,GAAI;AAAA,EACf,MAAA,EAAQ,CAAC,GAAA,KAA4B,IAAI,WAAW,GAAG,CAAA;AAAA,EACvD,OAAA,EAAS,CAAC,GAAA,KAA8B,IAAI,YAAY,GAAG,CAAA;AAAA,EAC3D,MAAA,EAAQ,CAAC,GAAA,KAA4B,IAAI,WAAW,GAAG,CAAA;AAAA,EACvD,KAAA,EAAO,CAAC,GAAA,KAA2B,IAAI,UAAU,GAAG,CAAA;AAAA;AAAA;AAAA,EAGpD,IAAA,EAAM,CAAC,GAAA,KAAqC,IAAI,SAAS,CAAC,GAAG,GAAG,CAAC,CAAA;AAAA;AAAA;AAAA;AAAA,EAKjE,IAAA,EAAM,CACJ,GAAA,EACA,OAAA,KACgB,IAAI,QAAA,CAAY,GAAA,EAAK,EAAE,OAAA,EAAS,MAAA,CAAO,OAAO,CAAA,EAAG,CAAA;AAAA;AAAA;AAAA,EAInE,KAAA,EAAO,CAAI,GAAA,KAAyB,IAAI,UAAU,GAAG,CAAA;AAAA;AAAA;AAAA;AAAA,EAKrD,KAAA,EAAO,CAA8B,QAAA,KAA8B,IAAI,UAAU,QAAQ,CAAA;AAAA;AAAA,EAGzF,QAAQ,CACN,IAAA,EACA,GAAA,EACA,QAAA,KACkB,IAAI,UAAA,CAAW,IAAA,EAAM,GAAA,EAAK,IAAI,QAAA,GAAW,EAAE,QAAA,EAAS,GAAI,EAAE,CAAA;AAAA;AAAA,EAG9E,OAA8B,KAAA,EAAwC;AACpE,IAAA,OAAO,EAAE,KAAA,EAAO,QAAA,EAAU,MAAM,aAAA,CAAc,KAAK,CAAA,EAAoB;AAAA,EACzE;AACF;;;AC5WO,SAAS,UAAU,GAAA,EAAqB;AAC7C,EAAA,MAAM,KAAA,GAAQ,GAAA,CACX,OAAA,CAAQ,QAAA,EAAU,GAAG,CAAA,CACrB,KAAA,CAAM,wBAAwB,CAAA,CAC9B,IAAA,CAAK,GAAG,CAAA,CACR,IAAA,GACA,WAAA,EAAY;AACf,EAAA,OAAO,KAAA,CAAM,MAAA,KAAW,CAAA,GAAI,KAAA,GAAQ,KAAA,CAAM,CAAC,CAAA,CAAE,WAAA,EAAY,GAAI,KAAA,CAAM,KAAA,CAAM,CAAC,CAAA;AAC5E;AAGA,IAAM,aAAA,GAA4B,CAAC,GAAA,KAAQ;AACzC,EAAA,MAAM,CAAA,GAAI,OAAO,GAAA,CAAI,OAAA;AACrB,EAAA,IAAI,CAAA,KAAM,aAAa,CAAA,KAAM,QAAA,IAAY,MAAM,QAAA,EAAU,OAAO,EAAE,IAAA,EAAM,CAAA,EAAE;AAC1E,EAAA,MAAM,IAAI,GAAA,CAAI,OAAA;AACd,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA,IAAK,EAAE,KAAA,CAAM,CAAC,CAAA,KAAM,OAAO,MAAM,QAAQ,CAAA,EAAG,OAAO,EAAE,MAAM,MAAA,EAAO;AACrF,EAAA,OAAO,IAAA;AACT,CAAA;AAEA,IAAM,YAAA,GAA2B,CAAC,GAAA,MAAS,EAAE,MAAM,SAAA,CAAU,GAAA,CAAI,GAAG,CAAA,EAAE,CAAA;AAGtE,IAAM,iBAAA,GAAgC,CAAC,GAAA,KAAQ;AAC7C,EAAA,IAAI,GAAA,CAAI,IAAA,CAAK,IAAA,KAAS,QAAA,EAAU,OAAO,IAAA;AACvC,EAAA,MAAM,UAAU,GAAA,CAAI,IAAA,CAAK,QAAQ,MAAA,IAAa,GAAA,CAAI,KAAK,GAAA,KAAQ,MAAA;AAC/D,EAAA,OAAO,OAAA,GAAU,EAAE,OAAA,EAAS,QAAA,EAAS,GAAI,IAAA;AAC3C,CAAA;AAGA,IAAM,kBAAA,GAAiC,OAAO,EAAE,WAAA,EAAa,EAAA,EAAG,CAAA;AASzD,IAAM,YAAA,GAAsC;AAAA,EACjD,aAAA;AAAA,EACA,YAAA;AAAA,EACA,iBAAA;AAAA,EACA;AACF;AAOO,SAAS,UAAA,CACd,IAAA,EACA,GAAA,EACA,KAAA,EACW;AACX,EAAA,MAAM,GAAA,GAAiB,EAAE,GAAG,IAAA,EAAK;AACjC,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,MAAM,QAAQ,IAAA,CAAK,EAAE,GAAG,GAAA,EAAK,IAAA,EAAM,KAAK,CAAA;AACxC,IAAA,IAAI,UAAU,IAAA,EAAM;AACpB,IAAA,KAAA,MAAW,CAAC,CAAA,EAAG,CAAC,KAAK,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,EAAG;AAC1C,MAAA,IAAI,CAAA,KAAM,MAAA,IAAa,GAAA,CAAI,CAAoB,MAAM,MAAA,EAAW;AAC9D,QAAC,GAAA,CAAgC,CAAC,CAAA,GAAI,CAAA;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;;;ACpDA,SAAS,QAAQ,WAAA,EAAiD;AAChE,EAAA,MAAM,MAA+B,EAAC;AACtC,EAAA,KAAA,MAAW,CAAC,CAAA,EAAG,CAAC,CAAA,IAAK,MAAA,CAAO,OAAA,CAAQ,WAAW,CAAA,EAAG,IAAI,CAAA,KAAM,MAAA,EAAW,GAAA,CAAI,CAAC,CAAA,GAAI,CAAA;AAChF,EAAA,OAAO,GAAA;AACT;AAqBO,SAAS,mBAAA,CACd,MAAA,EACA,KAAA,GAA+B,EAAC,EAChB;AAChB,EAAA,MAAM,IAAA,GAAa;AAAA,IACjB,UAAU,EAAC;AAAA,IACX,MAAA,sBAAY,GAAA,EAAI;AAAA,IAChB,WAAW,EAAC;AAAA,IACZ,SAAS,EAAC;AAAA,IACV,UAAA,sBAAgB,GAAA,EAAI;AAAA,IACpB,KAAA,EAAO,CAAC,GAAG,KAAA,EAAO,GAAG,YAAY;AAAA,GACnC;AACA,EAAA,MAAM,QAAQ,YAAA,CAAa,MAAA,CAAO,KAAA,EAAO,EAAA,EAAI,IAAI,IAAI,CAAA;AACrD,EAAA,OAAO;AAAA,IACL,KAAA;AAAA,IACA,UAAU,IAAA,CAAK,QAAA;AAAA,IACf,QAAQ,IAAA,CAAK,MAAA;AAAA,IACb,WAAW,IAAA,CAAK,SAAA;AAAA,IAChB,SAAS,IAAA,CAAK,OAAA;AAAA,IACd,YAAY,IAAA,CAAK;AAAA,GACnB;AACF;AAKA,SAAS,YAAA,CAAa,KAAA,EAAoB,EAAA,EAAY,IAAA,EAAc,IAAA,EAAuB;AACzF,EAAA,MAAM,WAAiD,EAAC;AACxD,EAAA,MAAM,QAAA,uBAAe,GAAA,EAAyB;AAE9C,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,EAAG;AAChD,IAAA,MAAM,OAAO,EAAA,KAAO,EAAA,GAAK,MAAM,CAAA,EAAG,EAAE,IAAI,GAAG,CAAA,CAAA;AAC3C,IAAA,QAAA,CAAS,GAAG,CAAA,GAAI,YAAA,CAAa,KAAA,EAAO,GAAA,EAAK,MAAM,IAAI,CAAA;AAEnD,IAAA,MAAM,EAAE,OAAA,EAAS,MAAA,EAAQ,SAAA,KAAc,KAAA,CAAM,OAAA;AAC7C,IAAA,IAAI,YAAY,MAAA,EAAW;AACzB,MAAA,IAAI,IAAA,GAAO,QAAA,CAAS,GAAA,CAAI,OAAA,CAAQ,KAAK,CAAA;AACrC,MAAA,IAAI,CAAC,IAAA,EAAM;AACT,QAAA,IAAA,GAAO,EAAE,EAAA,EAAI,KAAA,EAAO,QAAQ,KAAA,EAAO,KAAA,EAAO,EAAC,EAAE;AAC7C,QAAA,QAAA,CAAS,GAAA,CAAI,OAAA,CAAQ,KAAA,EAAO,IAAI,CAAA;AAChC,QAAA,IAAA,CAAK,QAAA,CAAS,KAAK,IAAI,CAAA;AAAA,MACzB;AACA,MAAA,IAAA,CAAK,KAAA,CAAM,KAAK,IAAI,CAAA;AAGpB,MAAA,IAAI,OAAA,CAAQ,cAAc,MAAA,EAAW;AACnC,QAAA,IAAA,CAAK,SAAA,GAAA,CAAa,IAAA,CAAK,SAAA,IAAa,KAAA,KAAU,OAAA,CAAQ,SAAA;AAAA,MACxD;AACA,MAAA,IAAI,OAAA,CAAQ,WAAW,MAAA,IAAa,IAAA,CAAK,WAAW,MAAA,EAAW,IAAA,CAAK,SAAS,OAAA,CAAQ,MAAA;AACrF,MAAA,IAAI,OAAA,CAAQ,SAAS,MAAA,IAAa,IAAA,CAAK,SAAS,MAAA,EAAW,IAAA,CAAK,OAAO,OAAA,CAAQ,IAAA;AAAA,IACjF;AACA,IAAA,IAAI,SAAA,EAAW,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,MAAM,SAAS,CAAA;AAC9C,IAAA,IAAI,cAAA,CAAe,KAAK,CAAA,IAAK,KAAA,CAAM,QAAQ,UAAA,EAAY,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,IAAI,CAAA;AAC/E,IAAA,IAAI,CAAC,cAAA,CAAe,KAAK,CAAA,IAAK,KAAA,CAAM,OAAA,CAAQ,MAAA,EAAQ,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA,GAAI,KAAA,CAAM,OAAA,CAAQ,MAAA;AACzF,IAAA,IAAI,CAAC,cAAA,CAAe,KAAK,CAAA,IAAK,KAAA,CAAM,OAAA,CAAQ,MAAA,EAAQ,IAAA,CAAK,OAAA,CAAQ,IAAI,CAAA,GAAI,KAAA,CAAM,OAAA,CAAQ,MAAA;AAAA,EACzF;AAEA,EAAA,OAAO,EAAE,MAAM,QAAA,EAAS;AAC1B;AAEA,SAAS,YAAA,CACP,KAAA,EACA,GAAA,EACA,IAAA,EACA,IAAA,EACsB;AACtB,EAAA,IAAI,cAAA,CAAe,KAAK,CAAA,EAAG;AACzB,IAAA,MAAM,KAAA,GAAQ,YAAA;AAAA,MACZ,KAAA,CAAM,QAAA;AAAA,MACN,IAAA;AAAA,MACA,KAAA,CAAM,WAAA,CAAY,IAAA,IAAQ,SAAA,CAAU,GAAG,CAAA;AAAA,MACvC;AAAA,KACF;AACA,IAAA,OAAO,KAAA,CAAM,WAAA,CAAY,WAAA,KAAgB,MAAA,GACrC,KAAA,GACA,EAAE,GAAG,KAAA,EAAO,WAAA,EAAa,KAAA,CAAM,WAAA,CAAY,WAAA,EAAY;AAAA,EAC7D;AACA,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,MAAA,IAAU,KAAA,CAAM,QAAQ,QAAA,EAAU;AAClD,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,aAAa,IAAI,CAAA,8FAAA;AAAA,KACnB;AAAA,EACF;AACA,EAAA,MAAM,OAAkB,OAAA,CAAQ;AAAA,IAC9B,GAAG,KAAA,CAAM,WAAA;AAAA,IACT,GAAI,MAAM,IAAA,KAAS,IAAA,GAAO,EAAC,GAAI,EAAE,IAAA,EAAM,KAAA,CAAM,IAAA;AAAK,GACnD,CAAA;AACD,EAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,IAAA,EAAM,EAAE,GAAA,EAAK,IAAA,EAAM,OAAA,EAAS,KAAA,CAAM,OAAA,EAAQ,EAAG,IAAA,CAAK,KAAK,CAAA;AAChF,EAAA,MAAM,EAAE,WAAA,EAAa,QAAA,EAAU,MAAA,KAAW,KAAA,CAAM,OAAA;AAChD,EAAA,OAAO;AAAA,IACL,GAAG,KAAA;AAAA,IACH,SAAS,KAAA,CAAM,OAAA;AAAA;AAAA;AAAA,IAGf,GAAI,WAAA,KAAgB,MAAA,GAAY,EAAC,GAAI,EAAE,WAAA,EAAY;AAAA,IACnD,GAAI,QAAA,KAAa,MAAA,GAAY,EAAC,GAAI,EAAE,QAAA,EAAS;AAAA,IAC7C,GAAI,MAAA,KAAW,MAAA,GAAY,EAAC,GAAI,EAAE,MAAA;AAAO,GAC3C;AACF;ACvIA,IAAM,IAAA,uBAAgC,GAAA,EAAI;AAOnC,SAAS,iBAAA,CACd,MAAA,EACA,MAAA,EACA,SAAA,EAC2C;AAC3C,EAAA,MAAM,IAAA,GAAO,OAAA;AAAA,IACX,MAAO,aAAa,SAAA,CAAU,MAAA,GAAS,IAAI,IAAI,GAAA,CAAI,SAAS,CAAA,GAAI,IAAA;AAAA,IAChE,CAAC,SAAS;AAAA,GACZ;AACA,EAAA,MAAM,cAAA,GAAiB,OAAA;AAAA,IACrB,MAAO,MAAA,GAAS,iBAAA,CAAkB,MAAA,EAAQ,MAAA,EAAQ,IAAI,CAAA,GAAI,MAAA;AAAA,IAC1D,CAAC,MAAA,EAAQ,MAAA,EAAQ,IAAI;AAAA,GACvB;AACA,EAAA,OAAO,EAAE,MAAA,EAAQ,cAAA,EAAgB,IAAA,EAAK;AACxC","file":"chunk-YCFKW5RX.js","sourcesContent":["import type { Display } from '@weasel-js/quantity';\nimport type { PrefLeaf, PrefNumberUnit } from '@weasel-js/ui';\nimport { type Auto, isAuto } from './auto';\nimport type {\n Annotations,\n BranchAnnotations,\n BranchOptions,\n ConfigBranch,\n ConfigEntry,\n ConfigNode,\n ConfigOption,\n ConfigSchema,\n ConfigShape,\n ControlRenderer,\n InDialogOptions,\n InferConfig,\n NodeOptions,\n SectionOption,\n} from './types';\n\n/** Whether a schema entry is a branch rather than a leaf. */\nexport function isConfigBranch(entry: ConfigEntry): entry is ConfigBranch {\n return 'children' in entry;\n}\n\n/** Shared chaining surface. Every method clones, so a node can be reused as a\n * base for several leaves without one bleeding into the next. */\nexport abstract class BaseNode<T> implements ConfigNode<T> {\n abstract readonly kind: string | null;\n\n // `default` is a reserved word in a parameter position, so the field is\n // declared rather than taken as a parameter property.\n readonly default: T;\n\n constructor(\n defaultValue: T,\n readonly annotations: Readonly<Annotations> = {},\n readonly options: Readonly<NodeOptions> = {},\n ) {\n this.default = defaultValue;\n }\n\n /** Clone into the same subclass. Subclasses with extra constructor\n * arguments override this. */\n protected with(annotations: Annotations, options: NodeOptions): this {\n const Ctor = this.constructor as new (d: T, a: Annotations, o: NodeOptions) => this;\n return new Ctor(this.default, annotations, options);\n }\n\n protected ann(patch: Annotations): this {\n return this.with({ ...this.annotations, ...patch }, this.options);\n }\n\n protected opt(patch: NodeOptions): this {\n return this.with(this.annotations, { ...this.options, ...patch });\n }\n\n /** Human-readable label. Defaults to the key, title-cased. */\n label(name: string): this {\n return this.ann({ name });\n }\n\n /** Longer help text, surfaced as a tooltip on the row label. */\n describe(description: string): this {\n return this.ann({ description });\n }\n\n /** Omit from the panel unless it is asked to show hidden leaves. */\n hidden(): this {\n return this.ann({ hidden: true });\n }\n\n /** Pair with a sibling sharing this id, side-by-side on one row. */\n pair(pair: string): this {\n return this.ann({ pair });\n }\n\n /** Render under a named section heading. `collapsed` opens the section\n * folded, and `layout` and `pack` lay out its rows — say each on any one of\n * the section's leaves. */\n section(label: string, opts: Omit<SectionOption, 'label'> = {}): this {\n return this.opt({ section: { label, ...opts } });\n }\n\n /** Show this row only while the predicate holds. Presentational — the value\n * stays in config and the instrument still reads it. */\n showIf(predicate: (config: Record<string, unknown>) => boolean): this {\n return this.opt({ showIf: predicate });\n }\n\n /** Draw this one row yourself, keeping the kind, default and validation. */\n render(renderer: ControlRenderer): this {\n return this.opt({ render: renderer, dialog: undefined });\n }\n\n /** Draw this row as a button that opens a dialog, with `body` as the\n * dialog's content — for an editor too big for the row. */\n dialog(body: ControlRenderer, opts?: InDialogOptions): this {\n return this.opt({ dialog: { ...opts, body }, render: undefined });\n }\n\n /**\n * Compute this leaf's value while it is auto, instead of leaving it\n * `undefined`. The resolver is given the config with every other auto path\n * already resolved.\n */\n auto(resolve: (config: Record<string, unknown>) => T): this {\n return this.opt({ autoResolve: resolve as (c: Record<string, unknown>) => unknown });\n }\n\n /**\n * Start this leaf auto rather than pinned at its default. Takes `auto` and\n * nothing else — the constructor argument already declares the value, and a\n * second way to say it would fight with the first.\n */\n initial(value: Auto): this {\n if (!isAuto(value)) throw new Error('[labkit] .initial() takes `auto` and nothing else');\n return this.opt({ unpinned: true });\n }\n\n /** Never auto. The row's label does not toggle. */\n manual(): this {\n return this.opt({ manual: true });\n }\n}\n\n/** A numeric leaf, built by `f.number`. */\nexport class NumberNode extends BaseNode<number> {\n readonly kind = 'number';\n\n /** Bound the value. A number with both bounds renders as a slider. */\n range(min: number, max: number): this {\n return this.ann({ min, max });\n }\n\n step(step: number): this {\n return this.ann({ step });\n }\n\n /** A display suffix for the value — `'px'`, `'ms'`, `'°'`. Presentation only:\n * it is never parsed, and the stored value stays a plain number. */\n suffix(suffix: string): this {\n return this.ann({ suffix });\n }\n\n /** Show, speak and read the value through a display — `compact()` reads\n * `2.00M`. Presentation only: the stored value stays a plain number. */\n display(display: Display): this {\n return this.ann({ display });\n }\n\n /**\n * Store the value in one unit and edit it in another — radians stored,\n * degrees typed. `min`, `max` and `step` are declared in the stored unit\n * alongside the value, and convert with it. `prefUnit` builds one from a\n * `UnitSystem`.\n */\n unit(unit: PrefNumberUnit): this {\n return this.ann({ unit });\n }\n\n /** Force a slider even without both bounds. */\n slider(): this {\n return this.ann({ control: 'slider' });\n }\n\n /** Force a typed input even when both bounds are set. */\n input(): this {\n return this.ann({ control: 'input' });\n }\n}\n\n/** A true/false leaf, built by `f.boolean`; a checkbox unless `.toggle()`. */\nexport class BooleanNode extends BaseNode<boolean> {\n readonly kind = 'boolean';\n\n /** Ask for a switch rather than a checkbox. */\n toggle(): this {\n return this.ann({ control: 'switch' });\n }\n}\n\n/** A text leaf, built by `f.string`. */\nexport class StringNode extends BaseNode<string> {\n readonly kind = 'string';\n\n placeholder(placeholder: string): this {\n return this.ann({ placeholder });\n }\n\n maxLength(maxLength: number): this {\n return this.ann({ maxLength });\n }\n\n /** Milliseconds to debounce live writes. 0 commits every keystroke. */\n debounce(debounceMs: number): this {\n return this.ann({ debounceMs });\n }\n}\n\n/** A string-list leaf, built by `f.list`. */\nexport class ListNode extends BaseNode<string[]> {\n readonly kind = 'list';\n\n /** Shown in an empty entry. */\n placeholder(placeholder: string): this {\n return this.ann({ placeholder });\n }\n}\n\n/** A color leaf holding a hex string, built by `f.color`. */\nexport class ColorNode extends BaseNode<string> {\n readonly kind = 'color';\n\n /** The value carries alpha as `#rrggbbaa`, and the row gets an opacity\n * track beside the swatch. */\n alpha(): this {\n return this.ann({ alpha: true });\n }\n}\n\n/** A fixed-choice leaf, built by `f.enum`; a select unless `.radio()`. */\nexport class EnumNode<T extends string> extends BaseNode<T> {\n readonly kind = 'enum';\n\n /** Render as a segmented control rather than a select. */\n radio(): this {\n return this.ann({ control: 'radio' });\n }\n\n /** Relabel the choices. Named `labels` rather than `options` because\n * `options` is the node's own extras bag. */\n labels(options: readonly ConfigOption[]): this {\n return this.ann({ options });\n }\n}\n\n/** A leaf whose kind the rule chain decides. */\nexport class ValueNode<T> extends BaseNode<T> {\n readonly kind = null;\n}\n\n/** A leaf of a kind labkit does not ship a row for. */\nexport class CustomNode<T> extends BaseNode<T> {\n constructor(\n readonly kind: string,\n defaultValue: T,\n annotations: Readonly<Annotations> = {},\n options: Readonly<NodeOptions> = {},\n ) {\n super(defaultValue, annotations, options);\n }\n\n protected override with(annotations: Annotations, options: NodeOptions): this {\n return new CustomNode(this.kind, this.default, annotations, options) as this;\n }\n}\n\n/**\n * A branch of the schema: its children nest under its key, so a leaf inside\n * one is read and written at a dotted path. Distinct from `.section()`, which\n * puts a heading over sibling leaves and leaves their paths alone.\n */\nexport class GroupNode<S extends ConfigShape> implements ConfigBranch<S> {\n constructor(\n readonly children: S,\n readonly annotations: Readonly<BranchAnnotations> = {},\n readonly options: Readonly<BranchOptions> = {},\n ) {}\n\n private with(annotations: BranchAnnotations, options: BranchOptions): GroupNode<S> {\n return new GroupNode(this.children, annotations, options);\n }\n\n /** Heading for the group's rows. Defaults to the key, title-cased. */\n label(name: string): GroupNode<S> {\n return this.with({ ...this.annotations, name }, this.options);\n }\n\n /** Longer help text. Carried on the resolved `PrefGroup`; both weasel-ui's\n * `PrefsForm` and `ControlPanel` draw it under the heading. */\n describe(description: string): GroupNode<S> {\n return this.with({ ...this.annotations, description }, this.options);\n }\n\n /** Render this whole group under a named section heading. */\n section(label: string, opts: Omit<SectionOption, 'label'> = {}): GroupNode<S> {\n return this.with(this.annotations, { ...this.options, section: { label, ...opts } });\n }\n\n /** Show this group only while the predicate holds. Presentational — every\n * value beneath it stays in config and the instrument still reads it. */\n showIf(predicate: (config: Record<string, unknown>) => boolean): GroupNode<S> {\n return this.with(this.annotations, { ...this.options, showIf: predicate });\n }\n\n /** Put a button on the group's heading that returns every value beneath it\n * to its default, leaving the rest of the config alone. */\n resettable(): GroupNode<S> {\n return this.with(this.annotations, { ...this.options, resettable: true });\n }\n}\n\n/** Every default in a shape, nested the way the shape is. */\nfunction shapeDefaults(shape: ConfigShape): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(shape)) {\n out[key] = isConfigBranch(entry) ? shapeDefaults(entry.children) : entry.default;\n }\n return out;\n}\n\nconst expand = <T extends string>(\n options: readonly T[] | readonly ConfigOption[],\n): readonly ConfigOption[] =>\n options.map((o) => (typeof o === 'string' ? { value: o, label: o } : o));\n\n/**\n * Builds an instrument's config: the values, their types, and how each is\n * edited, in one declaration.\n *\n * ```ts\n * const config = f.schema({\n * showGrid: f.boolean(true),\n * cellSize: f.number(20).range(5, 80).step(5).label('Grid spacing'),\n * // Nested: read and written at `grid.color`.\n * grid: f.group({ color: f.color('#ffffff') }),\n * })\n * ```\n */\nexport const f = {\n number: (def: number): NumberNode => new NumberNode(def),\n boolean: (def: boolean): BooleanNode => new BooleanNode(def),\n string: (def: string): StringNode => new StringNode(def),\n color: (def: string): ColorNode => new ColorNode(def),\n /** A list of strings. Its row summarizes the list, and opens a dialog to\n * edit it one entry per field. */\n list: (def: readonly string[]): ListNode => new ListNode([...def]),\n\n /** A fixed set of choices. The default's literal type flows into the config\n * type, so `f.enum('fast', ['fast', 'accurate'])` gives\n * `'fast' | 'accurate'` with no `as const`. */\n enum: <const T extends string>(\n def: T,\n options: readonly T[] | readonly ConfigOption[],\n ): EnumNode<T> => new EnumNode<T>(def, { options: expand(options) }),\n\n /** A leaf with no declared kind: the rule chain decides, which is how a lab\n * states a convention like \"every `*Color` key is a color picker\". */\n value: <T>(def: T): ValueNode<T> => new ValueNode(def),\n\n /** A branch: its children nest under this key, so `grid: f.group({ size })`\n * puts the value at `config.grid.size` and addresses it as `'grid.size'`.\n * Groups nest as deeply as the schema wants. */\n group: <const S extends ConfigShape>(children: S): GroupNode<S> => new GroupNode(children),\n\n /** A leaf of a kind a lab supplies the control for, through `controls`. */\n custom: <T>(\n kind: string,\n def: T,\n validate?: (leaf: PrefLeaf, config: Record<string, unknown>) => string[],\n ): CustomNode<T> => new CustomNode(kind, def, {}, validate ? { validate } : {}),\n\n /** Collect leaves and groups into an instrument's config. */\n schema<S extends ConfigShape>(nodes: S): ConfigSchema<InferConfig<S>> {\n return { nodes, defaults: () => shapeDefaults(nodes) as InferConfig<S> };\n },\n};\n","import type { ConfigRule, ConfigRuleContext, LeafPatch } from './types';\n\n/** `cellSize` -> `Cell size`. Splits camelCase without shattering acronyms. */\nexport function titleCase(key: string): string {\n const words = key\n .replace(/[_-]+/g, ' ')\n .split(/(?<=[a-z0-9])(?=[A-Z])/)\n .join(' ')\n .trim()\n .toLowerCase();\n return words.length === 0 ? words : words[0].toUpperCase() + words.slice(1);\n}\n\n/** An `f.value` leaf takes its kind from the shape of its default. */\nconst kindFromValue: ConfigRule = (ctx) => {\n const t = typeof ctx.default;\n if (t === 'boolean' || t === 'number' || t === 'string') return { kind: t };\n const d = ctx.default;\n if (Array.isArray(d) && d.every((v) => typeof v === 'string')) return { kind: 'list' };\n return null;\n};\n\nconst labelFromKey: ConfigRule = (ctx) => ({ name: titleCase(ctx.key) });\n\n/** A number bounded on both sides is a slider; one bound or none is an input. */\nconst sliderWhenBounded: ConfigRule = (ctx) => {\n if (ctx.leaf.kind !== 'number') return null;\n const bounded = ctx.leaf.min !== undefined && ctx.leaf.max !== undefined;\n return bounded ? { control: 'slider' } : null;\n};\n\n/** `PrefBase.description` is required, so every leaf must end up with one. */\nconst descriptionDefault: ConfigRule = () => ({ description: '' });\n\n/**\n * labkit's own inference, expressed in the same vocabulary a consumer\n * extends. Runs after the consumer's rules, so a lab always wins.\n *\n * If one of these ever needs something a `ConfigRule` cannot express, the\n * seam is too weak — that is the point of writing them this way.\n */\nexport const builtinRules: readonly ConfigRule[] = [\n kindFromValue,\n labelFromKey,\n sliderWhenBounded,\n descriptionDefault,\n];\n\n/**\n * Fold a rule chain into one leaf patch. Gap-filling: a property already\n * settled is never overwritten, so the accumulator seeded with the author's\n * annotations beats every rule, and earlier rules beat later ones.\n */\nexport function applyRules(\n seed: LeafPatch,\n ctx: Omit<ConfigRuleContext, 'leaf'>,\n rules: readonly ConfigRule[],\n): LeafPatch {\n const acc: LeafPatch = { ...seed };\n for (const rule of rules) {\n const patch = rule({ ...ctx, leaf: acc });\n if (patch === null) continue;\n for (const [k, v] of Object.entries(patch)) {\n if (v !== undefined && acc[k as keyof LeafPatch] === undefined) {\n (acc as Record<string, unknown>)[k] = v;\n }\n }\n }\n return acc;\n}\n","import type { PrefGroup, PrefLeaf } from '@weasel-js/ui';\nimport { isConfigBranch } from './builder';\nimport { applyRules, builtinRules, titleCase } from './rules';\nimport type {\n ConfigEntry,\n ConfigRule,\n ConfigSchema,\n ConfigShape,\n ControlRenderer,\n DialogSpec,\n LeafPatch,\n ResolvedConfig,\n SectionSpec,\n} from './types';\n\n/** Drop keys whose value is undefined, so an unset annotation stays a gap the\n * rule chain can fill rather than a settled `undefined`. */\nfunction defined(annotations: Record<string, unknown>): LeafPatch {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(annotations)) if (v !== undefined) out[k] = v;\n return out as LeafPatch;\n}\n\n/** What the walk collects on the side of the `PrefGroup` tree. */\ninterface Sink {\n sections: SectionSpec[];\n showIf: Map<string, (config: Record<string, unknown>) => boolean>;\n renderers: Record<string, ControlRenderer>;\n dialogs: Record<string, DialogSpec>;\n resettable: Set<string>;\n chain: readonly ConfigRule[];\n}\n\n/**\n * Resolve a schema into the vocabulary weasel-ui renders, running each leaf\n * through the consumer's rules and then labkit's own.\n *\n * The group mirrors the schema's own nesting: an `f.group` becomes a nested\n * `PrefGroup`, so a leaf's dotted path within the tree is the path its value\n * is written at, and both `ControlPanel` and `PrefsForm` address it\n * identically.\n */\nexport function resolveConfigSchema<TC>(\n schema: ConfigSchema<TC>,\n rules: readonly ConfigRule[] = [],\n): ResolvedConfig {\n const sink: Sink = {\n sections: [],\n showIf: new Map(),\n renderers: {},\n dialogs: {},\n resettable: new Set(),\n chain: [...rules, ...builtinRules],\n };\n const group = resolveShape(schema.nodes, '', '', sink);\n return {\n group,\n sections: sink.sections,\n showIf: sink.showIf,\n renderers: sink.renderers,\n dialogs: sink.dialogs,\n resettable: sink.resettable,\n };\n}\n\n/** A section while it is still being filled. */\ntype OpenSection = Omit<SectionSpec, 'paths'> & { paths: string[] };\n\nfunction resolveShape(shape: ConfigShape, at: string, name: string, sink: Sink): PrefGroup {\n const children: Record<string, PrefLeaf | PrefGroup> = {};\n const sections = new Map<string, OpenSection>();\n\n for (const [key, entry] of Object.entries(shape)) {\n const path = at === '' ? key : `${at}.${key}`;\n children[key] = resolveEntry(entry, key, path, sink);\n\n const { section, showIf: predicate } = entry.options;\n if (section !== undefined) {\n let spec = sections.get(section.label);\n if (!spec) {\n spec = { at, label: section.label, paths: [] };\n sections.set(section.label, spec);\n sink.sections.push(spec);\n }\n spec.paths.push(path);\n // One node saying `collapsed` settles the section, so a schema does not\n // have to repeat it on every node under the heading.\n if (section.collapsed !== undefined) {\n spec.collapsed = (spec.collapsed ?? false) || section.collapsed;\n }\n if (section.layout !== undefined && spec.layout === undefined) spec.layout = section.layout;\n if (section.pack !== undefined && spec.pack === undefined) spec.pack = section.pack;\n }\n if (predicate) sink.showIf.set(path, predicate);\n if (isConfigBranch(entry) && entry.options.resettable) sink.resettable.add(path);\n if (!isConfigBranch(entry) && entry.options.render) sink.renderers[path] = entry.options.render;\n if (!isConfigBranch(entry) && entry.options.dialog) sink.dialogs[path] = entry.options.dialog;\n }\n\n return { name, children };\n}\n\nfunction resolveEntry(\n entry: ConfigEntry,\n key: string,\n path: string,\n sink: Sink,\n): PrefLeaf | PrefGroup {\n if (isConfigBranch(entry)) {\n const group = resolveShape(\n entry.children,\n path,\n entry.annotations.name ?? titleCase(key),\n sink,\n );\n return entry.annotations.description === undefined\n ? group\n : { ...group, description: entry.annotations.description };\n }\n if (entry.options.manual && entry.options.unpinned) {\n throw new Error(\n `[labkit] \"${path}\" is both .manual() and .initial(auto) — a field that can never be auto cannot start auto`,\n );\n }\n const seed: LeafPatch = defined({\n ...entry.annotations,\n ...(entry.kind === null ? {} : { kind: entry.kind }),\n });\n const patch = applyRules(seed, { key, path, default: entry.default }, sink.chain);\n const { autoResolve, unpinned, manual } = entry.options;\n return {\n ...patch,\n default: entry.default,\n // Extra keys survive onto the leaf at runtime; `ControlPanel` reads them\n // back with its `extra<T>` helper, the same as `min`, `step` and the rest.\n ...(autoResolve === undefined ? {} : { autoResolve }),\n ...(unpinned === undefined ? {} : { unpinned }),\n ...(manual === undefined ? {} : { manual }),\n } as PrefLeaf;\n}\n","import { useMemo } from 'react';\nimport { resolveAutoConfig } from './autoConfig';\nimport type { ResolvedConfig } from './types';\n\nconst NONE: ReadonlySet<string> = new Set();\n\n/**\n * The config an instrument reads: the stored one with every unpinned path\n * resolved. The single place resolution happens, so `Trial` and\n * `useTrialState` cannot disagree about what an instrument is looking at.\n */\nexport function useResolvedConfig<TC>(\n schema: ResolvedConfig | undefined,\n config: TC,\n autoPaths: readonly string[] | undefined,\n): { config: TC; auto: ReadonlySet<string> } {\n const auto = useMemo(\n () => (autoPaths && autoPaths.length > 0 ? new Set(autoPaths) : NONE),\n [autoPaths],\n );\n const resolvedConfig = useMemo(\n () => (schema ? resolveAutoConfig(schema, config, auto) : config),\n [schema, config, auto],\n );\n return { config: resolvedConfig, auto };\n}\n"]}