@weasel-js/labkit 1.4.4 → 1.5.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 (278) hide show
  1. package/README.md +54 -6
  2. package/dist/_dts/{CanvasStackContext-kTEZvEgE.d.ts → CanvasStackContext-CJNMS6mz.d.ts} +1 -1
  3. package/dist/_dts/{PrefsForm.d-DDHFANkc.d.ts → PrefsForm.d-DPy9I8It.d.ts} +4 -0
  4. package/dist/_dts/Select.d-Gwr8fLpK.d.ts +453 -0
  5. package/dist/_dts/{frac-z7ker2Vx.d.ts → frac-B4okC2lX.d.ts} +291 -31
  6. package/dist/_dts/{index-Blv9uQzu.d.ts → index-BNdz-tNd.d.ts} +105 -17
  7. package/dist/_dts/index-CSJoj4LC.d.ts +248 -0
  8. package/dist/_dts/{types-B9_zrHmb.d.ts → types-DzoNY1pO.d.ts} +91 -4
  9. package/dist/_dts/usePanZoom-Pzf9f2jh.d.ts +160 -0
  10. package/dist/_dts/useTrialState-uLlmaccn.d.ts +212 -0
  11. package/dist/canvas/index.d.ts +18 -129
  12. package/dist/canvas/index.js +4 -2
  13. package/dist/chrome/index.d.ts +5 -93
  14. package/dist/chrome/index.js +10 -6
  15. package/dist/chunk-7PFH3CNM.js +150 -0
  16. package/dist/chunk-7PFH3CNM.js.map +1 -0
  17. package/dist/{chunk-I6JCVE24.js → chunk-CEIUBPPW.js} +143 -17
  18. package/dist/chunk-CEIUBPPW.js.map +1 -0
  19. package/dist/chunk-EH7UEQV7.js +590 -0
  20. package/dist/chunk-EH7UEQV7.js.map +1 -0
  21. package/dist/{chunk-MOM3GOVY.js → chunk-EHGVROVV.js} +125 -27
  22. package/dist/chunk-EHGVROVV.js.map +1 -0
  23. package/dist/{chunk-TJ7QY3OC.js → chunk-ERFMC6WU.js} +160 -78
  24. package/dist/chunk-ERFMC6WU.js.map +1 -0
  25. package/dist/{chunk-CTRKTLYZ.js → chunk-H6ZAOWNE.js} +11 -11
  26. package/dist/chunk-H6ZAOWNE.js.map +1 -0
  27. package/dist/{chunk-64ZCN3DA.js → chunk-IHUVQ3TX.js} +250 -160
  28. package/dist/chunk-IHUVQ3TX.js.map +1 -0
  29. package/dist/{chunk-FQMJUVHJ.js → chunk-IJJUK4FU.js} +53 -46
  30. package/dist/chunk-IJJUK4FU.js.map +1 -0
  31. package/dist/chunk-IXTQI7JX.js +737 -0
  32. package/dist/chunk-IXTQI7JX.js.map +1 -0
  33. package/dist/{chunk-BKFVHKJH.js → chunk-MDVTK5U3.js} +4 -4
  34. package/dist/{chunk-BKFVHKJH.js.map → chunk-MDVTK5U3.js.map} +1 -1
  35. package/dist/chunk-PHK4W562.js +72 -0
  36. package/dist/chunk-PHK4W562.js.map +1 -0
  37. package/dist/chunk-PJ7WEQBJ.js +18 -0
  38. package/dist/chunk-PJ7WEQBJ.js.map +1 -0
  39. package/dist/chunk-RWNDDKOH.js +682 -0
  40. package/dist/chunk-RWNDDKOH.js.map +1 -0
  41. package/dist/chunk-T3EC2DXD.js +12628 -0
  42. package/dist/chunk-T3EC2DXD.js.map +1 -0
  43. package/dist/{chunk-SMHP6XZ4.js → chunk-UN4U4P63.js} +10 -8
  44. package/dist/chunk-UN4U4P63.js.map +1 -0
  45. package/dist/chunk-WIG6XHJ7.js +35 -0
  46. package/dist/chunk-WIG6XHJ7.js.map +1 -0
  47. package/dist/chunk-Z76KEZAX.js +15 -0
  48. package/dist/chunk-Z76KEZAX.js.map +1 -0
  49. package/dist/chunk-ZDHY47EV.js +363 -0
  50. package/dist/chunk-ZDHY47EV.js.map +1 -0
  51. package/dist/config/index.d.ts +276 -0
  52. package/dist/config/index.js +8 -0
  53. package/dist/config/index.js.map +1 -0
  54. package/dist/controls/index.d.ts +3 -4
  55. package/dist/controls/index.js +4 -3
  56. package/dist/dragdrop/index.d.ts +3 -5
  57. package/dist/index.d.ts +266 -413
  58. package/dist/index.js +1775 -2251
  59. package/dist/index.js.map +1 -1
  60. package/dist/layers/index.d.ts +4 -6
  61. package/dist/layers/index.js +2 -2
  62. package/dist/loupe/index.d.ts +69 -20
  63. package/dist/loupe/index.js +2 -2
  64. package/dist/passthrough/weasel-ui.d.ts +175 -309
  65. package/dist/passthrough/weasel-ui.js +1 -1
  66. package/dist/primitives/index.d.ts +55 -6
  67. package/dist/primitives/index.js +6 -4
  68. package/dist/state/index.d.ts +13 -8
  69. package/dist/state/index.js +18 -13
  70. package/dist/state/index.js.map +1 -1
  71. package/dist/styles.css +223 -76
  72. package/dist/surface/index.d.ts +44 -7
  73. package/dist/surface/index.js +2 -1
  74. package/dist/ui/layers/index.js +1 -1
  75. package/dist/undo/index.d.ts +3 -5
  76. package/package.json +15 -8
  77. package/src/annotations/AnnotationOverlay.tsx +56 -5
  78. package/src/annotations/Annotations.less +4 -0
  79. package/src/annotations/Annotations.overlay.test.tsx +105 -6
  80. package/src/annotations/drawOne.test.ts +1 -1
  81. package/src/annotations/drawOne.ts +2 -2
  82. package/src/annotations/paint.test.ts +2 -2
  83. package/src/annotations/paint.ts +1 -1
  84. package/src/annotations/preload.ts +6 -0
  85. package/src/annotations/store.test.ts +18 -0
  86. package/src/annotations/store.ts +5 -4
  87. package/src/annotations/svgNodes.test.ts +2 -2
  88. package/src/annotations/svgNodes.ts +1 -0
  89. package/src/annotations/toolMap.test.ts +9 -5
  90. package/src/annotations/toolMap.ts +6 -3
  91. package/src/annotations/types.ts +6 -3
  92. package/src/canvas/CameraWheelContext.ts +12 -0
  93. package/src/canvas/CanvasStack.stories.tsx +1 -1
  94. package/src/canvas/CanvasStack.test.tsx +25 -0
  95. package/src/canvas/CanvasStack.tsx +31 -4
  96. package/src/canvas/Stage.less +18 -0
  97. package/src/canvas/Stage.test.tsx +186 -0
  98. package/src/canvas/Stage.tsx +134 -0
  99. package/src/canvas/camera.test.ts +17 -2
  100. package/src/canvas/camera.ts +9 -9
  101. package/src/canvas/index.ts +4 -0
  102. package/src/canvas/usePanZoom.test.ts +28 -0
  103. package/src/canvas/usePanZoom.ts +21 -9
  104. package/src/chrome/ChromeRegions.stories.tsx +1 -4
  105. package/src/chrome/LabChrome.tsx +130 -0
  106. package/src/chrome/builtins.test.ts +1 -0
  107. package/src/chrome/builtins.tsx +21 -31
  108. package/src/chrome/index.ts +16 -0
  109. package/src/chrome/labRegions.test.tsx +208 -0
  110. package/src/chrome/labTypes.ts +50 -0
  111. package/src/chrome/merge.ts +7 -9
  112. package/src/chrome/regions/PaletteRegion.test.tsx +6 -4
  113. package/src/chrome/regions/PaletteRegion.tsx +16 -9
  114. package/src/chrome/regions/SidebarRegion.stories.tsx +44 -0
  115. package/src/chrome/regions/SidebarRegion.tsx +25 -13
  116. package/src/chrome/regions/StatusRegion.tsx +15 -8
  117. package/src/chrome/regions/ToolbarRegion.tsx +34 -16
  118. package/src/chrome/types.ts +50 -20
  119. package/src/config/auto.test.ts +12 -0
  120. package/src/config/auto.ts +15 -0
  121. package/src/config/autoConfig.test.ts +107 -0
  122. package/src/config/autoConfig.ts +153 -0
  123. package/src/config/builder.test.ts +42 -0
  124. package/src/config/builder.ts +68 -17
  125. package/src/config/declarationEmit.fixture.ts +11 -0
  126. package/src/config/declarationEmit.test.ts +51 -0
  127. package/src/config/entry.test.ts +46 -0
  128. package/src/config/index.ts +16 -1
  129. package/src/config/nodeClasses.test.ts +18 -0
  130. package/src/config/path.ts +7 -2
  131. package/src/config/resolve.test.ts +25 -0
  132. package/src/config/resolve.ts +17 -1
  133. package/src/config/types.ts +34 -2
  134. package/src/config/useResolvedConfig.ts +26 -0
  135. package/src/controls/AutoControls.stories.tsx +88 -0
  136. package/src/controls/ControlPanel.stories.tsx +42 -1
  137. package/src/controls/ControlPanel.test.tsx +530 -4
  138. package/src/controls/ControlPanel.tsx +562 -82
  139. package/src/fake-indexeddb-auto.d.ts +5 -0
  140. package/src/index.test.ts +7 -0
  141. package/src/index.ts +66 -5
  142. package/src/instrument/serializers.test.ts +22 -0
  143. package/src/instrument/serializers.ts +40 -1
  144. package/src/instrument/types.ts +25 -1
  145. package/src/lab/Lab.chrome.test.tsx +16 -0
  146. package/src/lab/Lab.less +4 -13
  147. package/src/lab/Lab.persist.test.tsx +147 -0
  148. package/src/lab/Lab.stories.tsx +1 -3
  149. package/src/lab/Lab.surface.test.tsx +71 -3
  150. package/src/lab/Lab.test.tsx +168 -16
  151. package/src/lab/Lab.tsx +434 -129
  152. package/src/lab/LabContext.ts +8 -0
  153. package/src/lab/LabFit.stories.less +18 -0
  154. package/src/lab/LabFit.stories.tsx +219 -0
  155. package/src/lab/LabFullChrome.stories.tsx +1 -4
  156. package/src/lab/LabHeader.test.tsx +40 -9
  157. package/src/lab/LabHeader.tsx +7 -14
  158. package/src/lab/LabPalette.tsx +13 -26
  159. package/src/lab/LabRoot.test.tsx +56 -0
  160. package/src/lab/LabRoot.tsx +47 -0
  161. package/src/lab/LabShell.less +73 -15
  162. package/src/lab/LabShell.stories.tsx +50 -1
  163. package/src/lab/LabShell.tsx +18 -27
  164. package/src/lab/LabSwitcher.less +18 -11
  165. package/src/lab/LabSwitcher.test.tsx +14 -9
  166. package/src/lab/LabSwitcher.tsx +8 -4
  167. package/src/lab/Workspace.less +1 -1
  168. package/src/lab/Workspace.stories.tsx +1 -1
  169. package/src/lab/Workspace.surface.test.tsx +1 -0
  170. package/src/lab/fitCheck.test.ts +57 -0
  171. package/src/lab/fitCheck.ts +125 -0
  172. package/src/lab/index.ts +2 -0
  173. package/src/layers/LayerList.stories.tsx +1 -1
  174. package/src/loupe/AGENTS.md +17 -5
  175. package/src/loupe/Loupe.less +1 -1
  176. package/src/loupe/LoupeGestures.tsx +67 -0
  177. package/src/loupe/TrialLoupe.tsx +43 -28
  178. package/src/loupe/canvasLens.ts +1 -1
  179. package/src/loupe/index.ts +4 -0
  180. package/src/loupe/loupeActions.ts +98 -0
  181. package/src/loupe/types.ts +1 -1
  182. package/src/loupe/useLoupe.test.tsx +60 -3
  183. package/src/loupe/useLoupe.ts +29 -49
  184. package/src/passthrough/weasel-ui.ts +9 -0
  185. package/src/primitives/FloatingPanel.stories.tsx +14 -2
  186. package/src/primitives/FloatingPanel.test.tsx +47 -16
  187. package/src/primitives/FloatingPanel.tsx +11 -25
  188. package/src/primitives/FpsMeter.stories.tsx +1 -1
  189. package/src/primitives/JobProgress.stories.tsx +1 -1
  190. package/src/primitives/Legend.stories.tsx +1 -1
  191. package/src/primitives/ScaleIndicator.stories.tsx +1 -1
  192. package/src/primitives/Sidebar.less +7 -0
  193. package/src/primitives/Sidebar.stories.tsx +1 -1
  194. package/src/primitives/Split.less +14 -0
  195. package/src/primitives/Split.test.tsx +133 -0
  196. package/src/primitives/Split.tsx +175 -0
  197. package/src/primitives/StatusBar.stories.tsx +1 -1
  198. package/src/primitives/Toolbar.stories.tsx +1 -1
  199. package/src/primitives/ZoomControl.tsx +5 -2
  200. package/src/primitives/index.ts +2 -0
  201. package/src/specimen/Specimen.less +95 -0
  202. package/src/specimen/Specimen.stories.tsx +13 -0
  203. package/src/specimen/Specimen.test.tsx +13 -0
  204. package/src/specimen/Specimen.tsx +888 -0
  205. package/src/state/Persistence.stories.tsx +75 -0
  206. package/src/state/Persistence.tsx +33 -0
  207. package/src/state/SingletonExperiment.test.tsx +47 -53
  208. package/src/state/SingletonExperiment.tsx +24 -13
  209. package/src/state/adapterContract.ts +116 -0
  210. package/src/state/adapters.test.ts +113 -61
  211. package/src/state/adapters.ts +328 -69
  212. package/src/state/document.test.ts +71 -96
  213. package/src/state/document.ts +66 -52
  214. package/src/state/helpers.test.ts +1 -1
  215. package/src/state/index.ts +8 -0
  216. package/src/state/labRecords.test.ts +105 -0
  217. package/src/state/labRecords.ts +160 -0
  218. package/src/state/openLabStore.test.ts +658 -0
  219. package/src/state/openLabStore.ts +403 -0
  220. package/src/state/records.test.ts +234 -0
  221. package/src/state/records.ts +219 -0
  222. package/src/state/store.test.ts +169 -635
  223. package/src/state/store.ts +146 -163
  224. package/src/state/toolSlot.test.ts +1 -1
  225. package/src/state/types.ts +65 -17
  226. package/src/state/useOpenOnce.ts +68 -0
  227. package/src/state/usePersistedState.test.tsx +125 -0
  228. package/src/state/usePersistedState.ts +66 -0
  229. package/src/state/useTrialState.test.tsx +87 -3
  230. package/src/state/useTrialState.ts +9 -1
  231. package/src/state/view.test.ts +49 -15
  232. package/src/state/view.ts +18 -1
  233. package/src/styles.less +3 -0
  234. package/src/surface/AGENTS.md +39 -7
  235. package/src/surface/SurfaceContext.ts +26 -3
  236. package/src/surface/index.ts +2 -0
  237. package/src/surface/useSurfaceTile.test.tsx +1 -0
  238. package/src/surface/useSurfaceTile.ts +6 -5
  239. package/src/surface/useTiledSurface.test.tsx +231 -1
  240. package/src/surface/useTiledSurface.ts +76 -15
  241. package/src/theme/Interstellar.stories.tsx +9 -9
  242. package/src/theme/interstellar.test.ts +17 -6
  243. package/src/theme/interstellar.theme.json +180 -0
  244. package/src/theme/interstellar.ts +8 -5
  245. package/src/tools/labTool.ts +11 -0
  246. package/src/trial/Trial.annotations.persist.test.tsx +100 -58
  247. package/src/trial/Trial.annotations.test.tsx +59 -7
  248. package/src/trial/Trial.canvas.test.tsx +32 -0
  249. package/src/trial/Trial.config.test.tsx +75 -2
  250. package/src/trial/Trial.job.test.tsx +3 -3
  251. package/src/trial/Trial.less +5 -26
  252. package/src/trial/Trial.stories.tsx +5 -3
  253. package/src/trial/Trial.test.tsx +31 -4
  254. package/src/trial/Trial.trialId.test.tsx +23 -1
  255. package/src/trial/Trial.tsx +139 -52
  256. package/src/trial/TrialBody.tsx +17 -141
  257. package/src/trial/TrialChrome.tsx +24 -7
  258. package/src/trial/trialOps.test.ts +101 -1
  259. package/src/trial/trialOps.ts +67 -7
  260. package/dist/_dts/types-BP2OCcpg.d.ts +0 -155
  261. package/dist/_dts/types-x92Kfeme.d.ts +0 -62
  262. package/dist/_dts/useTrialState-D6Vb3T-g.d.ts +0 -99
  263. package/dist/chunk-2P6PP5N4.js +0 -553
  264. package/dist/chunk-2P6PP5N4.js.map +0 -1
  265. package/dist/chunk-64ZCN3DA.js.map +0 -1
  266. package/dist/chunk-CTRKTLYZ.js.map +0 -1
  267. package/dist/chunk-E44SU6XS.js +0 -50
  268. package/dist/chunk-E44SU6XS.js.map +0 -1
  269. package/dist/chunk-FQMJUVHJ.js.map +0 -1
  270. package/dist/chunk-I6JCVE24.js.map +0 -1
  271. package/dist/chunk-MOM3GOVY.js.map +0 -1
  272. package/dist/chunk-SMHP6XZ4.js.map +0 -1
  273. package/dist/chunk-TJ7QY3OC.js.map +0 -1
  274. package/dist/chunk-U3IYHIAE.js +0 -343
  275. package/dist/chunk-U3IYHIAE.js.map +0 -1
  276. package/dist/chunk-W3ECWC2K.js +0 -7335
  277. package/dist/chunk-W3ECWC2K.js.map +0 -1
  278. package/src/theme/interstellar.tokens.json +0 -61
package/dist/index.js CHANGED
@@ -1,670 +1,83 @@
1
+ import { resolveLoupe, TrialLoupe } from './chunk-ERFMC6WU.js';
2
+ export { CanvasLoupe, DomLoupe, LOUPE_DEFAULTS, LOUPE_MAGNIFY_ID, LOUPE_PEEK_ID, LoupeBubble, LoupeGestures, TrialLoupe, createLoupeActions, drawCanvasLens, lensCamera, lensSourceRect, resolveLoupe, sampleStack, useLoupe } from './chunk-ERFMC6WU.js';
1
3
  import { createEventBus, pushSnapshot, redo, undo } from './chunk-RL2LOLNI.js';
2
4
  export { clearUndo, createEventBus, emptyStack, pushSnapshot, redo, undo } from './chunk-RL2LOLNI.js';
3
5
  import { useDragDrop, DragOverlay, Palette } from './chunk-RS3HRQWU.js';
4
- import { useSurfaceOptional, useSurfaceCanvas, useTileId, useTiledSurface, SurfaceContext, SurfaceCanvasContext } from './chunk-FQMJUVHJ.js';
5
- export { SurfaceCanvasContext, SurfaceContext, composeRects, rectsEqual, toDeviceRect, useSurface, useSurfaceCanvas, useSurfaceOptional, useSurfaceTile, useTileId, useTiledSurface } from './chunk-FQMJUVHJ.js';
6
+ import { useTiledSurface } from './chunk-IJJUK4FU.js';
7
+ export { composeRects, rectsEqual, toDeviceRect, useTiledSurface } from './chunk-IJJUK4FU.js';
6
8
  import { useJob } from './chunk-HXXTULDN.js';
7
9
  export { useJob } from './chunk-HXXTULDN.js';
8
- import { ANNOTATION_WEASEL_TOOLS, annotationToolInfo, suppressContributions, mergeContributions, builtinContributions, TitleBarRegion, ToolbarRegion, PaletteRegion, ViewportRegion, SidebarRegion, StatusRegion, AnnotationsContext } from './chunk-64ZCN3DA.js';
9
- export { ANNOTATION_TOOLS, ANNOTATION_WEASEL_TOOLS, AnnotationsContext, ExportMenu, MarkList, PaletteRegion, SidebarRegion, StatusRegion, TitleBarRegion, ToolbarRegion, ViewportRegion, annotationToolInfo, builtinContributions, mergeContributions, suppressContributions, useAnnotations, useAnnotationsOptional } from './chunk-64ZCN3DA.js';
10
- import { JobProgress } from './chunk-MOM3GOVY.js';
11
- export { FloatingPanel, JobProgress, Legend, Sidebar } from './chunk-MOM3GOVY.js';
12
- export { FpsMeter, ScaleIndicator, StatusBar, Toolbar, ZoomControl } from './chunk-SMHP6XZ4.js';
13
- import { newId, createLabStore, panelKey, noneAdapter } from './chunk-2P6PP5N4.js';
14
- export { CURRENT_DOCUMENT_VERSION, createMemoryAdapter, dockPanel, labDocumentKey, localStorageAdapter, noneAdapter, panelKey, quarantineKey, sessionStorageAdapter, undockPanel, urlHashAdapter, useTrialState } from './chunk-2P6PP5N4.js';
15
- import { LabStoreContext, TrialIdProvider } from './chunk-2YILQ7Y5.js';
16
- export { LabStoreContext, LabStoreProvider, TrialIdContext, TrialIdProvider, useLabStore, useTrialId } from './chunk-2YILQ7Y5.js';
17
- import { fromConfigFields } from './chunk-U3IYHIAE.js';
18
- export { ControlPanel, fromConfigFields, isLeafVisible } from './chunk-U3IYHIAE.js';
19
- import { hasConfigPath, schemaNodeAtPath, withValueAtPath, fillConfigDefaults } from './chunk-E44SU6XS.js';
20
- export { fillConfigDefaults, hasConfigPath, schemaNodeAtPath, valueAtPath, withValueAtPath } from './chunk-E44SU6XS.js';
21
- import { CanvasStack } from './chunk-I6JCVE24.js';
22
- export { CanvasStack, PITCH_LIMIT, clampPitch, orbitAfterDrag, orbitAfterWheel, useOrbit, usePanZoom, wrapYaw } from './chunk-I6JCVE24.js';
23
- import { LayerList } from './chunk-BKFVHKJH.js';
24
- export { LayerList } from './chunk-BKFVHKJH.js';
25
- import './chunk-SBXNQW4G.js';
26
- import { E, w, T, C, g, r6 } from './chunk-W3ECWC2K.js';
27
- export { N2 as CheckboxRow, M2 as ColorRow, y3 as CurveField, z as EffectCard, V as EffectCardList, c3 as LayerStack, F as NumberRow, H as PropertyGroup, T2 as PropertyList, w2 as PropertyPanel, D as PropertyRow, E2 as PropertySpan, I as SelectRow, A2 as SliderRow, R as Subpanel, P2 as TextRow, L2 as ToggleRow } from './chunk-W3ECWC2K.js';
28
- import { resolveLoupe, TrialLoupe } from './chunk-TJ7QY3OC.js';
29
- export { CanvasLoupe, DomLoupe, LOUPE_DEFAULTS, LoupeBubble, TrialLoupe, drawCanvasLens, lensCamera, lensSourceRect, resolveLoupe, sampleStack, useLoupe } from './chunk-TJ7QY3OC.js';
30
- export { centerOn, zoomAt } from './chunk-CTRKTLYZ.js';
10
+ import { suppressContributions, mergeContributions, builtinContributions, TitleBarRegion, ToolbarRegion, PaletteRegion, ViewportRegion, SidebarRegion, StatusRegion, resolveLabTool, AnnotationsContext, useLabChromeContext, labContributions, LabHeaderRegion, LabFooterRegion, LabAsideRegion, LabSidebarRegion } from './chunk-IHUVQ3TX.js';
11
+ export { AnnotationsContext, ExportMenu, LabAsideRegion, LabFooterRegion, LabHeaderRegion, LabSidebarRegion, MarkList, PaletteRegion, SidebarRegion, StatusRegion, TitleBarRegion, ToolbarRegion, ViewportRegion, builtinContributions, contributionsIn, labContributions, mergeContributions, suppressContributions, toolContributions, useAnnotations, useAnnotationsOptional, useLabChromeContext } from './chunk-IHUVQ3TX.js';
12
+ import { Split, JobProgress, Sidebar, Legend, FloatingPanel } from './chunk-EHGVROVV.js';
13
+ export { FloatingPanel, JobProgress, Legend, Sidebar, Split } from './chunk-EHGVROVV.js';
14
+ import { Toolbar, StatusBar, ScaleIndicator, ZoomControl } from './chunk-UN4U4P63.js';
15
+ export { FpsMeter, ScaleIndicator, StatusBar, Toolbar, ZoomControl } from './chunk-UN4U4P63.js';
16
+ import { unruledConfigSchema, createLabStore, openLabStore, panelKey } from './chunk-IXTQI7JX.js';
17
+ export { CURRENT_DOCUMENT_VERSION, dockPanel, labDocumentKey, openLabStore, panelKey, quarantineKey, undockPanel, useTrialState } from './chunk-IXTQI7JX.js';
18
+ import { newId, useWarnIgnoredChange, useOpenOnce, PersistenceContext, createRecordCache, noneAdapter, defaultStorage, usePersistedState } from './chunk-RWNDDKOH.js';
19
+ export { Persistence, createIndexedDbAdapter, createMemoryAdapter, indexedDbAdapter, localStorageAdapter, noneAdapter, sessionStorageAdapter, urlHashAdapter, usePersistedState } from './chunk-RWNDDKOH.js';
20
+ export { ControlPanel } from './chunk-EH7UEQV7.js';
21
+ import { useConfigSchema } from './chunk-PJ7WEQBJ.js';
22
+ export { useConfigSchema } from './chunk-PJ7WEQBJ.js';
23
+ import { useLabContext, LabContext } from './chunk-Z76KEZAX.js';
24
+ export { LabContext, useLabContext } from './chunk-Z76KEZAX.js';
25
+ import { useResolvedConfig } from './chunk-ZDHY47EV.js';
26
+ export { BaseNode, BooleanNode, ColorNode, CustomNode, EnumNode, GroupNode, NumberNode, StringNode, ValueNode, applyRules, builtinRules, f, isConfigBranch, resolveConfigSchema, titleCase, useResolvedConfig } from './chunk-ZDHY47EV.js';
27
+ export { fromConfigFields, isLeafVisible } from './chunk-PHK4W562.js';
28
+ import { hasConfigPath, schemaNodeAtPath, applyConfigWrite, resolveAutoConfig, isAuto, isRecord, autoPathsOf, fillConfigDefaults } from './chunk-7PFH3CNM.js';
29
+ export { auto, autoPathsOf, fillConfigDefaults, hasConfigPath, isAuto, resolveAutoConfig, schemaNodeAtPath, valueAtPath, withValueAtPath } from './chunk-7PFH3CNM.js';
30
+ import { CameraWheelContext, as2DView, DEFAULT_VIEW, CanvasStack, Stage, withZoom, fitStage } from './chunk-CEIUBPPW.js';
31
+ export { CanvasStack, DEFAULT_VIEW, PITCH_LIMIT, Stage, as2DView, clampPitch, fitStage, orbitAfterDrag, orbitAfterWheel, useOrbit, usePanZoom, wrapYaw } from './chunk-CEIUBPPW.js';
32
+ export { centerOn, zoomAt } from './chunk-H6ZAOWNE.js';
31
33
  import { applyCamera } from './chunk-2AYGEN57.js';
32
34
  export { DEFAULT_FRAME, applyCamera, resolveFrame, screenToWorld, worldToScreen } from './chunk-2AYGEN57.js';
35
+ import { useSurfaceOptional, useSurfaceCanvas, useTileId, SurfaceContext, SurfaceCanvasContext } from './chunk-WIG6XHJ7.js';
36
+ export { SurfaceCanvasContext, SurfaceContext, useSurface, useSurfaceCanvas, useSurfaceOptional, useSurfaceTile, useTileId } from './chunk-WIG6XHJ7.js';
33
37
  export { CanvasStackContext } from './chunk-CQLQPQ4P.js';
34
- import { textCommand, linePath, markerDrawCommands, ellipsePath, rectPath, createScene, asNodeId as asNodeId$1, WeaselProvider, SceneCanvas, sceneFromJSON, PathBuilder, renderSceneToPixels, useActiveToolContext, getMarker, getPaintKind, boundsOfPath, PATH_M, PATH_L, PATH_C, PATH_Q, PATH_Z, SCRIPT_METRICS } from '@weasel-js/core';
35
- import { tileGeometry } from '@weasel-js/core/patterns-builtin';
36
- import { createContext, useState, useRef, useEffect, useContext, useMemo, useId, useCallback, Children, useLayoutEffect, useSyncExternalStore } from 'react';
37
- import { createPortal } from 'react-dom';
38
+ import { LabStoreContext, TrialIdProvider } from './chunk-2YILQ7Y5.js';
39
+ export { LabStoreContext, LabStoreProvider, TrialIdContext, TrialIdProvider, useLabStore, useTrialId } from './chunk-2YILQ7Y5.js';
40
+ import { LayerList } from './chunk-MDVTK5U3.js';
41
+ export { LayerList } from './chunk-MDVTK5U3.js';
42
+ import './chunk-SBXNQW4G.js';
43
+ import { E, w, T, a2, p, u, c, n, f, r, h, serializeSvg, C, l7, r3, a5, Lt, o2, a4, l3, u3, r7, r8, r6, o5, s5, c6, l6, c3, s3, u2, g2, g, y2, l4, b2, c7, r2, l2, C2, S, O, k, F2, L3, V, H, U, B, R2, J, A2, c4, n5, s4, m2, c2, l, g3, l5, S4, r10, n6, r11, i5, i4, s2, n7, o4, r9, c5, w2, L2, n8, k2, $2 } from './chunk-T3EC2DXD.js';
44
+ export { a5 as Button, R2 as CheckboxRow, y as CloseIcon, $ as CollapseIcon, L3 as ColorRow, S2 as CurveField, G2 as EffectCard, q as EffectCardList, te as ExpandIcon, o as Icon, u2 as Input, l9 as Jog, c4 as LayerStack, V as NumberRow, I as PauseIcon, F as PlayIcon, J as PropertyGroup, k as PropertyList, O as PropertyPanel, j2 as PropertyRow, A3 as PropertySpan, v as ResetIcon, g as Select, H as SelectRow, F2 as SliderRow, R as StepBackIcon, z as StepForwardIcon, L as StopIcon, W as Subpanel, B as TextRow, r3 as ToggleBar, U as ToggleRow, q2 as TokenPanel, I2 as inferTokenType } from './chunk-T3EC2DXD.js';
45
+ import { textCommand, linePath, markerDrawCommands, ellipsePath, rectPath, createScene, asNodeId as asNodeId$1, WeaselProvider, SceneCanvas, sceneFromJSON, PathBuilder, renderSceneToPixels, useActiveToolContext } from '@weasel-js/core';
46
+ import { createContext, useState, useRef, useEffect, useCallback, useContext, useMemo, useId, Children, useLayoutEffect, useSyncExternalStore } from 'react';
47
+ import { flushSync, createPortal } from 'react-dom';
38
48
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
49
+ import { useThemeOptional, ThemeProvider } from '@weasel-js/theme/react';
39
50
  import { useStore } from 'zustand/react';
40
- import { asNodeId, Store, createNode, stripStrategy, floatingStrategy, gridStrategy } from 'windease';
41
- import { Provider, StrategyRegistryProvider, Container, DragProvider, useDragHandle } from 'windease/react';
51
+ import { defineTheme, weaselTheme } from '@weasel-js/theme';
52
+ import { Provider, StrategyRegistryProvider, DragProvider, Container, useDragHandle } from 'windease/react';
53
+ import { asNodeId, Store, createNode, floatingStrategy, gridStrategy } from 'windease';
54
+ import { Focusable } from 'react-aria-components';
42
55
 
