@weasel-js/labkit 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/dist/_dts/{CanvasStackContext-DQgcH1ny.d.ts → CanvasStackContext-D2jnIwfk.d.ts} +1 -1
  2. package/dist/_dts/FloatingPanel-7j0IcXwz.d.ts +30 -0
  3. package/dist/_dts/{LayerList.d-2c9yup5Y.d.ts → LayerList.d-DiuEArKq.d.ts} +4 -4
  4. package/dist/_dts/PrefsRow.d-TOZ7BgD9.d.ts +79 -0
  5. package/dist/_dts/PropertyPanel.d-D-1paCbE.d.ts +141 -0
  6. package/dist/_dts/{Select.d-BchGMC2Y.d.ts → Select.d-Bensr_MT.d.ts} +15 -6
  7. package/dist/_dts/Stage-CCWfQQWM.d.ts +202 -0
  8. package/dist/_dts/index-B2Yj5aA0.d.ts +274 -0
  9. package/dist/_dts/{index-B0--8PEM.d.ts → index-CsYqUHD3.d.ts} +12 -13
  10. package/dist/_dts/{types-B7y8hX1D.d.ts → types-CRPSGFAp.d.ts} +33 -5
  11. package/dist/_dts/{frac-DTRq7Xpb.d.ts → types-CSm9jgol.d.ts} +700 -650
  12. package/dist/_dts/{useTrialState-CP4jfuKl.d.ts → useTrialState-CTZPgeZ7.d.ts} +2 -3
  13. package/dist/canvas/index.d.ts +40 -8
  14. package/dist/canvas/index.js +5 -3
  15. package/dist/chrome/index.d.ts +5 -6
  16. package/dist/chrome/index.js +9 -7
  17. package/dist/chunk-24W5VEJW.js +18 -0
  18. package/dist/chunk-24W5VEJW.js.map +1 -0
  19. package/dist/chunk-275JRRVD.js +728 -0
  20. package/dist/chunk-275JRRVD.js.map +1 -0
  21. package/dist/chunk-3RY6WL5T.js +38 -0
  22. package/dist/chunk-3RY6WL5T.js.map +1 -0
  23. package/dist/chunk-4LJSC7MB.js +143 -0
  24. package/dist/chunk-4LJSC7MB.js.map +1 -0
  25. package/dist/{chunk-3FI7Y4R4.js → chunk-7BO7HNGF.js} +5 -5
  26. package/dist/{chunk-3FI7Y4R4.js.map → chunk-7BO7HNGF.js.map} +1 -1
  27. package/dist/chunk-AWQG7FCC.js +19 -0
  28. package/dist/chunk-AWQG7FCC.js.map +1 -0
  29. package/dist/chunk-CZZPXUOU.js +3 -0
  30. package/dist/{chunk-SBXNQW4G.js.map → chunk-CZZPXUOU.js.map} +1 -1
  31. package/dist/{chunk-ZORVNOKX.js → chunk-FMK7UQ6V.js} +24 -145
  32. package/dist/chunk-FMK7UQ6V.js.map +1 -0
  33. package/dist/{chunk-BVL7Y574.js → chunk-FZ7YFQN7.js} +26 -10
  34. package/dist/chunk-FZ7YFQN7.js.map +1 -0
  35. package/dist/{chunk-USO6TERP.js → chunk-GOZLNM4L.js} +11 -9
  36. package/dist/chunk-GOZLNM4L.js.map +1 -0
  37. package/dist/{chunk-H5VIV7MQ.js → chunk-I5DUQ5HM.js} +88 -82
  38. package/dist/chunk-I5DUQ5HM.js.map +1 -0
  39. package/dist/chunk-J7OWWTCW.js +143 -0
  40. package/dist/chunk-J7OWWTCW.js.map +1 -0
  41. package/dist/{chunk-H6ZAOWNE.js → chunk-JVBYV6TL.js} +3 -3
  42. package/dist/{chunk-H6ZAOWNE.js.map → chunk-JVBYV6TL.js.map} +1 -1
  43. package/dist/{chunk-VQ47KWD5.js → chunk-KMJ7RWD3.js} +18 -7
  44. package/dist/chunk-KMJ7RWD3.js.map +1 -0
  45. package/dist/{chunk-ERFMC6WU.js → chunk-M7HP3FTK.js} +20 -11
  46. package/dist/chunk-M7HP3FTK.js.map +1 -0
  47. package/dist/chunk-MIM5R4ZT.js +14972 -0
  48. package/dist/chunk-MIM5R4ZT.js.map +1 -0
  49. package/dist/{chunk-HFRE4QQV.js → chunk-T5IBSPAM.js} +96 -32
  50. package/dist/chunk-T5IBSPAM.js.map +1 -0
  51. package/dist/chunk-UNAZSLWE.js +294 -0
  52. package/dist/chunk-UNAZSLWE.js.map +1 -0
  53. package/dist/{chunk-RS3HRQWU.js → chunk-WPLAANH4.js} +4 -3
  54. package/dist/chunk-WPLAANH4.js.map +1 -0
  55. package/dist/{chunk-AWH3SRPW.js → chunk-Y23G2VZL.js} +5 -37
  56. package/dist/chunk-Y23G2VZL.js.map +1 -0
  57. package/dist/{chunk-MOLIUNVH.js → chunk-YT75VYRD.js} +4 -4
  58. package/dist/{chunk-MOLIUNVH.js.map → chunk-YT75VYRD.js.map} +1 -1
  59. package/dist/chunk-ZIFPHIWZ.js +302 -0
  60. package/dist/chunk-ZIFPHIWZ.js.map +1 -0
  61. package/dist/config/index.d.ts +48 -40
  62. package/dist/config/index.js +4 -5
  63. package/dist/controls/index.d.ts +91 -7
  64. package/dist/controls/index.js +5 -4
  65. package/dist/dragdrop/index.d.ts +9 -7
  66. package/dist/dragdrop/index.js +3 -2
  67. package/dist/index.d.ts +294 -82
  68. package/dist/index.js +425 -402
  69. package/dist/index.js.map +1 -1
  70. package/dist/layers/index.d.ts +4 -5
  71. package/dist/layers/index.js +2 -1
  72. package/dist/loupe/index.d.ts +20 -17
  73. package/dist/loupe/index.js +5 -3
  74. package/dist/overview/index.d.ts +56 -0
  75. package/dist/overview/index.js +289 -0
  76. package/dist/overview/index.js.map +1 -0
  77. package/dist/passthrough/weasel-ui.d.ts +239 -69
  78. package/dist/passthrough/weasel-ui.js +3 -2
  79. package/dist/primitives/index.d.ts +5 -204
  80. package/dist/primitives/index.js +6 -4
  81. package/dist/state/index.d.ts +8 -8
  82. package/dist/state/index.js +4 -5
  83. package/dist/state/index.js.map +1 -1
  84. package/dist/styles.css +152 -82
  85. package/dist/undo/index.d.ts +4 -4
  86. package/package.json +17 -12
  87. package/src/annotations/AnnotationOverlay.tsx +2 -2
  88. package/src/annotations/Annotations.less +0 -10
  89. package/src/annotations/MarkList.tsx +4 -8
  90. package/src/annotations/index.ts +1 -0
  91. package/src/annotations/paint.test.ts +3 -14
  92. package/src/annotations/paint.ts +3 -19
  93. package/src/annotations/store.test.ts +26 -0
  94. package/src/annotations/store.ts +25 -6
  95. package/src/annotations/svgNodes.test.ts +6 -9
  96. package/src/annotations/svgNodes.ts +3 -3
  97. package/src/annotations/toolMap.test.ts +37 -2
  98. package/src/annotations/toolMap.ts +13 -0
  99. package/src/annotations/types.ts +27 -0
  100. package/src/canvas/AGENTS.md +9 -10
  101. package/src/canvas/CameraInput.test.tsx +136 -0
  102. package/src/canvas/CameraInput.tsx +322 -0
  103. package/src/canvas/CanvasStack.tsx +47 -58
  104. package/src/canvas/LinkedCursor.less +16 -0
  105. package/src/canvas/LinkedCursor.tsx +54 -0
  106. package/src/canvas/Stage.test.tsx +33 -4
  107. package/src/canvas/Stage.tsx +47 -37
  108. package/src/canvas/cameraRegistry.test.ts +55 -0
  109. package/src/canvas/cameraRegistry.ts +86 -0
  110. package/src/canvas/cameraView.test.ts +60 -0
  111. package/src/canvas/cameraView.ts +61 -0
  112. package/src/canvas/cameraZoom.test.ts +46 -0
  113. package/src/canvas/cameraZoom.ts +24 -0
  114. package/src/canvas/index.ts +23 -2
  115. package/src/canvas/sharedScope.test.tsx +94 -0
  116. package/src/chrome/ChromeRegions.stories.tsx +1 -0
  117. package/src/chrome/regions/PaletteRegion.less +0 -7
  118. package/src/chrome/regions/SidebarRegion.less +12 -2
  119. package/src/chrome/regions/SidebarRegion.test.tsx +9 -2
  120. package/src/config/builder.test.ts +2 -1
  121. package/src/config/builder.ts +15 -8
  122. package/src/config/entry.test.ts +3 -1
  123. package/src/config/fromConfigField.test.ts +14 -0
  124. package/src/config/fromConfigField.ts +19 -4
  125. package/src/config/index.ts +4 -1
  126. package/src/config/path.ts +2 -5
  127. package/src/config/resolve.ts +15 -1
  128. package/src/config/sectionTree.test.ts +12 -6
  129. package/src/config/sectionTree.ts +15 -5
  130. package/src/config/types.ts +34 -9
  131. package/src/controls/ControlPanel.less +1 -1
  132. package/src/controls/ControlPanel.stories.tsx +3 -2
  133. package/src/controls/ControlPanel.test.tsx +76 -13
  134. package/src/controls/ControlPanel.tsx +80 -11
  135. package/src/controls/inDialog.test.tsx +18 -0
  136. package/src/controls/inDialog.tsx +2 -10
  137. package/src/index.test.ts +1 -1
  138. package/src/index.ts +23 -5
  139. package/src/instrument/SineWave.smoke.test.tsx +2 -0
  140. package/src/instrument/types.ts +10 -1
  141. package/src/lab/Lab.nebula.test.tsx +11 -2
  142. package/src/lab/Lab.test.tsx +3 -1
  143. package/src/lab/Lab.tsx +87 -76
  144. package/src/lab/{LabFit.stories.less → LabFit.browser.test.less} +1 -1
  145. package/src/lab/{LabFit.stories.tsx → LabFit.browser.test.tsx} +41 -63
  146. package/src/lab/LabFullChrome.stories.tsx +1 -0
  147. package/src/lab/LabHeader.test.tsx +24 -14
  148. package/src/lab/LabHeader.tsx +11 -12
  149. package/src/lab/LabShell.less +8 -17
  150. package/src/lab/LabSwitcher.less +6 -6
  151. package/src/lab/LabZoom.test.tsx +223 -0
  152. package/src/lab/LabZoom.tsx +170 -0
  153. package/src/lab/index.ts +1 -0
  154. package/src/loupe/AGENTS.md +7 -6
  155. package/src/loupe/LoupeGestures.tsx +15 -10
  156. package/src/loupe/TrialLoupe.tsx +5 -5
  157. package/src/loupe/index.ts +1 -0
  158. package/src/loupe/loupeActions.ts +6 -2
  159. package/src/overview/AGENTS.md +38 -0
  160. package/src/overview/OverviewMarks.test.tsx +100 -0
  161. package/src/overview/OverviewMarks.tsx +93 -0
  162. package/src/overview/TrialOverview.less +53 -0
  163. package/src/overview/TrialOverview.stories.tsx +50 -0
  164. package/src/overview/TrialOverview.test.tsx +116 -0
  165. package/src/overview/TrialOverview.tsx +376 -0
  166. package/src/overview/index.ts +2 -0
  167. package/src/passthrough/weasel-ui.test.ts +2 -0
  168. package/src/passthrough/weasel-ui.ts +23 -4
  169. package/src/primitives/FloatingPanel.test.tsx +11 -0
  170. package/src/primitives/FloatingPanel.tsx +2 -5
  171. package/src/primitives/JobProgress.less +1 -1
  172. package/src/primitives/Legend.stories.tsx +1 -0
  173. package/src/primitives/Readout.browser.test.tsx +137 -0
  174. package/src/primitives/Readout.less +12 -0
  175. package/src/primitives/Readout.stories.tsx +58 -0
  176. package/src/primitives/Readout.test.tsx +93 -0
  177. package/src/primitives/Readout.tsx +56 -0
  178. package/src/primitives/index.ts +2 -0
  179. package/src/specimen/Specimen.stories.tsx +1 -0
  180. package/src/specimen/Specimen.tsx +21 -4
  181. package/src/state/Persistence.browser.test.tsx +47 -0
  182. package/src/state/labRecords.test.ts +4 -1
  183. package/src/state/store.test.ts +9 -5
  184. package/src/state/usePersistedState.test.tsx +1 -1
  185. package/src/state/useTrialState.test.tsx +1 -1
  186. package/src/styles.less +3 -0
  187. package/src/theme/Interstellar.stories.tsx +4 -6
  188. package/src/theme/base.less +21 -47
  189. package/src/theme/interstellar.test.ts +1 -1
  190. package/src/theme/interstellar.theme.json +10 -3
  191. package/src/tools/labTool.ts +6 -2
  192. package/src/trial/Trial.annotations.test.tsx +16 -0
  193. package/src/trial/Trial.less +0 -13
  194. package/src/trial/Trial.loupe.test.tsx +73 -1
  195. package/src/trial/Trial.stories.tsx +2 -0
  196. package/src/trial/Trial.tsx +23 -1
  197. package/src/trial/TrialChrome.tsx +21 -3
  198. package/src/trial/trialOps.test.ts +4 -4
  199. package/dist/_dts/PrefsRow.d-DFpPIeIC.d.ts +0 -39
  200. package/dist/_dts/inDialog-D5ZKGjiD.d.ts +0 -23
  201. package/dist/_dts/index-DINmCuLA.d.ts +0 -453
  202. package/dist/_dts/number.d-OeQYQoUO.d.ts +0 -28
  203. package/dist/_dts/usePanZoom-Bq73pwFe.d.ts +0 -117
  204. package/dist/chunk-2AYGEN57.js +0 -32
  205. package/dist/chunk-2AYGEN57.js.map +0 -1
  206. package/dist/chunk-AWH3SRPW.js.map +0 -1
  207. package/dist/chunk-BVL7Y574.js.map +0 -1
  208. package/dist/chunk-ERFMC6WU.js.map +0 -1
  209. package/dist/chunk-H5VIV7MQ.js.map +0 -1
  210. package/dist/chunk-HFRE4QQV.js.map +0 -1
  211. package/dist/chunk-RS3HRQWU.js.map +0 -1
  212. package/dist/chunk-SBXNQW4G.js +0 -3
  213. package/dist/chunk-TT5C5S6R.js +0 -368
  214. package/dist/chunk-TT5C5S6R.js.map +0 -1
  215. package/dist/chunk-USO6TERP.js.map +0 -1
  216. package/dist/chunk-VQ47KWD5.js.map +0 -1
  217. package/dist/chunk-WUJXJC5O.js +0 -14568
  218. package/dist/chunk-WUJXJC5O.js.map +0 -1
  219. package/dist/chunk-ZORVNOKX.js.map +0 -1
  220. package/src/canvas/usePanZoom.test.ts +0 -244
  221. package/src/canvas/usePanZoom.ts +0 -147
  222. package/src/state/Persistence.stories.tsx +0 -75