43
- var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
44
- function multiply(a, b) {
45
- return [
46
- a[0] * b[0] + a[2] * b[1],
47
- a[1] * b[0] + a[3] * b[1],
48
- a[0] * b[2] + a[2] * b[3],
49
- a[1] * b[2] + a[3] * b[3],
50
- a[0] * b[4] + a[2] * b[5] + a[4],
51
- a[1] * b[4] + a[3] * b[5] + a[5]
52
- ];
53
- }
54
- function isIdentity(m, eps = 1e-9) {
55
- return Math.abs(m[0] - 1) < eps && Math.abs(m[1]) < eps && Math.abs(m[2]) < eps && Math.abs(m[3] - 1) < eps && Math.abs(m[4]) < eps && Math.abs(m[5]) < eps;
56
- }
57
- function formatMatrix(m) {
58
- if (isIdentity(m)) return null;
59
- return `matrix(${m.map((n) => trimNumber(n)).join(" ")})`;
60
- }
61
- function trimNumber(n) {
62
- if (!Number.isFinite(n)) return "0";
63
- const r2 = Math.round(n * 1e6) / 1e6;
64
- if (Number.isInteger(r2)) return String(r2);
65
- return String(r2);
66
- }
67
- function patternSpecOf(paint) {
68
- return "tile" in paint.pattern ? paint.pattern : null;
69
- }
70
- function patternXml(id, paint, onWarn) {
71
- const spec = patternSpecOf(paint);
72
- if (!spec) {
73
- onWarn?.("pattern fill carries a TextureHandle, which has no vector form \u2014 omitted from <defs>");
74
- return "";
75
- }
76
- const geometry = tileGeometry(spec);
77
- const body = geometry.shapes.map(shapeXml).join("");
78
- const origin = paint.origin ?? { x: 0, y: 0 };
79
- const attrs = [
80
- `id="${id}"`,
81
- 'patternUnits="userSpaceOnUse"',
82
- `width="${trimNumber(geometry.size)}"`,
83
- `height="${trimNumber(geometry.size)}"`
84
- ];
85
- if (origin.x !== 0 || origin.y !== 0) {
86
- attrs.push(`x="${trimNumber(origin.x)}"`, `y="${trimNumber(origin.y)}"`);
87
- }
88
- attrs.push(`data-weasel-tile="${escapeAttr(JSON.stringify(spec))}"`);
89
- return `<pattern ${attrs.join(" ")}>${body}</pattern>`;
90
- }
91
- function shapeXml(shape) {
92
- switch (shape.kind) {
93
- case "line":
94
- return `<line x1="${trimNumber(shape.x1)}" y1="${trimNumber(shape.y1)}" x2="${trimNumber(shape.x2)}" y2="${trimNumber(shape.y2)}" stroke="${shape.color}" stroke-width="${trimNumber(shape.width)}"/>`;
95
- case "circle":
96
- return `<circle cx="${trimNumber(shape.cx)}" cy="${trimNumber(shape.cy)}" r="${trimNumber(shape.r)}" fill="${shape.color}"/>`;
97
- case "ellipse": {
98
- const deg = shape.rotation * 180 / Math.PI;
99
- const transform = deg === 0 ? "" : ` transform="rotate(${trimNumber(deg)} ${trimNumber(shape.cx)} ${trimNumber(shape.cy)})"`;
100
- return `<ellipse cx="${trimNumber(shape.cx)}" cy="${trimNumber(shape.cy)}" rx="${trimNumber(shape.rx)}" ry="${trimNumber(shape.ry)}" fill="${shape.color}"${transform}/>`;
101
- }
102
- case "rect":
103
- return `<rect x="${trimNumber(shape.x)}" y="${trimNumber(shape.y)}" width="${trimNumber(shape.width)}" height="${trimNumber(shape.height)}" fill="${shape.color}"/>`;
104
- default:
105
- return "";
106
- }
107
- }
108
- function escapeAttr(s) {
109
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
110
- }
111
- function serializePathD(path) {
112
- if (path.kind === "rect") {
113
- const { x, y: y2, width, height } = path;
114
- return `M${trimNumber(x)} ${trimNumber(y2)}h${trimNumber(width)}v${trimNumber(height)}h${trimNumber(-width)}Z`;
115
- }
116
- const cmds = path.commands;
117
- const xs = path.coords;
118
- const out = [];
119
- let ci = 0;
120
- for (let i = 0; i < cmds.length; i++) {
121
- const c2 = cmds[i];
122
- if (c2 === PATH_M) {
123
- out.push(`M${trimNumber(xs[ci])} ${trimNumber(xs[ci + 1])}`);
124
- ci += 2;
125
- } else if (c2 === PATH_L) {
126
- out.push(`L${trimNumber(xs[ci])} ${trimNumber(xs[ci + 1])}`);
127
- ci += 2;
128
- } else if (c2 === PATH_C) {
129
- out.push(
130
- `C${trimNumber(xs[ci])} ${trimNumber(xs[ci + 1])} ${trimNumber(xs[ci + 2])} ${trimNumber(xs[ci + 3])} ${trimNumber(xs[ci + 4])} ${trimNumber(xs[ci + 5])}`
131
- );
132
- ci += 6;
133
- } else if (c2 === PATH_Q) {
134
- out.push(
135
- `Q${trimNumber(xs[ci])} ${trimNumber(xs[ci + 1])} ${trimNumber(xs[ci + 2])} ${trimNumber(xs[ci + 3])}`
136
- );
137
- ci += 4;
138
- } else if (c2 === PATH_Z) {
139
- out.push("Z");
140
- }
141
- }
142
- return out.join("");
143
- }
144
- var PaintServerRegistry = class {
145
- byPaint = /* @__PURE__ */ new Map();
146
- order = [];
147
- counter = 0;
148
- // Marker keys referenced by any stroke, in first-use order. The `<defs>`
149
- // id is the marker key itself, not a minted counter id: `parseSvg` reads a
150
- // `marker-end="url(#id)"` fragment back as the marker key directly (it
151
- // never inspects the `<marker>` element), so a synthetic id would come
152
- // back unresolvable and warn on round-trip.
153
- markerKeys = [];
154
- markerKeySet = /* @__PURE__ */ new Set();
155
- register(paint) {
156
- const existing = this.byPaint.get(paint);
157
- if (existing) return existing;
158
- const id = paint.fill === "pattern" ? `pat${this.counter++}` : `grad${this.counter++}`;
159
- this.byPaint.set(paint, id);
160
- this.order.push(paint);
161
- return id;
162
- }
163
- /** The `<defs>` id for a marker key — the key itself, minting nothing.
164
- * `undefined` when nothing is registered under `key`, so a caller emits no
165
- * attribute at all rather than a `url(#…)` pointing at a def that will
166
- * never be written. */
167
- markerId(key) {
168
- if (getMarker(key) === void 0) return void 0;
169
- if (!this.markerKeySet.has(key)) {
170
- this.markerKeySet.add(key);
171
- this.markerKeys.push(key);
172
- }
173
- return key;
174
- }
175
- /** Emit `<defs>...</defs>` XML for every registered paint server and marker. */
176
- toDefsXml(onWarn) {
177
- if (this.order.length === 0 && this.markerKeys.length === 0) return "";
178
- const parts = ["<defs>"];
179
- for (const paint of this.order) {
180
- parts.push(paintServerXml(this.byPaint.get(paint), paint, onWarn));
181
- }
182
- for (const key of this.markerKeys) {
183
- const entry = getMarker(key);
184
- if (!entry) continue;
185
- parts.push(entry.toSvg ? entry.toSvg(key, entry) : defaultMarkerXml(key, entry));
186
- }
187
- parts.push("</defs>");
188
- return parts.join("");
189
- }
190
- };
191
- function defaultMarkerXml(id, entry) {
192
- const path = entry.path({ size: 1, stroke: { paint: { fill: "solid", color: "#000" } } });
193
- const d = serializePathD(path);
194
- const fill = entry.fill === "none" ? "none" : "context-stroke";
195
- const outline = entry.outline ? ` stroke="context-stroke" stroke-width="${entry.outline.width}" stroke-linecap="round" stroke-linejoin="round"` : "";
196
- return `<marker id="${id}" markerUnits="strokeWidth" markerWidth="8" markerHeight="8" refX="0" refY="0" orient="auto" overflow="visible"><path d="${d}" fill="${fill}"${outline}/></marker>`;
197
- }
198
- function paintServerXml(id, paint, onWarn) {
199
- if (paint.fill === "pattern") return patternXml(id, paint, onWarn);
200
- const builtin = gradientXml(id, paint);
201
- if (builtin) return builtin;
202
- const custom = getPaintKind(paint.fill)?.toSvg?.(id, paint);
203
- if (custom) return custom;
204
- onWarn?.(`${paint.fill} fill has no vector form \u2014 omitted from <defs>`);
205
- return "";
206
- }
207
- function gradientUnitsAttr(units) {
208
- return units === "bounds" ? "objectBoundingBox" : "userSpaceOnUse";
209
- }
210
- function gradientXml(id, paint) {
211
- if (paint.fill === "linear-gradient") {
212
- const stops = paint.stops.map(stopXml).join("");
213
- return `<linearGradient id="${id}" gradientUnits="${gradientUnitsAttr(paint.units)}" x1="${trimNumber(paint.from.x)}" y1="${trimNumber(paint.from.y)}" x2="${trimNumber(paint.to.x)}" y2="${trimNumber(paint.to.y)}">${stops}</linearGradient>`;
214
- }
215
- if (paint.fill === "radial-gradient") {
216
- const stops = paint.stops.map(stopXml).join("");
217
- return `<radialGradient id="${id}" gradientUnits="${gradientUnitsAttr(paint.units)}" cx="${trimNumber(paint.center.x)}" cy="${trimNumber(paint.center.y)}" r="${trimNumber(paint.radius)}">${stops}</radialGradient>`;
218
- }
219
- return "";
220
- }
221
- function stopXml(s) {
222
- const m = /^#([0-9a-f]{6})([0-9a-f]{2})$/i.exec(s.color);
223
- if (m) {
224
- const alpha = parseInt(m[2], 16) / 255;
225
- return `<stop offset="${trimNumber(s.offset)}" stop-color="#${m[1].toLowerCase()}" stop-opacity="${trimNumber(alpha)}"/>`;
226
- }
227
- return `<stop offset="${trimNumber(s.offset)}" stop-color="${s.color}"/>`;
228
- }
229
- function serializeSvg(nodes, opts = {}) {
230
- const registry = new PaintServerRegistry();
231
- registerPaintServers(nodes, registry);
232
- const bounds = opts.viewBox ?? computeBounds(nodes);
233
- const vb = `${trimNumber(bounds.x)} ${trimNumber(bounds.y)} ${trimNumber(bounds.width)} ${trimNumber(bounds.height)}`;
234
- const namespaces = opts.namespaces ?? {};
235
- const rootAttrs = [`xmlns="http://www.w3.org/2000/svg"`];
236
- for (const [prefix, uri] of Object.entries(namespaces)) {
237
- rootAttrs.push(`xmlns:${prefix}="${escapeAttr2(uri)}"`);
238
- }
239
- rootAttrs.push(`viewBox="${vb}"`);
240
- if (opts.width != null) rootAttrs.push(`width="${trimNumber(opts.width)}"`);
241
- if (opts.height != null) rootAttrs.push(`height="${trimNumber(opts.height)}"`);
242
- if (opts.documentMeta) {
243
- for (const prefix of Object.keys(namespaces)) {
244
- const bucket = opts.documentMeta[prefix];
245
- if (!bucket?.attrs) continue;
246
- for (const [name, value] of Object.entries(bucket.attrs)) {
247
- rootAttrs.push(`${prefix}:${name}="${escapeAttr2(value)}"`);
248
- }
249
- }
250
- }
251
- let docMetaXml = "";
252
- if (opts.documentMeta) {
253
- for (const prefix of Object.keys(namespaces)) {
254
- const bucket = opts.documentMeta[prefix];
255
- if (!bucket?.elements) continue;
256
- for (const [localName, list] of Object.entries(bucket.elements)) {
257
- for (const el of list) docMetaXml += namespacedElementXml(prefix, localName, el);
258
- }
259
- }
260
- }
261
- const defsXml = registry.toDefsXml(opts.onWarn);
262
- const bodyXml = nodes.map((n) => nodeXml(n, registry, namespaces)).join("");
263
- const titleXml = opts.title && opts.title.length > 0 ? `<title>${escapeText(opts.title)}</title>` : "";
264
- return `<svg ${rootAttrs.join(" ")}>${titleXml}${defsXml}${docMetaXml}${bodyXml}</svg>`;
265
- }
266
- function namespacedElementXml(prefix, localName, el) {
267
- const attrs = [];
268
- for (const [name, value] of Object.entries(el.attrs)) {
269
- attrs.push(`${name}="${escapeAttr2(value)}"`);
270
- }
271
- const head = attrs.length > 0 ? `<${prefix}:${localName} ${attrs.join(" ")}>` : `<${prefix}:${localName}>`;
272
- let body = "";
273
- if (el.children) {
274
- for (const [childName, list] of Object.entries(el.children)) {
275
- for (const child of list) body += namespacedElementXml(prefix, childName, child);
276
- }
277
- } else if (el.text != null) {
278
- body = escapeText(el.text);
279
- }
280
- return `${head}${body}</${prefix}:${localName}>`;
281
- }
282
- function metaAttrsXml(meta, namespaces) {
283
- if (!meta) return "";
284
- const parts = [];
285
- for (const prefix of Object.keys(namespaces)) {
286
- const bucket = meta[prefix];
287
- if (!bucket?.attrs) continue;
288
- for (const [name, value] of Object.entries(bucket.attrs)) {
289
- parts.push(`${prefix}:${name}="${escapeAttr2(value)}"`);
290
- }
291
- }
292
- return parts.length > 0 ? ` ${parts.join(" ")}` : "";
293
- }
294
- function metaElementsXml(meta, namespaces) {
295
- if (!meta) return "";
296
- let out = "";
297
- for (const prefix of Object.keys(namespaces)) {
298
- const bucket = meta[prefix];
299
- if (!bucket?.elements) continue;
300
- for (const [localName, list] of Object.entries(bucket.elements)) {
301
- for (const el of list) out += namespacedElementXml(prefix, localName, el);
302
- }
303
- }
304
- return out;
305
- }
306
- function registerPaintServers(nodes, registry) {
307
- for (const n of nodes) {
308
- if (n.kind === "group") {
309
- registerPaintServers(n.children, registry);
310
- } else if (n.kind === "path") {
311
- if (n.fill.kind === "gradient") registry.register(n.fill.paint);
312
- if (n.stroke && n.stroke.paint.kind === "gradient") registry.register(n.stroke.paint.paint);
313
- registerMarkers(n.stroke, registry);
314
- } else if (n.kind === "text") {
315
- registerTextPaint(n.fill, registry);
316
- registerTextPaint(n.stroke?.paint, registry);
317
- registerMarkers(n.stroke, registry);
318
- for (const run of n.runs ?? []) {
319
- registerTextPaint(run.fill, registry);
320
- registerTextPaint(run.stroke?.paint, registry);
321
- }
322
- }
323
- }
324
- }
325
- function registerTextPaint(paint, registry) {
326
- if (paint && !("color" in paint)) registry.register(paint);
327
- }
328
- function markerKeyOfRef(ref) {
329
- if (typeof ref === "string") return ref;
330
- if (ref && typeof ref === "object" && "key" in ref) return String(ref.key);
331
- return void 0;
332
- }
333
- function registerMarkers(stroke, registry) {
334
- if (!stroke) return;
335
- for (const ref of [stroke.markerStart, stroke.markerMid, stroke.markerEnd]) {
336
- const key = markerKeyOfRef(ref);
337
- if (key) registry.markerId(key);
338
- }
339
- }
340
- function nodeXml(node, registry, namespaces) {
341
- if (node.kind === "group") return groupXml(node, registry, namespaces);
342
- if (node.kind === "text") return textXml(node, registry, namespaces);
343
- if (node.kind === "image") return imageXml(node, namespaces);
344
- return pathXml(node, registry, namespaces);
345
- }
346
- function imageXml(node, namespaces) {
347
- const attrs = [
348
- `href="${escapeAttr2(node.href)}"`,
349
- `x="${trimNumber(node.x)}"`,
350
- `y="${trimNumber(node.y)}"`,
351
- `width="${trimNumber(node.width)}"`,
352
- `height="${trimNumber(node.height)}"`,
353
- `preserveAspectRatio="none"`
354
- ];
355
- if (node.opacity != null && node.opacity !== 1) {
356
- attrs.push(`opacity="${trimNumber(node.opacity)}"`);
357
- }
358
- if (node.rotation != null && node.rotation !== 0) {
359
- const cx = node.x + node.width / 2;
360
- const cy = node.y + node.height / 2;
361
- const deg = node.rotation * 180 / Math.PI;
362
- attrs.push(`transform="rotate(${trimNumber(deg)} ${trimNumber(cx)} ${trimNumber(cy)})"`);
363
- }
364
- const metaAttrs = metaAttrsXml(node.meta, namespaces);
365
- const metaEls = metaElementsXml(node.meta, namespaces);
366
- if (metaEls) return `<image ${attrs.join(" ")}${metaAttrs}>${metaEls}</image>`;
367
- return `<image ${attrs.join(" ")}${metaAttrs}/>`;
368
- }
369
- function groupXml(node, registry, namespaces) {
370
- const attrs = [];
371
- if (node.transform) {
372
- const m = formatMatrix(node.transform);
373
- if (m) attrs.push(`transform="${m}"`);
374
- }
375
- if (node.opacity != null && node.opacity !== 1) {
376
- attrs.push(`opacity="${trimNumber(node.opacity)}"`);
377
- }
378
- const metaAttrs = metaAttrsXml(node.meta, namespaces);
379
- const head = attrs.length > 0 ? `<g ${attrs.join(" ")}${metaAttrs}>` : `<g${metaAttrs}>`;
380
- const body = node.children.map((c2) => nodeXml(c2, registry, namespaces)).join("");
381
- return `${head}${body}${metaElementsXml(node.meta, namespaces)}</g>`;
382
- }
383
- function pathXml(node, registry, namespaces) {
384
- const attrs = [`d="${serializePathD(node.path)}"`];
385
- const fillAttrs = paintAttrs(node.fill, "fill", registry);
386
- for (const a of fillAttrs) attrs.push(a);
387
- if (node.path.kind === "polygon" && node.path.fillRule === "evenodd") {
388
- attrs.push(`fill-rule="evenodd"`);
389
- }
390
- if (node.stroke) {
391
- const strokeAttrs = strokeAttrsFor(node.stroke, registry);
392
- for (const a of strokeAttrs) attrs.push(a);
393
- } else {
394
- attrs.push('stroke="none"');
395
- }
396
- if (node.opacity != null && node.opacity !== 1) {
397
- attrs.push(`opacity="${trimNumber(node.opacity)}"`);
398
- }
399
- if (node.rotation != null && node.rotation !== 0) {
400
- const b = pathBounds(node.path);
401
- const cx = b.minX + (b.maxX - b.minX) / 2;
402
- const cy = b.minY + (b.maxY - b.minY) / 2;
403
- const deg = node.rotation * 180 / Math.PI;
404
- attrs.push(`transform="rotate(${trimNumber(deg)} ${trimNumber(cx)} ${trimNumber(cy)})"`);
405
- }
406
- const metaAttrs = metaAttrsXml(node.meta, namespaces);
407
- const metaEls = metaElementsXml(node.meta, namespaces);
408
- if (metaEls) {
409
- return `<path ${attrs.join(" ")}${metaAttrs}>${metaEls}</path>`;
410
- }
411
- return `<path ${attrs.join(" ")}${metaAttrs}/>`;
412
- }
413
- function paintAttrs(paint, name, registry, includeOpacity = true) {
414
- if (paint.kind === "none") return [`${name}="none"`];
415
- if (paint.kind === "solid") {
416
- const out = [`${name}="${paint.color}"`];
417
- if (includeOpacity && paint.opacity != null && paint.opacity !== 1) {
418
- out.push(`${name}-opacity="${trimNumber(paint.opacity)}"`);
419
- }
420
- return out;
421
- }
422
- const id = registry.register(paint.paint);
423
- return [`${name}="url(#${id})"`];
424
- }
425
- function strokeWidthAttrs(width) {
426
- if (typeof width === "number") return [`stroke-width="${trimNumber(width)}"`];
427
- return [`stroke-width="${trimNumber(width.px)}"`, 'vector-effect="non-scaling-stroke"'];
428
- }
429
- function coreStrokeAttrs(stroke, registry) {
430
- const paint = stroke?.paint;
431
- if (!stroke || !paint) return [];
432
- const width = stroke.width ?? 1;
433
- if (!((typeof width === "object" ? width.px : width) > 0)) return [];
434
- const attrs = [];
435
- if ("color" in paint) {
436
- attrs.push(`stroke="${paint.color}"`);
437
- if (paint.opacity != null && paint.opacity !== 1) {
438
- attrs.push(`stroke-opacity="${trimNumber(paint.opacity)}"`);
439
- }
440
- } else {
441
- attrs.push(`stroke="url(#${registry.register(paint)})"`);
442
- }
443
- attrs.push(...strokeWidthAttrs(width));
444
- if (stroke.cap) attrs.push(`stroke-linecap="${stroke.cap}"`);
445
- if (stroke.join) attrs.push(`stroke-linejoin="${stroke.join}"`);
446
- if (stroke.dash && stroke.dash.length > 0) {
447
- attrs.push(`stroke-dasharray="${stroke.dash.map(trimNumber).join(" ")}"`);
448
- }
449
- if (stroke.miterLimit != null) attrs.push(`stroke-miterlimit="${trimNumber(stroke.miterLimit)}"`);
450
- for (const [field, attr] of [
451
- ["markerStart", "marker-start"],
452
- ["markerMid", "marker-mid"],
453
- ["markerEnd", "marker-end"]
454
- ]) {
455
- const ref = stroke[field];
456
- if (ref === void 0) continue;
457
- const key = typeof ref === "string" ? ref : ref.key;
458
- const id = registry.markerId(key);
459
- if (id === void 0) continue;
460
- attrs.push(`${attr}="url(#${id})"`);
461
- }
462
- return attrs;
463
- }
464
- function strokeAttrsFor(stroke, registry) {
465
- const attrs = paintAttrs(stroke.paint, "stroke", registry, false);
466
- attrs.push(...strokeWidthAttrs(stroke.width));
467
- const opacity = stroke.opacity ?? (stroke.paint.kind === "solid" ? stroke.paint.opacity : void 0);
468
- if (opacity != null && opacity !== 1) {
469
- attrs.push(`stroke-opacity="${trimNumber(opacity)}"`);
470
- }
471
- if (stroke.cap) {
472
- attrs.push(`stroke-linecap="${stroke.cap}"`);
473
- }
474
- if (stroke.join) {
475
- attrs.push(`stroke-linejoin="${stroke.join}"`);
476
- }
477
- if (stroke.dash && stroke.dash.length > 0) {
478
- attrs.push(`stroke-dasharray="${stroke.dash.map(trimNumber).join(" ")}"`);
479
- }
480
- if (stroke.miterLimit != null) {
481
- attrs.push(`stroke-miterlimit="${trimNumber(stroke.miterLimit)}"`);
482
- }
483
- for (const [field, attr] of [
484
- ["markerStart", "marker-start"],
485
- ["markerMid", "marker-mid"],
486
- ["markerEnd", "marker-end"]
487
- ]) {
488
- const ref = stroke[field];
489
- if (ref === void 0) continue;
490
- const id = registry.markerId(ref);
491
- if (id === void 0) continue;
492
- attrs.push(`${attr}="url(#${id})"`);
493
- }
494
- return attrs;
495
- }
496
- function computeBounds(nodes) {
497
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
498
- const visit = (n, ctm) => {
499
- if (n.kind === "group") {
500
- const childCtm = n.transform ? multiply(ctm, n.transform) : ctm;
501
- n.children.forEach((c2) => visit(c2, childCtm));
502
- return;
503
- }
504
- const b = n.kind === "text" || n.kind === "image" ? { minX: n.x, minY: n.y, maxX: n.x + n.width, maxY: n.y + n.height } : pathBounds(n.path);
505
- const local = n.rotation ? rotateAboutCenter(ctm, b, n.rotation) : ctm;
506
- for (const [x, y2] of corners(b)) {
507
- const px = local[0] * x + local[2] * y2 + local[4];
508
- const py = local[1] * x + local[3] * y2 + local[5];
509
- if (px < minX) minX = px;
510
- if (py < minY) minY = py;
511
- if (px > maxX) maxX = px;
512
- if (py > maxY) maxY = py;
513
- }
514
- };
515
- nodes.forEach((n) => visit(n, IDENTITY_MATRIX));
516
- if (!Number.isFinite(minX)) {
517
- return { x: 0, y: 0, width: 0, height: 0 };
518
- }
519
- return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
520
- }
521
- function corners(b) {
522
- return [[b.minX, b.minY], [b.maxX, b.minY], [b.maxX, b.maxY], [b.minX, b.maxY]];
523
- }
524
- function rotateAboutCenter(ctm, b, angle) {
525
- const cx = (b.minX + b.maxX) / 2;
526
- const cy = (b.minY + b.maxY) / 2;
527
- const c2 = Math.cos(angle);
528
- const s = Math.sin(angle);
529
- return multiply(ctm, [c2, s, -s, c2, cx - cx * c2 + cy * s, cy - cx * s - cy * c2]);
530
- }
531
- function textXml(node, registry, namespaces) {
532
- const attrs = [
533
- `x="${trimNumber(node.x)}"`,
534
- `y="${trimNumber(node.y)}"`,
535
- `dominant-baseline="text-before-edge"`,
536
- // The runs model stores real line breaks; SVG's default whitespace
537
- // handling would collapse them (and any indentation) away on re-import.
538
- `xml:space="preserve"`,
539
- `data-weasel-width="${trimNumber(node.width)}"`,
540
- `data-weasel-height="${trimNumber(node.height)}"`
541
- ];
542
- const style = node.style;
543
- if (style?.fontSize != null) attrs.push(`font-size="${trimNumber(style.fontSize)}"`);
544
- if (style?.fontFamily) attrs.push(`font-family="${escapeAttr2(style.fontFamily)}"`);
545
- if (style?.fontWeight != null) attrs.push(`font-weight="${String(style.fontWeight)}"`);
546
- if (style?.fontStyle && style.fontStyle !== "normal") attrs.push(`font-style="${style.fontStyle}"`);
547
- const direction = style?.direction ?? "ltr";
548
- if (direction === "rtl") attrs.push('direction="rtl"');
549
- if (style?.align != null || direction === "rtl") {
550
- const align = style?.align ?? "left";
551
- const anchor = align === "center" ? "middle" : align === "start" ? "start" : align === "end" ? "end" : align === "left" === (direction === "ltr") ? "start" : "end";
552
- if (anchor !== "start") attrs.push(`text-anchor="${anchor}"`);
553
- }
554
- if (style?.letterSpacing != null && style.letterSpacing !== 0) {
555
- attrs.push(`letter-spacing="${trimNumber(style.letterSpacing)}"`);
556
- }
557
- const decoration = textDecorationValue(style?.underline, style?.strikethrough, style?.overline);
558
- if (decoration) attrs.push(`text-decoration="${decoration}"`);
559
- if (node.fill === null) {
560
- attrs.push('fill="none"');
561
- } else if (node.fill) {
562
- if ("color" in node.fill) {
563
- attrs.push(`fill="${node.fill.color}"`);
564
- if (node.fill.opacity != null && node.fill.opacity !== 1) {
565
- attrs.push(`fill-opacity="${trimNumber(node.fill.opacity)}"`);
566
- }
567
- } else {
568
- const id = registry.register(node.fill);
569
- attrs.push(`fill="url(#${id})"`);
570
- }
571
- }
572
- for (const a of coreStrokeAttrs(node.stroke, registry)) attrs.push(a);
573
- if (node.opacity != null && node.opacity !== 1) {
574
- attrs.push(`opacity="${trimNumber(node.opacity)}"`);
575
- }
576
- if (node.rotation != null && node.rotation !== 0) {
577
- const cx = node.x + node.width / 2;
578
- const cy = node.y + node.height / 2;
579
- const deg = node.rotation * 180 / Math.PI;
580
- attrs.push(`transform="rotate(${trimNumber(deg)} ${trimNumber(cx)} ${trimNumber(cy)})"`);
581
- }
582
- const body = node.runs && node.runs.length > 0 ? node.runs.map((r2) => runXml(r2, registry)).join("") : escapeText(node.text);
583
- const metaAttrs = metaAttrsXml(node.meta, namespaces);
584
- const metaEls = metaElementsXml(node.meta, namespaces);
585
- return `<text ${attrs.join(" ")}${metaAttrs}>${body}${metaEls}</text>`;
586
- }
587
- function runXml(run, registry) {
588
- const attrs = [];
589
- if (run.bold) attrs.push(`font-weight="700"`);
590
- if (run.italic) attrs.push(`font-style="italic"`);
591
- if (run.fontFamily) attrs.push(`font-family="${escapeAttr2(run.fontFamily)}"`);
592
- const presetScale = run.script != null && run.baselineShift != null ? SCRIPT_METRICS[run.script].size : void 0;
593
- const scale = run.fontScale ?? presetScale;
594
- if (run.fontSize != null) attrs.push(`font-size="${trimNumber(run.fontSize)}"`);
595
- else if (scale != null) attrs.push(`font-size="${trimNumber(scale * 100)}%"`);
596
- if (run.baselineShift != null) {
597
- attrs.push(`baseline-shift="${trimNumber(run.baselineShift * 100)}%"`);
598
- } else if (run.script) {
599
- attrs.push(`baseline-shift="${run.script}"`);
600
- }
601
- if (run.letterSpacing != null) {
602
- attrs.push(`letter-spacing="${trimNumber(run.letterSpacing)}"`);
603
- }
604
- const runDecoration = textDecorationValue(run.underline, run.strikethrough, run.overline);
605
- if (runDecoration) attrs.push(`text-decoration="${runDecoration}"`);
606
- if (run.fill) {
607
- if ("color" in run.fill) {
608
- attrs.push(`fill="${run.fill.color}"`);
609
- } else {
610
- const id = registry.register(run.fill);
611
- attrs.push(`fill="url(#${id})"`);
612
- }
613
- }
614
- for (const a of coreStrokeAttrs(run.stroke, registry)) attrs.push(a);
615
- const head = attrs.length > 0 ? `<tspan ${attrs.join(" ")}>` : "<tspan>";
616
- return `${head}${escapeText(run.text)}</tspan>`;
617
- }
618
- function textDecorationValue(underline, strikethrough, overline) {
619
- const tokens = [];
620
- if (underline) tokens.push("underline");
621
- if (strikethrough) tokens.push("line-through");
622
- if (overline) tokens.push("overline");
623
- return tokens.length > 0 ? tokens.join(" ") : null;
624
- }
625
- function escapeAttr2(s) {
626
- return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
627
- }
628
- function escapeText(s) {
629
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
630
- }
631
- function pathBounds(path) {
632
- if (path.kind === "rect") {
633
- return {
634
- minX: path.x,
635
- minY: path.y,
636
- maxX: path.x + path.width,
637
- maxY: path.y + path.height
638
- };
639
- }
640
- const b = boundsOfPath(path);
641
- return { minX: b.x, minY: b.y, maxX: b.x + b.width, maxY: b.y + b.height };
642
- }
643
56
  var MARK_COLOR = "#e5484d";
644
57
  var MARK_WIDTH = 2;
645
58
  var TEXT_SIZE = 14;