@@ -0,0 +1,302 @@
1
+ import { useSurfaceOptional } from './chunk-WIG6XHJ7.js';
2
+ import { CameraContext, pointerElsewhere, STAGE_VIEW_ID, normalize2DView, useCameraView, CameraScope, CameraInput } from './chunk-UNAZSLWE.js';
3
+ import { resolveFrame, DEFAULT_FRAME } from './chunk-24W5VEJW.js';
4
+ import { CanvasStackContext } from './chunk-CQLQPQ4P.js';
5
+ import { useContext, useRef, useState, useEffect, useMemo } from 'react';
6
+ import { usePointerPosition, crosshairRects, useVisibleRaf } from '@weasel-js/core';
7
+ import { jsxs, jsx } from 'react/jsx-runtime';
8
+
9
+ var ARMS = ["left", "right", "up", "down"];
10
+ function LinkedCursor() {
11
+ const camera = useContext(CameraContext);
12
+ const pointer = usePointerPosition();
13
+ if (!camera) return null;
14
+ const local = pointerElsewhere(pointer, STAGE_VIEW_ID, camera.frame);
15
+ if (!local) return null;
16
+ const v = camera.view.get();
17
+ const x = (local.x - v.x) * v.scale.x;
18
+ const y = (local.y * camera.frame.yDir - v.y) * v.scale.y;
19
+ const { halo, bars } = crosshairRects(x, y, 1);
20
+ const arms = ARMS.map((name, i) => ({ name, halo: halo[i], bar: bars[i] }));
21
+ return /* @__PURE__ */ jsxs("svg", { className: "lk-linked-cursor", "aria-hidden": "true", children: [
22
+ arms.map(
23
+ ({ name, halo: r }) => r ? /* @__PURE__ */ jsx(
24
+ "rect",
25
+ {
26
+ className: "lk-linked-cursor__halo",
27
+ x: r.x,
28
+ y: r.y,
29
+ width: r.w,
30
+ height: r.h
31
+ },
32
+ `halo-${name}`
33
+ ) : null
34
+ ),
35
+ arms.map(
36
+ ({ name, bar: r }) => r ? /* @__PURE__ */ jsx(
37
+ "rect",
38
+ {
39
+ className: "lk-linked-cursor__bar",
40
+ x: r.x,
41
+ y: r.y,
42
+ width: r.w,
43
+ height: r.h
44
+ },
45
+ `bar-${name}`
46
+ ) : null
47
+ )
48
+ ] });
49
+ }
50
+ function useLayerScheduler({
51
+ layers,
52
+ view,
53
+ frame = DEFAULT_FRAME,
54
+ canvasRefs,
55
+ size,
56
+ host
57
+ }) {
58
+ const dirty = useRef(/* @__PURE__ */ new Set());
59
+ const lastRenderRef = useRef(/* @__PURE__ */ new Map());
60
+ useEffect(() => {
61
+ for (const layer of layers) {
62
+ const prev = lastRenderRef.current.get(layer.id);
63
+ if (prev !== layer.render) dirty.current.add(layer.id);
64
+ lastRenderRef.current.set(layer.id, layer.render);
65
+ }
66
+ for (const id of [...lastRenderRef.current.keys()]) {
67
+ if (!layers.find((l) => l.id === id)) lastRenderRef.current.delete(id);
68
+ }
69
+ }, [layers]);
70
+ useEffect(() => {
71
+ for (const layer of layers) dirty.current.add(layer.id);
72
+ }, [view, frame, size, layers]);
73
+ const frameLoop = useVisibleRaf(
74
+ () => {
75
+ if (dirty.current.size === 0) return;
76
+ const map = canvasRefs.current;
77
+ if (!map) {
78
+ dirty.current.clear();
79
+ return;
80
+ }
81
+ for (const layer of layers) {
82
+ if (!dirty.current.has(layer.id)) continue;
83
+ if (!layer.visible) continue;
84
+ const canvas = map.get(layer.id);
85
+ if (!canvas) continue;
86
+ const ctx = canvas.getContext("2d");
87
+ if (!ctx) continue;
88
+ ctx.save();
89
+ ctx.setTransform(size.dpr, 0, 0, size.dpr, 0, 0);
90
+ ctx.clearRect(0, 0, size.width, size.height);
91
+ layer.render(ctx, view, frame);
92
+ ctx.restore();
93
+ }
94
+ dirty.current.clear();
95
+ },
96
+ { target: host }
97
+ );
98
+ useEffect(() => {
99
+ if (dirty.current.size > 0) frameLoop.request();
100
+ });
101
+ }
102
+ function CanvasStack({
103
+ layers,
104
+ view: viewProp,
105
+ onViewChange,
106
+ worldSpec,
107
+ minZoom,
108
+ maxZoom,
109
+ width = "100%",
110
+ height = "100%",
111
+ className,
112
+ onResize,
113
+ onHitTest,
114
+ children,
115
+ yoke
116
+ }) {
117
+ const view = normalize2DView(viewProp);
118
+ const containerRef = useRef(null);
119
+ const canvasMap = useRef(/* @__PURE__ */ new Map());
120
+ const [size, setSize] = useState({ width: 0, height: 0, dpr: 1 });
121
+ const setCanvasRef = (id, el) => {
122
+ if (el) canvasMap.current.set(id, el);
123
+ else canvasMap.current.delete(id);
124
+ };
125
+ useEffect(() => {
126
+ const el = containerRef.current;
127
+ if (!el) return;
128
+ const update = () => {
129
+ const rect = el.getBoundingClientRect();
130
+ const dpr = window.devicePixelRatio ?? 1;
131
+ setSize((prev) => {
132
+ if (prev.width === rect.width && prev.height === rect.height && prev.dpr === dpr) {
133
+ return prev;
134
+ }
135
+ return { width: rect.width, height: rect.height, dpr };
136
+ });
137
+ };
138
+ update();
139
+ if (typeof ResizeObserver === "undefined") return;
140
+ const ro = new ResizeObserver(update);
141
+ ro.observe(el);
142
+ return () => ro.disconnect();
143
+ }, []);
144
+ const onResizeRef = useRef(onResize);
145
+ onResizeRef.current = onResize;
146
+ useEffect(() => {
147
+ if (size.width === 0 && size.height === 0) return;
148
+ onResizeRef.current?.({ width: size.width, height: size.height });
149
+ }, [size.width, size.height]);
150
+ const frame = useMemo(() => resolveFrame(worldSpec, size), [worldSpec, size]);
151
+ const camera = useCameraView({
152
+ view: viewProp,
153
+ onViewChange,
154
+ frame,
155
+ hostRef: containerRef,
156
+ minZoom,
157
+ maxZoom
158
+ });
159
+ const cameraCtx = useMemo(
160
+ () => ({ view: camera, frame, element: () => containerRef.current }),
161
+ [camera, frame]
162
+ );
163
+ useLayerScheduler({ layers, view, frame, canvasRefs: canvasMap, size, host: containerRef });
164
+ const ctxValue = useMemo(
165
+ () => ({
166
+ view,
167
+ frame,
168
+ surface: { element: containerRef, size, canvases: canvasMap, layers }
169
+ }),
170
+ [view, frame, size, layers]
171
+ );
172
+ const containerStyle = { width, height };
173
+ const canvasPx = {
174
+ width: Math.max(0, Math.round(size.width * size.dpr)),
175
+ height: Math.max(0, Math.round(size.height * size.dpr))
176
+ };
177
+ const canvasCss = {
178
+ width: size.width,
179
+ height: size.height
180
+ };
181
+ return /* @__PURE__ */ jsx(CameraScope, { yoke, children: /* @__PURE__ */ jsx(CameraContext.Provider, { value: cameraCtx, children: /* @__PURE__ */ jsxs(CanvasStackContext.Provider, { value: ctxValue, children: [
182
+ /* @__PURE__ */ jsx(CameraInput, { hostRef: containerRef, camera, frame, onTap: onHitTest }),
183
+ /* @__PURE__ */ jsxs(
184
+ "div",
185
+ {
186
+ ref: containerRef,
187
+ className: className ? `lk-canvas-stack ${className}` : "lk-canvas-stack",
188
+ style: containerStyle,
189
+ children: [
190
+ layers.map((layer) => /* @__PURE__ */ jsx(
191
+ "canvas",
192
+ {
193
+ ref: (el) => setCanvasRef(layer.id, el),
194
+ className: "lk-canvas-stack__canvas",
195
+ width: canvasPx.width,
196
+ height: canvasPx.height,
197
+ style: { ...canvasCss, display: layer.visible ? "block" : "none" }
198
+ },
199
+ layer.id
200
+ )),
201
+ /* @__PURE__ */ jsx(LinkedCursor, {}),
202
+ /* @__PURE__ */ jsx("div", { className: "lk-canvas-stack__overlay", children })
203
+ ]
204
+ }
205
+ )
206
+ ] }) }) });
207
+ }
208
+ function fitStage(size, viewport) {
209
+ if (!(size.width > 0 && size.height > 0 && viewport.width > 0 && viewport.height > 0)) {
210
+ return { zoom: 1, pan: { x: 0, y: 0 } };
211
+ }
212
+ const zoom = Math.min(1, viewport.width / size.width, viewport.height / size.height);
213
+ return {
214
+ zoom,
215
+ pan: {
216
+ x: (viewport.width - size.width * zoom) / 2,
217
+ y: (viewport.height - size.height * zoom) / 2
218
+ }
219
+ };
220
+ }
221
+ function Stage({
222
+ size,
223
+ view: viewProp,
224
+ onViewChange,
225
+ minZoom,
226
+ maxZoom,
227
+ onResize,
228
+ hostRef,
229
+ overlay,
230
+ children,
231
+ yoke
232
+ }) {
233
+ const view = normalize2DView(viewProp);
234
+ const host = useRef(null);
235
+ const surface = useSurfaceOptional();
236
+ const camera = useCameraView({
237
+ view: viewProp,
238
+ onViewChange,
239
+ frame: DEFAULT_FRAME,
240
+ hostRef: host,
241
+ minZoom,
242
+ maxZoom
243
+ });
244
+ const cameraCtx = useMemo(
245
+ () => ({
246
+ view: camera,
247
+ frame: DEFAULT_FRAME,
248
+ element: () => host.current,
249
+ content: { x: 0, y: 0, width: size.width, height: size.height }
250
+ }),
251
+ [camera, size.width, size.height]
252
+ );
253
+ const onResizeRef = useRef(onResize);
254
+ onResizeRef.current = onResize;
255
+ useEffect(() => {
256
+ const el = host.current;
257
+ if (!el) return;
258
+ const measure = () => {
259
+ const rect = el.getBoundingClientRect();
260
+ if (rect.width > 0 && rect.height > 0) {
261
+ onResizeRef.current?.({ width: rect.width, height: rect.height });
262
+ }
263
+ };
264
+ measure();
265
+ if (typeof ResizeObserver === "undefined") return;
266
+ const ro = new ResizeObserver(measure);
267
+ ro.observe(el);
268
+ return () => ro.disconnect();
269
+ }, []);
270
+ useEffect(() => {
271
+ surface?.invalidateRects();
272
+ }, [surface, view.zoom, view.pan.x, view.pan.y]);
273
+ const cameraVars = {
274
+ ["--lk-stage-x"]: `${view.pan.x}px`,
275
+ ["--lk-stage-y"]: `${view.pan.y}px`,
276
+ ["--lk-stage-zoom"]: String(view.zoom),
277
+ ["--lk-stage-w"]: `${size.width}px`,
278
+ ["--lk-stage-h"]: `${size.height}px`
279
+ };
280
+ return /* @__PURE__ */ jsx(CameraScope, { yoke, children: /* @__PURE__ */ jsxs(CameraContext.Provider, { value: cameraCtx, children: [
281
+ /* @__PURE__ */ jsx(CameraInput, { hostRef: host, camera, frame: DEFAULT_FRAME }),
282
+ /* @__PURE__ */ jsxs(
283
+ "div",
284
+ {
285
+ ref: (el) => {
286
+ host.current = el;
287
+ if (hostRef) hostRef.current = el;
288
+ },
289
+ className: "lk-stage",
290
+ children: [
291
+ /* @__PURE__ */ jsx("div", { className: "lk-stage__content", style: cameraVars, children }),
292
+ /* @__PURE__ */ jsx(LinkedCursor, {}),
293
+ overlay
294
+ ]
295
+ }
296
+ )
297
+ ] }) });
298
+ }
299
+
300
+ export { CanvasStack, LinkedCursor, Stage, fitStage };
301
+ //# sourceMappingURL=chunk-ZIFPHIWZ.js.map
302
+ //# sourceMappingURL=chunk-ZIFPHIWZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/canvas/LinkedCursor.tsx","../src/canvas/useLayerScheduler.ts","../src/canvas/CanvasStack.tsx","../src/canvas/Stage.tsx"],"names":["useRef","useEffect","jsx","jsxs","useMemo"],"mappings":"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","file":"chunk-ZIFPHIWZ.js","sourcesContent":["import { crosshairRects, usePointerPosition } from '@weasel-js/core';\nimport { useContext } from 'react';\nimport { CameraContext, pointerElsewhere, STAGE_VIEW_ID } from './CameraInput';\n\n/** `crosshairRects` returns its four arms in this order. */\nconst ARMS = ['left', 'right', 'up', 'down'] as const;\n\n/**\n * The other side of a linked cursor: a crosshair on the camera's own element\n * wherever the shared pointer is over some other view — an overview, say. The\n * same crosshair core paints on a weasel canvas.\n */\nexport function LinkedCursor() {\n const camera = useContext(CameraContext);\n const pointer = usePointerPosition();\n if (!camera) return null;\n const local = pointerElsewhere(pointer, STAGE_VIEW_ID, camera.frame);\n if (!local) return null;\n // `pointerElsewhere` hands back the instrument's world; the camera works\n // frame-local, and the map is its own inverse.\n const v = camera.view.get();\n const x = (local.x - v.x) * v.scale.x;\n const y = (local.y * camera.frame.yDir - v.y) * v.scale.y;\n const { halo, bars } = crosshairRects(x, y, 1);\n const arms = ARMS.map((name, i) => ({ name, halo: halo[i], bar: bars[i] }));\n return (\n <svg className=\"lk-linked-cursor\" aria-hidden=\"true\">\n {arms.map(({ name, halo: r }) =>\n r ? (\n <rect\n key={`halo-${name}`}\n className=\"lk-linked-cursor__halo\"\n x={r.x}\n y={r.y}\n width={r.w}\n height={r.h}\n />\n ) : null,\n )}\n {arms.map(({ name, bar: r }) =>\n r ? (\n <rect\n key={`bar-${name}`}\n className=\"lk-linked-cursor__bar\"\n x={r.x}\n y={r.y}\n width={r.w}\n height={r.h}\n />\n ) : null,\n )}\n </svg>\n );\n}\n","import { useVisibleRaf } from '@weasel-js/core';\nimport { type RefObject, useEffect, useRef } from 'react';\nimport type { ViewTransform } from '../instrument/types';\nimport { DEFAULT_FRAME, type WorldFrame } from './worldSpec';\n\n/** One layer of a canvas stack: its id, whether it is currently shown, and how\n * it paints itself. */\nexport interface CanvasLayerDescriptor {\n id: string;\n visible: boolean;\n render: (ctx: CanvasRenderingContext2D, view: ViewTransform, frame: WorldFrame) => void;\n}\n\ninterface SchedulerOptions {\n layers: CanvasLayerDescriptor[];\n view: ViewTransform;\n /** The instrument's coordinate system, resolved against `size`. */\n frame?: WorldFrame;\n canvasRefs: RefObject<Map<string, HTMLCanvasElement>>;\n size: { width: number; height: number; dpr: number };\n /** The element the stack occupies. Given one, the scheduler also stops while\n * the stack sits outside the viewport. */\n host?: RefObject<Element | null>;\n}\n\nexport function useLayerScheduler({\n layers,\n view,\n frame = DEFAULT_FRAME,\n canvasRefs,\n size,\n host,\n}: SchedulerOptions): void {\n const dirty = useRef<Set<string>>(new Set());\n const lastRenderRef = useRef<Map<string, CanvasLayerDescriptor['render']>>(new Map());\n\n useEffect(() => {\n for (const layer of layers) {\n const prev = lastRenderRef.current.get(layer.id);\n if (prev !== layer.render) dirty.current.add(layer.id);\n lastRenderRef.current.set(layer.id, layer.render);\n }\n for (const id of [...lastRenderRef.current.keys()]) {\n if (!layers.find((l) => l.id === id)) lastRenderRef.current.delete(id);\n }\n }, [layers]);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: re-mark dirty when view or size changes\n useEffect(() => {\n for (const layer of layers) dirty.current.add(layer.id);\n }, [view, frame, size, layers]);\n\n // Painting only what is dirty is not the same as doing nothing: a hidden tab\n // still commits React updates, and the effect above marks every layer dirty\n // on any view or size change. The gate is what actually stops the work.\n const frameLoop = useVisibleRaf(\n () => {\n if (dirty.current.size === 0) return;\n const map = canvasRefs.current;\n if (!map) {\n dirty.current.clear();\n return;\n }\n for (const layer of layers) {\n if (!dirty.current.has(layer.id)) continue;\n if (!layer.visible) continue;\n const canvas = map.get(layer.id);\n if (!canvas) continue;\n const ctx = canvas.getContext('2d');\n if (!ctx) continue;\n ctx.save();\n ctx.setTransform(size.dpr, 0, 0, size.dpr, 0, 0);\n ctx.clearRect(0, 0, size.width, size.height);\n layer.render(ctx, view, frame);\n ctx.restore();\n }\n dirty.current.clear();\n },\n { target: host },\n );\n\n useEffect(() => {\n if (dirty.current.size > 0) frameLoop.request();\n });\n}\n","import type { Yoke } from '@weasel-js/core';\nimport { type CSSProperties, type ReactNode, useEffect, useMemo, useRef, useState } from 'react';\nimport type { Point, ViewTransform } from '../instrument/types';\nimport { normalize2DView } from '../state/view';\nimport { CameraContext, CameraInput, CameraScope, useCameraView } from './CameraInput';\nimport { CanvasStackContext } from './CanvasStackContext';\nimport { LinkedCursor } from './LinkedCursor';\nimport { type CanvasLayerDescriptor, useLayerScheduler } from './useLayerScheduler';\nimport { resolveFrame, type ViewportSize, type WorldSpec } from './worldSpec';\n\n/** Props for `<CanvasStack>`. */\nexport interface CanvasStackProps {\n layers: CanvasLayerDescriptor[];\n view: ViewTransform;\n onViewChange: (v: ViewTransform) => void;\n /** The instrument's coordinate system. Omitted, world (0,0) sits at the\n * element's top-left with y running down. */\n worldSpec?: WorldSpec;\n minZoom?: number;\n maxZoom?: number;\n width?: number | string;\n height?: number | string;\n className?: string;\n /** Fired whenever the stack is measured, so a consumer can place a view that\n * only makes sense in terms of the viewport. */\n onResize?: (size: ViewportSize) => void;\n onHitTest?: (worldPos: Point) => void;\n children?: ReactNode;\n /** A yoke to join, from `useYoke()`: cameras on one yoke share their tool\n * and gesture in flight. Omitted, this one keeps its own. */\n yoke?: Yoke;\n}\n\n/** Stacks one `<canvas>` per layer and drives them from a shared view, so a\n * layer that changes rarely is not redrawn with one that changes every frame.\n * Handles sizing and device pixel ratio; pan and zoom route through weasel's\n * gesture dispatcher (`<CameraInput>`). */\nexport function CanvasStack({\n layers,\n view: viewProp,\n onViewChange,\n worldSpec,\n minZoom,\n maxZoom,\n width = '100%',\n height = '100%',\n className,\n onResize,\n onHitTest,\n children,\n yoke,\n}: CanvasStackProps) {\n const view = normalize2DView(viewProp);\n const containerRef = useRef<HTMLDivElement | null>(null);\n const canvasMap = useRef<Map<string, HTMLCanvasElement>>(new Map());\n const [size, setSize] = useState({ width: 0, height: 0, dpr: 1 });\n\n const setCanvasRef = (id: string, el: HTMLCanvasElement | null): void => {\n if (el) canvasMap.current.set(id, el);\n else canvasMap.current.delete(id);\n };\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n const update = () => {\n const rect = el.getBoundingClientRect();\n const dpr = window.devicePixelRatio ?? 1;\n setSize((prev) => {\n if (prev.width === rect.width && prev.height === rect.height && prev.dpr === dpr) {\n return prev;\n }\n return { width: rect.width, height: rect.height, dpr };\n });\n };\n update();\n if (typeof ResizeObserver === 'undefined') return;\n const ro = new ResizeObserver(update);\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n\n const onResizeRef = useRef(onResize);\n onResizeRef.current = onResize;\n useEffect(() => {\n if (size.width === 0 && size.height === 0) return;\n onResizeRef.current?.({ width: size.width, height: size.height });\n }, [size.width, size.height]);\n\n const frame = useMemo(() => resolveFrame(worldSpec, size), [worldSpec, size]);\n\n const camera = useCameraView({\n view: viewProp,\n onViewChange,\n frame,\n hostRef: containerRef,\n minZoom,\n maxZoom,\n });\n const cameraCtx = useMemo(\n () => ({ view: camera, frame, element: () => containerRef.current }),\n [camera, frame],\n );\n useLayerScheduler({ layers, view, frame, canvasRefs: canvasMap, size, host: containerRef });\n\n const ctxValue = useMemo(\n () => ({\n view,\n frame,\n surface: { element: containerRef, size, canvases: canvasMap, layers },\n }),\n [view, frame, size, layers],\n );\n\n const containerStyle: CSSProperties = { width, height };\n const canvasPx = {\n width: Math.max(0, Math.round(size.width * size.dpr)),\n height: Math.max(0, Math.round(size.height * size.dpr)),\n };\n const canvasCss: CSSProperties = {\n width: size.width,\n height: size.height,\n };\n\n return (\n <CameraScope yoke={yoke}>\n <CameraContext.Provider value={cameraCtx}>\n <CanvasStackContext.Provider value={ctxValue}>\n <CameraInput hostRef={containerRef} camera={camera} frame={frame} onTap={onHitTest} />\n <div\n ref={containerRef}\n className={className ? `lk-canvas-stack ${className}` : 'lk-canvas-stack'}\n style={containerStyle}\n >\n {layers.map((layer) => (\n <canvas\n key={layer.id}\n ref={(el) => setCanvasRef(layer.id, el)}\n className=\"lk-canvas-stack__canvas\"\n width={canvasPx.width}\n height={canvasPx.height}\n style={{ ...canvasCss, display: layer.visible ? 'block' : 'none' }}\n />\n ))}\n <LinkedCursor />\n <div className=\"lk-canvas-stack__overlay\">{children}</div>\n </div>\n </CanvasStackContext.Provider>\n </CameraContext.Provider>\n </CameraScope>\n );\n}\n","import type { Yoke } from '@weasel-js/core';\nimport { type CSSProperties, type ReactNode, useEffect, useMemo, useRef } from 'react';\nimport type { ViewTransform } from '../instrument/types';\nimport { normalize2DView } from '../state/view';\nimport { useSurfaceOptional } from '../surface/useSurfaceTile';\nimport { CameraContext, CameraInput, CameraScope, useCameraView } from './CameraInput';\nimport { LinkedCursor } from './LinkedCursor';\nimport { DEFAULT_FRAME, type ViewportSize } from './worldSpec';\n\n/** Props for `<Stage>`. */\nexport interface StageProps {\n /** The content's own size in CSS pixels, at zoom 1. */\n size: ViewportSize;\n view: ViewTransform;\n onViewChange: (v: ViewTransform) => void;\n minZoom?: number;\n maxZoom?: number;\n /** Fired whenever the stage's viewport is measured at a non-zero size. */\n onResize?: (viewport: ViewportSize) => void;\n /** Handed the viewport element, for a consumer tracking the pointer over it. */\n hostRef?: { current: HTMLDivElement | null };\n /** Drawn over the content in viewport pixels, outside the camera. */\n overlay?: ReactNode;\n children?: ReactNode;\n /** A yoke to join, from `useYoke()`: cameras on one yoke share their tool\n * and gesture in flight. Omitted, this one keeps its own. */\n yoke?: Yoke;\n}\n\n/**\n * The camera a stage opens at: the content centered in its viewport, shrunk to\n * fit if it has to be and never enlarged past its own size.\n */\nexport function fitStage(size: ViewportSize, viewport: ViewportSize): ViewTransform {\n if (!(size.width > 0 && size.height > 0 && viewport.width > 0 && viewport.height > 0)) {\n return { zoom: 1, pan: { x: 0, y: 0 } };\n }\n const zoom = Math.min(1, viewport.width / size.width, viewport.height / size.height);\n return {\n zoom,\n pan: {\n x: (viewport.width - size.width * zoom) / 2,\n y: (viewport.height - size.height * zoom) / 2,\n },\n };\n}\n\n/**\n * DOM content of a fixed size, panned and zoomed by a camera — what\n * `<CanvasStack>` is for layers drawn to a canvas. Pan and zoom route through\n * weasel's gesture dispatcher (`<CameraInput>`). The content is laid out at\n * its own size and transformed, so everything inside keeps its layout and a\n * measurement of any element in it reports where it is actually drawn.\n */\nexport function Stage({\n size,\n view: viewProp,\n onViewChange,\n minZoom,\n maxZoom,\n onResize,\n hostRef,\n overlay,\n children,\n yoke,\n}: StageProps) {\n const view = normalize2DView(viewProp);\n const host = useRef<HTMLDivElement | null>(null);\n const surface = useSurfaceOptional();\n const camera = useCameraView({\n view: viewProp,\n onViewChange,\n frame: DEFAULT_FRAME,\n hostRef: host,\n minZoom,\n maxZoom,\n });\n const cameraCtx = useMemo(\n () => ({\n view: camera,\n frame: DEFAULT_FRAME,\n element: () => host.current,\n content: { x: 0, y: 0, width: size.width, height: size.height },\n }),\n [camera, size.width, size.height],\n );\n\n const onResizeRef = useRef(onResize);\n onResizeRef.current = onResize;\n useEffect(() => {\n const el = host.current;\n if (!el) return;\n const measure = () => {\n const rect = el.getBoundingClientRect();\n if (rect.width > 0 && rect.height > 0) {\n onResizeRef.current?.({ width: rect.width, height: rect.height });\n }\n };\n measure();\n if (typeof ResizeObserver === 'undefined') return;\n const ro = new ResizeObserver(measure);\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n\n // A transform moves every tile inside the content without resizing anything,\n // and a ResizeObserver reports only resizes.\n // biome-ignore lint/correctness/useExhaustiveDependencies: the camera is the trigger, not an input — the effect re-measures whenever it moves.\n useEffect(() => {\n surface?.invalidateRects();\n }, [surface, view.zoom, view.pan.x, view.pan.y]);\n\n // The camera changes every frame of a pan, and a transform has nowhere to\n // live but the element's own style: set as custom properties the stylesheet\n // reads.\n const cameraVars = {\n ['--lk-stage-x' as string]: `${view.pan.x}px`,\n ['--lk-stage-y' as string]: `${view.pan.y}px`,\n ['--lk-stage-zoom' as string]: String(view.zoom),\n ['--lk-stage-w' as string]: `${size.width}px`,\n ['--lk-stage-h' as string]: `${size.height}px`,\n } as CSSProperties;\n\n return (\n <CameraScope yoke={yoke}>\n <CameraContext.Provider value={cameraCtx}>\n <CameraInput hostRef={host} camera={camera} frame={DEFAULT_FRAME} />\n <div\n ref={(el) => {\n host.current = el;\n if (hostRef) hostRef.current = el;\n }}\n className=\"lk-stage\"\n >\n <div className=\"lk-stage__content\" style={cameraVars}>\n {children}\n </div>\n <LinkedCursor />\n {overlay}\n </div>\n </CameraContext.Provider>\n </CameraScope>\n );\n}\n"]}
@@ -1,11 +1,12 @@
1
- import { a2 as Auto, O as Instrument } from '../_dts/frac-DTRq7Xpb.js';
2
- export { ax as auto, aD as isAuto } from '../_dts/frac-DTRq7Xpb.js';
3
- import { R as ResolvedConfig, l as ConfigNode, A as Annotations, N as NodeOptions, v as SectionOption, C as ControlRenderer, n as ConfigOption, p as ConfigShape, h as ConfigBranch, B as BranchAnnotations, e as BranchOptions, c as ConfigSchema, I as InferConfig, i as ConfigEntry, b as ConfigField, d as ConfigRule, L as LeafPatch, o as ConfigRuleContext } from '../_dts/types-B7y8hX1D.js';
4
- export { m as ConfigOf, a as ConfigPath, E as EntryValue, q as NodeValue, S as SectionSpec, V as ValueAtPath } from '../_dts/types-B7y8hX1D.js';
5
- import { ToolPrefNumberFormat, ToolPrefNumberUnit, ToolPrefLeaf, ToolPrefGroup } from '@weasel-js/core';
6
- import { I as InDialogOptions } from '../_dts/inDialog-D5ZKGjiD.js';
1
+ import { a5 as Auto } from '../_dts/types-CSm9jgol.js';
2
+ import { Instrument } from '../index.js';
3
+ export { aC as auto, aI as isAuto } from '../_dts/types-CSm9jgol.js';
4
+ import { R as ResolvedConfig, l as ConfigNode, A as Annotations, N as NodeOptions, S as SectionOption, d as ControlRenderer, I as InDialogOptions, n as ConfigOption, p as ConfigShape, h as ConfigBranch, B as BranchAnnotations, e as BranchOptions, b as ConfigSchema, q as InferConfig, i as ConfigEntry, c as ConfigRule, L as LeafPatch, o as ConfigRuleContext } from '../_dts/types-CRPSGFAp.js';
5
+ import { ConfigField } from '../controls/index.js';
6
+ export { m as ConfigOf, C as ConfigPath, D as DialogSpec, E as EntryValue, r as NodeValue, t as SectionSpec, V as ValueAtPath } from '../_dts/types-CRPSGFAp.js';
7
+ import { D as Display } from '../_dts/PrefsRow.d-TOZ7BgD9.js';
8
+ import { ToolPrefNumberUnit, ToolPrefLeaf, ToolPrefGroup } from '@weasel-js/core';
7
9
  import 'react';