646
- var toWorld = (p, content) => ({
647
- x: p.x * content.w,
648
- y: p.y * content.h
59
+ var toWorld = (p2, content) => ({
60
+ x: p2.x * content.w,
61
+ y: p2.y * content.h
649
62
  });
650
- function vertices(m, content) {
651
- const stored = m.data.points;
652
- if (stored && stored.length >= 2) return stored.map((p) => toWorld(p, content));
653
- const { x, y: y2, width, height } = m.pose;
63
+ function vertices(m2, content) {
64
+ const stored = m2.data.points;
65
+ if (stored && stored.length >= 2) return stored.map((p2) => toWorld(p2, content));
66
+ const { x, y: y3, width, height } = m2.pose;
654
67
  return [
655
- { x, y: y2 },
656
- { x: x + width, y: y2 + height }
68
+ { x, y: y3 },
69
+ { x: x + width, y: y3 + height }
657
70
  ];
658
71
  }
659
72
  function polyline(points) {
660
- const b = new PathBuilder();
73
+ const b2 = new PathBuilder();
661
74
  const first = points[0];
662
75
  if (!first) return rectPath(0, 0, 0, 0);
663
- b.moveTo(first.x, first.y);
664
- for (const p of points.slice(1)) b.lineTo(p.x, p.y);
665
- return b.build();
76
+ b2.moveTo(first.x, first.y);
77
+ for (const p2 of points.slice(1)) b2.lineTo(p2.x, p2.y);
78
+ return b2.build();
666
79
  }
667
- function markCommands(m, content, style = {}) {
80
+ function markCommands(m2, content, style = {}) {
668
81
  const color = style.color ?? MARK_COLOR;
669
82
  const stroke = {
670
83
  paint: { color },
@@ -673,35 +86,35 @@ function markCommands(m, content, style = {}) {
673
86
  join: "round",
674
87
  ...style.stale ? { dash: [6, 4] } : {}
675
88
  };
676
- switch (m.data.kind) {
89
+ switch (m2.data.kind) {
677
90
  case "rect":
678
91
  return [
679
- { kind: "path", path: rectPath(m.pose.x, m.pose.y, m.pose.width, m.pose.height), stroke }
92
+ { kind: "path", path: rectPath(m2.pose.x, m2.pose.y, m2.pose.width, m2.pose.height), stroke }
680
93
  ];
681
94
  case "ellipse":
682
- return [{ kind: "path", path: ellipsePath(m.pose), stroke }];
95
+ return [{ kind: "path", path: ellipsePath(m2.pose), stroke }];
683
96
  case "line": {
684
- const [a, b] = vertices(m, content);
685
- return [{ kind: "path", path: linePath(a, b), stroke }];
97
+ const [a4, b2] = vertices(m2, content);
98
+ return [{ kind: "path", path: linePath(a4, b2), stroke }];
686
99
  }
687
100
  case "arrow": {
688
- const [a, b] = vertices(m, content);
101
+ const [a4, b2] = vertices(m2, content);
689
102
  const marked = { ...stroke, markerEnd: "arrow" };
690
- const path = linePath(a, b);
103
+ const path = linePath(a4, b2);
691
104
  return [
692
105
  { kind: "path", path, stroke: marked },
693
106
  ...markerDrawCommands(path, marked, MARK_WIDTH, void 0)
694
107
  ];
695
108
  }
696
109
  case "stroke":
697
- return [{ kind: "path", path: polyline(vertices(m, content)), stroke }];
110
+ return [{ kind: "path", path: polyline(vertices(m2, content)), stroke }];
698
111
  case "text": {
699
- const text = m.data.title;
112
+ const text = m2.data.title;
700
113
  if (!text) return [];
701
114
  return [
702
115
  textCommand(
703
- m.pose.x,
704
- m.pose.y,
116
+ m2.pose.x,
117
+ m2.pose.y,
705
118
  text,
706
119
  { fontSize: TEXT_SIZE },
707
120
  void 0,
@@ -721,8 +134,8 @@ function seenFrom(config, keys) {
721
134
  const out = {};
722
135
  if (config === null || typeof config !== "object") return out;
723
136
  const src = config;
724
- for (const k of keys) {
725
- if (k in src) out[k] = src[k];
137
+ for (const k3 of keys) {
138
+ if (k3 in src) out[k3] = src[k3];
726
139
  }
727
140
  return out;
728
141
  }
@@ -730,9 +143,9 @@ function isStale(seen, config, keys) {
730
143
  if (!seen || keys.length === 0) return false;
731
144
  if (config === null || typeof config !== "object") return false;
732
145
  const src = config;
733
- for (const k of keys) {
734
- if (!(k in seen)) continue;
735
- if (!Object.is(seen[k], src[k])) return true;
146
+ for (const k3 of keys) {
147
+ if (!(k3 in seen)) continue;
148
+ if (!Object.is(seen[k3], src[k3])) return true;
736
149
  }
737
150
  return false;
738
151
  }
@@ -743,7 +156,7 @@ function createMarkDrawOne(opts) {
743
156
  }
744
157
  function resolveMarkStyle(data, opts) {
745
158
  return {
746
- color: opts.meaning?.statuses?.find((s) => s.id === data.status)?.color,
159
+ color: opts.meaning?.statuses?.find((s5) => s5.id === data.status)?.color,
747
160
  // Read off the node rather than through the store's `isStale`, which
748
161
  // wants a projected Annotation this path does not have.
749
162
  stale: isStale(data.seen, opts.config, opts.positionDependsOn ?? [])
@@ -770,10 +183,11 @@ function toSvgStroke(stroke) {
770
183
  ...stroke.cap ? { cap: stroke.cap } : {},
771
184
  ...stroke.join ? { join: stroke.join } : {},
772
185
  ...stroke.dash ? { dash: [...stroke.dash] } : {},
773
- ...stroke.miterLimit != null ? { miterLimit: stroke.miterLimit } : {}
186
+ ...stroke.miterLimit != null ? { miterLimit: stroke.miterLimit } : {},
187
+ ...stroke.align ? { align: stroke.align } : {}
774
188
  };
775
189
  }
776
- function toSvgNode(cmd, m) {
190
+ function toSvgNode(cmd, m2) {
777
191
  if (cmd.kind === "path") {
778
192
  return {
779
193
  kind: "path",
@@ -787,8 +201,8 @@ function toSvgNode(cmd, m) {
787
201
  kind: "text",
788
202
  x: cmd.x,
789
203
  y: cmd.y,
790
- width: m.pose.width,
791
- height: m.pose.height,
204
+ width: m2.pose.width,
205
+ height: m2.pose.height,
792
206
  text: cmd.runs.map((run) => run.text).join(""),
793
207
  style: cmd.style,
794
208
  ...cmd.runs[0]?.fill ? { fill: cmd.runs[0].fill } : {}
@@ -796,8 +210,8 @@ function toSvgNode(cmd, m) {
796
210
  }
797
211
  throw new Error(`[labkit] a mark drew a '${cmd.kind}' command, which has no vector form`);
798
212
  }
799
- function markSvgNodes(m, content, style = {}) {
800
- return markCommands(m, content, style).map((cmd) => toSvgNode(cmd, m));
213
+ function markSvgNodes(m2, content, style = {}) {
214
+ return markCommands(m2, content, style).map((cmd) => toSvgNode(cmd, m2));
801
215
  }
802
216
 
803
217
  // src/annotations/capture.ts
@@ -806,7 +220,7 @@ function capturePlan(base, format) {
806
220
  if (format === "svg") return "svg-document";
807
221
  return base?.kind === "svg" ? "svg-document" : "raster-stack";
808
222
  }
809
- function nestBaseSvg(markup, w3, h) {
223
+ function nestBaseSvg(markup, w3, h2) {
810
224
  const doc = new DOMParser().parseFromString(markup, "image/svg+xml");
811
225
  const root = doc.documentElement;
812
226
  if (doc.getElementsByTagName("parsererror").length > 0 || root.localName !== "svg") {
@@ -815,16 +229,16 @@ function nestBaseSvg(markup, w3, h) {
815
229
  root.setAttribute("x", "0");
816
230
  root.setAttribute("y", "0");
817
231
  root.setAttribute("width", String(w3));
818
- root.setAttribute("height", String(h));
232
+ root.setAttribute("height", String(h2));
819
233
  return new XMLSerializer().serializeToString(root);
820
234
  }
821
- function embedRasterBase(base, w3, h, onWarn) {
235
+ function embedRasterBase(base, w3, h2, onWarn) {
822
236
  const href = base.kind === "canvas" ? base.canvas.toDataURL() : base.kind === "image" ? base.src : "";
823
237
  if (base.kind === "image" && !href.startsWith("data:")) {
824
238
  onWarn?.(`base image '${href}' is a reference, so this SVG is not self-contained`);
825
239
  }
826
- return serializeSvg([{ kind: "image", href, x: 0, y: 0, width: w3, height: h }], {
827
- viewBox: { x: 0, y: 0, width: w3, height: h }
240
+ return serializeSvg([{ kind: "image", href, x: 0, y: 0, width: w3, height: h2 }], {
241
+ viewBox: { x: 0, y: 0, width: w3, height: h2 }
828
242
  });
829
243
  }
830
244
  function markNodesOf(scene, draw) {
@@ -840,15 +254,15 @@ function markNodesOf(scene, draw) {
840
254
  }
841
255
  function composeCaptureSvg(args) {
842
256
  const { base, scene, draw, scale, onWarn } = args;
843
- const { w: w3, h } = draw.content;
844
- const baseXml = base === void 0 ? "" : base.kind === "svg" ? nestBaseSvg(base.markup, w3, h) : embedRasterBase(base, w3, h, onWarn);
257
+ const { w: w3, h: h2 } = draw.content;
258
+ const baseXml = base === void 0 ? "" : base.kind === "svg" ? nestBaseSvg(base.markup, w3, h2) : embedRasterBase(base, w3, h2, onWarn);
845
259
  const marksXml = serializeSvg(markNodesOf(scene, draw), {
846
- viewBox: { x: 0, y: 0, width: w3, height: h },
260
+ viewBox: { x: 0, y: 0, width: w3, height: h2 },
847
261
  onWarn
848
262
  });
849
263
  const outW = Math.max(1, Math.round(w3 * scale));
850
- const outH = Math.max(1, Math.round(h * scale));
851
- return `<svg xmlns="${SVG_NS}" width="${outW}" height="${outH}" viewBox="0 0 ${w3} ${h}">${baseXml}${marksXml}</svg>`;
264
+ const outH = Math.max(1, Math.round(h2 * scale));
265
+ return `<svg xmlns="${SVG_NS}" width="${outW}" height="${outH}" viewBox="0 0 ${w3} ${h2}">${baseXml}${marksXml}</svg>`;
852
266
  }
853
267
  function context2d(width, height) {
854
268
  const canvas = document.createElement("canvas");
@@ -942,26 +356,26 @@ async function captureTarget(args, opts = {}) {
942
356
  }
943
357
 
944
358
  // src/annotations/frac.ts
945
- function fracToWorld(f2, content) {
359
+ function fracToWorld(f3, content) {
946
360
  return {
947
- x: f2.x * content.w,
948
- y: f2.y * content.h,
949
- width: f2.w * content.w,
950
- height: f2.h * content.h
361
+ x: f3.x * content.w,
362
+ y: f3.y * content.h,
363
+ width: f3.w * content.w,
364
+ height: f3.h * content.h
951
365
  };
952
366
  }
953
- function worldToFrac(r2, content) {
367
+ function worldToFrac(r10, content) {
954
368
  const sx = content.w === 0 ? 0 : 1 / content.w;
955
369
  const sy = content.h === 0 ? 0 : 1 / content.h;
956
- return { x: r2.x * sx, y: r2.y * sy, w: r2.width * sx, h: r2.height * sy };
370
+ return { x: r10.x * sx, y: r10.y * sy, w: r10.width * sx, h: r10.height * sy };
957
371
  }
958
372
  var DP = 1e4;
959
- function roundFrac(f2) {
373
+ function roundFrac(f3) {
960
374
  return {
961
- x: Math.round(f2.x * DP) / DP,
962
- y: Math.round(f2.y * DP) / DP,
963
- w: Math.round(f2.w * DP) / DP,
964
- h: Math.round(f2.h * DP) / DP
375
+ x: Math.round(f3.x * DP) / DP,
376
+ y: Math.round(f3.y * DP) / DP,
377
+ w: Math.round(f3.w * DP) / DP,
378
+ h: Math.round(f3.h * DP) / DP
965
379
  };
966
380
  }
967
381
  function fracContains(box, pt, tol = 0) {
@@ -1003,8 +417,8 @@ var MarkHistory = class {
1003
417
  /** The target `undo` would act on, skipping any whose scene has nothing
1004
418
  * left — a scene can drop entries this ordering still lists. */
1005
419
  next(stack, can, at) {
1006
- for (let i = stack.length - 1; i >= 0; i--) {
1007
- const target = stack[i];
420
+ for (let i3 = stack.length - 1; i3 >= 0; i3--) {
421
+ const target = stack[i3];
1008
422
  if (target === void 0) continue;
1009
423
  const scene = at(target);
1010
424
  if (scene && can(scene)) return target;
@@ -1012,13 +426,13 @@ var MarkHistory = class {
1012
426
  return null;
1013
427
  }
1014
428
  canUndo(at) {
1015
- return this.next(this.undoable, (s) => s.canUndo(), at) !== null;
429
+ return this.next(this.undoable, (s5) => s5.canUndo(), at) !== null;
1016
430
  }
1017
431
  canRedo(at) {
1018
- return this.next(this.redoable, (s) => s.canRedo(), at) !== null;
432
+ return this.next(this.redoable, (s5) => s5.canRedo(), at) !== null;
1019
433
  }
1020
434
  undo(at) {
1021
- const target = this.next(this.undoable, (s) => s.canUndo(), at);
435
+ const target = this.next(this.undoable, (s5) => s5.canUndo(), at);
1022
436
  const scene = target === null ? void 0 : at(target);
1023
437
  if (!scene) return false;
1024
438
  this.replaying = "undo";
@@ -1029,7 +443,7 @@ var MarkHistory = class {
1029
443
  }
1030
444
  }
1031
445
  redo(at) {
1032
- const target = this.next(this.redoable, (s) => s.canRedo(), at);
446
+ const target = this.next(this.redoable, (s5) => s5.canRedo(), at);
1033
447
  const scene = target === null ? void 0 : at(target);
1034
448
  if (!scene) return false;
1035
449
  this.replaying = "redo";
@@ -1041,9 +455,9 @@ var MarkHistory = class {
1041
455
  }
1042
456
  };
1043
457
  function drop(stack, value) {
1044
- for (let i = stack.length - 1; i >= 0; i--) {
1045
- if (stack[i] === value) {
1046
- stack.splice(i, 1);
458
+ for (let i3 = stack.length - 1; i3 >= 0; i3--) {
459
+ if (stack[i3] === value) {
460
+ stack.splice(i3, 1);
1047
461
  return;
1048
462
  }
1049
463
  }
@@ -1104,21 +518,21 @@ function createAnnotationStore(opts) {
1104
518
  if (!scene) return [];
1105
519
  const out = [];
1106
520
  for (const id of scene.renderOrder()) {
1107
- const a = project(target, String(id));
1108
- if (a) out.push(a);
521
+ const a4 = project(target, String(id));
522
+ if (a4) out.push(a4);
1109
523
  }
1110
524
  return out;
1111
525
  };
1112
526
  const all = () => liveTargets().flatMap(marksOn);
1113
- const matches = (a, q) => {
1114
- if (q.target !== void 0 && a.target !== q.target) return false;
1115
- if (q.kind !== void 0 && a.kind !== q.kind) return false;
1116
- if (q.status !== void 0 && a.status !== q.status) return false;
1117
- if (q.tags !== void 0) {
1118
- const have = a.tags ?? [];
1119
- for (const t of q.tags) if (!have.includes(t)) return false;
527
+ const matches = (a4, q3) => {
528
+ if (q3.target !== void 0 && a4.target !== q3.target) return false;
529
+ if (q3.kind !== void 0 && a4.kind !== q3.kind) return false;
530
+ if (q3.status !== void 0 && a4.status !== q3.status) return false;
531
+ if (q3.tags !== void 0) {
532
+ const have = a4.tags ?? [];
533
+ for (const t of q3.tags) if (!have.includes(t)) return false;
1120
534
  }
1121
- if (q.where && !q.where(a)) return false;
535
+ if (q3.where && !q3.where(a4)) return false;
1122
536
  return true;
1123
537
  };
1124
538
  const nodeOf = (id) => {
@@ -1135,18 +549,18 @@ function createAnnotationStore(opts) {
1135
549
  const parts = splitId(id);
1136
550
  return parts ? project(parts.target, parts.node) : void 0;
1137
551
  },
1138
- query(q) {
1139
- return q ? all().filter((a) => matches(a, q)) : all();
552
+ query(q3) {
553
+ return q3 ? all().filter((a4) => matches(a4, q3)) : all();
1140
554
  },
1141
555
  hitTest(target, pt, tol = 0) {
1142
- return marksOn(target).filter((a) => fracContains(a.frac, pt, tol)).reverse();
556
+ return marksOn(target).filter((a4) => fracContains(a4.frac, pt, tol)).reverse();
1143
557
  },
1144
558
  within(target, box) {
1145
- return marksOn(target).filter((a) => fracEncloses(box, a.frac));
559
+ return marksOn(target).filter((a4) => fracEncloses(box, a4.frac));
1146
560
  },
1147
- isStale(a, config) {
1148
- const keys = targetOf(a.target)?.positionDependsOn ?? [];
1149
- return isStale(a.seen, config, keys);
561
+ isStale(a4, config) {
562
+ const keys = targetOf(a4.target)?.positionDependsOn ?? [];
563
+ return isStale(a4.seen, config, keys);
1150
564
  },
1151
565
  canUndo() {
1152
566
  return history.canUndo(historySceneAt);
@@ -1184,7 +598,7 @@ function createAnnotationStore(opts) {
1184
598
  for (const [target, scene] of scenes) {
1185
599
  const next = wanted.get(target) ?? [];
1186
600
  const now = scene.getSelection();
1187
- if (now.length === next.length && next.every((n, i) => String(now[i]) === n)) continue;
601
+ if (now.length === next.length && next.every((n6, i3) => String(now[i3]) === n6)) continue;
1188
602
  scene.setSelection(next.map(asNodeId$1));
1189
603
  }
1190
604
  },
@@ -1246,7 +660,7 @@ function createAnnotationStore(opts) {
1246
660
  content: info.content,
1247
661
  positionDependsOn: info.positionDependsOn,
1248
662
  config: opts.config?.(),
1249
- meaning: opts.meaning
663
+ meaning: typeof opts.meaning === "function" ? opts.meaning() : opts.meaning
1250
664
  },
1251
665
  base: info.base,
1252
666
  onWarn: (message) => console.warn(`[labkit] capture: ${message}`)
@@ -1269,18 +683,46 @@ function annotationsFromJSON(raw, targets, rest = {}) {
1269
683
  return createAnnotationStore({ targets, restore: doc.scenes, ...rest });
1270
684
  }
1271
685
 
686
+ // src/annotations/toolMap.ts
687
+ var TOOLS = {
688
+ select: { weaselTool: "select" },
689
+ stroke: { weaselTool: "pencil", kind: "stroke" },
690
+ line: { weaselTool: "line", kind: "line" },
691
+ arrow: { weaselTool: "line", kind: "arrow" },
692
+ rect: { weaselTool: "rect", kind: "rect" },
693
+ ellipse: { weaselTool: "ellipse", kind: "ellipse" },
694
+ text: { weaselTool: "text", kind: "text" }
695
+ };
696
+ var ANNOTATION_TOOLS = [
697
+ // Not in `TOOLS`: an id the map does not carry is what leaves the overlay idle.
698
+ { id: "pointer", label: "Interact", icon: a2, group: "annotate" },
699
+ { id: "select", label: "Select", icon: p, group: "annotate" },
700
+ { id: "stroke", label: "Freehand", icon: u, group: "annotate" },
701
+ { id: "line", label: "Line", icon: c, group: "annotate" },
702
+ { id: "arrow", label: "Arrow", icon: n, group: "annotate" },
703
+ { id: "rect", label: "Rectangle", icon: f, group: "annotate" },
704
+ { id: "ellipse", label: "Ellipse", icon: r, group: "annotate" },
705
+ { id: "text", label: "Text", icon: h, group: "annotate" }
706
+ ];
707
+ function annotationToolInfo(id) {
708
+ return id === null ? void 0 : TOOLS[id];
709
+ }
710
+ var ANNOTATION_WEASEL_TOOLS = [
711
+ ...new Set(Object.values(TOOLS).map((t) => t.weaselTool))
712
+ ];
713
+
1272
714
  // src/annotations/view.ts
1273
- function toWeaselView(v) {
1274
- return { x: v.pan.x, y: v.pan.y, scale: { x: v.zoom, y: v.zoom } };
715
+ function toWeaselView(v2) {
716
+ return { x: v2.pan.x, y: v2.pan.y, scale: { x: v2.zoom, y: v2.zoom } };
1275
717
  }
1276
- function fromWeaselView(v) {
1277
- return { zoom: v.scale.x, pan: { x: v.x, y: v.y } };
718
+ function fromWeaselView(v2) {
719
+ return { zoom: v2.scale.x, pan: { x: v2.x, y: v2.y } };
1278
720
  }
1279
721
  function fitView(content, pane) {
1280
722
  const zoom = content.w > 0 && content.h > 0 ? Math.min(pane.w / content.w, pane.h / content.h) : 1;
1281
723
  return { zoom: Number.isFinite(zoom) && zoom > 0 ? zoom : 1, pan: { x: 0, y: 0 } };
1282
724
  }
1283
- var sameRect = (a, b) => a !== null && a.x === b.x && a.y === b.y && a.w === b.w && a.h === b.h;
725
+ var sameRect = (a4, b2) => a4 !== null && a4.x === b2.x && a4.y === b2.y && a4.w === b2.w && a4.h === b2.h;
1284
726
  function ToolBridge({ toolId }) {
1285
727
  const tools = useActiveToolContext();
1286
728
  const setActive = tools.setActive;
@@ -1290,9 +732,9 @@ function ToolBridge({ toolId }) {
1290
732
  return null;
1291
733
  }
1292
734
  function pointsOf(extras, content) {
1293
- const toFrac = (p) => ({
1294
- x: content.w === 0 ? 0 : p.x / content.w,
1295
- y: content.h === 0 ? 0 : p.y / content.h
735
+ const toFrac = (p2) => ({
736
+ x: content.w === 0 ? 0 : p2.x / content.w,
737
+ y: content.h === 0 ? 0 : p2.y / content.h
1296
738
  });
1297
739
  if (extras.kind === "line") {
1298
740
  return [toFrac(extras.a), toFrac(extras.b)];
@@ -1334,9 +776,40 @@ function AnnotationOverlay({
1334
776
  },
1335
777
  [surface, tileId]
1336
778
  );
779
+ const painted = useRef(false);
780
+ const canvasRef = useRef(canvas);
781
+ if (canvasRef.current !== canvas) painted.current = false;
782
+ canvasRef.current = canvas;
783
+ const unsubscribeFrame = useRef(null);
784
+ const attachSceneCanvas = useCallback((api) => {
785
+ sceneCanvas.current = api;
786
+ unsubscribeFrame.current?.();
787
+ unsubscribeFrame.current = api ? api.subscribeFrame(() => {
788
+ painted.current = true;
789
+ }) : null;
790
+ }, []);
791
+ useEffect(() => {
792
+ if (!surface) return;
793
+ return surface.registerClear(tileId, (size, dpr) => {
794
+ if (!painted.current) return;
795
+ const gl = canvasRef.current?.getContext("webgl2");
796
+ if (!gl) return;
797
+ gl.disable(gl.SCISSOR_TEST);
798
+ gl.viewport(0, 0, Math.round(size.width * dpr), Math.round(size.height * dpr));
799
+ gl.clearColor(0, 0, 0, 0);
800
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
801
+ });
802
+ }, [surface, tileId]);
803
+ const wheelSlot = useContext(CameraWheelContext);
804
+ useEffect(() => {
805
+ if (!input || !wheelSlot) return;
806
+ const wheel = (e) => wheelSlot.current?.(e);
807
+ input.addEventListener("wheel", wheel, { passive: false });
808
+ return () => input.removeEventListener("wheel", wheel);
809
+ }, [input, wheelSlot]);
1337
810
  useEffect(
1338
811
  () => surface?.registerPainter(tileId, (next) => {
1339
- setRect((prev) => sameRect(prev, next) ? prev : next);
812
+ flushSync(() => setRect((prev) => sameRect(prev, next) ? prev : next));
1340
813
  sceneCanvas.current?.requestRedraw();
1341
814
  }),
1342
815
  [surface, tileId]
@@ -1380,6 +853,7 @@ function AnnotationOverlay({
1380
853
  ref: setInput,
1381
854
  className: "lk-annotate__input",
1382
855
  "data-annotation-target": id,
856
+ "data-idle": annotationToolInfo(activeToolId) ? void 0 : "",
1383
857
  role: "application",
1384
858
  "aria-label": `Annotations on ${id}`,
1385
859
  tabIndex: 0,
@@ -1394,7 +868,7 @@ function AnnotationOverlay({
1394
868
  /* @__PURE__ */ jsx(
1395
869
  SceneCanvas,
1396
870
  {
1397
- ref: sceneCanvas,
871
+ ref: attachSceneCanvas,
1398
872
  scene,
1399
873
  width: rect.w,
1400
874
  height: rect.h,
@@ -1434,314 +908,6 @@ function AnnotationTargets({
1434
908
  )) });
1435
909
  }
1436
910
 
1437
- // src/config/builder.ts
1438
- function isConfigBranch(entry) {
1439
- return "children" in entry;
1440
- }
1441
- var BaseNode = class {
1442
- constructor(defaultValue, annotations = {}, options = {}) {
1443
- this.annotations = annotations;
1444
- this.options = options;
1445
- this.default = defaultValue;
1446
- }
1447
- annotations;
1448
- options;
1449
- // `default` is a reserved word in a parameter position, so the field is
1450
- // declared rather than taken as a parameter property.
1451
- default;
1452
- /** Clone into the same subclass. Subclasses with extra constructor
1453
- * arguments override this. */
1454
- with(annotations, options) {
1455
- const Ctor = this.constructor;
1456
- return new Ctor(this.default, annotations, options);
1457
- }
1458
- ann(patch) {
1459
- return this.with({ ...this.annotations, ...patch }, this.options);
1460
- }
1461
- opt(patch) {
1462
- return this.with(this.annotations, { ...this.options, ...patch });
1463
- }
1464
- /** Human-readable label. Defaults to the key, title-cased. */
1465
- label(name) {
1466
- return this.ann({ name });
1467
- }
1468
- /** Longer help text, surfaced as a tooltip on the row label. */
1469
- describe(description) {
1470
- return this.ann({ description });
1471
- }
1472
- /** Omit from the panel unless it is asked to show hidden leaves. */
1473
- hidden() {
1474
- return this.ann({ hidden: true });
1475
- }
1476
- /** Pair with a sibling sharing this id, side-by-side on one row. */
1477
- pair(pair) {
1478
- return this.ann({ pair });
1479
- }
1480
- /** Render under a named section heading. `collapsed` opens the section
1481
- * folded — say it on any one of the section's leaves. */
1482
- section(label, opts = {}) {
1483
- return this.opt({ section: { label, ...opts } });
1484
- }
1485
- /** Show this row only while the predicate holds. Presentational — the value
1486
- * stays in config and the instrument still reads it. */
1487
- showIf(predicate) {
1488
- return this.opt({ showIf: predicate });
1489
- }
1490
- /** Draw this one row yourself, keeping the kind, default and validation. */
1491
- render(renderer) {
1492
- return this.opt({ render: renderer });
1493
- }
1494
- };
1495
- var NumberNode = class extends BaseNode {
1496
- kind = "number";
1497
- /** Bound the value. A number with both bounds renders as a slider. */
1498
- range(min, max) {
1499
- return this.ann({ min, max });
1500
- }
1501
- step(step) {
1502
- return this.ann({ step });
1503
- }
1504
- /** A display suffix for the value — `'px'`, `'ms'`, `'°'`. Presentation only:
1505
- * it is never parsed, and the stored value stays a plain number. */
1506
- suffix(suffix) {
1507
- return this.ann({ suffix });
1508
- }
1509
- /** Force a slider even without both bounds. */
1510
- slider() {
1511
- return this.ann({ control: "slider" });
1512
- }
1513
- /** Force a typed input even when both bounds are set. */
1514
- input() {
1515
- return this.ann({ control: "input" });
1516
- }
1517
- };
1518
- var BooleanNode = class extends BaseNode {
1519
- kind = "boolean";
1520
- /** Ask for a switch. `ControlPanel` still draws a checkbox; weasel-ui's
1521
- * `PrefsForm` honors the distinction. */
1522
- toggle() {
1523
- return this.ann({ control: "switch" });
1524
- }
1525
- };
1526
- var StringNode = class extends BaseNode {
1527
- kind = "string";
1528
- placeholder(placeholder) {
1529
- return this.ann({ placeholder });
1530
- }
1531
- maxLength(maxLength) {
1532
- return this.ann({ maxLength });
1533
- }
1534
- /** Milliseconds to debounce live writes. 0 commits every keystroke. */
1535
- debounce(debounceMs) {
1536
- return this.ann({ debounceMs });
1537
- }
1538
- };
1539
- var ColorNode = class extends BaseNode {
1540
- kind = "color";
1541
- };
1542
- var EnumNode = class extends BaseNode {
1543
- kind = "enum";
1544
- /** Render as a segmented control rather than a select. */
1545
- radio() {
1546
- return this.ann({ control: "radio" });
1547
- }
1548
- /** Relabel the choices. Named `labels` rather than `options` because
1549
- * `options` is the node's own extras bag. */
1550
- labels(options) {
1551
- return this.ann({ options });
1552
- }
1553
- };
1554
- var ValueNode = class extends BaseNode {
1555
- kind = null;
1556
- };
1557
- var CustomNode = class _CustomNode extends BaseNode {
1558
- constructor(kind, defaultValue, annotations = {}, options = {}) {
1559
- super(defaultValue, annotations, options);
1560
- this.kind = kind;
1561
- }
1562
- kind;
1563
- with(annotations, options) {
1564
- return new _CustomNode(this.kind, this.default, annotations, options);
1565
- }
1566
- };
1567
- var GroupNode = class _GroupNode {
1568
- constructor(children, annotations = {}, options = {}) {
1569
- this.children = children;
1570
- this.annotations = annotations;
1571
- this.options = options;
1572
- }
1573
- children;
1574
- annotations;
1575
- options;
1576
- with(annotations, options) {
1577
- return new _GroupNode(this.children, annotations, options);
1578
- }
1579
- /** Heading for the group's rows. Defaults to the key, title-cased. */
1580
- label(name) {
1581
- return this.with({ ...this.annotations, name }, this.options);
1582
- }
1583
- /** Longer help text. Carried on the resolved `PrefGroup`, where weasel-ui's
1584
- * `PrefsForm` draws it under the heading; `ControlPanel` has no place for
1585
- * it yet and shows the heading alone. */
1586
- describe(description) {
1587
- return this.with({ ...this.annotations, description }, this.options);
1588
- }
1589
- /** Render this whole group under a named section heading. */
1590
- section(label, opts = {}) {
1591
- return this.with(this.annotations, { ...this.options, section: { label, ...opts } });
1592
- }
1593
- /** Show this group only while the predicate holds. Presentational — every
1594
- * value beneath it stays in config and the instrument still reads it. */
1595
- showIf(predicate) {
1596
- return this.with(this.annotations, { ...this.options, showIf: predicate });
1597
- }
1598
- };
1599
- function shapeDefaults(shape) {
1600
- const out = {};
1601
- for (const [key, entry] of Object.entries(shape)) {
1602
- out[key] = isConfigBranch(entry) ? shapeDefaults(entry.children) : entry.default;
1603
- }
1604
- return out;
1605
- }
1606
- var expand = (options) => options.map((o) => typeof o === "string" ? { value: o, label: o } : o);
1607
- var f = {
1608
- number: (def) => new NumberNode(def),
1609
- boolean: (def) => new BooleanNode(def),
1610
- string: (def) => new StringNode(def),
1611
- color: (def) => new ColorNode(def),
1612
- /** A fixed set of choices. The default's literal type flows into the config
1613
- * type, so `f.enum('fast', ['fast', 'accurate'])` gives
1614
- * `'fast' | 'accurate'` with no `as const`. */
1615
- enum: (def, options) => new EnumNode(def, { options: expand(options) }),
1616
- /** A leaf with no declared kind: the rule chain decides, which is how a lab
1617
- * states a convention like "every `*Color` key is a color picker". */
1618
- value: (def) => new ValueNode(def),
1619
- /** A branch: its children nest under this key, so `grid: f.group({ size })`
1620
- * puts the value at `config.grid.size` and addresses it as `'grid.size'`.
1621
- * Groups nest as deeply as the schema wants. */
1622
- group: (children) => new GroupNode(children),
1623
- /** A leaf of a kind a lab supplies the control for, through `controls`. */
1624
- custom: (kind, def, validate) => new CustomNode(kind, def, {}, validate ? { validate } : {}),
1625
- /** Collect leaves and groups into an instrument's config. */
1626
- schema(nodes) {
1627
- return { nodes, defaults: () => shapeDefaults(nodes) };
1628
- }
1629
- };
1630
-
1631
- // src/config/rules.ts
1632
- function titleCase(key) {
1633
- const words = key.replace(/[_-]+/g, " ").split(/(?<=[a-z0-9])(?=[A-Z])/).join(" ").trim().toLowerCase();
1634
- return words.length === 0 ? words : words[0].toUpperCase() + words.slice(1);
1635
- }
1636
- var kindFromValue = (ctx) => {
1637
- const t = typeof ctx.default;
1638
- if (t === "boolean" || t === "number" || t === "string") return { kind: t };
1639
- return null;
1640
- };
1641
- var labelFromKey = (ctx) => ({ name: titleCase(ctx.key) });
1642
- var sliderWhenBounded = (ctx) => {
1643
- if (ctx.leaf.kind !== "number") return null;
1644
- const bounded = ctx.leaf.min !== void 0 && ctx.leaf.max !== void 0;
1645
- return bounded ? { control: "slider" } : null;
1646
- };
1647
- var descriptionDefault = () => ({ description: "" });
1648
- var builtinRules = [
1649
- kindFromValue,
1650
- labelFromKey,
1651
- sliderWhenBounded,
1652
- descriptionDefault
1653
- ];
1654
- function applyRules(seed, ctx, rules) {
1655
- const acc = { ...seed };
1656
- for (const rule of rules) {
1657
- const patch = rule({ ...ctx, leaf: acc });
1658
- if (patch === null) continue;
1659
- for (const [k, v] of Object.entries(patch)) {
1660
- if (v !== void 0 && acc[k] === void 0) {
1661
- acc[k] = v;
1662
- }
1663
- }
1664
- }
1665
- return acc;
1666
- }
1667
-
1668
- // src/config/resolve.ts
1669
- function defined(annotations) {
1670
- const out = {};
1671
- for (const [k, v] of Object.entries(annotations)) if (v !== void 0) out[k] = v;
1672
- return out;
1673
- }
1674
- function resolveConfigSchema(schema, rules = []) {
1675
- const sink = {
1676
- sections: [],
1677
- showIf: /* @__PURE__ */ new Map(),
1678
- renderers: {},
1679
- chain: [...rules, ...builtinRules]
1680
- };
1681
- const group = resolveShape(schema.nodes, "", "", sink);
1682
- return { group, sections: sink.sections, showIf: sink.showIf, renderers: sink.renderers };
1683
- }
1684
- function resolveShape(shape, at, name, sink) {
1685
- const children = {};
1686
- const sections = /* @__PURE__ */ new Map();
1687
- for (const [key, entry] of Object.entries(shape)) {
1688
- const path = at === "" ? key : `${at}.${key}`;
1689
- children[key] = resolveEntry(entry, key, path, sink);
1690
- const { section, showIf: predicate } = entry.options;
1691
- if (section !== void 0) {
1692
- let spec = sections.get(section.label);
1693
- if (!spec) {
1694
- spec = { at, label: section.label, paths: [] };
1695
- sections.set(section.label, spec);
1696
- sink.sections.push(spec);
1697
- }
1698
- spec.paths.push(path);
1699
- if (section.collapsed !== void 0) {
1700
- spec.collapsed = (spec.collapsed ?? false) || section.collapsed;
1701
- }
1702
- }
1703
- if (predicate) sink.showIf.set(path, predicate);
1704
- if (!isConfigBranch(entry) && entry.options.render) sink.renderers[path] = entry.options.render;
1705
- }
1706
- return { name, children };
1707
- }
1708
- function resolveEntry(entry, key, path, sink) {
1709
- if (isConfigBranch(entry)) {
1710
- const group = resolveShape(
1711
- entry.children,
1712
- path,
1713
- entry.annotations.name ?? titleCase(key),
1714
- sink
1715
- );
1716
- return entry.annotations.description === void 0 ? group : { ...group, description: entry.annotations.description };
1717
- }
1718
- const seed = defined({
1719
- ...entry.annotations,
1720
- ...entry.kind === null ? {} : { kind: entry.kind }
1721
- });
1722
- const patch = applyRules(seed, { key, path, default: entry.default }, sink.chain);
1723
- return { ...patch, default: entry.default };
1724
- }
1725
- var LabContext = createContext(null);
1726
- function useLabContext() {
1727
- const ctx = useContext(LabContext);
1728
- if (ctx === null) {
1729
- throw new Error("useLabContext must be used inside <Lab>");
1730
- }
1731
- return ctx;
1732
- }
1733
-
1734
- // src/config/useConfigSchema.ts
1735
- var NO_RULES = [];
1736
- function useConfigSchema(instrument) {
1737
- const lab = useContext(LabContext);
1738
- const rules = lab?.configRules ?? NO_RULES;
1739
- return useMemo(
1740
- () => instrument.config ? resolveConfigSchema(instrument.config, rules) : fromConfigFields(instrument.configSchema?.() ?? []),
1741
- [instrument, rules]
1742
- );
1743
- }
1744
-
1745
911
  // src/instrument/defineInstrument.ts
1746
912
  function defineInstrument(spec) {
1747
913
  if (spec.config && !("defaultConfig" in spec && spec.defaultConfig)) {
@@ -1799,957 +965,207 @@ function validateConfigSchema(fields) {
1799
965
  }
1800
966
  return { valid: errors.length === 0, errors };
1801
967
  }
968
+ var AnnotationPreloadContext = createContext(null);
1802
969
 
1803
- // ../theme/dist/chunk-D6AFL2JW.js
1804
- var THEME_SOURCES = {
1805
- "weasel": {
1806
- "primitives": {
1807
- "gray-50": {
1808
- "type": "color",
1809
- "value": "#f5f5f6"
1810
- },
1811
- "gray-100": {
1812
- "type": "color",
1813
- "value": "#e6e7e9"
1814
- },
1815
- "gray-200": {
1816
- "type": "color",
1817
- "value": "#c9cbcf"
1818
- },
1819
- "gray-300": {
1820
- "type": "color",
1821
- "value": "#9ea1a8"
1822
- },
1823
- "gray-400": {
1824
- "type": "color",
1825
- "value": "#6f737b"
1826
- },
1827
- "gray-500": {
1828
- "type": "color",
1829
- "value": "#4d5058"
1830
- },
1831
- "gray-600": {
1832
- "type": "color",
1833
- "value": "#383b42"
1834
- },
1835
- "gray-700": {
1836
- "type": "color",
1837
- "value": "#25272c"
1838
- },
1839
- "gray-800": {
1840
- "type": "color",
1841
- "value": "#181a1e"
1842
- },
1843
- "gray-900": {
1844
- "type": "color",
1845
- "value": "#0e0f12"
1846
- },
1847
- "accent-soft": {
1848
- "type": "color",
1849
- "value": "#1d1454"
1850
- },
1851
- "accent-base": {
1852
- "type": "color",
1853
- "value": "#2e1f7a",
1854
- "description": "Midnight blue-violet. Deep and saturated; anchors primary actions on light and dark surfaces alike."
1855
- },
1856
- "accent-strong": {
1857
- "type": "color",
1858
- "value": "#5841b8"
1859
- },
1860
- "danger-base": {
1861
- "type": "color",
1862
- "value": "#d94a3f"
1863
- },
1864
- "warning-base": {
1865
- "type": "color",
1866
- "value": "#d99a3f"
1867
- },
1868
- "success-base": {
1869
- "type": "color",
1870
- "value": "#2ec27e"
1871
- },
1872
- "fg-on-accent": {
1873
- "type": "color",
1874
- "value": "{color.gray-50}"
1875
- },
1876
- "line-subtle": {
1877
- "type": "color",
1878
- "value": "{color.fg}",
1879
- "alpha": 0.1,
1880
- "description": "Barely-there separators (table rows)."
1881
- },
1882
- "line": {
1883
- "type": "color",
1884
- "value": "{color.fg}",
1885
- "alpha": 0.2,
1886
- "description": "Primary gridlines on sunken surfaces."
1887
- },
1888
- "line-strong": {
1889
- "type": "color",
1890
- "value": "{color.fg}",
1891
- "alpha": 0.4,
1892
- "description": "Axes and emphasized dividers."
1893
- },
1894
- "curve-color": {
1895
- "type": "color",
1896
- "value": "{color.accent-strong}",
1897
- "description": "Drawn data curves. Routes through the bright accent because accent-base is too dim on sunken surfaces."
1898
- },
1899
- "accent": {
1900
- "type": "color",
1901
- "value": "{color.accent-base}"
1902
- },
1903
- "accent-hover": {
1904
- "type": "color",
1905
- "value": "{color.accent-strong}"
1906
- },
1907
- "danger": {
1908
- "type": "color",
1909
- "value": "{color.danger-base}"
1910
- },
1911
- "warning": {
1912
- "type": "color",
1913
- "value": "{color.warning-base}"
1914
- },
1915
- "success": {
1916
- "type": "color",
1917
- "value": "{color.success-base}"
1918
- },
1919
- "focus-ring": {
1920
- "type": "color",
1921
- "value": "{color.accent-strong}"
1922
- },
1923
- "glass-tint": {
1924
- "type": "color",
1925
- "value": "{color.accent}",
1926
- "description": "Frosted-glass tint. Components compose it as color-mix(in srgb, var(--wzl-glass-tint) 78%, transparent) with backdrop-filter: blur(3px)."
1927
- },
1928
- "surface-hover": {
1929
- "type": "color",
1930
- "value": "{color.fg}",
1931
- "alpha": 0.1,
1932
- "description": "Hover wash over a raised surface. Same formula as line-subtle, different job \u2014 do not collapse them."
1933
- },
1934
- "surface-pressed": {
1935
- "type": "color",
1936
- "value": "{color.fg}",
1937
- "alpha": 0.18,
1938
- "description": "Pressed wash over a raised surface."
1939
- },
1940
- "swatch-green": {
1941
- "type": "color",
1942
- "value": "#2fdd18",
1943
- "description": "First of ten categorical spot colors for plotting, status and data viz. Ordered \u2014 take them in sequence. Mode-invariant on purpose: a series keeps its color when the mode flips."
1944
- },
1945
- "swatch-pink": {
1946
- "type": "color",
1947
- "value": "#ff5885"
1948
- },
1949
- "swatch-cyan": {
1950
- "type": "color",
1951
- "value": "#00dfff"
1952
- },
1953
- "swatch-gold": {
1954
- "type": "color",
1955
- "value": "#dcb700"
1956
- },
1957
- "swatch-amber": {
1958
- "type": "color",
1959
- "value": "#ff8c00"
1960
- },
1961
- "swatch-violet": {
1962
- "type": "color",
1963
- "value": "#a497ff"
1964
- },
1965
- "swatch-mint": {
1966
- "type": "color",
1967
- "value": "#00e7af"
1968
- },
1969
- "swatch-sky": {
1970
- "type": "color",
1971
- "value": "#00b8ff"
1972
- },
1973
- "swatch-orange": {
1974
- "type": "color",
1975
- "value": "#ff6b00"
1976
- },
1977
- "swatch-magenta": {
1978
- "type": "color",
1979
- "value": "#ff6eff"
1980
- },
1981
- "radius-sm": {
1982
- "type": "dimension",
1983
- "value": "3px"
1984
- },
1985
- "radius-md": {
1986
- "type": "dimension",
1987
- "value": "5px"
1988
- },
1989
- "radius-lg": {
1990
- "type": "dimension",
1991
- "value": "14px"
1992
- },
1993
- "radius-pill": {
1994
- "type": "dimension",
1995
- "value": "999px",
1996
- "description": "Fully rounded ends \u2014 segmented controls, progress tracks."
1997
- },
1998
- "border-w": {
1999
- "type": "dimension",
2000
- "value": "1px"
2001
- },
2002
- "line-width": {
2003
- "type": "dimension",
2004
- "value": "2px",
2005
- "description": "Structural lines \u2014 gridlines, dividers, axes."
2006
- },
2007
- "curve-width": {
2008
- "type": "dimension",
2009
- "value": "3px",
2010
- "description": "Drawn data curves. Heavier than structural lines so data reads as primary content."
2011
- },
2012
- "tb-height": {
2013
- "type": "dimension",
2014
- "value": "28px",
2015
- "description": "Height of a horizontal toolbar strip (ActionsBar, OptionsBar, ToolOptionsBar). Taller than control-h so the controls inside a strip are not flush with its edges."
2016
- },
2017
- "control-h": {
2018
- "type": "dimension",
2019
- "value": "24px",
2020
- "description": "Height of an interactive control \u2014 button, input, select, segmented control. Distinct from tb-height, which sizes the strip a row of them sits in."
2021
- },
2022
- "slider-track-h": {
2023
- "type": "dimension",
2024
- "value": "4px",
2025
- "description": "Height of a linear range track. The kit's single-thumb ranges all wear this; Slider's default 24px canvas track does not."
2026
- },
2027
- "slider-thumb-size": {
2028
- "type": "dimension",
2029
- "value": "8px",
2030
- "description": "Diameter of a linear range thumb."
2031
- },
2032
- "slider-track-mix": {
2033
- "type": "dimension",
2034
- "value": "18%",
2035
- "description": "Accent proportion in a range track, as a color-mix percentage \u2014 not a color. Mixed into a real property, never into another custom property."
2036
- },
2037
- "slider-thumb-mix": {
2038
- "type": "dimension",
2039
- "value": "70%",
2040
- "description": "Accent proportion in a range thumb, as a color-mix percentage \u2014 not a color."
2041
- },
2042
- "field-pad-x": {
2043
- "type": "dimension",
2044
- "value": "8px",
2045
- "description": "Horizontal padding inside a boxed field. Pairs with --wzl-field-h, which is a container override and has no :root default."
2046
- },
2047
- "glass-blur": {
2048
- "type": "dimension",
2049
- "value": "3px",
2050
- "description": "Backdrop blur radius for frosted surfaces. Pairs with glass-tint."
2051
- },
2052
- "font-size-2xs": {
2053
- "type": "dimension",
2054
- "value": "9px",
2055
- "description": "Shortcut keys and index badges. The floor for chrome text."
2056
- },
2057
- "font-size-xs": {
2058
- "type": "dimension",
2059
- "value": "10px",
2060
- "description": "Tool-button labels under an icon."
2061
- },
2062
- "font-size-sm": {
2063
- "type": "dimension",
2064
- "value": "11px",
2065
- "description": "Chrome labels, status readouts, section headings."
2066
- },
2067
- "font-size": {
2068
- "type": "dimension",
2069
- "value": "13px",
2070
- "description": "Controls and body text."
2071
- },
2072
- "font-size-lg": {
2073
- "type": "dimension",
2074
- "value": "16px",
2075
- "description": "Panel and dialog titles."
2076
- },
2077
- "font-size-xl": {
2078
- "type": "dimension",
2079
- "value": "20px",
2080
- "description": "The largest chrome text \u2014 a lab or app title."
2081
- },
2082
- "space-xs": {
2083
- "type": "dimension",
2084
- "value": "4px"
2085
- },
2086
- "space-sm": {
2087
- "type": "dimension",
2088
- "value": "8px"
2089
- },
2090
- "space-md": {
2091
- "type": "dimension",
2092
- "value": "12px"
2093
- },
2094
- "space-lg": {
2095
- "type": "dimension",
2096
- "value": "16px"
2097
- },
2098
- "tracking-none": {
2099
- "type": "dimension",
2100
- "value": "0"
2101
- },
2102
- "tracking-wide": {
2103
- "type": "dimension",
2104
- "value": "0.06em",
2105
- "description": "Uppercase section headings."
2106
- },
2107
- "tracking-wider": {
2108
- "type": "dimension",
2109
- "value": "0.08em",
2110
- "description": "Uppercase at 11px and below."
2111
- },
2112
- "z-toolbar": {
2113
- "type": "number",
2114
- "value": 10
2115
- },
2116
- "z-overlay": {
2117
- "type": "number",
2118
- "value": 20
2119
- },
2120
- "z-modal": {
2121
- "type": "number",
2122
- "value": 30
2123
- },
2124
- "leading-tight": {
2125
- "type": "number",
2126
- "value": 1,
2127
- "description": "Single-line controls; the box sets the height."
2128
- },
2129
- "leading-snug": {
2130
- "type": "number",
2131
- "value": 1.2,
2132
- "description": "Headings and two-line labels."
2133
- },
2134
- "leading": {
2135
- "type": "number",
2136
- "value": 1.4,
2137
- "description": "Body and anything that wraps."
2138
- },
2139
- "backdrop": {
2140
- "type": "gradient",
2141
- "value": "none",
2142
- "description": "Layered backdrop fill for a page or lab root. `none` here; a theme with a signature background sets it."
2143
- },
2144
- "motion-fast": {
2145
- "type": "duration",
2146
- "value": "120ms",
2147
- "description": "Micro-interactions \u2014 hover, focus, pressed."
2148
- },
2149
- "motion-medium": {
2150
- "type": "duration",
2151
- "value": "240ms",
2152
- "description": "Entrances and exits \u2014 toasts, popovers, panels."
2153
- },
2154
- "ease-in-cubic": {
2155
- "type": "cubicBezier",
2156
- "value": [
2157
- 0.32,
2158
- 0,
2159
- 0.67,
2160
- 0
2161
- ]
2162
- },
2163
- "ease-out-cubic": {
2164
- "type": "cubicBezier",
2165
- "value": [
2166
- 0.33,
2167
- 1,
2168
- 0.68,
2169
- 1
2170
- ]
2171
- },
2172
- "ease-in-out-cubic": {
2173
- "type": "cubicBezier",
2174
- "value": [
2175
- 0.65,
2176
- 0,
2177
- 0.35,
2178
- 1
2179
- ]
2180
- },
2181
- "ease-out-back": {
2182
- "type": "cubicBezier",
2183
- "value": [
2184
- 0.34,
2185
- 1.56,
2186
- 0.64,
2187
- 1
2188
- ]
2189
- },
2190
- "font-ui": {
2191
- "type": "fontFamily",
2192
- "value": [
2193
- "Oswald",
2194
- "Helvetica Neue Condensed",
2195
- "Arial Narrow",
2196
- "system-ui",
2197
- "sans-serif"
2198
- ],
2199
- "description": "Condensed UI/display face. The heavier cuts get blocky at any size, so the weight tokens stay in the 200-400 range."
2200
- },
2201
- "font-display": {
2202
- "type": "fontFamily",
2203
- "value": [
2204
- "Oswald",
2205
- "Helvetica Neue Condensed",
2206
- "Arial Narrow",
2207
- "system-ui",
2208
- "sans-serif"
2209
- ]
2210
- },
2211
- "font-body": {
2212
- "type": "fontFamily",
2213
- "value": [
2214
- "Inter",
2215
- "system-ui",
2216
- "-apple-system",
2217
- "Segoe UI",
2218
- "sans-serif"
2219
- ]
2220
- },
2221
- "font-mono": {
2222
- "type": "fontFamily",
2223
- "value": [
2224
- "ui-monospace",
2225
- "SFMono-Regular",
2226
- "Menlo",
2227
- "Consolas",
2228
- "monospace"
2229
- ]
2230
- },
2231
- "font-weight-light": {
2232
- "type": "fontWeight",
2233
- "value": 200
2234
- },
2235
- "font-weight-normal": {
2236
- "type": "fontWeight",
2237
- "value": 300
2238
- },
2239
- "font-weight-medium": {
2240
- "type": "fontWeight",
2241
- "value": 350
2242
- },
2243
- "font-weight-bold": {
2244
- "type": "fontWeight",
2245
- "value": 400
970
+ // src/theme/interstellar.theme.json
971
+ var interstellar_theme_default = {
972
+ name: "interstellar",
973
+ extends: "weasel",
974
+ pins: {
975
+ "radius-md": {
976
+ value: "6px",
977
+ type: "dimension"
978
+ },
979
+ "glass-blur": {
980
+ value: "12px",
981
+ type: "dimension"
982
+ },
983
+ "font-weight-light": {
984
+ value: 300,
985
+ type: "fontWeight"
986
+ },
987
+ "font-weight-medium": {
988
+ value: 500,
989
+ type: "fontWeight"
990
+ },
991
+ "font-weight-bold": {
992
+ value: 700,
993
+ type: "fontWeight"
994
+ },
995
+ surface: {
996
+ by: "mode",
997
+ dark: {
998
+ value: "#0a0a14",
999
+ type: "color"
1000
+ },
1001
+ light: {
1002
+ value: "#fafaf7",
1003
+ type: "color"
2246
1004
  }
2247
1005
  },
2248
- "modes": {
2249
- "dark": {
2250
- "surface": {
2251
- "type": "color",
2252
- "value": "{color.gray-800}"
2253
- },
2254
- "surface-raised": {
2255
- "type": "color",
2256
- "value": "{color.gray-700}"
2257
- },
2258
- "surface-sunken": {
2259
- "type": "color",
2260
- "value": "{color.gray-900}"
2261
- },
2262
- "fg": {
2263
- "type": "color",
2264
- "value": "{color.gray-100}"
2265
- },
2266
- "fg-muted": {
2267
- "type": "color",
2268
- "value": "{color.gray-300}"
2269
- },
2270
- "fg-subtle": {
2271
- "type": "color",
2272
- "value": "{color.gray-400}"
2273
- },
2274
- "border": {
2275
- "type": "color",
2276
- "value": "{color.gray-700}"
2277
- },
2278
- "border-strong": {
2279
- "type": "color",
2280
- "value": "{color.gray-600}"
2281
- },
2282
- "accent-fg": {
2283
- "type": "color",
2284
- "value": "{color.accent-strong}",
2285
- "description": "Accent for foreground use \u2014 text, icons, selected labels drawn on a surface. accent is a fill; the midnight base is too dim to read as text on dark surfaces."
2286
- },
2287
- "fg-inverse": {
2288
- "type": "color",
2289
- "value": "{color.gray-900}",
2290
- "description": "Text drawn on a filled foreground-colored element (e.g. a slider thumb). Flips with the mode, so it is a semantic, not an alias to a primitive."
2291
- },
2292
- "shadow": {
2293
- "type": "color",
2294
- "value": "rgba(0, 0, 0, 0.6)",
2295
- "description": "Elevation. Always darker than any surface \u2014 never derived from fg, which is near-white on dark."
2296
- },
2297
- "border-raised": {
2298
- "type": "color",
2299
- "value": "{color.gray-400}",
2300
- "description": "The line around a raised surface. Clears 3:1 against surface and surface-raised in either mode, which the general-purpose border does not; over surface-sunken it does not, so a panel on a sunken field needs its own answer."
2301
- }
2302
- },
2303
- "light": {
2304
- "surface": {
2305
- "type": "color",
2306
- "value": "{color.gray-50}"
2307
- },
2308
- "surface-raised": {
2309
- "type": "color",
2310
- "value": "{color.gray-100}"
2311
- },
2312
- "surface-sunken": {
2313
- "type": "color",
2314
- "value": "{color.gray-200}"
2315
- },
2316
- "fg": {
2317
- "type": "color",
2318
- "value": "{color.gray-900}"
2319
- },
2320
- "fg-muted": {
2321
- "type": "color",
2322
- "value": "{color.gray-600}"
2323
- },
2324
- "fg-subtle": {
2325
- "type": "color",
2326
- "value": "{color.gray-500}"
2327
- },
2328
- "border": {
2329
- "type": "color",
2330
- "value": "{color.gray-200}"
2331
- },
2332
- "border-strong": {
2333
- "type": "color",
2334
- "value": "{color.gray-300}"
2335
- },
2336
- "accent-fg": {
2337
- "type": "color",
2338
- "value": "{color.accent-base}",
2339
- "description": "Flips to the deep base in light mode, where it is the legible one."
2340
- },
2341
- "fg-inverse": {
2342
- "type": "color",
2343
- "value": "{color.gray-50}",
2344
- "description": "Flips with the mode \u2014 see the dark layer."
2345
- },
2346
- "shadow": {
2347
- "type": "color",
2348
- "value": "rgba(0, 0, 0, 0.18)"
2349
- },
2350
- "border-raised": {
2351
- "type": "color",
2352
- "value": "{color.gray-400}"
2353
- }
1006
+ "surface-raised": {
1007
+ by: "mode",
1008
+ dark: {
1009
+ value: "rgba(20, 20, 36, 0.55)",
1010
+ type: "color"
1011
+ },
1012
+ light: {
1013
+ value: "#ffffff",
1014
+ type: "color"
2354
1015
  }
2355
- }
2356
- }
2357
- };
2358
- var weaselTheme = {
2359
- name: "weasel",
2360
- extends: null,
2361
- defaultMode: "dark",
2362
- tokens: THEME_SOURCES.weasel.primitives,
2363
- modes: THEME_SOURCES.weasel.modes
2364
- };
2365
- function hexToRgba(hex, alpha) {
2366
- const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
2367
- if (!m) throw new Error(`Expected a hex color, got "${hex}"`);
2368
- const body = m[1];
2369
- const full = body.length === 3 ? body.replace(/./g, (c2) => c2 + c2) : body;
2370
- const r2 = Number.parseInt(full.slice(0, 2), 16);
2371
- const g2 = Number.parseInt(full.slice(2, 4), 16);
2372
- const b = Number.parseInt(full.slice(4, 6), 16);
2373
- return `rgba(${r2}, ${g2}, ${b}, ${alpha})`;
2374
- }
2375
- var REF = /^\{([^}]+)\}$/;
2376
- function refTarget(value) {
2377
- if (typeof value !== "string") return null;
2378
- const m = REF.exec(value.trim());
2379
- if (!m) return null;
2380
- const path = m[1];
2381
- const dot = path.indexOf(".");
2382
- return dot === -1 ? path : path.slice(dot + 1);
2383
- }
2384
- function fontStack(value) {
2385
- return value.map((f2) => typeof f2 === "string" && /\s/.test(f2) ? `'${f2}'` : String(f2)).join(", ");
2386
- }
2387
- function serialize(type, value) {
2388
- if (Array.isArray(value)) {
2389
- if (type === "cubicBezier") return `cubic-bezier(${value.join(", ")})`;
2390
- if (type === "fontFamily") return fontStack(value);
2391
- return value.join(", ");
2392
- }
2393
- return String(value);
2394
- }
2395
- function resolveTokens(tokens) {
2396
- const out = {};
2397
- const inProgress = /* @__PURE__ */ new Set();
2398
- const resolveOne = (name) => {
2399
- const cached = out[name];
2400
- if (cached !== void 0) return cached;
2401
- const token = tokens[name];
2402
- if (!token) throw new Error(`Unknown token "${name}"`);
2403
- if (inProgress.has(name)) {
2404
- throw new Error(`Reference cycle at token "${name}" (${[...inProgress].join(" \u2192 ")})`);
2405
- }
2406
- inProgress.add(name);
2407
- const target = refTarget(token.value);
2408
- let value;
2409
- if (target === null) {
2410
- value = serialize(token.type, token.value);
2411
- } else if (!tokens[target]) {
2412
- throw new Error(`Token "${name}" references "${target}", which is not defined`);
2413
- } else {
2414
- value = resolveOne(target);
2415
- }
2416
- if (token.alpha !== void 0) value = hexToRgba(value, token.alpha);
2417
- inProgress.delete(name);
2418
- out[name] = value;
2419
- return value;
2420
- };
2421
- for (const name of Object.keys(tokens)) resolveOne(name);
2422
- return out;
2423
- }
2424
- function chain(theme) {
2425
- const out = [];
2426
- for (let t = theme; t; t = t.extends) out.unshift(t);
2427
- return out;
2428
- }
2429
- function resolveTheme(theme, mode) {
2430
- const themes = chain(theme);
2431
- const effectiveMode = themes.some((t) => mode in t.modes) ? mode : theme.defaultMode;
2432
- let merged = {};
2433
- for (const t of themes) {
2434
- merged = { ...merged, ...t.tokens, ...t.modes[effectiveMode] ?? {} };
2435
- }
2436
- const resolved = resolveTokens(merged);
2437
- const out = {};
2438
- for (const [name, value] of Object.entries(resolved)) out[`--wzl-${name}`] = value;
2439
- return out;
2440
- }
2441
- var STYLE_ID = "wzl-themes";
2442
- var emitted = /* @__PURE__ */ new Map();
2443
- var sheet = null;
2444
- var styleEl = null;
2445
- function canAdopt() {
2446
- return typeof CSSStyleSheet !== "undefined" && "replaceSync" in CSSStyleSheet.prototype && Array.isArray(document.adoptedStyleSheets);
2447
- }
2448
- function flushRules() {
2449
- const css = [...emitted.values()].join("\n");
2450
- if (canAdopt()) {
2451
- if (!sheet) {
2452
- sheet = new CSSStyleSheet();
2453
- document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
2454
- }
2455
- sheet.replaceSync(css);
2456
- return;
2457
- }
2458
- if (!styleEl) {
2459
- styleEl = document.createElement("style");
2460
- styleEl.id = STYLE_ID;
2461
- document.head.appendChild(styleEl);
2462
- }
2463
- styleEl.textContent = css;
2464
- }
2465
- function applyTheme(el, theme, mode) {
2466
- if (typeof document === "undefined") return;
2467
- const key = `${theme.name}::${mode}`;
2468
- const resolved = resolveTheme(theme, mode);
2469
- const body = Object.entries(resolved).map(([name, value]) => `${name}: ${value};`).join(" ");
2470
- const rule = `[data-wzl-theme='${theme.name}'][data-wzl-mode='${mode}'] { ${body} }`;
2471
- if (emitted.get(key) !== rule) {
2472
- emitted.set(key, rule);
2473
- flushRules();
2474
- }
2475
- el.setAttribute("data-wzl-theme", theme.name);
2476
- el.setAttribute("data-wzl-mode", mode);
2477
- }
2478
- var ThemeContext = createContext(null);
2479
- function ThemeProvider({
2480
- theme = weaselTheme,
2481
- mode,
2482
- className,
2483
- style,
2484
- children
2485
- }) {
2486
- const ref = useRef(null);
2487
- const effectiveMode = mode ?? theme.defaultMode;
2488
- const value = useMemo(
2489
- () => ({ theme, mode: effectiveMode, resolved: resolveTheme(theme, effectiveMode) }),
2490
- [theme, effectiveMode]
2491
- );
2492
- useLayoutEffect(() => {
2493
- if (ref.current) applyTheme(ref.current, theme, effectiveMode);
2494
- }, [theme, effectiveMode]);
2495
- return /* @__PURE__ */ jsx(ThemeContext.Provider, { value, children: /* @__PURE__ */ jsx(
2496
- "div",
2497
- {
2498
- ref,
2499
- className,
2500
- style,
2501
- "data-wzl-theme": theme.name,
2502
- "data-wzl-mode": effectiveMode,
2503
- children
2504
- }
2505
- ) });
2506
- }
2507
- function useThemeOptional() {
2508
- return useContext(ThemeContext);
2509
- }
2510
-
2511
- // src/instrument/serializers.ts
2512
- function configDefaultsOf(instruments) {
2513
- const out = {};
2514
- for (const instrument of instruments) out[instrument.name] = () => instrument.defaultConfig();
2515
- return out;
2516
- }
2517
- function serializersOf(instruments) {
2518
- const out = {};
2519
- for (const instrument of instruments) {
2520
- const { name, serialize: serialize2, deserialize } = instrument;
2521
- if (!serialize2 && !deserialize) continue;
2522
- out[name] = {
2523
- ...serialize2 ? { serialize: (state) => serialize2(state) } : {},
2524
- ...deserialize ? { deserialize: (data, config) => deserialize(data, config) } : {}
2525
- };
2526
- }
2527
- return out;
2528
- }
2529
-
2530
- // ../theme/dist/index.js
2531
- var ALPHA_EXT = "com.weasel.alpha";
2532
- var isToken = (v) => typeof v === "object" && v !== null && "$value" in v;
2533
- function flattenTokens(doc) {
2534
- const out = {};
2535
- for (const [groupName, group] of Object.entries(doc)) {
2536
- if (groupName.startsWith("$")) continue;
2537
- if (typeof group !== "object" || group === null) continue;
2538
- const g2 = group;
2539
- const groupType = g2.$type;
2540
- for (const [leaf, node] of Object.entries(g2)) {
2541
- if (leaf.startsWith("$")) continue;
2542
- if (!isToken(node)) continue;
2543
- const type = node.$type ?? groupType;
2544
- if (!type) {
2545
- throw new Error(`Token "${leaf}" has no $type and its group "${groupName}" declares none`);
1016
+ },
1017
+ "surface-sunken": {
1018
+ by: "mode",
1019
+ dark: {
1020
+ value: "#02020a",
1021
+ type: "color"
1022
+ },
1023
+ light: {
1024
+ value: "#f0ebe2",
1025
+ type: "color"
2546
1026
  }
2547
- if (leaf in out) {
2548
- throw new Error(`Duplicate token name "${leaf}" \u2014 leaf keys must be unique across type groups`);
1027
+ },
1028
+ border: {
1029
+ by: "mode",
1030
+ dark: {
1031
+ value: "rgba(255, 255, 255, 0.14)",
1032
+ type: "color"
1033
+ },
1034
+ light: {
1035
+ value: "#c9bba5",
1036
+ type: "color"
2549
1037
  }
2550
- const alphaRaw = node.$extensions?.[ALPHA_EXT];
2551
- const token = {
2552
- type,
2553
- value: node.$value,
2554
- alpha: typeof alphaRaw === "number" ? alphaRaw : void 0,
2555
- description: node.$description
2556
- };
2557
- out[leaf] = token;
2558
- }
2559
- }
2560
- return out;
2561
- }
2562
- function loadDTCG(doc) {
2563
- if (typeof doc.name !== "string" || doc.name === "") {
2564
- throw new Error('DTCG document needs a string "name"');
2565
- }
2566
- const modes = {};
2567
- for (const [mode, body] of Object.entries(doc.modes ?? {})) {
2568
- modes[mode] = flattenTokens(body);
2569
- }
2570
- const base = doc.extends === void 0 ? weaselTheme : doc.extends;
2571
- return {
2572
- name: doc.name,
2573
- extends: base,
2574
- defaultMode: typeof doc.defaultMode === "string" ? doc.defaultMode : base?.defaultMode ?? "dark",
2575
- tokens: flattenTokens(doc.primitives ?? {}),
2576
- modes
2577
- };
2578
- }
2579
-
2580
- // src/theme/interstellar.tokens.json
2581
- var interstellar_tokens_default = {
2582
- name: "interstellar",
2583
- defaultMode: "dark",
2584
- primitives: {
2585
- dimension: {
2586
- $type: "dimension",
2587
- "radius-md": { $value: "6px" },
2588
- "glass-blur": { $value: "12px" }
2589
1038
  },
2590
- fontWeight: {
2591
- $type: "fontWeight",
2592
- "font-weight-light": { $value: 300 },
2593
- "font-weight-medium": { $value: 500 },
2594
- "font-weight-bold": { $value: 700 }
2595
- }
2596
- },
2597
- modes: {
2598
- dark: {
2599
- color: {
2600
- $type: "color",
2601
- surface: { $value: "#0a0a14" },
2602
- "surface-raised": { $value: "rgba(20, 20, 36, 0.55)" },
2603
- "surface-sunken": { $value: "#02020a" },
2604
- border: { $value: "rgba(255, 255, 255, 0.14)" },
2605
- "line-subtle": { $value: "rgba(255, 255, 255, 0.08)" },
2606
- fg: { $value: "#e8e8f0" },
2607
- "fg-muted": { $value: "#a8a8c0" },
2608
- "fg-subtle": { $value: "#5a5a78" },
2609
- accent: { $value: "#b08adb" },
2610
- "accent-hover": { $value: "#c6a3ee" },
2611
- "focus-ring": { $value: "rgba(176, 138, 219, 0.45)" },
2612
- shadow: { $value: "rgba(0, 0, 0, 0.7)" },
2613
- "border-raised": { $value: "rgba(255, 255, 255, 0.36)" }
2614
- },
2615
- gradient: {
2616
- $type: "gradient",
2617
- backdrop: {
2618
- $value: "radial-gradient(ellipse 60% 80% at 18% 30%, rgba(150, 80, 200, 0.22), transparent 70%), radial-gradient(ellipse 55% 90% at 78% 70%, rgba(220, 100, 140, 0.18), transparent 65%), radial-gradient(ellipse 50% 70% at 50% 50%, rgba(60, 110, 200, 0.18), transparent 75%), radial-gradient(ellipse at center, #0a0a18 0%, #02020a 100%)"
2619
- }
1039
+ "line-subtle": {
1040
+ by: "mode",
1041
+ dark: {
1042
+ value: "rgba(255, 255, 255, 0.08)",
1043
+ type: "color"
1044
+ },
1045
+ light: {
1046
+ value: "rgba(0, 0, 0, 0.08)",
1047
+ type: "color"
1048
+ }
1049
+ },
1050
+ fg: {
1051
+ by: "mode",
1052
+ dark: {
1053
+ value: "#e8e8f0",
1054
+ type: "color"
1055
+ },
1056
+ light: {
1057
+ value: "#2a2018",
1058
+ type: "color"
1059
+ }
1060
+ },
1061
+ "fg-muted": {
1062
+ by: "mode",
1063
+ dark: {
1064
+ value: "#a8a8c0",
1065
+ type: "color"
1066
+ },
1067
+ light: {
1068
+ value: "#6b5c4a",
1069
+ type: "color"
1070
+ }
1071
+ },
1072
+ "fg-subtle": {
1073
+ by: "mode",
1074
+ dark: {
1075
+ value: "#5a5a78",
1076
+ type: "color"
1077
+ },
1078
+ light: {
1079
+ value: "#a89b85",
1080
+ type: "color"
1081
+ }
1082
+ },
1083
+ accent: {
1084
+ by: "mode",
1085
+ dark: {
1086
+ value: "#b08adb",
1087
+ type: "color"
1088
+ },
1089
+ light: {
1090
+ value: "#a86f3c",
1091
+ type: "color"
1092
+ }
1093
+ },
1094
+ "accent-hover": {
1095
+ by: "mode",
1096
+ dark: {
1097
+ value: "#c6a3ee",
1098
+ type: "color"
1099
+ },
1100
+ light: {
1101
+ value: "#c2854f",
1102
+ type: "color"
2620
1103
  }
2621
1104
  },
2622
- light: {
2623
- color: {
2624
- $type: "color",
2625
- surface: { $value: "#fafaf7" },
2626
- "surface-raised": { $value: "#ffffff" },
2627
- "surface-sunken": { $value: "#f0ebe2" },
2628
- border: { $value: "#c9bba5" },
2629
- "line-subtle": { $value: "rgba(0, 0, 0, 0.08)" },
2630
- fg: { $value: "#2a2018" },
2631
- "fg-muted": { $value: "#6b5c4a" },
2632
- "fg-subtle": { $value: "#a89b85" },
2633
- accent: { $value: "#a86f3c" },
2634
- "accent-hover": { $value: "#c2854f" },
2635
- "focus-ring": { $value: "rgba(168, 111, 60, 0.35)" },
2636
- shadow: { $value: "rgba(80, 60, 40, 0.18)" },
2637
- "border-raised": { $value: "#9e8b71" }
1105
+ "focus-ring": {
1106
+ by: "mode",
1107
+ dark: {
1108
+ value: "rgba(176, 138, 219, 0.45)",
1109
+ type: "color"
1110
+ },
1111
+ light: {
1112
+ value: "rgba(168, 111, 60, 0.35)",
1113
+ type: "color"
1114
+ }
1115
+ },
1116
+ shadow: {
1117
+ by: "mode",
1118
+ dark: {
1119
+ value: "rgba(0, 0, 0, 0.7)",
1120
+ type: "color"
1121
+ },
1122
+ light: {
1123
+ value: "rgba(80, 60, 40, 0.18)",
1124
+ type: "color"
1125
+ }
1126
+ },
1127
+ "border-strong": {
1128
+ by: "mode",
1129
+ dark: {
1130
+ value: "rgba(255, 255, 255, 0.36)",
1131
+ type: "color"
1132
+ },
1133
+ light: {
1134
+ value: "#9e8b71",
1135
+ type: "color"
1136
+ }
1137
+ },
1138
+ backdrop: {
1139
+ by: "mode",
1140
+ dark: {
1141
+ value: "radial-gradient(ellipse 60% 80% at 18% 30%, rgba(150, 80, 200, 0.22), transparent 70%), radial-gradient(ellipse 55% 90% at 78% 70%, rgba(220, 100, 140, 0.18), transparent 65%), radial-gradient(ellipse 50% 70% at 50% 50%, rgba(60, 110, 200, 0.18), transparent 75%), radial-gradient(ellipse at center, #0a0a18 0%, #02020a 100%)",
1142
+ type: "gradient"
1143
+ },
1144
+ light: {
1145
+ value: "none",
1146
+ type: "gradient"
2638
1147
  }
2639
1148
  }
2640
1149
  }
2641
1150
  };
2642
1151
 
2643
1152
  // src/theme/interstellar.ts
2644
- var interstellarTheme = loadDTCG(interstellar_tokens_default);
2645
-
2646
- // src/state/view.ts
2647
- var DEFAULT_VIEW = { zoom: 1, pan: { x: 0, y: 0 } };
2648
- function as2DView(view) {
2649
- if (typeof view !== "object" || view === null) return null;
2650
- const v = view;
2651
- if (typeof v.zoom !== "number") return null;
2652
- if (typeof v.pan !== "object" || v.pan === null) return null;
2653
- if (typeof v.pan.x !== "number" || typeof v.pan.y !== "number") return null;
2654
- return { zoom: v.zoom, pan: { x: v.pan.x, y: v.pan.y } };
2655
- }
2656
- var ZONE_ID = asNodeId("lk-trial-body");
2657
- var SIDEBAR_ID = asNodeId("lk-trial-sidebar");
2658
- var CONTENT_ID = asNodeId("lk-trial-content");
2659
- var STRATEGIES = { strip: stripStrategy };
2660
- function TrialBody({
2661
- sidebar,
2662
- children,
2663
- contentClassName,
2664
- width,
2665
- defaultWidth = 320,
2666
- minWidth = 140,
2667
- maxWidth = 720,
2668
- contentMinWidth = 160,
2669
- onWidthChange,
2670
- viewport
2671
- }) {
2672
- const storeRef = useRef(null);
2673
- if (storeRef.current === null) {
2674
- const store2 = new Store();
2675
- store2.registerNode(
2676
- createNode({
2677
- kind: "zone",
2678
- id: ZONE_ID,
2679
- container: { strategyId: "strip", config: { axis: "x", resizable: true } }
2680
- })
2681
- );
2682
- store2.registerNode(
2683
- createNode({
2684
- kind: "sidebar",
2685
- id: SIDEBAR_ID,
2686
- parentId: ZONE_ID,
2687
- meta: { title: "Sidebar" },
2688
- placement: { size: { w: width ?? defaultWidth } },
2689
- hints: { minSize: { w: minWidth, h: 0 }, maxSize: { w: maxWidth, h: 0 } }
2690
- })
2691
- );
2692
- store2.registerNode(
2693
- createNode({
2694
- kind: "content",
2695
- id: CONTENT_ID,
2696
- parentId: ZONE_ID,
2697
- meta: { title: "Content" },
2698
- hints: { minSize: { w: contentMinWidth, h: 0 } }
2699
- })
2700
- );
2701
- store2.showNode(SIDEBAR_ID);
2702
- store2.showNode(CONTENT_ID);
2703
- storeRef.current = store2;
2704
- }
2705
- const store = storeRef.current;
2706
- const widthRef = useRef(width ?? defaultWidth);
2707
- useEffect(() => {
2708
- store.setHints(SIDEBAR_ID, {
2709
- minSize: { w: minWidth, h: 0 },
2710
- maxSize: { w: maxWidth, h: 0 }
2711
- });
2712
- store.setHints(CONTENT_ID, { minSize: { w: contentMinWidth, h: 0 } });
2713
- }, [store, minWidth, maxWidth, contentMinWidth]);
2714
- useEffect(() => {
2715
- if (width === void 0 || width === widthRef.current) return;
2716
- widthRef.current = width;
2717
- store.patchPlacement(SIDEBAR_ID, { size: { w: width } });
2718
- }, [store, width]);
2719
- const onWidthChangeRef = useRef(onWidthChange);
2720
- onWidthChangeRef.current = onWidthChange;
2721
- useEffect(
2722
- () => store.events.on("node.placementChanged", () => {
2723
- const next = store.getPlacement(SIDEBAR_ID)?.size?.w;
2724
- if (typeof next !== "number" || next === widthRef.current) return;
2725
- widthRef.current = next;
2726
- onWidthChangeRef.current?.(next);
2727
- }),
2728
- [store]
2729
- );
2730
- const chrome = useMemo(
2731
- () => ({
2732
- sidebar: () => /* @__PURE__ */ jsx("div", { className: "lk-trial__sidebar", children: sidebar }),
2733
- content: () => /* @__PURE__ */ jsx(
2734
- "div",
2735
- {
2736
- className: contentClassName ? `lk-trial__content ${contentClassName}` : "lk-trial__content",
2737
- children
2738
- }
2739
- )
2740
- }),
2741
- [sidebar, children, contentClassName]
2742
- );
2743
- return /* @__PURE__ */ jsx(Provider, { store, children: /* @__PURE__ */ jsx(StrategyRegistryProvider, { strategies: STRATEGIES, children: /* @__PURE__ */ jsx(
2744
- Container,
1153
+ var interstellarTheme = defineTheme({
1154
+ ...interstellar_theme_default,
1155
+ extends: weaselTheme
1156
+ });
1157
+ function TrialBody({ sidebar, children, contentClassName, ...rest }) {
1158
+ return /* @__PURE__ */ jsx(
1159
+ Split,
2745
1160
  {
2746
- parentId: ZONE_ID,
2747
- chrome,
2748
- className: "lk-trial__panes windease-zone",
2749
- affordances: true,
2750
- viewport
1161
+ ...rest,
1162
+ sidebar,
1163
+ className: "lk-trial__panes",
1164
+ sidebarClassName: "lk-trial__sidebar",
1165
+ contentClassName: contentClassName ? `lk-trial__content ${contentClassName}` : "lk-trial__content",
1166
+ children
2751
1167
  }
2752
- ) }) });
1168
+ );
2753
1169
  }
2754
1170
  var TrialDragContext = createContext(null);
2755
1171
  function useTrialDrag() {
@@ -2834,7 +1250,7 @@ function Portalled({
2834
1250
  }
2835
1251
  function UndockedSections({ trialId, sections, onDock }) {
2836
1252
  if (sections.length === 0) return null;
2837
- return /* @__PURE__ */ jsx(Fragment, { children: sections.map((c2) => /* @__PURE__ */ jsx(Portalled, { trialId, contribution: c2, onDock }, c2.id)) });
1253
+ return /* @__PURE__ */ jsx(Fragment, { children: sections.map((c8) => /* @__PURE__ */ jsx(Portalled, { trialId, contribution: c8, onDock }, c8.id)) });
2838
1254
  }
2839
1255
  var NO_OP = () => {
2840
1256
  };
@@ -2857,27 +1273,32 @@ function TrialChrome({
2857
1273
  const lab = useLabContext();
2858
1274
  const storeCtx = useContext(LabStoreContext);
2859
1275
  if (!storeCtx) throw new Error("[labkit] TrialChrome requires <LabStoreProvider>");
2860
- const updateTrialView = useStore(storeCtx.store, (s) => s.updateTrialView);
2861
- const updateTrialSidebarWidth = useStore(storeCtx.store, (s) => s.updateTrialSidebarWidth);
2862
- const setTrialTitle = useStore(storeCtx.store, (s) => s.setTrialTitle);
2863
- const setTrialSectionCollapsed = useStore(storeCtx.store, (s) => s.setTrialSectionCollapsed);
2864
- const updateTrialConfig = useStore(storeCtx.store, (s) => s.updateTrialConfig);
2865
- const updateTrialState = useStore(storeCtx.store, (s) => s.updateTrialState);
2866
- const undockPanelAction = useStore(storeCtx.store, (s) => s.undockPanel);
2867
- const dockPanelAction = useStore(storeCtx.store, (s) => s.dockPanel);
2868
- const undockedPanels = useStore(storeCtx.store, (s) => s.undockedPanels);
1276
+ const updateTrialView = useStore(storeCtx.store, (s5) => s5.updateTrialView);
1277
+ const updateTrialSidebarWidth = useStore(storeCtx.store, (s5) => s5.updateTrialSidebarWidth);
1278
+ const setTrialTitle = useStore(storeCtx.store, (s5) => s5.setTrialTitle);
1279
+ const setTrialSectionCollapsed = useStore(storeCtx.store, (s5) => s5.setTrialSectionCollapsed);
1280
+ const updateTrialConfig = useStore(storeCtx.store, (s5) => s5.updateTrialConfig);
1281
+ const updateTrialState = useStore(storeCtx.store, (s5) => s5.updateTrialState);
1282
+ const undockPanelAction = useStore(storeCtx.store, (s5) => s5.undockPanel);
1283
+ const dockPanelAction = useStore(storeCtx.store, (s5) => s5.dockPanel);
1284
+ const undockedPanels = useStore(storeCtx.store, (s5) => s5.undockedPanels);
2869
1285
  const undockedIds = useMemo(
2870
- () => Object.values(undockedPanels).filter((p) => p.trialId === trialId).map((p) => p.sectionId),
1286
+ () => Object.values(undockedPanels).filter((p2) => p2.trialId === trialId).map((p2) => p2.sectionId),
2871
1287
  [undockedPanels, trialId]
2872
1288
  );
2873
1289
  const view2d = as2DView(record.view);
2874
1290
  const configSchema = useConfigSchema(instrument);
2875
- const title = record.title ?? record.instrumentName;
1291
+ const { config: resolvedConfig, auto: auto2 } = useResolvedConfig(
1292
+ configSchema,
1293
+ record.config,
1294
+ record.auto
1295
+ );
1296
+ const title = record.title ?? instrument.title ?? instrument.name;
2876
1297
  const collapsedSections = record.collapsedSections ?? NO_SECTIONS;
2877
1298
  const ctx = useMemo(() => {
2878
1299
  const setZoom = (z2) => {
2879
1300
  if (!view2d) return;
2880
- updateTrialView(trialId, { ...view2d, zoom: z2 });
1301
+ updateTrialView(trialId, withZoom(view2d, z2));
2881
1302
  };
2882
1303
  return {
2883
1304
  trialId,
@@ -2896,6 +1317,7 @@ function TrialChrome({
2896
1317
  configSchema,
2897
1318
  configFields: instrument.config ? [] : instrument.configSchema?.() ?? [],
2898
1319
  config: record.config,
1320
+ auto: auto2,
2899
1321
  setConfig: (path, value) => {
2900
1322
  const prevConfig = record.config;
2901
1323
  if (process.env.NODE_ENV !== "production" && !hasConfigPath(prevConfig, path) && schemaNodeAtPath(configSchema.group, path) === void 0) {
@@ -2905,8 +1327,9 @@ function TrialChrome({
2905
1327
  }
2906
1328
  updateTrialConfig(trialId, path, value);
2907
1329
  if (instrument.onConfigChange) {
2908
- const nextConfig = withValueAtPath(prevConfig, path, value);
2909
- const nextState = instrument.onConfigChange(nextConfig, prevConfig, record.state);
1330
+ const next = applyConfigWrite(prevConfig, record.auto ?? [], path, value);
1331
+ const nextConfig = resolveAutoConfig(configSchema, next.config, new Set(next.autoPaths));
1332
+ const nextState = instrument.onConfigChange(nextConfig, resolvedConfig, record.state);
2910
1333
  updateTrialState(trialId, nextState);
2911
1334
  }
2912
1335
  },
@@ -2915,7 +1338,7 @@ function TrialChrome({
2915
1338
  undockedPanels: undockedIds,
2916
1339
  undockPanel: (sectionId, as) => undockPanelAction(trialId, sectionId, as),
2917
1340
  dockPanel: (sectionId) => dockPanelAction(trialId, sectionId),
2918
- savedSnapshots: lab.savedSnapshots.filter((s) => s.trialId === trialId),
1341
+ savedSnapshots: lab.savedSnapshots.filter((s5) => s5.trialId === trialId),
2919
1342
  saveSnapshot: (name) => lab.saveSnapshot(trialId, name),
2920
1343
  loadSnapshot: (snapshotId) => lab.loadSnapshot(trialId, snapshotId),
2921
1344
  clone: () => lab.cloneTrial(trialId),
@@ -2940,6 +1363,8 @@ function TrialChrome({
2940
1363
  activeToolId,
2941
1364
  setActiveTool,
2942
1365
  configSchema,
1366
+ resolvedConfig,
1367
+ auto2,
2943
1368
  undockedIds,
2944
1369
  undockPanelAction,
2945
1370
  dockPanelAction,
@@ -2959,7 +1384,7 @@ function TrialChrome({
2959
1384
  ),
2960
1385
  [instrument, ctx, trialChrome, chrome, suppress, lab.controls]
2961
1386
  );
2962
- const inRegion = (region) => contributions.filter((c2) => c2.region === region);
1387
+ const inRegion = (region) => contributions.filter((c8) => c8.region === region);
2963
1388
  const handleKeyDown = (e) => {
2964
1389
  const mod = e.metaKey || e.ctrlKey;
2965
1390
  if (!mod) return;
@@ -2976,6 +1401,8 @@ function TrialChrome({
2976
1401
  "section",
2977
1402
  {
2978
1403
  className: "lk-trial",
1404
+ "data-trial-id": trialId,
1405
+ "data-focused": lab.trials.length > 1 && lab.focusedTrialId === trialId ? "true" : void 0,
2979
1406
  "aria-label": `Trial ${title}`,
2980
1407
  tabIndex: -1,
2981
1408
  onKeyDown: handleKeyDown,
@@ -2996,14 +1423,14 @@ function TrialChrome({
2996
1423
  {
2997
1424
  width: record.sidebarWidth,
2998
1425
  onWidthChange: (w3) => updateTrialSidebarWidth(trialId, w3),
2999
- contentClassName: instrument.canvas ? "lk-trial__content--flush" : void 0,
1426
+ contentClassName: instrument.canvas || instrument.stage ? "lk-trial__content--flush" : void 0,
3000
1427
  sidebar: /* @__PURE__ */ jsxs(Fragment, { children: [
3001
1428
  /* @__PURE__ */ jsx(SidebarRegion, { contributions: inRegion("sidebar"), ctx }),
3002
1429
  /* @__PURE__ */ jsx(
3003
1430
  UndockedSections,
3004
1431
  {
3005
1432
  trialId,
3006
- sections: inRegion("sidebar").filter((c2) => undockedIds.includes(c2.id)),
1433
+ sections: inRegion("sidebar").filter((c8) => undockedIds.includes(c8.id)),
3007
1434
  onDock: ctx.dockPanel
3008
1435
  }
3009
1436
  )
@@ -3027,25 +1454,30 @@ function Trial({ id, chrome, suppress }) {
3027
1454
  const lab = useLabContext();
3028
1455
  const storeCtx = useContext(LabStoreContext);
3029
1456
  if (!storeCtx) throw new Error("[labkit] <Trial> requires <LabStoreProvider>");
3030
- const record = useStore(storeCtx.store, (s) => s.trials.find((w3) => w3.id === id));
1457
+ const instruments = useStore(storeCtx.store, (s5) => s5.instruments) ?? lab.instruments;
1458
+ const record = useStore(storeCtx.store, (s5) => s5.trials.find((w3) => w3.id === id));
1459
+ const instrument = record ? instruments.find((i3) => i3.name === record.instrumentName) : void 0;
1460
+ const kept = useKeptMarks(instrument?.annotations?.storage, id);
3031
1461
  if (!record) {
3032
1462
  return /* @__PURE__ */ jsxs("div", { className: "lk-trial lk-trial--unknown", children: [
3033
1463
  "Trial not found: ",
3034
1464
  id
3035
1465
  ] });
3036
1466
  }
3037
- const instrument = lab.instruments.find((i) => i.name === record.instrumentName);
3038
1467
  if (!instrument) {
3039
1468
  return /* @__PURE__ */ jsxs("div", { className: "lk-trial lk-trial--unknown", children: [
3040
1469
  "Unknown instrument: ",
3041
1470
  record.instrumentName
3042
1471
  ] });
3043
1472
  }
1473
+ if (!kept.ready) return /* @__PURE__ */ jsx("div", { className: "lk-trial lk-trial--loading" });
3044
1474
  return /* @__PURE__ */ jsx(
3045
1475
  TrialRuntime,
3046
1476
  {
1477
+ keptMarks: kept.value,
3047
1478
  record,
3048
1479
  instrument,
1480
+ instruments,
3049
1481
  store: storeCtx.store,
3050
1482
  isLast: lab.trials.length <= 1,
3051
1483
  chrome,
@@ -3053,17 +1485,53 @@ function Trial({ id, chrome, suppress }) {
3053
1485
  }
3054
1486
  );
3055
1487
  }
3056
- function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
1488
+ function useKeptMarks(storage, trialId) {
1489
+ const preload = useContext(AnnotationPreloadContext);
1490
+ const [loaded, setLoaded] = useState(
1491
+ () => storage && preload?.has(trialId) ? { value: preload.get(trialId) } : null
1492
+ );
1493
+ useEffect(() => {
1494
+ if (!storage || loaded) return;
1495
+ let live = true;
1496
+ storage.load().then(
1497
+ (value) => {
1498
+ if (live) setLoaded({ value });
1499
+ },
1500
+ (error) => {
1501
+ console.warn(`[labkit] could not load the marks of trial "${trialId}"`, error);
1502
+ if (live) setLoaded({ value: null });
1503
+ }
1504
+ );
1505
+ return () => {
1506
+ live = false;
1507
+ };
1508
+ }, [storage, loaded, trialId]);
1509
+ if (!storage) return { ready: true, value: void 0 };
1510
+ return loaded ? { ready: true, value: loaded.value } : { ready: false, value: void 0 };
1511
+ }
1512
+ function TrialRuntime({
1513
+ keptMarks,
1514
+ record,
1515
+ instrument,
1516
+ instruments,
1517
+ store,
1518
+ isLast,
1519
+ chrome,
1520
+ suppress
1521
+ }) {
1522
+ const wheelSlot = useRef(null);
3057
1523
  const canvasContainerRef = useRef(null);
3058
1524
  const loupeHostRef = useRef(null);
3059
- const updateTrialState = useStore(store, (s) => s.updateTrialState);
3060
- const updateTrialConfig = useStore(store, (s) => s.updateTrialConfig);
3061
- const updateTrialView = useStore(store, (s) => s.updateTrialView);
3062
- const updateTrialUndoStack = useStore(store, (s) => s.updateTrialUndoStack);
3063
- const updateTrialAnnotations = useStore(store, (s) => s.updateTrialAnnotations);
3064
- const labToolId = useStore(store, (s) => s.activeToolId);
3065
- const setLabTool = useStore(store, (s) => s.setLabTool);
3066
- const setTrialTool = useStore(store, (s) => s.setTrialTool);
1525
+ const updateTrialState = useStore(store, (s5) => s5.updateTrialState);
1526
+ const updateTrialConfig = useStore(store, (s5) => s5.updateTrialConfig);
1527
+ const updateTrialView = useStore(store, (s5) => s5.updateTrialView);
1528
+ const updateTrialUndoStack = useStore(store, (s5) => s5.updateTrialUndoStack);
1529
+ const updateTrialAnnotations = useStore(store, (s5) => s5.updateTrialAnnotations);
1530
+ const labToolId = useStore(store, (s5) => s5.activeToolId);
1531
+ const setLabTool = useStore(store, (s5) => s5.setLabTool);
1532
+ const setTrialTool = useStore(store, (s5) => s5.setTrialTool);
1533
+ const schema = useConfigSchema(instrument);
1534
+ const { config } = useResolvedConfig(schema, record.config, record.auto);
3067
1535
  const busRef = useRef(null);
3068
1536
  if (busRef.current === null) busRef.current = createEventBus();
3069
1537
  const bus = busRef.current;
@@ -3071,7 +1539,7 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
3071
1539
  const [layerOrder, setLayerOrder] = useState(null);
3072
1540
  const [loupeOn, setLoupeOn] = useState(false);
3073
1541
  const visibleLayers = useMemo(
3074
- () => (instrument.canvas?.layers ?? []).filter((l) => layerVisibility[l.id] !== false).map((l) => l.id),
1542
+ () => (instrument.canvas?.layers ?? []).filter((l9) => layerVisibility[l9.id] !== false).map((l9) => l9.id),
3075
1543
  [instrument.canvas, layerVisibility]
3076
1544
  );
3077
1545
  const undoCap = instrument.undo;
@@ -3084,25 +1552,28 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
3084
1552
  const snap = structuredClone(current.state);
3085
1553
  updateTrialUndoStack(record.id, (prev) => pushSnapshot(prev, snap, maxDepth));
3086
1554
  };
3087
- const setView = (v) => updateTrialView(record.id, v);
1555
+ const setView = (v2) => updateTrialView(record.id, v2);
1556
+ const stage = instrument.canvas ? void 0 : instrument.stage;
3088
1557
  const placeView = (size) => {
3089
- const declared = instrument.canvas?.initialView;
1558
+ const declared = instrument.canvas?.initialView ?? (stage ? stage.initialView ?? ((vp) => fitStage(stage.size, vp)) : void 0);
3090
1559
  if (typeof declared !== "function") return;
3091
1560
  if (record.view != null) return;
3092
1561
  setView(declared(size));
3093
1562
  };
3094
1563
  const annotationsCap = instrument.annotations;
3095
1564
  const targetsRef = useRef(() => []);
3096
- targetsRef.current = () => annotationsCap ? annotationsCap.targets(record.state, record.config, { id: record.id, view: record.view }) : [];
1565
+ targetsRef.current = () => annotationsCap ? annotationsCap.targets(record.state, config, { id: record.id, view: record.view }) : [];
3097
1566
  const annotationsRef = useRef(null);
3098
- const configRef = useRef(record.config);
3099
- configRef.current = record.config;
1567
+ const configRef = useRef(config);
1568
+ configRef.current = config;
1569
+ const capRef = useRef(annotationsCap);
1570
+ capRef.current = annotationsCap;
3100
1571
  if (annotationsRef.current === null) {
3101
- const kept = annotationsCap?.storage ? annotationsCap.storage.load() : record.annotations;
1572
+ const kept = annotationsCap?.storage ? keptMarks : record.annotations;
3102
1573
  annotationsRef.current = annotationsFromJSON(kept, () => targetsRef.current(), {
3103
- meaning: annotationsCap?.meaning,
1574
+ meaning: () => capRef.current?.meaning,
3104
1575
  config: () => configRef.current,
3105
- onCapture: (result) => annotationsCap?.onCapture?.(result)
1576
+ onCapture: (result) => capRef.current?.onCapture?.(result)
3106
1577
  });
3107
1578
  }
3108
1579
  const annotations = annotationsRef.current;
@@ -3146,8 +1617,9 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
3146
1617
  flush();
3147
1618
  };
3148
1619
  }, [annotations, annotationsCap]);
3149
- const declaresTools = instrument.tools != null || instrument.annotations != null;
3150
- const resolvedToolId = declaresTools ? record.activeToolId ?? instrument.tools?.initial ?? instrument.tools?.tools[0]?.id ?? (instrument.annotations ? "select" : null) : labToolId;
1620
+ const declaresTools = instrument.tools != null;
1621
+ const labTool = resolveLabTool(labToolId, instruments);
1622
+ const resolvedToolId = declaresTools ? record.activeToolId ?? instrument.tools?.initial ?? instrument.tools?.tools[0]?.id ?? null : labTool;
3151
1623
  const setActiveTool = (id) => {
3152
1624
  if (declaresTools) setTrialTool(record.id, id);
3153
1625
  else setLabTool(id);
@@ -3157,13 +1629,13 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
3157
1629
  const job = useJob({
3158
1630
  capability: jobCap ?? { run: async function* () {
3159
1631
  }, onItem: (_i, st) => st },
3160
- config: record.config,
1632
+ config,
3161
1633
  state: record.state,
3162
1634
  setState: (next) => updateTrialState(record.id, next)
3163
1635
  });
3164
1636
  const renderCtx = {
3165
1637
  state: record.state,
3166
- config: record.config,
1638
+ config,
3167
1639
  setState: (next) => {
3168
1640
  snapshotIfNeeded("state.change");
3169
1641
  updateTrialState(record.id, next);
@@ -3184,7 +1656,7 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
3184
1656
  zoom: view2d?.zoom ?? 1,
3185
1657
  setZoom: (z2) => {
3186
1658
  if (!view2d) return;
3187
- updateTrialView(record.id, { ...view2d, zoom: z2 });
1659
+ updateTrialView(record.id, withZoom(view2d, z2));
3188
1660
  },
3189
1661
  activeToolId: resolvedToolId,
3190
1662
  visibleLayers
@@ -3222,33 +1694,33 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
3222
1694
  const loupeCap = useMemo(() => {
3223
1695
  const declared = instrument.loupe;
3224
1696
  if (declared == null) return null;
3225
- return resolveLoupe(typeof declared === "function" ? declared(record.config) : declared);
3226
- }, [instrument.loupe, record.config]);
3227
- const loupeBindings = loupeCap ? { on: loupeOn, toggle: () => setLoupeOn((v) => !v) } : void 0;
1697
+ return resolveLoupe(typeof declared === "function" ? declared(config) : declared);
1698
+ }, [instrument.loupe, config]);
1699
+ const loupeBindings = loupeCap ? { on: loupeOn, toggle: () => setLoupeOn((v2) => !v2) } : void 0;
3228
1700
  const canvasLayers = useMemo(() => {
3229
1701
  if (!instrument.canvas) return [];
3230
1702
  const baseLayers = instrument.canvas.layers;
3231
- const ordered = layerOrder ? [...baseLayers].sort((a, b) => layerOrder.indexOf(a.id) - layerOrder.indexOf(b.id)) : baseLayers;
1703
+ const ordered = layerOrder ? [...baseLayers].sort((a4, b2) => layerOrder.indexOf(a4.id) - layerOrder.indexOf(b2.id)) : baseLayers;
3232
1704
  return ordered.map((layer) => ({
3233
1705
  id: layer.id,
3234
1706
  visible: layerVisibility[layer.id] !== false,
3235
1707
  render: (ctx, view, frame) => {
3236
1708
  applyCamera(ctx, view, frame);
3237
- layer.draw(ctx, { state: record.state, config: record.config, zoom: view.zoom });
1709
+ layer.draw(ctx, { state: record.state, config, zoom: view.zoom });
3238
1710
  }
3239
1711
  }));
3240
- }, [instrument.canvas, record.state, record.config, layerVisibility, layerOrder]);
1712
+ }, [instrument.canvas, record.state, config, layerVisibility, layerOrder]);
3241
1713
  const layerDescriptors = useMemo(() => {
3242
1714
  if (!instrument.layers) return [];
3243
- return instrument.layers.ids.map((l) => typeof l === "string" ? { id: l, label: l } : l);
1715
+ return instrument.layers.ids.map((l9) => typeof l9 === "string" ? { id: l9, label: l9 } : l9);
3244
1716
  }, [instrument.layers]);
3245
1717
  const dragDropResult = useDragDrop({
3246
- capability: instrument.dragDrop ?? { palette: [], onDrop: (_p, _i, s) => s },
1718
+ capability: instrument.dragDrop ?? { palette: [], onDrop: (_p, _i, s5) => s5 },
3247
1719
  canvasContainerRef,
3248
1720
  view: view2d ?? DEFAULT_VIEW,
3249
1721
  worldSpec: instrument.canvas?.worldSpec,
3250
1722
  state: record.state,
3251
- config: record.config,
1723
+ config,
3252
1724
  setState: (next) => {
3253
1725
  snapshotIfNeeded("canvas.itemAdded");
3254
1726
  updateTrialState(record.id, next);
@@ -3260,9 +1732,9 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
3260
1732
  });
3261
1733
  const paletteItems = useMemo(() => {
3262
1734
  if (!instrument.dragDrop) return [];
3263
- const p = instrument.dragDrop.palette;
3264
- return typeof p === "function" ? p(record.state, record.config) : p;
3265
- }, [instrument.dragDrop, record.state, record.config]);
1735
+ const p2 = instrument.dragDrop.palette;
1736
+ return typeof p2 === "function" ? p2(record.state, config) : p2;
1737
+ }, [instrument.dragDrop, record.state, config]);
3266
1738
  const layersWithFeedback = useMemo(() => {
3267
1739
  if (!dragDropResult.drag?.feedback) return canvasLayers;
3268
1740
  const fb = dragDropResult.drag.feedback;
@@ -3275,12 +1747,12 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
3275
1747
  render: (ctx) => {
3276
1748
  const el = canvasContainerRef.current;
3277
1749
  if (!el) return;
3278
- const r2 = el.getBoundingClientRect();
1750
+ const r10 = el.getBoundingClientRect();
3279
1751
  ctx.save();
3280
1752
  ctx.strokeStyle = fb.ok ? "#3a7" : "#c44";
3281
1753
  ctx.lineWidth = 2;
3282
1754
  ctx.beginPath();
3283
- ctx.arc(screen.x - r2.left, screen.y - r2.top, 16, 0, Math.PI * 2);
1755
+ ctx.arc(screen.x - r10.left, screen.y - r10.top, 16, 0, Math.PI * 2);
3284
1756
  ctx.stroke();
3285
1757
  ctx.restore();
3286
1758
  }
@@ -3293,7 +1765,7 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
3293
1765
  capability: loupeCap,
3294
1766
  enabled: loupeOn,
3295
1767
  state: record.state,
3296
- config: record.config,
1768
+ config,
3297
1769
  view: view2d ?? DEFAULT_VIEW,
3298
1770
  worldSpec: instrument.canvas?.worldSpec,
3299
1771
  hostRef: loupeHostRef
@@ -3320,6 +1792,21 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
3320
1792
  ),
3321
1793
  /* @__PURE__ */ jsx(DragOverlay, { drag: dragDropResult.drag })
3322
1794
  ] });
1795
+ } else if (stage) {
1796
+ body = /* @__PURE__ */ jsx(
1797
+ Stage,
1798
+ {
1799
+ size: stage.size,
1800
+ view: view2d ?? DEFAULT_VIEW,
1801
+ onViewChange: setView,
1802
+ onResize: placeView,
1803
+ minZoom: stage.minZoom,
1804
+ maxZoom: stage.maxZoom,
1805
+ hostRef: loupeHostRef,
1806
+ overlay: lens,
1807
+ children: instrument.render(renderCtx)
1808
+ }
1809
+ );
3323
1810
  } else if (lens) {
3324
1811
  body = /* @__PURE__ */ jsxs("div", { ref: loupeHostRef, className: "lk-trial__loupe-host", children: [
3325
1812
  instrument.render(renderCtx),
@@ -3340,7 +1827,7 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
3340
1827
  layers: layerDescriptors,
3341
1828
  visibility: layerVisibility,
3342
1829
  onReorder: (next) => {
3343
- setLayerOrder(next.map((l) => l.id));
1830
+ setLayerOrder(next.map((l9) => l9.id));
3344
1831
  bus.emit("layers.reorder");
3345
1832
  },
3346
1833
  onToggle: (lid, visible) => {
@@ -3374,13 +1861,13 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
3374
1861
  {
3375
1862
  capability: annotationsCap,
3376
1863
  state: record.state,
3377
- config: record.config,
1864
+ config,
3378
1865
  trial: { id: record.id, view: record.view },
3379
1866
  annotations,
3380
- activeToolId: resolvedToolId
1867
+ activeToolId: labTool
3381
1868
  }
3382
1869
  ) : null;
3383
- return /* @__PURE__ */ jsx(TrialIdProvider, { trialId: record.id, children: /* @__PURE__ */ jsx(AnnotationsContext.Provider, { value: annotationsCap ? annotations : null, children: /* @__PURE__ */ jsxs(
1870
+ return /* @__PURE__ */ jsx(TrialIdProvider, { trialId: record.id, children: /* @__PURE__ */ jsx(AnnotationsContext.Provider, { value: annotationsCap ? annotations : null, children: /* @__PURE__ */ jsx(CameraWheelContext.Provider, { value: wheelSlot, children: /* @__PURE__ */ jsxs(
3384
1871
  TrialChrome,
3385
1872
  {
3386
1873
  job: jobCap ? job : void 0,
@@ -3400,29 +1887,53 @@ function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
3400
1887
  annotationOverlays
3401
1888
  ]
3402
1889
  }
3403
- ) }) });
1890
+ ) }) }) });
3404
1891
  }
3405
1892
 
3406
1893
  // src/trial/trialOps.ts
3407
1894
  var DEFAULT_VIEW2 = { zoom: 1, pan: { x: 0, y: 0 } };
3408
1895
  function findInstrument(instruments, name) {
3409
- const found = instruments.find((i) => i.name === name);
1896
+ const found = instruments.find((i3) => i3.name === name);
3410
1897
  if (!found) {
3411
1898
  throw new Error(`[labkit] Unknown instrument: "${name}"`);
3412
1899
  }
3413
1900
  return found;
3414
1901
  }
3415
1902
  function initialView(instrument) {
3416
- const declared = instrument.canvas?.initialView;
3417
- if (typeof declared === "function") return null;
1903
+ const stage = instrument.canvas ? void 0 : instrument.stage;
1904
+ const declared = instrument.canvas?.initialView ?? stage?.initialView;
1905
+ if (typeof declared === "function" || stage && declared === void 0) return null;
3418
1906
  return declared ? structuredClone(declared) : { ...DEFAULT_VIEW2, pan: { ...DEFAULT_VIEW2.pan } };
3419
1907
  }
3420
1908
  function seedConfig(defaults, seed) {
3421
1909
  return seed ? fillConfigDefaults(seed, defaults) : defaults;
3422
1910
  }
1911
+ function splitAutoSeed(seed, at = "") {
1912
+ const config = {};
1913
+ const autoPaths = [];
1914
+ for (const [key, value] of Object.entries(seed ?? {})) {
1915
+ const path = at === "" ? key : `${at}.${key}`;
1916
+ if (isAuto(value)) {
1917
+ autoPaths.push(path);
1918
+ } else if (isRecord(value)) {
1919
+ const inner = splitAutoSeed(value, path);
1920
+ config[key] = inner.config;
1921
+ autoPaths.push(...inner.autoPaths);
1922
+ } else {
1923
+ config[key] = value;
1924
+ }
1925
+ }
1926
+ return { config, autoPaths };
1927
+ }
1928
+ function schemaAutoPaths(instrument) {
1929
+ const schema = unruledConfigSchema(instrument);
1930
+ return schema ? autoPathsOf(schema) : [];
1931
+ }
3423
1932
  function addTrial(trials, instruments, instrumentName, options = {}) {
3424
1933
  const instrument = findInstrument(instruments, instrumentName);
3425
- const config = seedConfig(instrument.defaultConfig(), options.config);
1934
+ const seeded = splitAutoSeed(options.config);
1935
+ const autoPaths = [.../* @__PURE__ */ new Set([...schemaAutoPaths(instrument), ...seeded.autoPaths])];
1936
+ const config = seedConfig(instrument.defaultConfig(), seeded.config);
3426
1937
  const state = instrument.initialState(config);
3427
1938
  const record = {
3428
1939
  id: newId(),
@@ -3432,7 +1943,9 @@ function addTrial(trials, instruments, instrumentName, options = {}) {
3432
1943
  view: initialView(instrument),
3433
1944
  undoStack: { past: [], future: [] }
3434
1945
  };
3435
- if (options.config) record.configSeed = options.config;
1946
+ if (options.config) record.configSeed = seeded.config;
1947
+ if (seeded.autoPaths.length > 0) record.autoSeed = seeded.autoPaths;
1948
+ if (autoPaths.length > 0) record.auto = autoPaths;
3436
1949
  return [...trials, record];
3437
1950
  }
3438
1951
  function cloneTrial(trials, id) {
@@ -3461,14 +1974,25 @@ function resetTrial(trials, id, instruments) {
3461
1974
  const instrument = findInstrument(instruments, current.instrumentName);
3462
1975
  const config = seedConfig(instrument.defaultConfig(), current.configSeed);
3463
1976
  const state = instrument.initialState(config);
1977
+ const autoPaths = [.../* @__PURE__ */ new Set([...schemaAutoPaths(instrument), ...current.autoSeed ?? []])];
1978
+ const { auto: _unpinnedNow, ...kept } = current;
3464
1979
  const reset = {
3465
- ...current,
1980
+ ...kept,
3466
1981
  config,
3467
1982
  state,
3468
1983
  view: initialView(instrument)
3469
1984
  };
1985
+ if (autoPaths.length > 0) reset.auto = autoPaths;
3470
1986
  return [...trials.slice(0, idx), reset, ...trials.slice(idx + 1)];
3471
1987
  }
1988
+ function swapTrial(trials, instruments, id, instrumentName, options = {}) {
1989
+ const idx = trials.findIndex((w3) => w3.id === id);
1990
+ const current = trials[idx];
1991
+ if (!current) return trials;
1992
+ const [record] = addTrial([], instruments, instrumentName, options);
1993
+ if (current.sidebarWidth !== void 0) record.sidebarWidth = current.sidebarWidth;
1994
+ return [...trials.slice(0, idx), record, ...trials.slice(idx + 1)];
1995
+ }
3472
1996
  function reorderTrials(trials, ids) {
3473
1997
  const byId = new Map(trials.map((w3) => [w3.id, w3]));
3474
1998
  const named = ids.map((id) => byId.get(id)).filter((w3) => w3 !== void 0);
@@ -3476,6 +2000,86 @@ function reorderTrials(trials, ids) {
3476
2000
  const rest = trials.filter((w3) => !seen.has(w3.id));
3477
2001
  return [...named, ...rest];
3478
2002
  }
2003
+ function findOvershoot(bound, items, tolerance = 1) {
2004
+ let worst = null;
2005
+ for (const { item, edges } of items) {
2006
+ const y3 = edges.bottom - bound.bottom;
2007
+ const x = edges.right - bound.right;
2008
+ const by = Math.max(x, y3);
2009
+ if (by <= tolerance) continue;
2010
+ if (!worst || by > worst.by) worst = { item, by, axis: y3 >= x ? "y" : "x" };
2011
+ }
2012
+ return worst;
2013
+ }
2014
+ function tagAndClasses(el) {
2015
+ return el.tagName.toLowerCase() + [...el.classList].map((c8) => `.${c8}`).join("");
2016
+ }
2017
+ function describeElement(el) {
2018
+ if (el.classList.length > 0) return tagAndClasses(el);
2019
+ let up = el.parentElement;
2020
+ while (up && up.classList.length === 0) up = up.parentElement;
2021
+ return up ? `${tagAndClasses(el)} in ${tagAndClasses(up)}` : tagAndClasses(el);
2022
+ }
2023
+ function edgesOf(el) {
2024
+ const r10 = el.getBoundingClientRect();
2025
+ return { top: r10.top, left: r10.left, bottom: r10.bottom, right: r10.right };
2026
+ }
2027
+ function* candidates(root) {
2028
+ yield { item: root, edges: edgesOf(root) };
2029
+ for (const el of root.querySelectorAll("*")) yield { item: el, edges: edgesOf(el) };
2030
+ }
2031
+ function overflows(el) {
2032
+ return el.scrollHeight > el.clientHeight + 1 || el.scrollWidth > el.clientWidth + 1;
2033
+ }
2034
+ function explainLabScroll(lab, shellBody) {
2035
+ if (overflows(shellBody)) {
2036
+ const r11 = shellBody.getBoundingClientRect();
2037
+ const bound2 = {
2038
+ top: r11.top,
2039
+ left: r11.left,
2040
+ bottom: r11.top + shellBody.clientHeight,
2041
+ right: r11.left + shellBody.clientWidth
2042
+ };
2043
+ const hit2 = findOvershoot(bound2, candidates(shellBody));
2044
+ return hit2 ? `.lk-shell-body scrolls: ${describeElement(hit2.item)} reaches ${Math.round(hit2.by)}px past it (${hit2.axis})` : ".lk-shell-body scrolls";
2045
+ }
2046
+ const doc = document.scrollingElement ?? document.documentElement;
2047
+ if (!overflows(doc)) return null;
2048
+ const r10 = lab.getBoundingClientRect();
2049
+ const bound = {
2050
+ top: r10.top,
2051
+ left: r10.left,
2052
+ bottom: r10.top + Math.min(r10.height, doc.clientHeight),
2053
+ right: r10.left + Math.min(r10.width, doc.clientWidth)
2054
+ };
2055
+ const hit = findOvershoot(bound, candidates(lab));
2056
+ return hit ? `the page scrolls: ${describeElement(hit.item)} reaches ${Math.round(hit.by)}px past the lab's box (${hit.axis})` : null;
2057
+ }
2058
+ var SETTLE_MS = 300;
2059
+ function useLabFitWarning(labBody) {
2060
+ useEffect(() => {
2061
+ if (process.env.NODE_ENV === "production") return;
2062
+ if (!labBody || typeof ResizeObserver === "undefined") return;
2063
+ const lab = labBody.closest(".lk-lab");
2064
+ const shellBody = labBody.closest(".lk-shell-body");
2065
+ if (!lab || !shellBody) return;
2066
+ let timer;
2067
+ const observer = new ResizeObserver(() => {
2068
+ clearTimeout(timer);
2069
+ timer = setTimeout(() => {
2070
+ const why = explainLabScroll(lab, shellBody);
2071
+ if (!why) return;
2072
+ observer.disconnect();
2073
+ console.warn(`[labkit] <Lab> should fit its container without scrolling, but ${why}.`);
2074
+ }, SETTLE_MS);
2075
+ });
2076
+ for (const el of [document.documentElement, lab, shellBody, labBody]) observer.observe(el);
2077
+ return () => {
2078
+ clearTimeout(timer);
2079
+ observer.disconnect();
2080
+ };
2081
+ }, [labBody]);
2082
+ }
3479
2083
  var MODES = [
3480
2084
  { value: "auto", label: "Auto", glyph: /* @__PURE__ */ jsx(E, { size: 14 }) },
3481
2085
  { value: "light", label: "Light", glyph: /* @__PURE__ */ jsx(w, { size: 14 }) },
@@ -3486,11 +2090,11 @@ var MODE_ITEMS = MODES.map(({ value, label, glyph }) => ({
3486
2090
  label: glyph,
3487
2091
  ariaLabel: label
3488
2092
  }));
3489
- function LabHeader() {
2093
+ function LabHeader({ addTrial: addTrial2 = true }) {
3490
2094
  const lab = useLabContext();
3491
2095
  const only = lab.instruments.length === 1 ? lab.instruments[0] : null;
3492
2096
  return /* @__PURE__ */ jsxs(Fragment, { children: [
3493
- only ? /* @__PURE__ */ jsxs(
2097
+ !addTrial2 ? null : only ? /* @__PURE__ */ jsxs(
3494
2098
  "button",
3495
2099
  {
3496
2100
  type: "button",
@@ -3501,29 +2105,20 @@ function LabHeader() {
3501
2105
  /* @__PURE__ */ jsx("span", { children: "Add trial" })
3502
2106
  ]
3503
2107
  }
3504
- ) : (
3505
- // Held at null so the control stays an "add one" action rather than
3506
- // reading as the current instrument.
3507
- /* @__PURE__ */ jsx(
3508
- g,
3509
- {
3510
- className: "lk-lab-header__add-select",
3511
- "aria-label": "Add trial",
3512
- placeholder: "Add trial\u2026",
3513
- selectedKey: null,
3514
- options: lab.instruments.map((i) => ({ value: i.name, label: i.name })),
3515
- onSelectionChange: (name) => {
3516
- if (name != null) lab.addTrial(String(name));
3517
- }
3518
- }
3519
- )
2108
+ ) : /* @__PURE__ */ jsx(
2109
+ l7,
2110
+ {
2111
+ "aria-label": "Add trial",
2112
+ label: "Add trial\u2026",
2113
+ items: lab.instruments.map((i3) => ({ value: i3.name, label: i3.title ?? i3.name })),
2114
+ onAction: (name) => lab.addTrial(name)
2115
+ }
3520
2116
  ),
3521
2117
  /* @__PURE__ */ jsx(
3522
- r6,
2118
+ r3,
3523
2119
  {
3524
2120
  className: "lk-lab-header__mode",
3525
2121
  ariaLabel: "Color mode",
3526
- size: "sm",
3527
2122
  variant: "flat",
3528
2123
  items: MODE_ITEMS,
3529
2124
  value: lab.mode,
@@ -3534,32 +2129,47 @@ function LabHeader() {
3534
2129
  )
3535
2130
  ] });
3536
2131
  }
3537
- function LabPalette({ tools }) {
3538
- const storeCtx = useContext(LabStoreContext);
3539
- if (!storeCtx) throw new Error("[labkit] LabPalette requires <LabStoreProvider>");
3540
- const activeToolId = useStore(storeCtx.store, (s) => s.activeToolId);
3541
- const setLabTool = useStore(storeCtx.store, (s) => s.setLabTool);
3542
- const contributions = tools.map((t) => ({
3543
- id: t.id,
3544
- region: "palette",
3545
- group: t.group,
3546
- item: { icon: t.icon, label: t.label, shortcut: t.shortcut }
3547
- }));
3548
- const ctx = {
3549
- activeToolId,
3550
- setActiveTool: setLabTool
3551
- };
3552
- return /* @__PURE__ */ jsx(PaletteRegion, { contributions, ctx });
2132
+ function LabPalette({ tools, contributions }) {
2133
+ const ctx = useLabChromeContext();
2134
+ const all = labContributions(tools, contributions);
2135
+ return /* @__PURE__ */ jsx(PaletteRegion, { contributions: all.filter((c8) => c8.region === "palette"), ctx });
3553
2136
  }
3554
- function currentPage(path, pages) {
3555
- const here = path.split("?")[0].split("#")[0].replace(/\/$/, "").replace(/\.html$/, "");
3556
- return pages.findIndex((p) => here.endsWith(p.href.replace(/\.html$/, "")));
2137
+ function query() {
2138
+ if (typeof window === "undefined" || !window.matchMedia) return null;
2139
+ return window.matchMedia("(prefers-color-scheme: light)");
3557
2140
  }
3558
- function LabSwitcher({ title, pages, path, className }) {
2141
+ function useResolvedMode(mode) {
2142
+ const [system, setSystem] = useState(
2143
+ () => query()?.matches ? "light" : "dark"
2144
+ );
2145
+ useEffect(() => {
2146
+ if (mode !== "auto") return;
2147
+ const mq = query();
2148
+ if (!mq) return;
2149
+ const onChange = (e) => setSystem(e.matches ? "light" : "dark");
2150
+ mq.addEventListener("change", onChange);
2151
+ return () => mq.removeEventListener("change", onChange);
2152
+ }, [mode]);
2153
+ return mode === "auto" ? system : mode;
2154
+ }
2155
+ function LabRoot({ children, mode = "auto", className }) {
2156
+ const resolved = useResolvedMode(mode);
2157
+ const outer = useThemeOptional();
2158
+ const root = /* @__PURE__ */ jsx("div", { className: className ? `lk-root ${className}` : "lk-root", "data-wzl-portal-host": "", children });
2159
+ return outer ? root : /* @__PURE__ */ jsx(ThemeProvider, { theme: interstellarTheme, selection: { mode: resolved }, children: root });
2160
+ }
2161
+ function currentPage(path, pages) {
2162
+ const here = path.replace(/[?#].*$/, "").replace(/\/$/, "").replace(/\.html$/, "");
2163
+ return pages.findIndex((p2) => here.endsWith(p2.href.replace(/\.html$/, "")));
2164
+ }
2165
+ function LabSwitcher({ title, pages, path, className }) {
3559
2166
  const [open, setOpen] = useState(false);
3560
2167
  const root = useRef(null);
3561
2168
  const menuId = useId();
3562
- const here = currentPage(path ?? (typeof window === "undefined" ? "" : window.location.pathname), pages);
2169
+ const here = currentPage(
2170
+ path ?? (typeof window === "undefined" ? "" : window.location.pathname),
2171
+ pages
2172
+ );
3563
2173
  const close = useCallback(() => setOpen(false), []);
3564
2174
  useEffect(() => {
3565
2175
  if (!open) return;
@@ -3598,13 +2208,13 @@ function LabSwitcher({ title, pages, path, className }) {
3598
2208
  ]
3599
2209
  }
3600
2210
  ) }),
3601
- open && /* @__PURE__ */ jsx("div", { className: "lk-switcher-menu", id: menuId, role: "menu", children: pages.map((page, i) => /* @__PURE__ */ jsx(
2211
+ open && /* @__PURE__ */ jsx("div", { className: "lk-switcher-menu", id: menuId, role: "menu", children: pages.map((page, i3) => /* @__PURE__ */ jsx(
3602
2212
  "a",
3603
2213
  {
3604
2214
  href: page.href,
3605
2215
  role: "menuitem",
3606
2216
  className: "lk-switcher-item",
3607
- "aria-current": i === here ? "page" : void 0,
2217
+ "aria-current": i3 === here ? "page" : void 0,
3608
2218
  onClick: close,
3609
2219
  children: page.label
3610
2220
  },
@@ -3612,24 +2222,6 @@ function LabSwitcher({ title, pages, path, className }) {
3612
2222
  )) })
3613
2223
  ] });
3614
2224
  }
3615
- function query() {
3616
- if (typeof window === "undefined" || !window.matchMedia) return null;
3617
- return window.matchMedia("(prefers-color-scheme: light)");
3618
- }
3619
- function useResolvedMode(mode) {
3620
- const [system, setSystem] = useState(
3621
- () => query()?.matches ? "light" : "dark"
3622
- );
3623
- useEffect(() => {
3624
- if (mode !== "auto") return;
3625
- const mq = query();
3626
- if (!mq) return;
3627
- const onChange = (e) => setSystem(e.matches ? "light" : "dark");
3628
- mq.addEventListener("change", onChange);
3629
- return () => mq.removeEventListener("change", onChange);
3630
- }, [mode]);
3631
- return mode === "auto" ? system : mode;
3632
- }
3633
2225
  function LabShell({
3634
2226
  title,
3635
2227
  children,
@@ -3639,41 +2231,26 @@ function LabShell({
3639
2231
  pages,
3640
2232
  path
3641
2233
  }) {
3642
- const resolved = useResolvedMode(mode);
3643
- const outer = useThemeOptional();
3644
- const shell = (
3645
- // Overlays portal here rather than to the themed wrapper above: `.lk-root`
3646
- // carries the element defaults a lab's bare markup is styled by.
3647
- /* @__PURE__ */ jsxs("div", { className: "lk-root lk-shell", "data-wzl-portal-host": "", children: [
3648
- /* @__PURE__ */ jsxs("header", { className: "lk-shell-header", children: [
3649
- pages && pages.length > 0 ? /* @__PURE__ */ jsx(
3650
- LabSwitcher,
3651
- {
3652
- title,
3653
- pages,
3654
- path,
3655
- className: "lk-shell-title"
3656
- }
3657
- ) : /* @__PURE__ */ jsx("h1", { className: "lk-shell-title", children: title }),
3658
- header && /* @__PURE__ */ jsx("div", { className: "lk-shell-header-actions", children: header })
3659
- ] }),
3660
- /* @__PURE__ */ jsx("main", { className: "lk-shell-body", children }),
3661
- footer && /* @__PURE__ */ jsx("footer", { className: "lk-shell-footer", children: footer })
3662
- ] })
3663
- );
3664
- return outer ? shell : /* @__PURE__ */ jsx(ThemeProvider, { theme: interstellarTheme, mode: resolved, children: shell });
2234
+ return /* @__PURE__ */ jsxs(LabRoot, { mode, className: "lk-shell", children: [
2235
+ /* @__PURE__ */ jsxs("header", { className: "lk-shell-header", children: [
2236
+ pages && pages.length > 0 ? /* @__PURE__ */ jsx(LabSwitcher, { title, pages, path, className: "lk-shell-title" }) : /* @__PURE__ */ jsx("h1", { className: "lk-shell-title", children: title }),
2237
+ header && /* @__PURE__ */ jsx("div", { className: "lk-shell-header-actions", children: header })
2238
+ ] }),
2239
+ /* @__PURE__ */ jsx("main", { className: "lk-shell-body", children }),
2240
+ footer && /* @__PURE__ */ jsx("footer", { className: "lk-shell-footer", children: footer })
2241
+ ] });
3665
2242
  }
3666
- var ZONE_ID2 = asNodeId("lk-workspace");
3667
- var STRATEGIES2 = { grid: gridStrategy, floating: floatingStrategy };
2243
+ var ZONE_ID = asNodeId("lk-workspace");
2244
+ var STRATEGIES = { grid: gridStrategy, floating: floatingStrategy };
3668
2245
  var FLOAT_ZONE_ID = asNodeId("lk-workspace-floating");
3669
2246
  var PANEL_KIND = "panel";
3670
2247
  var KIND = "trial";
3671
2248
  function extentOf(store, id) {
3672
- const p = store.getNode(id)?.membership?.placement;
3673
- if (!p) return null;
2249
+ const p2 = store.getNode(id)?.membership?.placement;
2250
+ if (!p2) return null;
3674
2251
  const out = {};
3675
- if (p.size) out.size = p.size;
3676
- if (p.span) out.span = p.span;
2252
+ if (p2.size) out.size = p2.size;
2253
+ if (p2.span) out.span = p2.span;
3677
2254
  return Object.keys(out).length > 0 ? out : null;
3678
2255
  }
3679
2256
  function PanelFrame({ panel, floating = false }) {
@@ -3694,11 +2271,11 @@ function Workspace({
3694
2271
  viewport
3695
2272
  }) {
3696
2273
  const items = Children.toArray(children);
3697
- const tilePanels = useMemo(() => (panels ?? []).filter((p) => p.as === "tile"), [panels]);
3698
- const floatPanels = useMemo(() => (panels ?? []).filter((p) => p.as === "floating"), [panels]);
2274
+ const tilePanels = useMemo(() => (panels ?? []).filter((p2) => p2.as === "tile"), [panels]);
2275
+ const floatPanels = useMemo(() => (panels ?? []).filter((p2) => p2.as === "floating"), [panels]);
3699
2276
  const wantedIds = [
3700
- ...items.map((_, i) => ids?.[i] ?? `lk-ws-${i}`),
3701
- ...tilePanels.map((p) => `lk-panel-${p.key}`)
2277
+ ...items.map((_, i3) => ids?.[i3] ?? `lk-ws-${i3}`),
2278
+ ...tilePanels.map((p2) => `lk-panel-${p2.key}`)
3702
2279
  ];
3703
2280
  const idKey = wantedIds.join(",");
3704
2281
  const idKeyRef = useRef(null);
@@ -3718,7 +2295,7 @@ function Workspace({
3718
2295
  store2.registerNode(
3719
2296
  createNode({
3720
2297
  kind: "zone",
3721
- id: ZONE_ID2,
2298
+ id: ZONE_ID,
3722
2299
  container: { strategyId: "grid", config: { resizable, gap, padding } }
3723
2300
  })
3724
2301
  );
@@ -3734,10 +2311,10 @@ function Workspace({
3734
2311
  const store = storeRef.current;
3735
2312
  const surface = useSurfaceOptional();
3736
2313
  useLayoutEffect(() => {
3737
- store.updateContainerConfig(ZONE_ID2, { resizable, gap, padding });
2314
+ store.updateContainerConfig(ZONE_ID, { resizable, gap, padding });
3738
2315
  }, [store, resizable, gap, padding]);
3739
2316
  useLayoutEffect(() => {
3740
- const present = new Set(store.getContainerView(ZONE_ID2)?.childOrder ?? []);
2317
+ const present = new Set(store.getContainerView(ZONE_ID)?.childOrder ?? []);
3741
2318
  const wanted = new Set(nodeIds);
3742
2319
  for (const id of present) {
3743
2320
  if (!wanted.has(id)) store.unregisterNode(id);
@@ -3745,12 +2322,12 @@ function Workspace({
3745
2322
  for (const id of nodeIds) {
3746
2323
  if (present.has(id)) continue;
3747
2324
  const kind = String(id).startsWith("lk-panel-") ? PANEL_KIND : KIND;
3748
- store.registerNode(createNode({ kind, id, parentId: ZONE_ID2, focus: true }));
2325
+ store.registerNode(createNode({ kind, id, parentId: ZONE_ID, focus: true }));
3749
2326
  store.showNode(id);
3750
2327
  const saved = layoutRef.current?.[id];
3751
2328
  if (saved) store.patchPlacement(id, saved);
3752
2329
  }
3753
- store.setChildOrder(ZONE_ID2, [...nodeIds]);
2330
+ store.setChildOrder(ZONE_ID, [...nodeIds]);
3754
2331
  }, [store, nodeIds]);
3755
2332
  useEffect(() => {
3756
2333
  if (!surface) return;
@@ -3761,7 +2338,7 @@ function Workspace({
3761
2338
  if (!onLayoutChange) return;
3762
2339
  return store.events.on("node.placementChanged", () => {
3763
2340
  const next = {};
3764
- for (const child of store.getChildren(ZONE_ID2)) {
2341
+ for (const child of store.getChildren(ZONE_ID)) {
3765
2342
  const extent = extentOf(store, child.id);
3766
2343
  if (extent) next[child.id] = extent;
3767
2344
  }
@@ -3773,9 +2350,9 @@ function Workspace({
3773
2350
  [onReorder]
3774
2351
  );
3775
2352
  const chrome = useMemo(() => {
3776
- const byId = new Map(nodeIds.map((id, i) => [id, items[i]]));
2353
+ const byId = new Map(nodeIds.map((id, i3) => [id, items[i3]]));
3777
2354
  const panelByNode = new Map(
3778
- tilePanels.map((p) => [`lk-panel-${p.key}`, p])
2355
+ tilePanels.map((p2) => [`lk-panel-${p2.key}`, p2])
3779
2356
  );
3780
2357
  return {
3781
2358
  [PANEL_KIND]: ({ node }) => {
@@ -3793,11 +2370,11 @@ function Workspace({
3793
2370
  }
3794
2371
  };
3795
2372
  }, [nodeIds, items, reorderable, tilePanels]);
3796
- const floatingLayer = floatPanels.length === 0 ? null : /* @__PURE__ */ jsx("div", { className: "lk-workspace__floating", children: floatPanels.map((p) => /* @__PURE__ */ jsx(PanelFrame, { panel: p, floating: true }, p.key)) });
2373
+ const floatingLayer = floatPanels.length === 0 ? null : /* @__PURE__ */ jsx("div", { className: "lk-workspace__floating", children: floatPanels.map((p2) => /* @__PURE__ */ jsx(PanelFrame, { panel: p2, floating: true }, p2.key)) });
3797
2374
  const grid = /* @__PURE__ */ jsx(
3798
2375
  Container,
3799
2376
  {
3800
- parentId: ZONE_ID2,
2377
+ parentId: ZONE_ID,
3801
2378
  chrome,
3802
2379
  className: "lk-workspace windease-zone",
3803
2380
  affordances: resizable,
@@ -3805,7 +2382,7 @@ function Workspace({
3805
2382
  onChildOrderChange: reorderable ? commitOrder : void 0
3806
2383
  }
3807
2384
  );
3808
- return /* @__PURE__ */ jsx(Provider, { store, children: /* @__PURE__ */ jsx(StrategyRegistryProvider, { strategies: STRATEGIES2, children: floatingLayer ? (
2385
+ return /* @__PURE__ */ jsx(Provider, { store, children: /* @__PURE__ */ jsx(StrategyRegistryProvider, { strategies: STRATEGIES, children: floatingLayer ? (
3809
2386
  // The floating layer insets against the workspace, so it needs a
3810
2387
  // positioned box around both. Without one it insets against the page
3811
2388
  // and lands over the lab header.
@@ -3815,23 +2392,59 @@ function Workspace({
3815
2392
  ] })
3816
2393
  ) : reorderable ? /* @__PURE__ */ jsx(DragProvider, { children: grid }) : grid }) });
3817
2394
  }
3818
- function buildStore(instruments, defaultInstrument, storage, storageKey, initialMode) {
3819
- const store = createLabStore({
2395
+ function seedDefaultTrial(store, instruments, defaultInstrument) {
2396
+ if (store.getState().trials.length > 0) return;
2397
+ const record = addTrial([], instruments, defaultInstrument)[0];
2398
+ if (!record) return;
2399
+ const { undoStack: _undoStack, ...rest } = record;
2400
+ store.getState().addTrial(rest);
2401
+ }
2402
+ function openUnstoredLab({ instruments, defaultInstrument, mode }) {
2403
+ const store = createLabStore({ initialMode: mode ?? "auto", instruments });
2404
+ seedDefaultTrial(store, instruments, defaultInstrument);
2405
+ const records = createRecordCache({ storage: noneAdapter, prefix: "", writable: false });
2406
+ return { store, records, close: () => records.close(), marks: /* @__PURE__ */ new Map() };
2407
+ }
2408
+ async function openStoredLab({ instruments, defaultInstrument, mode }, storageKey, storage) {
2409
+ const opened = await openLabStore({
3820
2410
  storageKey,
3821
- storage,
3822
- initialMode,
3823
- serializers: serializersOf(instruments),
3824
- configDefaults: configDefaultsOf(instruments)
2411
+ storage: storage ?? await defaultStorage(),
2412
+ initialMode: mode ?? "auto",
2413
+ instruments
3825
2414
  });
3826
- if (store.getState().trials.length === 0) {
3827
- const seeded = addTrial([], instruments, defaultInstrument);
3828
- const record = seeded[0];
3829
- if (record) {
3830
- const { undoStack: _undoStack, ...rest } = record;
3831
- store.getState().addTrial(rest);
2415
+ seedDefaultTrial(opened.store, instruments, defaultInstrument);
2416
+ const marks = /* @__PURE__ */ new Map();
2417
+ await Promise.all(
2418
+ opened.store.getState().trials.map(async (trial) => {
2419
+ const kept = instruments.find((i3) => i3.name === trial.instrumentName)?.annotations?.storage;
2420
+ if (!kept) return;
2421
+ try {
2422
+ marks.set(trial.id, await kept.load());
2423
+ } catch (error) {
2424
+ console.warn(`[labkit] could not load the marks of trial "${trial.id}"`, error);
2425
+ marks.set(trial.id, null);
2426
+ }
2427
+ })
2428
+ );
2429
+ return { ...opened, marks };
2430
+ }
2431
+ function LabFallback({
2432
+ title,
2433
+ mode,
2434
+ pages,
2435
+ path
2436
+ }) {
2437
+ const resolvedMode = useResolvedMode(mode ?? "auto");
2438
+ return /* @__PURE__ */ jsx(ThemeProvider, { theme: interstellarTheme, selection: { mode: resolvedMode }, className: "lk-lab", children: /* @__PURE__ */ jsx(
2439
+ LabShell,
2440
+ {
2441
+ title: title ?? "Labkit",
2442
+ mode,
2443
+ ...pages ? { pages } : {},
2444
+ ...path !== void 0 ? { path } : {},
2445
+ children: null
3832
2446
  }
3833
- }
3834
- return store;
2447
+ ) });
3835
2448
  }
3836
2449
  var NEBULA_SLOTS = [
3837
2450
  { cx: "18%", cy: "30%", sx: "60%", sy: "80%", stop: "70%" },
@@ -3841,73 +2454,181 @@ var NEBULA_SLOTS = [
3841
2454
  { cx: "85%", cy: "18%", sx: "55%", sy: "80%", stop: "70%" }
3842
2455
  ];
3843
2456
  function buildNebula(colors) {
3844
- const blobs = colors.map((c2, i) => {
3845
- const p = NEBULA_SLOTS[i % NEBULA_SLOTS.length];
3846
- return `radial-gradient(ellipse ${p.sx} ${p.sy} at ${p.cx} ${p.cy}, color-mix(in srgb, ${c2} 22%, transparent), transparent ${p.stop})`;
2457
+ const blobs = colors.map((c8, i3) => {
2458
+ const p2 = NEBULA_SLOTS[i3 % NEBULA_SLOTS.length];
2459
+ return `radial-gradient(ellipse ${p2.sx} ${p2.sy} at ${p2.cx} ${p2.cy}, color-mix(in srgb, ${c8} 22%, transparent), transparent ${p2.stop})`;
3847
2460
  });
3848
2461
  blobs.push("radial-gradient(ellipse at center, #0a0a18 0%, #02020a 100%)");
3849
2462
  return blobs.join(", ");
3850
2463
  }
3851
- function Lab({
2464
+ function LabPanes({
2465
+ contributions,
2466
+ children
2467
+ }) {
2468
+ const surface = useSurfaceOptional();
2469
+ const [sidebarWidth, setSidebarWidth] = usePersistedState(
2470
+ "lk-lab-sidebar-width",
2471
+ void 0,
2472
+ { scope: "lab" }
2473
+ );
2474
+ const [asideWidth, setAsideWidth] = usePersistedState(
2475
+ "lk-lab-aside-width",
2476
+ void 0,
2477
+ { scope: "lab" }
2478
+ );
2479
+ const main = contributions.some((c8) => c8.region === "aside") ? /* @__PURE__ */ jsx(
2480
+ Split,
2481
+ {
2482
+ className: "lk-lab__panes",
2483
+ side: "end",
2484
+ sidebarClassName: "lk-lab__aside",
2485
+ contentClassName: "lk-lab__main",
2486
+ label: "Lab aside",
2487
+ defaultWidth: 320,
2488
+ width: asideWidth,
2489
+ onWidthChange: (w3) => {
2490
+ setAsideWidth(w3);
2491
+ surface?.invalidateRects();
2492
+ },
2493
+ sidebar: /* @__PURE__ */ jsx(LabAsideRegion, { contributions }),
2494
+ children
2495
+ }
2496
+ ) : children;
2497
+ if (!contributions.some((c8) => c8.region === "sidebar")) return main;
2498
+ return /* @__PURE__ */ jsx(
2499
+ Split,
2500
+ {
2501
+ className: "lk-lab__panes",
2502
+ sidebarClassName: "lk-lab__sidebar",
2503
+ contentClassName: "lk-lab__main",
2504
+ label: "Lab sidebar",
2505
+ defaultWidth: 260,
2506
+ width: sidebarWidth,
2507
+ onWidthChange: (w3) => {
2508
+ setSidebarWidth(w3);
2509
+ surface?.invalidateRects();
2510
+ },
2511
+ sidebar: /* @__PURE__ */ jsx(LabSidebarRegion, { contributions }),
2512
+ children: main
2513
+ }
2514
+ );
2515
+ }
2516
+ function Lab(props) {
2517
+ if (process.env.NODE_ENV !== "production" && props.instruments.length === 0) {
2518
+ throw new Error("[labkit] <Lab> requires a non-empty `instruments` array");
2519
+ }
2520
+ const { storageKey, storage } = props;
2521
+ useWarnIgnoredChange("<Lab>", { storageKey, storage });
2522
+ const opened = useOpenOnce(
2523
+ () => storageKey === void 0 ? openUnstoredLab(props) : openStoredLab(props, storageKey, storage)
2524
+ );
2525
+ if (!opened) {
2526
+ if (props.fallback !== void 0) return props.fallback;
2527
+ return /* @__PURE__ */ jsx(LabFallback, { title: props.title, mode: props.mode, pages: props.pages, path: props.path });
2528
+ }
2529
+ return /* @__PURE__ */ jsx(LabRuntime, { ...props, opened });
2530
+ }
2531
+ function LabRuntime({
3852
2532
  instruments,
3853
- defaultInstrument,
3854
- storage,
3855
- storageKey,
3856
2533
  mode,
3857
2534
  nebula,
3858
2535
  title,
2536
+ pages,
2537
+ path,
3859
2538
  footer,
3860
2539
  chrome,
2540
+ labChrome,
3861
2541
  suppress,
2542
+ addTrial: addTrial2,
3862
2543
  tools,
3863
2544
  configRules,
3864
2545
  controls,
3865
- children
2546
+ children,
2547
+ opened
3866
2548
  }) {
3867
- if (process.env.NODE_ENV !== "production" && instruments.length === 0) {
3868
- throw new Error("[labkit] <Lab> requires a non-empty `instruments` array");
3869
- }
3870
- const storeRef = useRef(null);
3871
- if (storeRef.current === null) {
3872
- storeRef.current = buildStore(
3873
- instruments,
3874
- defaultInstrument,
3875
- storage ?? noneAdapter,
3876
- storageKey ?? "labkit",
3877
- mode ?? "auto"
3878
- );
3879
- }
3880
- const store = storeRef.current;
3881
- const trials = useStore(store, (s) => s.trials);
3882
- const savedSnapshots = useStore(store, (s) => s.savedSnapshots);
3883
- const modeValue = useStore(store, (s) => s.mode);
3884
- const layout = useStore(store, (s) => s.layout);
3885
- const undockedPanels = useStore(store, (s) => s.undockedPanels);
2549
+ const { store } = opened;
2550
+ useLayoutEffect(() => {
2551
+ store.getState().setInstruments(instruments);
2552
+ }, [instruments, store]);
2553
+ const trials = useStore(store, (s5) => s5.trials);
2554
+ const [focusPick, setFocusPick] = useState(null);
2555
+ const focusedTrialId = trials.some((t) => t.id === focusPick) ? focusPick : trials[0]?.id ?? null;
2556
+ const savedSnapshots = useStore(store, (s5) => s5.savedSnapshots);
2557
+ const modeValue = useStore(store, (s5) => s5.mode);
2558
+ const layout = useStore(store, (s5) => s5.layout);
2559
+ const undockedPanels = useStore(store, (s5) => s5.undockedPanels);
3886
2560
  const panelHostsRef = useRef(null);
3887
2561
  if (panelHostsRef.current === null) panelHostsRef.current = createPanelHostRegistry();
3888
2562
  const outerSurface = useSurfaceOptional();
3889
- const outerCanvas = useSurfaceCanvas();
3890
- const [ownCanvas, setOwnCanvas] = useState(null);
3891
- const ownCanvasRef = useRef(null);
2563
+ const outerOver = useSurfaceCanvas("over");
2564
+ const outerUnder = useSurfaceCanvas("under");
2565
+ const [ownOver, setOwnOver] = useState(null);
2566
+ const [ownUnder, setOwnUnder] = useState(null);
2567
+ const ownOverRef = useRef(null);
2568
+ const ownUnderRef = useRef(null);
3892
2569
  const bufferRef = useRef({ w: 0, h: 0 });
3893
2570
  const surfaceRef = useRef(null);
3894
2571
  const onFrame = useCallback((frame) => {
3895
- const c2 = ownCanvasRef.current;
3896
- if (!c2) return;
2572
+ const canvases = [ownUnderRef.current, ownOverRef.current].filter((c8) => c8 !== null);
2573
+ if (canvases.length === 0) return;
3897
2574
  const w3 = Math.round(frame.size.width * frame.dpr);
3898
- const h = Math.round(frame.size.height * frame.dpr);
3899
- if (bufferRef.current.w === w3 && bufferRef.current.h === h) return;
3900
- bufferRef.current = { w: w3, h };
3901
- c2.width = w3;
3902
- c2.height = h;
3903
- c2.style.width = `${frame.size.width}px`;
3904
- c2.style.height = `${frame.size.height}px`;
3905
- surfaceRef.current?.invalidateAll();
2575
+ const h2 = Math.round(frame.size.height * frame.dpr);
2576
+ if (bufferRef.current.w !== w3 || bufferRef.current.h !== h2) {
2577
+ bufferRef.current = { w: w3, h: h2 };
2578
+ for (const c8 of canvases) {
2579
+ c8.width = w3;
2580
+ c8.height = h2;
2581
+ c8.style.width = `${frame.size.width}px`;
2582
+ c8.style.height = `${frame.size.height}px`;
2583
+ }
2584
+ surfaceRef.current?.invalidateAll();
2585
+ return;
2586
+ }
3906
2587
  }, []);
3907
2588
  const ownSurface = useTiledSurface({ onFrame });
3908
2589
  surfaceRef.current = ownSurface;
2590
+ const [labBody, setLabBody] = useState(null);
2591
+ const attachOwnSurface = ownSurface.containerRef;
2592
+ const labBodyRef = useCallback(
2593
+ (el) => {
2594
+ setLabBody(el);
2595
+ if (!outerSurface) attachOwnSurface(el);
2596
+ },
2597
+ [outerSurface, attachOwnSurface]
2598
+ );
2599
+ useLabFitWarning(labBody);
2600
+ useEffect(() => {
2601
+ if (!labBody) return;
2602
+ const pick = (node) => {
2603
+ const owned = new Set(store.getState().trials.map((t) => t.id));
2604
+ const nearest = (from) => from?.closest(".lk-trial[data-trial-id]") ?? null;
2605
+ let trial = typeof node?.closest === "function" ? nearest(node) : null;
2606
+ while (trial && !owned.has(trial.dataset.trialId ?? "")) trial = nearest(trial.parentElement);
2607
+ if (trial?.dataset.trialId) setFocusPick(trial.dataset.trialId);
2608
+ };
2609
+ const onInput = (event) => pick(event.target);
2610
+ let pending;
2611
+ const doc = labBody.ownerDocument;
2612
+ const win = doc.defaultView;
2613
+ const onBlur = () => {
2614
+ clearTimeout(pending);
2615
+ pending = setTimeout(() => pick(doc.activeElement), 0);
2616
+ };
2617
+ labBody.addEventListener("pointerdown", onInput, true);
2618
+ labBody.addEventListener("focusin", onInput, true);
2619
+ win?.addEventListener("blur", onBlur);
2620
+ return () => {
2621
+ clearTimeout(pending);
2622
+ labBody.removeEventListener("pointerdown", onInput, true);
2623
+ labBody.removeEventListener("focusin", onInput, true);
2624
+ win?.removeEventListener("blur", onBlur);
2625
+ };
2626
+ }, [labBody, store]);
3909
2627
  const surface = outerSurface ?? ownSurface;
3910
- const surfaceCanvas = outerSurface ? outerCanvas : ownCanvas;
2628
+ const surfaceCanvases = useMemo(
2629
+ () => outerSurface ? { over: outerOver, under: outerUnder } : { over: ownOver, under: ownUnder },
2630
+ [outerSurface, outerOver, outerUnder, ownOver, ownUnder]
2631
+ );
3911
2632
  const workspacePanels = useMemo(
3912
2633
  () => Object.entries(undockedPanels).map(([key, panel]) => ({
3913
2634
  key,
@@ -3917,6 +2638,13 @@ function Lab({
3917
2638
  [undockedPanels]
3918
2639
  );
3919
2640
  const resolvedMode = useResolvedMode(modeValue);
2641
+ const annotates = instruments.some((i3) => i3.annotations != null);
2642
+ const labChromeAll = useMemo(
2643
+ () => labContributions([...annotates ? ANNOTATION_TOOLS : [], ...tools ?? []], labChrome),
2644
+ [annotates, tools, labChrome]
2645
+ );
2646
+ const hasFooterChrome = labChromeAll.some((c8) => c8.region === "footer");
2647
+ const hasPaneChrome = labChromeAll.some((c8) => c8.region === "sidebar" || c8.region === "aside");
3920
2648
  useEffect(() => {
3921
2649
  if (mode && mode !== store.getState().mode) {
3922
2650
  store.getState().setMode(mode);
@@ -3927,18 +2655,23 @@ function Lab({
3927
2655
  const currentById = new Map(store.getState().trials.map((w3) => [w3.id, w3]));
3928
2656
  const merged = next.map((w3) => currentById.get(w3.id) ?? w3);
3929
2657
  store.setState({ trials: merged });
3930
- store.getState().setMode(store.getState().mode);
2658
+ };
2659
+ const replaceAndFocusAdded = (next) => {
2660
+ const before = new Set(store.getState().trials.map((w3) => w3.id));
2661
+ replaceTrials(next);
2662
+ const added = next.find((w3) => !before.has(w3.id));
2663
+ if (added) setFocusPick(added.id);
3931
2664
  };
3932
2665
  return {
3933
2666
  instruments,
3934
2667
  trials,
3935
2668
  addTrial: (instrumentName, options) => {
3936
- const next = addTrial(store.getState().trials, instruments, instrumentName, options);
3937
- replaceTrials(next);
2669
+ replaceAndFocusAdded(
2670
+ addTrial(store.getState().trials, instruments, instrumentName, options)
2671
+ );
3938
2672
  },
3939
2673
  cloneTrial: (id) => {
3940
- const next = cloneTrial(store.getState().trials, id);
3941
- replaceTrials(next);
2674
+ replaceAndFocusAdded(cloneTrial(store.getState().trials, id));
3942
2675
  },
3943
2676
  closeTrial: (id) => {
3944
2677
  const next = closeTrial(store.getState().trials, id);
@@ -3947,20 +2680,31 @@ function Lab({
3947
2680
  reorderTrials: (ids) => {
3948
2681
  replaceTrials(reorderTrials(store.getState().trials, ids));
3949
2682
  },
2683
+ swapTrial: (id, instrumentName, options) => {
2684
+ const current = store.getState();
2685
+ const next = swapTrial(current.trials, instruments, id, instrumentName, options);
2686
+ if (next === current.trials) return;
2687
+ const record = next[current.trials.findIndex((w3) => w3.id === id)];
2688
+ const { [id]: extent, ...layoutRest } = current.layout;
2689
+ if (extent) current.setLayout({ ...layoutRest, [record.id]: extent });
2690
+ replaceTrials(next);
2691
+ if (focusedTrialId === id) setFocusPick(record.id);
2692
+ },
2693
+ focusedTrialId,
2694
+ focusTrial: (id) => setFocusPick(id),
3950
2695
  resetTrial: (id) => {
3951
2696
  const next = resetTrial(store.getState().trials, id, instruments);
3952
2697
  const record = next.find((w3) => w3.id === id);
3953
2698
  if (!record) return;
3954
- store.setState((s) => ({
3955
- trials: s.trials.map(
2699
+ store.setState((s5) => ({
2700
+ trials: s5.trials.map(
3956
2701
  (w3) => w3.id === id ? { ...w3, config: record.config, state: record.state, view: record.view } : w3
3957
2702
  )
3958
2703
  }));
3959
- store.getState().updateTrialView(id, record.view);
3960
2704
  },
3961
2705
  savedSnapshots,
3962
2706
  saveSnapshot: (trialId, name) => {
3963
- const taken = store.getState().savedSnapshots.filter((s) => s.trialId === trialId).length;
2707
+ const taken = store.getState().savedSnapshots.filter((s5) => s5.trialId === trialId).length;
3964
2708
  store.getState().saveSnapshot(trialId, name ?? `Snapshot ${taken + 1}`);
3965
2709
  },
3966
2710
  loadSnapshot: (trialId, snapshotId) => {
@@ -3970,19 +2714,41 @@ function Lab({
3970
2714
  store.getState().deleteSnapshot(snapshotId);
3971
2715
  },
3972
2716
  mode: modeValue,
3973
- setMode: (m) => {
3974
- store.getState().setMode(m);
2717
+ setMode: (m2) => {
2718
+ store.getState().setMode(m2);
3975
2719
  },
3976
2720
  configRules,
3977
2721
  controls
3978
2722
  };
3979
- }, [instruments, trials, savedSnapshots, modeValue, store, configRules, controls]);
2723
+ }, [
2724
+ instruments,
2725
+ trials,
2726
+ focusedTrialId,
2727
+ savedSnapshots,
2728
+ modeValue,
2729
+ store,
2730
+ configRules,
2731
+ controls
2732
+ ]);
3980
2733
  const backdropStyle = resolvedMode === "dark" && nebula && nebula.length > 0 ? { ["--wzl-backdrop"]: buildNebula(nebula) } : void 0;
3981
- return /* @__PURE__ */ jsx(LabStoreContext.Provider, { value: { store }, children: /* @__PURE__ */ jsx(LabContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
2734
+ const workspace = /* @__PURE__ */ jsx(
2735
+ Workspace,
2736
+ {
2737
+ panels: workspacePanels,
2738
+ ids: trials.map((w3) => w3.id),
2739
+ resizable: true,
2740
+ reorderable: true,
2741
+ onReorder: (ids) => contextValue.reorderTrials(ids),
2742
+ layout,
2743
+ onLayoutChange: (next) => store.getState().setLayout(next),
2744
+ children: trials.map((w3) => /* @__PURE__ */ jsx(Trial, { id: w3.id, chrome, suppress }, w3.id))
2745
+ }
2746
+ );
2747
+ return /* @__PURE__ */ jsx(LabStoreContext.Provider, { value: { store }, children: /* @__PURE__ */ jsx(PersistenceContext.Provider, { value: opened.records, children: /* @__PURE__ */ jsx(AnnotationPreloadContext.Provider, { value: opened.marks, children: /* @__PURE__ */ jsx(LabContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
3982
2748
  ThemeProvider,
3983
2749
  {
3984
2750
  theme: interstellarTheme,
3985
- mode: resolvedMode,
2751
+ selection: { mode: resolvedMode },
3986
2752
  className: "lk-lab",
3987
2753
  style: backdropStyle,
3988
2754
  children: /* @__PURE__ */ jsx(
@@ -3990,55 +2756,813 @@ function Lab({
3990
2756
  {
3991
2757
  title: title ?? "Labkit",
3992
2758
  mode: modeValue,
3993
- footer,
2759
+ ...pages ? { pages } : {},
2760
+ ...path !== void 0 ? { path } : {},
2761
+ footer: hasFooterChrome ? /* @__PURE__ */ jsxs(Fragment, { children: [
2762
+ footer,
2763
+ /* @__PURE__ */ jsx(LabFooterRegion, { contributions: labChromeAll })
2764
+ ] }) : footer,
3994
2765
  header: /* @__PURE__ */ jsxs(Fragment, { children: [
3995
- /* @__PURE__ */ jsx(LabHeader, {}),
3996
- children
2766
+ /* @__PURE__ */ jsx(LabHeader, { ...addTrial2 !== void 0 ? { addTrial: addTrial2 } : {} }),
2767
+ children,
2768
+ /* @__PURE__ */ jsx(LabHeaderRegion, { contributions: labChromeAll })
3997
2769
  ] }),
3998
- children: /* @__PURE__ */ jsx(PanelHostContext.Provider, { value: panelHostsRef.current, children: /* @__PURE__ */ jsx(SurfaceContext.Provider, { value: surface, children: /* @__PURE__ */ jsx(SurfaceCanvasContext.Provider, { value: surfaceCanvas, children: /* @__PURE__ */ jsxs(
3999
- "div",
4000
- {
4001
- className: "lk-lab__body",
4002
- ref: outerSurface ? void 0 : ownSurface.containerRef,
4003
- children: [
4004
- outerSurface ? null : (
4005
- // Above the trials and inert: the marks a tile paints have
4006
- // to sit over the instrument's own DOM, and nothing on this
4007
- // buffer takes input — each tile has its own input box.
4008
- /* @__PURE__ */ jsx(
4009
- "canvas",
4010
- {
4011
- className: "lk-lab__surface",
4012
- ref: (el) => {
4013
- ownCanvasRef.current = el;
4014
- setOwnCanvas(el);
4015
- }
2770
+ children: /* @__PURE__ */ jsx(PanelHostContext.Provider, { value: panelHostsRef.current, children: /* @__PURE__ */ jsx(SurfaceContext.Provider, { value: surface, children: /* @__PURE__ */ jsx(SurfaceCanvasContext.Provider, { value: surfaceCanvases, children: /* @__PURE__ */ jsxs("div", { className: "lk-lab__body", ref: labBodyRef, children: [
2771
+ outerSurface ? null : (
2772
+ // Two buffers stacked around the trials, both inert —
2773
+ // each tile takes input from its own box. A tile's marks
2774
+ // annotate the instrument's DOM from over it; an opaque
2775
+ // renderer sits under it, so the pane can still hold a
2776
+ // label. The under one is first so it paints first.
2777
+ /* @__PURE__ */ jsxs(Fragment, { children: [
2778
+ /* @__PURE__ */ jsx(
2779
+ "canvas",
2780
+ {
2781
+ className: "lk-lab__surface lk-lab__surface--under",
2782
+ ref: (el) => {
2783
+ ownUnderRef.current = el;
2784
+ setOwnUnder(el);
4016
2785
  }
4017
- )
2786
+ }
4018
2787
  ),
4019
- tools ? /* @__PURE__ */ jsx(LabPalette, { tools }) : null,
4020
2788
  /* @__PURE__ */ jsx(
4021
- Workspace,
2789
+ "canvas",
4022
2790
  {
4023
- panels: workspacePanels,
4024
- ids: trials.map((w3) => w3.id),
4025
- resizable: true,
4026
- reorderable: true,
4027
- onReorder: (ids) => contextValue.reorderTrials(ids),
4028
- layout,
4029
- onLayoutChange: (next) => store.getState().setLayout(next),
4030
- children: trials.map((w3) => /* @__PURE__ */ jsx(Trial, { id: w3.id, chrome, suppress }, w3.id))
2791
+ className: "lk-lab__surface lk-lab__surface--over",
2792
+ ref: (el) => {
2793
+ ownOverRef.current = el;
2794
+ setOwnOver(el);
2795
+ }
4031
2796
  }
4032
2797
  )
4033
- ]
4034
- }
4035
- ) }) }) })
2798
+ ] })
2799
+ ),
2800
+ hasPaneChrome ? /* @__PURE__ */ jsxs(LabPanes, { contributions: labChromeAll, children: [
2801
+ /* @__PURE__ */ jsx(LabPalette, { contributions: labChromeAll }),
2802
+ workspace
2803
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2804
+ /* @__PURE__ */ jsx(LabPalette, { contributions: labChromeAll }),
2805
+ workspace
2806
+ ] })
2807
+ ] }) }) }) })
4036
2808
  }
4037
2809
  )
4038
2810
  }
4039
- ) }) });
2811
+ ) }) }) }) });
2812
+ }
2813
+ var SPECIMEN_SECTIONS = [
2814
+ "Buttons & toggles",
2815
+ "Fields & pickers",
2816
+ "Panels & property rows",
2817
+ "Overlays & feedback",
2818
+ "Lists & data",
2819
+ "Tool chrome",
2820
+ "Editors & plots",
2821
+ "Lab chrome"
2822
+ ];
2823
+ function Section({ title, children }) {
2824
+ const id = useId();
2825
+ return /* @__PURE__ */ jsxs("section", { className: "lk-specimen__section", "aria-labelledby": id, children: [
2826
+ /* @__PURE__ */ jsx("h2", { id, className: "lk-specimen__heading", children: title }),
2827
+ /* @__PURE__ */ jsx("div", { className: "lk-specimen__grid", children })
2828
+ ] });
2829
+ }
2830
+ function Cell({ label, wide, children }) {
2831
+ return /* @__PURE__ */ jsxs("div", { className: wide ? "lk-specimen__cell lk-specimen__cell--wide" : "lk-specimen__cell", children: [
2832
+ /* @__PURE__ */ jsx("span", { className: "lk-specimen__label", children: label }),
2833
+ children
2834
+ ] });
2835
+ }
2836
+ function Dot() {
2837
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", width: 16, height: 16, "aria-hidden": "true", children: /* @__PURE__ */ jsx("circle", { cx: 8, cy: 8, r: 4, fill: "currentColor" }) });
2838
+ }
2839
+ var LEGEND = [
2840
+ { key: "contour", label: "contour", color: "var(--wzl-fg-muted)" },
2841
+ { key: "floor", label: "floor", color: "var(--wzl-fg-subtle)", mark: "dash" },
2842
+ { key: "authored", label: "authored", color: "var(--wzl-success)", mark: "dot" },
2843
+ { key: "replaced", label: "replaced", color: "var(--wzl-danger)", mark: "band" }
2844
+ ];
2845
+ var PREFS = {
2846
+ name: "Preferences",
2847
+ children: {
2848
+ canvas: {
2849
+ name: "Canvas",
2850
+ children: {
2851
+ showGrid: {
2852
+ kind: "boolean",
2853
+ name: "Show grid",
2854
+ description: "Draw a grid behind the scene.",
2855
+ default: true
2856
+ },
2857
+ zoomStep: {
2858
+ kind: "number",
2859
+ name: "Zoom step",
2860
+ description: "Percent the zoom controls move per click.",
2861
+ default: 10,
2862
+ min: 1,
2863
+ max: 50
2864
+ },
2865
+ format: {
2866
+ kind: "enum",
2867
+ name: "Format",
2868
+ description: "File type for exported images.",
2869
+ default: "svg",
2870
+ options: [
2871
+ { value: "svg", label: "SVG" },
2872
+ { value: "png", label: "PNG" }
2873
+ ]
2874
+ }
2875
+ }
2876
+ }
2877
+ }
2878
+ };
2879
+ var JOB = {
2880
+ status: "running",
2881
+ done: 34,
2882
+ total: 120,
2883
+ failures: [],
2884
+ error: null,
2885
+ start: () => {
2886
+ },
2887
+ cancel: () => {
2888
+ }
2889
+ };
2890
+ function setAt(values, path, value) {
2891
+ const [head, ...rest] = path.split(".");
2892
+ if (head === void 0) return values;
2893
+ if (rest.length === 0) return { ...values, [head]: value };
2894
+ const inner = values[head] ?? {};
2895
+ return { ...values, [head]: setAt(inner, rest.join("."), value) };
2896
+ }
2897
+ function ButtonsAndToggles() {
2898
+ const [snap, setSnap] = useState(true);
2899
+ const [wifi, setWifi] = useState(false);
2900
+ const [align, setAlign] = useState("center");
2901
+ const [bold, setBold] = useState(true);
2902
+ const [italic, setItalic] = useState(false);
2903
+ const [open, setOpen] = useState(true);
2904
+ return /* @__PURE__ */ jsxs(Section, { title: "Buttons & toggles", children: [
2905
+ /* @__PURE__ */ jsx(Cell, { label: "Button", children: /* @__PURE__ */ jsxs("div", { className: "lk-specimen__row", children: [
2906
+ /* @__PURE__ */ jsx(a5, { variant: "primary", children: "Save" }),
2907
+ /* @__PURE__ */ jsx(a5, { variant: "secondary", children: "Cancel" }),
2908
+ /* @__PURE__ */ jsx(a5, { variant: "ghost", children: "Ghost" }),
2909
+ /* @__PURE__ */ jsx(a5, { variant: "secondary", disabled: true, children: "Disabled" }),
2910
+ /* @__PURE__ */ jsx(a5, { variant: "secondary", iconOnly: true, ariaLabel: "Add", children: /* @__PURE__ */ jsx(Dot, {}) })
2911
+ ] }) }),
2912
+ /* @__PURE__ */ jsx(Cell, { label: "Badge", children: /* @__PURE__ */ jsxs("div", { className: "lk-specimen__row", children: [
2913
+ /* @__PURE__ */ jsx(Lt, { shape: "pill", tone: "accent", variant: "outline", size: "sm", children: "ACCENT" }),
2914
+ /* @__PURE__ */ jsx(Lt, { tone: "info", variant: "subtle", children: "INFO" }),
2915
+ /* @__PURE__ */ jsx(Lt, { tone: "warn", variant: "solid", children: "WARN" }),
2916
+ /* @__PURE__ */ jsx(Lt, { tone: "danger", variant: "solid", children: "DANGER" }),
2917
+ /* @__PURE__ */ jsx(Lt, { tone: "muted", children: "MUTED" })
2918
+ ] }) }),
2919
+ /* @__PURE__ */ jsx(Cell, { label: "Checkbox, Switch", children: /* @__PURE__ */ jsxs("div", { className: "lk-specimen__row", children: [
2920
+ /* @__PURE__ */ jsx(o2, { isSelected: snap, onChange: setSnap, children: "Snap to grid" }),
2921
+ /* @__PURE__ */ jsx(o2, { isIndeterminate: true, children: "Mixed" }),
2922
+ /* @__PURE__ */ jsx(a4, { isSelected: wifi, onChange: setWifi, children: "Wifi" })
2923
+ ] }) }),
2924
+ /* @__PURE__ */ jsx(Cell, { label: "RadioGroup", children: /* @__PURE__ */ jsxs(l3, { label: "Snap mode", orientation: "horizontal", defaultValue: "grid", children: [
2925
+ /* @__PURE__ */ jsx(u3, { value: "off", children: "Off" }),
2926
+ /* @__PURE__ */ jsx(u3, { value: "grid", children: "Grid" }),
2927
+ /* @__PURE__ */ jsx(u3, { value: "pixel", children: "Pixel" })
2928
+ ] }) }),
2929
+ /* @__PURE__ */ jsxs(Cell, { label: "ToggleBar", children: [
2930
+ /* @__PURE__ */ jsx(
2931
+ r3,
2932
+ {
2933
+ ariaLabel: "Align",
2934
+ value: align,
2935
+ onChange: setAlign,
2936
+ items: [
2937
+ { value: "left", label: "Left" },
2938
+ { value: "center", label: "Center" },
2939
+ { value: "right", label: "Right" }
2940
+ ]
2941
+ }
2942
+ ),
2943
+ /* @__PURE__ */ jsx(
2944
+ r3,
2945
+ {
2946
+ ariaLabel: "Align, flat",
2947
+ variant: "flat",
2948
+ value: align,
2949
+ onChange: setAlign,
2950
+ items: [
2951
+ { value: "left", label: "Left" },
2952
+ { value: "center", label: "Center" },
2953
+ { value: "right", label: "Right" }
2954
+ ]
2955
+ }
2956
+ )
2957
+ ] }),
2958
+ /* @__PURE__ */ jsx(Cell, { label: "OptionsBar, ActionsBar", children: /* @__PURE__ */ jsxs("div", { className: "lk-specimen__row", children: [
2959
+ /* @__PURE__ */ jsx(
2960
+ r7,
2961
+ {
2962
+ ariaLabel: "Text style",
2963
+ items: [
2964
+ { value: "b", label: "B", selected: bold, onChange: setBold },
2965
+ { value: "i", label: "I", selected: italic, onChange: setItalic }
2966
+ ]
2967
+ }
2968
+ ),
2969
+ /* @__PURE__ */ jsx(
2970
+ r8,
2971
+ {
2972
+ items: [
2973
+ { value: "undo", label: "Undo", onAction: () => {
2974
+ } },
2975
+ { value: "redo", label: "Redo", onAction: () => {
2976
+ } }
2977
+ ]
2978
+ }
2979
+ )
2980
+ ] }) }),
2981
+ /* @__PURE__ */ jsx(Cell, { label: "MenuButton, Disclosure", children: /* @__PURE__ */ jsxs("div", { className: "lk-specimen__row", children: [
2982
+ /* @__PURE__ */ jsx(
2983
+ l7,
2984
+ {
2985
+ label: "Add trial\u2026",
2986
+ onAction: () => {
2987
+ },
2988
+ items: [
2989
+ { value: "sine", label: "Sine wave" },
2990
+ { value: "annotate", label: "Annotate" }
2991
+ ]
2992
+ }
2993
+ ),
2994
+ /* @__PURE__ */ jsx(r6, { open, onToggle: () => setOpen((o5) => !o5), label: "Shapes" }),
2995
+ /* @__PURE__ */ jsx(r6, { open: false, onToggle: () => {
2996
+ }, label: "Down", direction: "down" })
2997
+ ] }) }),
2998
+ /* @__PURE__ */ jsx(Cell, { label: "Tabs", children: /* @__PURE__ */ jsxs(o5, { children: [
2999
+ /* @__PURE__ */ jsxs(s5, { "aria-label": "Sections", children: [
3000
+ /* @__PURE__ */ jsx(c6, { id: "props", children: "Props" }),
3001
+ /* @__PURE__ */ jsx(c6, { id: "events", children: "Events" }),
3002
+ /* @__PURE__ */ jsx(c6, { id: "snapshot", children: "Snapshot" })
3003
+ ] }),
3004
+ /* @__PURE__ */ jsx(l6, { id: "props", children: "Props panel." }),
3005
+ /* @__PURE__ */ jsx(l6, { id: "events", children: "Events panel." }),
3006
+ /* @__PURE__ */ jsx(l6, { id: "snapshot", children: "Snapshot panel." })
3007
+ ] }) }),
3008
+ /* @__PURE__ */ jsx(Cell, { label: "Keycaps", children: /* @__PURE__ */ jsxs("div", { className: "lk-specimen__row", children: [
3009
+ /* @__PURE__ */ jsx(c3, { keys: [{ label: "\u2318" }, { label: "\u21E7" }, { label: "K" }], separator: "+" }),
3010
+ /* @__PURE__ */ jsx(s3, { label: "K" }),
3011
+ /* @__PURE__ */ jsx(s3, { label: "\u2325", variant: "minimal" })
3012
+ ] }) })
3013
+ ] });
3014
+ }
3015
+ function FieldsAndPickers() {
3016
+ const [thumbs, setThumbs] = useState([{ value: 0.25 }, { value: 0.75 }]);
3017
+ const [rate, setRate] = useState(1);
3018
+ const [width, setWidth] = useState(6);
3019
+ const [color, setColor] = useState("#b08adbff");
3020
+ const [gradient, setGradient] = useState({
3021
+ fill: "linear-gradient",
3022
+ from: { x: 0.1, y: 0.1 },
3023
+ to: { x: 0.9, y: 0.9 },
3024
+ stops: [
3025
+ { offset: 0, color: "#0fb5a8" },
3026
+ { offset: 1, color: "#f4c43c" }
3027
+ ]
3028
+ });
3029
+ const [paint, setPaint] = useState({ fill: "solid", color: "#b08adb" });
3030
+ return /* @__PURE__ */ jsxs(Section, { title: "Fields & pickers", children: [
3031
+ /* @__PURE__ */ jsxs(Cell, { label: "Input", children: [
3032
+ /* @__PURE__ */ jsx(u2, { label: "Name", placeholder: "e.g. Pico" }),
3033
+ /* @__PURE__ */ jsx(
3034
+ u2,
3035
+ {
3036
+ label: "Size",
3037
+ leadingAdornment: /* @__PURE__ */ jsx("span", { children: "W" }),
3038
+ trailingAdornment: /* @__PURE__ */ jsx("span", { children: "px" }),
3039
+ defaultValue: "120"
3040
+ }
3041
+ ),
3042
+ /* @__PURE__ */ jsx(u2, { label: "Required", isInvalid: true, errorMessage: "Name is required." })
3043
+ ] }),
3044
+ /* @__PURE__ */ jsxs(Cell, { label: "NumberField, Select, ComboBox", children: [
3045
+ /* @__PURE__ */ jsx(g2, { label: "Opacity", minValue: 0, maxValue: 100, step: 5, defaultValue: 80 }),
3046
+ /* @__PURE__ */ jsx(
3047
+ g,
3048
+ {
3049
+ label: "Color",
3050
+ defaultSelectedKey: "g",
3051
+ options: [
3052
+ { value: "r", label: "Red" },
3053
+ { value: "g", label: "Green" },
3054
+ { value: "b", label: "Blue" }
3055
+ ]
3056
+ }
3057
+ ),
3058
+ /* @__PURE__ */ jsx(
3059
+ y2,
3060
+ {
3061
+ label: "Filter",
3062
+ placeholder: "Type to filter\u2026",
3063
+ options: [
3064
+ { value: "r", label: "Red" },
3065
+ { value: "g", label: "Green" }
3066
+ ]
3067
+ }
3068
+ )
3069
+ ] }),
3070
+ /* @__PURE__ */ jsxs(Cell, { label: "RangeSlider, Slider", children: [
3071
+ /* @__PURE__ */ jsx(l4, { label: "Range", defaultValue: [20, 80], minValue: 0, maxValue: 100 }),
3072
+ /* @__PURE__ */ jsx(
3073
+ l4,
3074
+ {
3075
+ label: "Opacity",
3076
+ defaultValue: 75,
3077
+ minValue: 0,
3078
+ maxValue: 100,
3079
+ formatOutput: (v2) => `${v2}%`
3080
+ }
3081
+ ),
3082
+ /* @__PURE__ */ jsx(
3083
+ b2,
3084
+ {
3085
+ ariaLabel: "Pair",
3086
+ min: 0,
3087
+ max: 1,
3088
+ step: 0.01,
3089
+ constraint: "ordered",
3090
+ thumbs,
3091
+ onInput: setThumbs,
3092
+ readoutPlacement: "inline-after"
3093
+ }
3094
+ )
3095
+ ] }),
3096
+ /* @__PURE__ */ jsxs(Cell, { label: "DetentSlider, InlineRange", children: [
3097
+ /* @__PURE__ */ jsx(
3098
+ c7,
3099
+ {
3100
+ items: [0.25, 0.5, 1, 2, 4],
3101
+ value: rate,
3102
+ onChange: setRate,
3103
+ formatLabel: (r10) => `${r10}\xD7`,
3104
+ labels: "all",
3105
+ ariaLabel: "Playback rate"
3106
+ }
3107
+ ),
3108
+ /* @__PURE__ */ jsx(
3109
+ r2,
3110
+ {
3111
+ "aria-label": "Width",
3112
+ min: 0,
3113
+ max: 20,
3114
+ value: width,
3115
+ onChange: (event) => setWidth(Number(event.target.value))
3116
+ }
3117
+ )
3118
+ ] }),
3119
+ /* @__PURE__ */ jsxs(Cell, { label: "ColorField, GradientEditor", children: [
3120
+ /* @__PURE__ */ jsx(l2, { value: color, alpha: true, onInput: setColor, onChange: setColor, "aria-label": "Color" }),
3121
+ /* @__PURE__ */ jsx(C2, { value: gradient, onInput: setGradient, onChange: setGradient, kindSwitch: true })
3122
+ ] }),
3123
+ /* @__PURE__ */ jsx(Cell, { label: "PaintInput", children: /* @__PURE__ */ jsx(
3124
+ S,
3125
+ {
3126
+ value: paint,
3127
+ onInput: setPaint,
3128
+ onChange: setPaint,
3129
+ allowNone: true,
3130
+ "aria-label": "Fill"
3131
+ }
3132
+ ) })
3133
+ ] });
3134
+ }
3135
+ function PanelsAndRows() {
3136
+ const [opacity, setOpacity] = useState(0.8);
3137
+ const [radius, setRadius] = useState(12);
3138
+ const [fill, setFill] = useState("#5841b8");
3139
+ const [alpha, setAlpha] = useState(1);
3140
+ const [stroke, setStroke] = useState("#2ec27e");
3141
+ const [count, setCount] = useState(8);
3142
+ const [cap, setCap] = useState("round");
3143
+ const [side, setSide] = useState("center");
3144
+ const [label, setLabel] = useState("Badge");
3145
+ const [visible, setVisible] = useState(true);
3146
+ const [prefs, setPrefs] = useState({});
3147
+ const [layers, setLayers] = useState([
3148
+ { id: "base", label: "Base coat" },
3149
+ { id: "wash", label: "Wash" }
3150
+ ]);
3151
+ const [panelCollapsed, setPanelCollapsed] = useState(false);
3152
+ return /* @__PURE__ */ jsxs(Section, { title: "Panels & property rows", children: [
3153
+ /* @__PURE__ */ jsx(Cell, { label: "PropertyPanel", children: /* @__PURE__ */ jsx(O, { title: "Shape", children: /* @__PURE__ */ jsxs(k, { children: [
3154
+ /* @__PURE__ */ jsx(
3155
+ F2,
3156
+ {
3157
+ label: "Opacity",
3158
+ value: opacity,
3159
+ min: 0,
3160
+ max: 1,
3161
+ step: 0.01,
3162
+ onChange: setOpacity
3163
+ }
3164
+ ),
3165
+ /* @__PURE__ */ jsx(
3166
+ F2,
3167
+ {
3168
+ label: "Radius",
3169
+ value: radius,
3170
+ min: 0,
3171
+ max: 64,
3172
+ unit: "px",
3173
+ onChange: setRadius
3174
+ }
3175
+ ),
3176
+ /* @__PURE__ */ jsx(
3177
+ L3,
3178
+ {
3179
+ label: "Fill",
3180
+ value: fill,
3181
+ onChange: setFill,
3182
+ alpha,
3183
+ onAlphaChange: setAlpha
3184
+ }
3185
+ ),
3186
+ /* @__PURE__ */ jsx(L3, { label: "Stroke", value: stroke, onChange: setStroke }),
3187
+ /* @__PURE__ */ jsx(V, { label: "Count", value: count, min: 0, max: 100, step: 1, onChange: setCount }),
3188
+ /* @__PURE__ */ jsx(
3189
+ H,
3190
+ {
3191
+ label: "Line cap",
3192
+ value: cap,
3193
+ onChange: setCap,
3194
+ options: [
3195
+ { value: "butt", label: "Butt" },
3196
+ { value: "round", label: "Round" }
3197
+ ]
3198
+ }
3199
+ ),
3200
+ /* @__PURE__ */ jsx(
3201
+ U,
3202
+ {
3203
+ label: "Align",
3204
+ value: side,
3205
+ onChange: setSide,
3206
+ options: [
3207
+ { value: "left", label: "L" },
3208
+ { value: "center", label: "C" },
3209
+ { value: "right", label: "R" }
3210
+ ]
3211
+ }
3212
+ ),
3213
+ /* @__PURE__ */ jsx(B, { label: "Label", value: label, onChange: setLabel }),
3214
+ /* @__PURE__ */ jsx(R2, { label: "Visible", value: visible, onChange: setVisible }),
3215
+ /* @__PURE__ */ jsx(J, { title: "Bevel", defaultCollapsed: true, children: /* @__PURE__ */ jsx(F2, { label: "Rings", value: 32, min: 4, max: 96, step: 1, onChange: () => {
3216
+ } }) })
3217
+ ] }) }) }),
3218
+ /* @__PURE__ */ jsx(Cell, { label: "PrefsForm", children: /* @__PURE__ */ jsx(
3219
+ A2,
3220
+ {
3221
+ schema: PREFS,
3222
+ values: prefs,
3223
+ onChange: (path, value) => setPrefs((prev) => setAt(prev, path, value))
3224
+ }
3225
+ ) }),
3226
+ /* @__PURE__ */ jsx(Cell, { label: "LayerStack", children: /* @__PURE__ */ jsx(
3227
+ c4,
3228
+ {
3229
+ items: layers,
3230
+ onReorder: (ids) => setLayers(ids.flatMap((id) => layers.filter((item) => item.id === id))),
3231
+ renderBody: (item) => /* @__PURE__ */ jsxs("div", { children: [
3232
+ "settings for ",
3233
+ item.label
3234
+ ] })
3235
+ }
3236
+ ) }),
3237
+ /* @__PURE__ */ jsx(Cell, { label: "Sidebar, SidebarPanel", children: /* @__PURE__ */ jsx("div", { className: "lk-specimen__box", children: /* @__PURE__ */ jsx(n5, { side: "right", ariaLabel: "Specimen sidebar", children: /* @__PURE__ */ jsx(
3238
+ s4,
3239
+ {
3240
+ title: "Selection",
3241
+ collapsed: panelCollapsed,
3242
+ onToggleCollapse: () => setPanelCollapsed((v2) => !v2),
3243
+ onHide: () => {
3244
+ },
3245
+ children: /* @__PURE__ */ jsx("div", { className: "lk-specimen__pad", children: "2 items selected" })
3246
+ }
3247
+ ) }) }) })
3248
+ ] });
3249
+ }
3250
+ function OverlaysAndFeedback() {
3251
+ const calloutAnchor = useRef(null);
3252
+ const [calloutOpen, setCalloutOpen] = useState(false);
3253
+ const [dialogOpen, setDialogOpen] = useState(false);
3254
+ const toasts = useMemo(() => m2(), []);
3255
+ return /* @__PURE__ */ jsxs(Section, { title: "Overlays & feedback", children: [
3256
+ /* @__PURE__ */ jsx(Cell, { label: "Tooltip, Callout, Dialog, Toast", children: /* @__PURE__ */ jsxs("div", { className: "lk-specimen__row", children: [
3257
+ /* @__PURE__ */ jsxs(c2, { children: [
3258
+ /* @__PURE__ */ jsx(Focusable, { children: /* @__PURE__ */ jsx("button", { type: "button", children: "Hover me" }) }),
3259
+ /* @__PURE__ */ jsx(l, { placement: "bottom", children: "Duplicates the selected layer" })
3260
+ ] }),
3261
+ /* @__PURE__ */ jsx("button", { type: "button", ref: calloutAnchor, onClick: () => setCalloutOpen(true), children: "Callout" }),
3262
+ /* @__PURE__ */ jsx(
3263
+ g3,
3264
+ {
3265
+ triggerRef: calloutAnchor,
3266
+ isOpen: calloutOpen,
3267
+ onOpenChange: setCalloutOpen,
3268
+ tone: "info",
3269
+ title: "Info",
3270
+ children: "Neutral guidance."
3271
+ }
3272
+ ),
3273
+ /* @__PURE__ */ jsx(a5, { onClick: () => setDialogOpen(true), children: "Dialog" }),
3274
+ /* @__PURE__ */ jsx(
3275
+ l5,
3276
+ {
3277
+ isOpen: dialogOpen,
3278
+ onOpenChange: setDialogOpen,
3279
+ title: "Preferences",
3280
+ footer: /* @__PURE__ */ jsx(a5, { variant: "primary", onClick: () => setDialogOpen(false), children: "OK" }),
3281
+ children: /* @__PURE__ */ jsx("p", { children: "Dialog body." })
3282
+ }
3283
+ ),
3284
+ /* @__PURE__ */ jsx(
3285
+ a5,
3286
+ {
3287
+ onClick: () => toasts.add("success", "Saved", { description: "All changes stored.", ttlMs: null }),
3288
+ children: "Toast"
3289
+ }
3290
+ ),
3291
+ /* @__PURE__ */ jsx(S4, { queue: toasts, placement: "bottom-right" })
3292
+ ] }) }),
3293
+ /* @__PURE__ */ jsx(Cell, { label: "Powerline", children: /* @__PURE__ */ jsx(
3294
+ r10,
3295
+ {
3296
+ variant: "solid",
3297
+ size: "sm",
3298
+ segments: [
3299
+ { text: "main", tone: "accent", endCap: "chevron" },
3300
+ { text: "\u2713 12", tone: "info", endCap: "slant" },
3301
+ { text: "~/proj", tone: "muted" }
3302
+ ]
3303
+ }
3304
+ ) }),
3305
+ /* @__PURE__ */ jsx(Cell, { label: "StatusBar", children: /* @__PURE__ */ jsxs(n6, { ariaLabel: "Editor status", children: [
3306
+ /* @__PURE__ */ jsx(r11, { children: "tool: select" }),
3307
+ /* @__PURE__ */ jsx(r11, { children: "sel: 3" }),
3308
+ /* @__PURE__ */ jsx(i5, {}),
3309
+ /* @__PURE__ */ jsx(r11, { muted: true, children: "0.7.0" })
3310
+ ] }) })
3311
+ ] });
3312
+ }
3313
+ function ListsAndData() {
3314
+ return /* @__PURE__ */ jsxs(Section, { title: "Lists & data", children: [
3315
+ /* @__PURE__ */ jsx(Cell, { label: "ItemList", children: /* @__PURE__ */ jsx(
3316
+ i4,
3317
+ {
3318
+ rows: [
3319
+ { id: "a", label: "Draw rect" },
3320
+ { id: "b", label: "Fill", selected: true },
3321
+ { id: "c", label: "Move", muted: true }
3322
+ ],
3323
+ empty: "Nothing"
3324
+ }
3325
+ ) }),
3326
+ /* @__PURE__ */ jsx(Cell, { label: "DataGrid", children: /* @__PURE__ */ jsx(
3327
+ s2,
3328
+ {
3329
+ rows: [
3330
+ { id: "1", name: "accent", count: 3 },
3331
+ { id: "2", name: "info", count: 1 },
3332
+ { id: "3", name: "danger", count: 2 }
3333
+ ],
3334
+ columns: [
3335
+ { id: "name", header: "name", accessor: (r10) => r10.name, sortable: true },
3336
+ { id: "count", header: "count", accessor: (r10) => r10.count, sortable: true }
3337
+ ],
3338
+ defaultSort: { columnId: "name", direction: "asc" }
3339
+ }
3340
+ ) })
3341
+ ] });
3342
+ }
3343
+ function ToolChrome() {
3344
+ const [tool, setTool] = useState("select");
3345
+ const [paneWidth, setPaneWidth] = useState(160);
3346
+ return /* @__PURE__ */ jsxs(Section, { title: "Tool chrome", children: [
3347
+ /* @__PURE__ */ jsx(Cell, { label: "ToolGroup, ToolButton", children: /* @__PURE__ */ jsx(n7, { orientation: "horizontal", ariaLabel: "Tools", children: ["select", "rect", "ellipse"].map((id) => /* @__PURE__ */ jsx(
3348
+ o4,
3349
+ {
3350
+ icon: /* @__PURE__ */ jsx(Dot, {}),
3351
+ label: id,
3352
+ shortcut: id.slice(0, 1).toUpperCase(),
3353
+ active: tool === id,
3354
+ tabbable: tool === id,
3355
+ onClick: () => setTool(id)
3356
+ },
3357
+ id
3358
+ )) }) }),
3359
+ /* @__PURE__ */ jsx(Cell, { label: "ToolOptionsBar", children: /* @__PURE__ */ jsxs(r9, { label: "Text", children: [
3360
+ /* @__PURE__ */ jsx(a5, { size: "sm", variant: "ghost", iconOnly: true, ariaLabel: "Bold", children: "B" }),
3361
+ /* @__PURE__ */ jsx(a5, { size: "sm", variant: "ghost", iconOnly: true, ariaLabel: "Italic", children: "I" })
3362
+ ] }) }),
3363
+ /* @__PURE__ */ jsx(Cell, { label: "ResizeHandle", children: /* @__PURE__ */ jsxs("div", { className: "lk-specimen__box", children: [
3364
+ /* @__PURE__ */ jsx("div", { className: "lk-specimen__box-main", children: "content" }),
3365
+ /* @__PURE__ */ jsx(
3366
+ c5,
3367
+ {
3368
+ value: paneWidth,
3369
+ min: 100,
3370
+ max: 220,
3371
+ invert: true,
3372
+ onInput: setPaneWidth,
3373
+ ariaLabel: "Resize"
3374
+ }
3375
+ ),
3376
+ /* @__PURE__ */ jsxs(
3377
+ "div",
3378
+ {
3379
+ className: "lk-specimen__pane",
3380
+ style: { "--lk-specimen-pane-w": `${paneWidth}px` },
3381
+ children: [
3382
+ paneWidth,
3383
+ "px"
3384
+ ]
3385
+ }
3386
+ )
3387
+ ] }) })
3388
+ ] });
3389
+ }
3390
+ function EditorsAndPlots() {
3391
+ const [curve, setCurve] = useState([
3392
+ { x: 0, y: 0 },
3393
+ { x: 0.3, y: 0.1 },
3394
+ { x: 0.7, y: 0.9 },
3395
+ { x: 1, y: 1 }
3396
+ ]);
3397
+ const [points, setPoints] = useState([
3398
+ { x: 0.2, y: 0.3 },
3399
+ { x: 0.5, y: 0.6 },
3400
+ { x: 0.8, y: 0.2 }
3401
+ ]);
3402
+ const [bands, setBands] = useState([
3403
+ { from: 1 / 64, data: { name: "Radial" } },
3404
+ { from: 1 / 12, data: { name: "Name plate" } }
3405
+ ]);
3406
+ const [band, setBand] = useState(null);
3407
+ const [tracks, setTracks] = useState(
3408
+ () => [
3409
+ {
3410
+ kind: "sampled",
3411
+ label: "x",
3412
+ keys: [
3413
+ { t: 0, value: 0 },
3414
+ { t: 800, value: 120, easing: "easeOutCubic" },
3415
+ { t: 1600, value: 40 }
3416
+ ],
3417
+ onTick: () => {
3418
+ }
3419
+ },
3420
+ {
3421
+ kind: "event",
3422
+ label: "footstep",
3423
+ events: [
3424
+ { t: 300, fire: () => {
3425
+ } },
3426
+ { t: 900, fire: () => {
3427
+ } }
3428
+ ]
3429
+ }
3430
+ ]
3431
+ );
3432
+ const [playhead, setPlayhead] = useState(0);
3433
+ return /* @__PURE__ */ jsxs(Section, { title: "Editors & plots", children: [
3434
+ /* @__PURE__ */ jsx(Cell, { label: "Plot2D", children: /* @__PURE__ */ jsx(
3435
+ w2,
3436
+ {
3437
+ width: 260,
3438
+ height: 130,
3439
+ grid: { divisions: 3 },
3440
+ axes: {},
3441
+ xRange: [0, 2e3],
3442
+ yRange: [-0.35, 1.2]
3443
+ }
3444
+ ) }),
3445
+ /* @__PURE__ */ jsx(Cell, { label: "CurveEditor", children: /* @__PURE__ */ jsx(
3446
+ L2,
3447
+ {
3448
+ value: curve,
3449
+ onInput: setCurve,
3450
+ domain: "1d",
3451
+ interpolation: "catmull-rom",
3452
+ endpoints: "pinned-both",
3453
+ addPointMode: "click-curve",
3454
+ axes: {},
3455
+ fill: { side: "below" },
3456
+ width: 260,
3457
+ height: 130
3458
+ }
3459
+ ) }),
3460
+ /* @__PURE__ */ jsx(Cell, { label: "PointPlotter", children: /* @__PURE__ */ jsx(
3461
+ n8,
3462
+ {
3463
+ value: points,
3464
+ onInput: setPoints,
3465
+ xRange: [0, 1],
3466
+ yRange: [0, 1],
3467
+ grid: { divisions: 3 },
3468
+ axes: {},
3469
+ addPointMode: "click-empty",
3470
+ width: 260,
3471
+ height: 130
3472
+ }
3473
+ ) }),
3474
+ /* @__PURE__ */ jsx(Cell, { label: "BandEditor", wide: true, children: /* @__PURE__ */ jsx(
3475
+ k2,
3476
+ {
3477
+ min: 1 / 64,
3478
+ max: 1 / 2,
3479
+ scale: "log",
3480
+ snap: true,
3481
+ value: bands,
3482
+ onChange: setBands,
3483
+ selectedIndex: band,
3484
+ onSelect: setBand,
3485
+ renderBand: (b2) => b2.data.name,
3486
+ ticks: [1 / 24, 1 / 12, 1 / 6].map((at) => ({ at, label: `1/${Math.round(1 / at)}` }))
3487
+ }
3488
+ ) }),
3489
+ /* @__PURE__ */ jsx(Cell, { label: "Timeline", wide: true, children: /* @__PURE__ */ jsx("div", { className: "lk-specimen__timeline", children: /* @__PURE__ */ jsx(
3490
+ $2,
3491
+ {
3492
+ tracks,
3493
+ duration: 2e3,
3494
+ playhead,
3495
+ onChange: setTracks,
3496
+ onScrub: setPlayhead
3497
+ }
3498
+ ) }) })
3499
+ ] });
3500
+ }
3501
+ function LabChrome() {
3502
+ const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
3503
+ const [zoom, setZoom] = useState(1);
3504
+ return /* @__PURE__ */ jsxs(Section, { title: "Lab chrome", children: [
3505
+ /* @__PURE__ */ jsx(Cell, { label: "Toolbar", wide: true, children: /* @__PURE__ */ jsxs(Toolbar, { "aria-label": "Trial actions", children: [
3506
+ /* @__PURE__ */ jsx(Toolbar.Title, { children: "My Trial" }),
3507
+ /* @__PURE__ */ jsxs(Toolbar.Group, { children: [
3508
+ /* @__PURE__ */ jsx(Toolbar.Button, { onClick: () => {
3509
+ }, children: "Undo" }),
3510
+ /* @__PURE__ */ jsx(Toolbar.Button, { onClick: () => {
3511
+ }, disabled: true, children: "Redo" })
3512
+ ] }),
3513
+ /* @__PURE__ */ jsx(Toolbar.Group, { end: true, children: /* @__PURE__ */ jsx(Toolbar.Button, { onClick: () => {
3514
+ }, children: "Save" }) })
3515
+ ] }) }),
3516
+ /* @__PURE__ */ jsx(Cell, { label: "StatusBar", children: /* @__PURE__ */ jsxs(StatusBar, { children: [
3517
+ /* @__PURE__ */ jsx(StatusBar.Section, { children: "Items: 12" }),
3518
+ /* @__PURE__ */ jsx(StatusBar.Section, { children: "Zoom: 100%" })
3519
+ ] }) }),
3520
+ /* @__PURE__ */ jsx(Cell, { label: "Sidebar", children: /* @__PURE__ */ jsxs("div", { className: "lk-specimen__box", children: [
3521
+ /* @__PURE__ */ jsx("div", { className: "lk-specimen__box-main", children: "main" }),
3522
+ /* @__PURE__ */ jsx(
3523
+ Sidebar,
3524
+ {
3525
+ title: "Controls",
3526
+ collapsed: sidebarCollapsed,
3527
+ onToggle: () => setSidebarCollapsed((v2) => !v2),
3528
+ children: /* @__PURE__ */ jsx("p", { children: "One slider" })
3529
+ }
3530
+ )
3531
+ ] }) }),
3532
+ /* @__PURE__ */ jsxs(Cell, { label: "Legend, JobProgress", children: [
3533
+ /* @__PURE__ */ jsx(Legend, { entries: LEGEND }),
3534
+ /* @__PURE__ */ jsx(JobProgress, { job: JOB })
3535
+ ] }),
3536
+ /* @__PURE__ */ jsxs(Cell, { label: "ScaleIndicator, ZoomControl", children: [
3537
+ /* @__PURE__ */ jsx(ScaleIndicator, { zoom, unit: "ft", pixelsPerUnit: 50 }),
3538
+ /* @__PURE__ */ jsx(ZoomControl, { zoom, onZoomChange: setZoom, min: 0.1, max: 8 })
3539
+ ] }),
3540
+ /* @__PURE__ */ jsx(Cell, { label: "FloatingPanel", children: /* @__PURE__ */ jsx("div", { className: "lk-specimen__floating-host", children: /* @__PURE__ */ jsx(FloatingPanel, { anchor: "top-right", children: /* @__PURE__ */ jsx(Legend, { entries: LEGEND.slice(0, 2) }) }) }) }),
3541
+ /* @__PURE__ */ jsx(Cell, { label: "Split", wide: true, children: /* @__PURE__ */ jsx("div", { className: "lk-specimen__box", children: /* @__PURE__ */ jsx(
3542
+ Split,
3543
+ {
3544
+ sidebar: /* @__PURE__ */ jsx("div", { className: "lk-specimen__pad", children: "sidebar" }),
3545
+ defaultWidth: 160,
3546
+ minWidth: 100,
3547
+ maxWidth: 260,
3548
+ children: /* @__PURE__ */ jsx("div", { className: "lk-specimen__pad", children: "content" })
3549
+ }
3550
+ ) }) })
3551
+ ] });
3552
+ }
3553
+ function Specimen({ className }) {
3554
+ return /* @__PURE__ */ jsxs("div", { className: className ? `lk-specimen ${className}` : "lk-specimen", "data-wzl-portal-host": "", children: [
3555
+ /* @__PURE__ */ jsx(ButtonsAndToggles, {}),
3556
+ /* @__PURE__ */ jsx(FieldsAndPickers, {}),
3557
+ /* @__PURE__ */ jsx(PanelsAndRows, {}),
3558
+ /* @__PURE__ */ jsx(OverlaysAndFeedback, {}),
3559
+ /* @__PURE__ */ jsx(ListsAndData, {}),
3560
+ /* @__PURE__ */ jsx(ToolChrome, {}),
3561
+ /* @__PURE__ */ jsx(EditorsAndPlots, {}),
3562
+ /* @__PURE__ */ jsx(LabChrome, {})
3563
+ ] });
4040
3564
  }
4041
3565
 
4042
- export { AnnotationOverlay, AnnotationTargets, DEFAULT_VIEW, Lab, LabContext, LabPalette, LabShell, LabSwitcher, Trial, TrialBody, TrialChrome, Workspace, addTrial, annotationsFromJSON, applyRules, as2DView, builtinRules, capturePlan, captureTarget, cloneTrial, closeTrial, composeCaptureSvg, createAnnotationScene, createAnnotationStore, createMarkDrawOne, currentPage, defineInstrument, f, fitView, fracContains, fracEncloses, fracToWorld, fromWeaselView, interstellarTheme, isConfigBranch, isStale, markCommands, markSvgNodes, reorderTrials, resetTrial, resolveConfigSchema, resolveMarkStyle, roundFrac, seenFrom, titleCase, toWeaselView, useConfigSchema, useLabContext, validateConfigSchema, worldToFrac };
3566
+ export { ANNOTATION_TOOLS, ANNOTATION_WEASEL_TOOLS, AnnotationOverlay, AnnotationTargets, Lab, LabPalette, LabRoot, LabShell, LabSwitcher, SPECIMEN_SECTIONS, Specimen, Trial, TrialBody, TrialChrome, Workspace, addTrial, annotationToolInfo, annotationsFromJSON, capturePlan, captureTarget, cloneTrial, closeTrial, composeCaptureSvg, createAnnotationScene, createAnnotationStore, createMarkDrawOne, currentPage, defineInstrument, fitView, fracContains, fracEncloses, fracToWorld, fromWeaselView, interstellarTheme, isStale, markCommands, markSvgNodes, reorderTrials, resetTrial, resolveMarkStyle, roundFrac, seenFrom, toWeaselView, validateConfigSchema, worldToFrac };
4043
3567
  //# sourceMappingURL=index.js.map
4044
3568
  //# sourceMappingURL=index.js.map