8
- import '../_dts/PrefsRow.d-DFpPIeIC.js';
9
10
  import '@weasel-js/theme';
10
11
  import '../_dts/types-DJ79Tg5J.js';
11
12
 
@@ -80,9 +81,9 @@ declare class NumberNode extends BaseNode<number> {
80
81
  /** A display suffix for the value — `'px'`, `'ms'`, `'°'`. Presentation only:
81
82
  * it is never parsed, and the stored value stays a plain number. */
82
83
  suffix(suffix: string): this;
83
- /** Show the value in a named format — `'compact'` reads `2.00M`. Presentation
84
- * only: the stored value stays a plain number, and a typed `2.5m` reads back. */
85
- format(format: ToolPrefNumberFormat): this;
84
+ /** Show, speak and read the value through a display — `compact()` reads
85
+ * `2.00M`. Presentation only: the stored value stays a plain number. */
86
+ display(display: Display): this;
86
87
  /**
87
88
  * Store the value in one unit and edit it in another — radians stored,
88
89
  * degrees typed. `min`, `max` and `step` are declared in the stored unit
@@ -157,6 +158,9 @@ declare class GroupNode<S extends ConfigShape> implements ConfigBranch<S> {
157
158
  /** Show this group only while the predicate holds. Presentational — every
158
159
  * value beneath it stays in config and the instrument still reads it. */
159
160
  showIf(predicate: (config: Record<string, unknown>) => boolean): GroupNode<S>;
161
+ /** Put a button on the group's heading that returns every value beneath it
162
+ * to its default, leaving the rest of the config alone. */
163
+ resettable(): GroupNode<S>;
160
164
  }
161
165
  /**
162
166
  * Builds an instrument's config: the values, their types, and how each is
@@ -202,37 +206,12 @@ declare const f: {
202
206
  *
203
207
  * Rules do not run here: a hand-written `ConfigField` already states its label
204
208
  * and control, which is everything the rule chain would have inferred.
205
- */
206
- declare function fromConfigFields(fields: readonly ConfigField[]): ResolvedConfig;
207
-
208
- /**
209
- * A resolved schema rearranged so its sections are groups.
210
- *
211
- * `PrefsForm`'s rail navigates the schema tree — top-level groups are its
212
- * items, their group children indent under them. A resolved schema keeps its
213
- * sections beside the tree instead, so a flat schema's rail comes out as one
214
- * unnamed item however many headings the panel draws. This puts the sections
215
- * where the rail looks for them.
216
- */
217
- interface SectionTree {
218
- /** The rail's schema. Top-level children are the schema's root sections,
219
- * followed by any root node no section claimed. */
220
- group: ToolPrefGroup;
221
- /** `config` renested to mirror `group` — the value tree `PrefsForm` takes. */
222
- values: Record<string, unknown>;
223
- /** The config path a path within `group` stands for. */
224
- pathAt: (railPath: string) => string;
225
- }
226
- /**
227
- * Turn a resolved schema's root sections into the top level of a `PrefGroup`,
228
- * for `PrefsForm`'s rail layout.
229
209
  *
230
- * A section naming a group brings that group in whole, so it lands as an
231
- * indented rail item; a section naming a leaf drops it loose into the
232
- * section's own pane. Sections nested under a group are left alone — they
233
- * bucket rows inside a pane, which is the panel's job and not the rail's.
210
+ * A dotted `key` is a config path, as it is to every read and write, so its
211
+ * leaf is filed under headless groups at that path rather than as one child
212
+ * whose name contains dots — which no path lookup would find.
234
213
  */
235
- declare function sectionTree(resolved: ResolvedConfig, config: Record<string, unknown>, showHidden?: boolean): SectionTree;
214
+ declare function fromConfigFields(fields: readonly ConfigField[]): ResolvedConfig;
236
215
 
237
216
  /** The value at a dotted path. `undefined` when a segment is missing or the
238
217
  * walk hits something that is not a record. */
@@ -285,6 +264,35 @@ declare const builtinRules: readonly ConfigRule[];
285
264
  */
286
265
  declare function applyRules(seed: LeafPatch, ctx: Omit<ConfigRuleContext, 'leaf'>, rules: readonly ConfigRule[]): LeafPatch;
287
266
 
267
+ /**
268
+ * A resolved schema rearranged so its sections are groups.
269
+ *
270
+ * `PrefsForm`'s rail navigates the schema tree — top-level groups are its
271
+ * items, their group children indent under them. A resolved schema keeps its
272
+ * sections beside the tree instead, so a flat schema's rail comes out as one
273
+ * unnamed item however many headings the panel draws. This puts the sections
274
+ * where the rail looks for them.
275
+ */
276
+ interface SectionTree {
277
+ /** The rail's schema. Top-level children are the schema's root sections,
278
+ * followed by any root node no section claimed. */
279
+ group: ToolPrefGroup;
280
+ /** `config` renested to mirror `group` — the value tree `PrefsForm` takes. */
281
+ values: Record<string, unknown>;
282
+ /** The config path a path within `group` stands for. */
283
+ pathAt: (railPath: string) => string;
284
+ }
285
+ /**
286
+ * Turn a resolved schema's root sections into the top level of a `PrefGroup`,
287
+ * for `PrefsForm`'s rail layout.
288
+ *
289
+ * A section naming a group brings that group in whole, so it lands as an
290
+ * indented rail item; a section naming a leaf drops it loose into the
291
+ * section's own pane. Sections nested under a group are left alone — they
292
+ * bucket rows inside a pane, which is the panel's job and not the rail's.
293
+ */
294
+ declare function sectionTree(resolved: ResolvedConfig, config: Record<string, unknown>, showHidden?: boolean): SectionTree;
295
+
288
296
  /**
289
297
  * An instrument's controls, resolved against the surrounding lab's rules.
290
298
  *
@@ -314,5 +322,5 @@ declare function useResolvedConfig<TC>(schema: ResolvedConfig | undefined, confi
314
322
  */
315
323
  declare function isLeafVisible(resolved: ResolvedConfig, path: string, config: Record<string, unknown>, showHidden?: boolean): boolean;
316
324
 
317
- export { Annotations, Auto, BaseNode, BooleanNode, BranchAnnotations, BranchOptions, ColorNode, ConfigBranch, ConfigEntry, ConfigNode, ConfigOption, ConfigRule, ConfigRuleContext, ConfigSchema, ConfigShape, ControlRenderer, CustomNode, EnumNode, GroupNode, InferConfig, LeafPatch, ListNode, NodeOptions, NumberNode, ResolvedConfig, StringNode, ValueNode, applyRules, autoPathsOf, builtinRules, f, fillConfigDefaults, fromConfigFields, hasConfigPath, isConfigBranch, isLeafVisible, resolveAutoConfig, resolveConfigSchema, schemaNodeAtPath, sectionTree, titleCase, useConfigSchema, useResolvedConfig, valueAtPath, withValueAtPath };
325
+ export { Annotations, Auto, BaseNode, BooleanNode, BranchAnnotations, BranchOptions, ColorNode, ConfigBranch, ConfigEntry, ConfigNode, ConfigOption, ConfigRule, ConfigRuleContext, ConfigSchema, ConfigShape, ControlRenderer, CustomNode, EnumNode, GroupNode, InDialogOptions, InferConfig, LeafPatch, ListNode, NodeOptions, NumberNode, ResolvedConfig, SectionOption, StringNode, ValueNode, applyRules, autoPathsOf, builtinRules, f, fillConfigDefaults, fromConfigFields, hasConfigPath, isConfigBranch, isLeafVisible, resolveAutoConfig, resolveConfigSchema, schemaNodeAtPath, sectionTree, titleCase, useConfigSchema, useResolvedConfig, valueAtPath, withValueAtPath };
318
326
  export type { SectionTree };
@@ -1,8 +1,7 @@
1
- export { sectionTree, useConfigSchema } from '../chunk-USO6TERP.js';
1
+ export { sectionTree, useConfigSchema } from '../chunk-GOZLNM4L.js';
2
2
  import '../chunk-Z76KEZAX.js';
3
- export { BaseNode, BooleanNode, ColorNode, CustomNode, EnumNode, GroupNode, ListNode, NumberNode, StringNode, ValueNode, applyRules, builtinRules, f, isConfigBranch, resolveConfigSchema, titleCase, useResolvedConfig } from '../chunk-BVL7Y574.js';
4
- export { fromConfigFields, isLeafVisible } from '../chunk-VQ47KWD5.js';
5
- export { auto, autoPathsOf, fillConfigDefaults, hasConfigPath, isAuto, resolveAutoConfig, schemaNodeAtPath, valueAtPath, withValueAtPath } from '../chunk-AWH3SRPW.js';
6
- import '../chunk-WUJXJC5O.js';
3
+ export { BaseNode, BooleanNode, ColorNode, CustomNode, EnumNode, GroupNode, ListNode, NumberNode, StringNode, ValueNode, applyRules, builtinRules, f, isConfigBranch, resolveConfigSchema, titleCase, useResolvedConfig } from '../chunk-FZ7YFQN7.js';
4
+ export { fromConfigFields, isLeafVisible } from '../chunk-KMJ7RWD3.js';
5
+ export { auto, autoPathsOf, fillConfigDefaults, hasConfigPath, isAuto, resolveAutoConfig, schemaNodeAtPath, valueAtPath, withValueAtPath } from '../chunk-Y23G2VZL.js';
7
6
  //# sourceMappingURL=index.js.map
8
7
  //# sourceMappingURL=index.js.map
@@ -1,9 +1,93 @@
1
- export { C as ControlPanel } from '../_dts/index-DINmCuLA.js';
2
- export { I as InDialogOptions, i as inDialog, s as summarizeValue } from '../_dts/inDialog-D5ZKGjiD.js';
3
- export { f as CheckboxField, g as ColorField, b as ConfigField, k as ConfigFieldType, r as NumberField, s as SelectField, t as SelectOption, u as SliderField, T as TextField } from '../_dts/types-B7y8hX1D.js';
4
- import 'react/jsx-runtime';
5
- import 'react';
6
- import '../_dts/PrefsRow.d-DFpPIeIC.js';
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { StanceProps } from '../passthrough/weasel-ui.js';
4
+ import { PropertyRowLayout } from '../index.js';
5
+ import { PropertyDensity, PropertyAlign } from '../passthrough/weasel-ui.js';
6
+ import { a as ConfigField, I as InDialogOptions } from '../_dts/types-CRPSGFAp.js';
7
+ import { ResolvedConfig, ControlRenderer } from '../config/index.js';
8
+ export { f as CheckboxField, g as ColorField, k as ConfigFieldType, s as NumberField, u as SelectField, v as SelectOption, w as SliderField, T as TextField } from '../_dts/types-CRPSGFAp.js';
7
9
  import '@weasel-js/theme';
8
10
  import '@weasel-js/core';
9
- import '../_dts/number.d-OeQYQoUO.js';
11
+
12
+ /** How a panel packs its rows into the two-column property grid.
13
+ * - `'auto'`: narrow controls (numbers, checkboxes, dropdowns, colors) sit
14
+ * two per row; the ones that need the width — text, sliders, segmented
15
+ * toggles — span it.
16
+ * - `'pairs'`: every row pairs. A control that needs the full width says so
17
+ * itself, with `<PropertyRow span>`.
18
+ * - `'one-up'`: one control per row, colors excepted.
19
+ */
20
+ type ControlPack = 'auto' | 'pairs' | 'one-up';
21
+ interface ControlPanelProps<TC extends Record<string, unknown>> extends StanceProps {
22
+ /** The instrument's resolved config schema. */
23
+ schema?: ResolvedConfig;
24
+ /** @deprecated Pass `schema`. A field list is adapted into one internally. */
25
+ fields?: ConfigField[];
26
+ config: TC;
27
+ /** Writes one value. The path is dotted for a leaf inside an `f.group`, and
28
+ * the bare key for one at the root. */
29
+ setConfig: (path: string, value: unknown) => void;
30
+ /**
31
+ * Control overrides and app-defined kinds, PrefsForm-style. Keys are config
32
+ * paths (checked first) or leaf kinds. A renderer returning `null` collapses
33
+ * the row; entries override the built-in rows on collision.
34
+ */
35
+ renderers?: Record<string, ControlRenderer>;
36
+ /** How rows pack into the two-column grid. Defaults to `'pairs'`. */
37
+ pack?: ControlPack;
38
+ /**
39
+ * Where a row's label sits relative to its control. Unset takes each row's
40
+ * own orientation — `block` (label above, which is what leaves a paired row
41
+ * room for its value) for most, `inline` for colors and checkboxes.
42
+ */
43
+ layout?: PropertyRowLayout;
44
+ /** Room the rows get. Passed straight to the property list. */
45
+ density?: PropertyDensity;
46
+ /** Cross-axis alignment of an inline row's label and control. */
47
+ align?: PropertyAlign;
48
+ /**
49
+ * Fold each section away behind a twisty, starting `'open'` or `'closed'`.
50
+ * Unset draws the heading alone, as the panel always has — unless a section
51
+ * in the schema declares how it opens, which makes that section foldable on
52
+ * its own and outranks this for that one section.
53
+ */
54
+ collapse?: 'open' | 'closed';
55
+ /**
56
+ * Which sections are folded, keyed as `onCollapse` reports them. Given, the
57
+ * panel keeps no state of its own: every toggle arrives at `onCollapse`
58
+ * instead, which is where a lab that remembers a trial's sections writes
59
+ * them.
60
+ */
61
+ collapsed?: Readonly<Record<string, boolean>>;
62
+ /** A fold moved. The key is a section's label — prefixed by its group's
63
+ * dotted path when the section sits inside one — or a group's own path. */
64
+ onCollapse?: (key: string, collapsed: boolean) => void;
65
+ /** Dotted paths currently unpinned. A row in this set hides its control and
66
+ * reads out `auto`, and clicking its label writes the sentinel back through
67
+ * `setConfig`. Omitted, the panel keeps the set itself: the labels still
68
+ * toggle and the sentinel never reaches `setConfig`, which would otherwise
69
+ * store it as the row's value. */
70
+ auto?: ReadonlySet<string>;
71
+ /** Draw leaves marked `hidden`. */
72
+ showHidden?: boolean;
73
+ /** Heads the panel. With `title`, `stance` or `tone` given, the rows sit in a
74
+ * `<PropertyPanel>` carrying them; with none, they are the bare list. */
75
+ title?: ReactNode;
76
+ className?: string;
77
+ }
78
+ /** Render an instrument's config schema as a stack of controls, each writing
79
+ * back through `setConfig`. Built on the property rows, so a lab's controls
80
+ * are the same aligned, themed rows the rest of the kit uses. */
81
+ declare function ControlPanel<TC extends Record<string, unknown>>({ schema, fields, config, setConfig, renderers, pack, layout, density, align, collapse, collapsed, onCollapse, auto: given, showHidden, title, stance, tone, className, }: ControlPanelProps<TC>): react.JSX.Element;
82
+
83
+ /** The one-line summary a dialog row shows when it is given none. */
84
+ declare function summarizeValue(value: unknown): string;
85
+ /**
86
+ * Moves a control into a modal: the row becomes a button showing a summary of
87
+ * the value, and the button opens a dialog whose body is `body`, handed the
88
+ * same context a row renderer gets. For a leaf whose editor will never fit a
89
+ * sidebar row.
90
+ */
91
+ declare function inDialog(body: ControlRenderer, opts?: InDialogOptions): ControlRenderer;
92
+
93
+ export { ConfigField, ControlPanel, InDialogOptions, inDialog, summarizeValue };
@@ -1,6 +1,7 @@
1
- export { ControlPanel } from '../chunk-HFRE4QQV.js';
2
- import '../chunk-VQ47KWD5.js';
3
- export { inDialog, summarizeValue } from '../chunk-AWH3SRPW.js';
4
- import '../chunk-WUJXJC5O.js';
1
+ export { ControlPanel, inDialog, summarizeValue } from '../chunk-T5IBSPAM.js';
2
+ import '../chunk-KMJ7RWD3.js';
3
+ import '../chunk-Y23G2VZL.js';
4
+ import '../chunk-MIM5R4ZT.js';
5
+ import '../chunk-275JRRVD.js';
5
6
  //# sourceMappingURL=index.js.map
6
7
  //# sourceMappingURL=index.js.map
@@ -1,10 +1,12 @@
1
- import { P as PaletteItem, n as Point, o as DragFeedback, q as DragDropCapability, m as ViewTransform, b as WorldSpec } from '../_dts/frac-DTRq7Xpb.js';
2
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import { n as PaletteItem, o as DragFeedback, q as DragDropCapability } from '../_dts/types-CSm9jgol.js';
2
+ import { WorldSpec } from '../canvas/index.js';
3
+ import { Point, ViewTransform } from '../index.js';
4
+ import * as react from 'react';
3
5
  import { RefObject, PointerEvent, ReactPortal } from 'react';
4
- import '../_dts/PrefsRow.d-DFpPIeIC.js';
5
- import '@weasel-js/theme';
6
6
  import '@weasel-js/core';
7
- import '../_dts/types-B7y8hX1D.js';
7
+ import '../_dts/PrefsRow.d-TOZ7BgD9.js';
8
+ import '@weasel-js/theme';
9
+ import '../_dts/types-CRPSGFAp.js';
8
10
  import '../_dts/types-DJ79Tg5J.js';
9
11
 
10
12
  interface DragState {
@@ -32,7 +34,7 @@ declare function useDragDrop<TS, TC>({ capability, canvasContainerRef, view, sta
32
34
  interface DragOverlayProps {
33
35
  drag: DragState | null;
34
36
  }
35
- declare function DragOverlay({ drag }: DragOverlayProps): react_jsx_runtime.JSX.Element | null;
37
+ declare function DragOverlay({ drag }: DragOverlayProps): react.JSX.Element | null;
36
38
 
37
39
  interface DragGhostProps {
38
40
  item: PaletteItem;
@@ -45,7 +47,7 @@ interface PaletteProps {
45
47
  onDragStart: (item: PaletteItem, e: PointerEvent) => void;
46
48
  className?: string;
47
49
  }
48
- declare function Palette({ items, onDragStart, className }: PaletteProps): react_jsx_runtime.JSX.Element;
50
+ declare function Palette({ items, onDragStart, className }: PaletteProps): react.JSX.Element;
49
51
 
50
52
  export { DragDropCapability, DragFeedback, DragGhost, DragOverlay, Palette, PaletteItem, useDragDrop };
51
53
  export type { DragGhostProps, DragOverlayProps, DragState, PaletteProps, UseDragDropArgs, UseDragDropResult };