@weasel-js/labkit 1.6.1 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. package/README.md +3 -3
  2. package/dist/_dts/{CanvasStackContext-Wo6OwIVM.d.ts → CanvasStackContext-Cjy6I-Iv.d.ts} +4 -1
  3. package/dist/_dts/{Stage-DWBBi8IU.d.ts → Stage-D6t7o0pF.d.ts} +59 -18
  4. package/dist/_dts/{index-CKyX0ME_.d.ts → index-BTmaRy6a.d.ts} +10 -6
  5. package/dist/_dts/{types-B7yBTTF9.d.ts → index-DCMA4Fy1.d.ts} +59 -113
  6. package/dist/_dts/{types-DTuViBvc.d.ts → types-BUBSIZuV.d.ts} +18 -8
  7. package/dist/_dts/{useTrialState-CMaV6UD4.d.ts → useTrialState-Bh8bzFgE.d.ts} +1 -1
  8. package/dist/canvas/index.d.ts +8 -6
  9. package/dist/canvas/index.js +6 -6
  10. package/dist/chrome/index.d.ts +6 -4
  11. package/dist/chrome/index.js +11 -10
  12. package/dist/chunk-2ES4W47N.js +8 -0
  13. package/dist/chunk-2ES4W47N.js.map +1 -0
  14. package/dist/{chunk-WPLAANH4.js → chunk-2N7UBSS2.js} +12 -12
  15. package/dist/chunk-2N7UBSS2.js.map +1 -0
  16. package/dist/chunk-4KSC7NXL.js +15491 -0
  17. package/dist/chunk-4KSC7NXL.js.map +1 -0
  18. package/dist/{chunk-SLAQPUI4.js → chunk-CEGFXTBD.js} +10 -63
  19. package/dist/chunk-CEGFXTBD.js.map +1 -0
  20. package/dist/chunk-CZZPXUOU.js +3 -0
  21. package/dist/{chunk-SBXNQW4G.js.map → chunk-CZZPXUOU.js.map} +1 -1
  22. package/dist/{chunk-QGMOA2YE.js → chunk-E23Q6A3Y.js} +296 -104
  23. package/dist/chunk-E23Q6A3Y.js.map +1 -0
  24. package/dist/{chunk-GHGO52SO.js → chunk-E6NARGU7.js} +5 -5
  25. package/dist/{chunk-GHGO52SO.js.map → chunk-E6NARGU7.js.map} +1 -1
  26. package/dist/{chunk-IJJUK4FU.js → chunk-EESJUSYR.js} +5 -6
  27. package/dist/chunk-EESJUSYR.js.map +1 -0
  28. package/dist/{chunk-HXXTULDN.js → chunk-EFXRM6XQ.js} +8 -11
  29. package/dist/chunk-EFXRM6XQ.js.map +1 -0
  30. package/dist/chunk-F4ACIK7Z.js +210 -0
  31. package/dist/chunk-F4ACIK7Z.js.map +1 -0
  32. package/dist/{chunk-6FLXL3I7.js → chunk-FB4MIZOO.js} +77 -45
  33. package/dist/chunk-FB4MIZOO.js.map +1 -0
  34. package/dist/{chunk-AWQG7FCC.js → chunk-FG3EXZDV.js} +3 -3
  35. package/dist/{chunk-AWQG7FCC.js.map → chunk-FG3EXZDV.js.map} +1 -1
  36. package/dist/{chunk-4TT3PNPR.js → chunk-GAHELLJT.js} +95 -95
  37. package/dist/chunk-GAHELLJT.js.map +1 -0
  38. package/dist/{chunk-HKV7LRHY.js → chunk-IBVRMHW5.js} +5 -5
  39. package/dist/{chunk-HKV7LRHY.js.map → chunk-IBVRMHW5.js.map} +1 -1
  40. package/dist/{chunk-ASOK76ZH.js → chunk-JFOJESJF.js} +72 -29
  41. package/dist/chunk-JFOJESJF.js.map +1 -0
  42. package/dist/{chunk-RWNDDKOH.js → chunk-PMLZP7RT.js} +18 -15
  43. package/dist/chunk-PMLZP7RT.js.map +1 -0
  44. package/dist/{chunk-TJ77A4A3.js → chunk-READTDQX.js} +5 -9
  45. package/dist/chunk-READTDQX.js.map +1 -0
  46. package/dist/{chunk-JVBYV6TL.js → chunk-ROGJWDQA.js} +3 -3
  47. package/dist/{chunk-JVBYV6TL.js.map → chunk-ROGJWDQA.js.map} +1 -1
  48. package/dist/{chunk-CGHF6RZV.js → chunk-T6DLGEJB.js} +18 -18
  49. package/dist/chunk-T6DLGEJB.js.map +1 -0
  50. package/dist/{chunk-24W5VEJW.js → chunk-ULYYNWGP.js} +2 -2
  51. package/dist/{chunk-24W5VEJW.js.map → chunk-ULYYNWGP.js.map} +1 -1
  52. package/dist/{chunk-DWMSB4CA.js → chunk-VYVGVBGY.js} +95 -32
  53. package/dist/chunk-VYVGVBGY.js.map +1 -0
  54. package/dist/{chunk-IRTGUQ5O.js → chunk-WLMDTI2Q.js} +16 -6
  55. package/dist/chunk-WLMDTI2Q.js.map +1 -0
  56. package/dist/{chunk-NEXR37YJ.js → chunk-XE5HJOPL.js} +6 -8
  57. package/dist/chunk-XE5HJOPL.js.map +1 -0
  58. package/dist/chunk-XVUKDSYV.js +38 -0
  59. package/dist/chunk-XVUKDSYV.js.map +1 -0
  60. package/dist/{chunk-O6FFFOHS.js → chunk-YCFKW5RX.js} +16 -8
  61. package/dist/chunk-YCFKW5RX.js.map +1 -0
  62. package/dist/{chunk-CQLQPQ4P.js → chunk-ZQIHJXP4.js} +2 -2
  63. package/dist/chunk-ZQIHJXP4.js.map +1 -0
  64. package/dist/config/index.d.ts +28 -13
  65. package/dist/config/index.js +4 -4
  66. package/dist/controls/index.d.ts +4 -7
  67. package/dist/controls/index.js +6 -5
  68. package/dist/dragdrop/index.d.ts +23 -3
  69. package/dist/dragdrop/index.js +4 -3
  70. package/dist/fonts/oswald-tabular-digits.woff2 +0 -0
  71. package/dist/index.d.ts +72 -487
  72. package/dist/index.js +603 -441
  73. package/dist/index.js.map +1 -1
  74. package/dist/job/index.d.ts +8 -0
  75. package/dist/job/index.js +1 -1
  76. package/dist/layers/index.d.ts +6 -5
  77. package/dist/layers/index.js +3 -2
  78. package/dist/loupe/index.d.ts +100 -30
  79. package/dist/loupe/index.js +585 -6
  80. package/dist/loupe/index.js.map +1 -1
  81. package/dist/overview/index.d.ts +4 -2
  82. package/dist/overview/index.js +20 -18
  83. package/dist/overview/index.js.map +1 -1
  84. package/dist/passthrough/weasel-canvas.d.ts +2 -0
  85. package/dist/passthrough/weasel-ui.d.ts +2 -2701
  86. package/dist/passthrough/weasel-ui.js +4 -3
  87. package/dist/primitives/index.d.ts +37 -2
  88. package/dist/primitives/index.js +9 -8
  89. package/dist/state/index.d.ts +7 -5
  90. package/dist/state/index.js +6 -6
  91. package/dist/styles.css +150 -126
  92. package/dist/surface/index.d.ts +10 -2
  93. package/dist/surface/index.js +1 -1
  94. package/dist/undo/index.d.ts +5 -3
  95. package/package.json +14 -13
  96. package/src/annotations/AnnotationOverlay.abandon.test.tsx +143 -0
  97. package/src/annotations/AnnotationOverlay.tsx +52 -33
  98. package/src/annotations/Annotations.less +0 -10
  99. package/src/annotations/Annotations.overlay.test.tsx +31 -0
  100. package/src/annotations/MarkList.tsx +4 -8
  101. package/src/annotations/capture.browser.test.ts +41 -0
  102. package/src/annotations/capture.ts +14 -7
  103. package/src/annotations/drawOne.ts +2 -2
  104. package/src/annotations/frac.test.ts +44 -1
  105. package/src/annotations/frac.ts +46 -18
  106. package/src/annotations/index.ts +1 -0
  107. package/src/annotations/paint.test.ts +76 -46
  108. package/src/annotations/paint.ts +65 -43
  109. package/src/annotations/store.test.ts +212 -2
  110. package/src/annotations/store.ts +130 -27
  111. package/src/annotations/svgNodes.test.ts +28 -19
  112. package/src/annotations/svgNodes.ts +6 -6
  113. package/src/annotations/types.ts +29 -10
  114. package/src/canvas/AGENTS.md +1 -0
  115. package/src/canvas/CameraInput.test.tsx +30 -0
  116. package/src/canvas/CameraInput.tsx +60 -48
  117. package/src/canvas/CanvasStack.tsx +11 -6
  118. package/src/canvas/CanvasStackContext.ts +4 -1
  119. package/src/canvas/LinkedCursor.less +1 -1
  120. package/src/canvas/Stage.test.tsx +33 -4
  121. package/src/canvas/Stage.tsx +9 -4
  122. package/src/canvas/cameraRegistry.test.ts +55 -0
  123. package/src/canvas/cameraRegistry.ts +87 -0
  124. package/src/canvas/cameraZoom.test.ts +46 -0
  125. package/src/canvas/cameraZoom.ts +24 -0
  126. package/src/canvas/index.ts +14 -2
  127. package/src/canvas/sharedScope.test.tsx +94 -0
  128. package/src/canvas/worldSpec.ts +3 -1
  129. package/src/chrome/ChromeRegions.stories.tsx +1 -0
  130. package/src/chrome/builtins.test.ts +7 -6
  131. package/src/chrome/builtins.tsx +5 -1
  132. package/src/chrome/index.ts +1 -1
  133. package/src/chrome/merge.test.ts +5 -0
  134. package/src/chrome/merge.ts +4 -2
  135. package/src/chrome/regions/PaletteRegion.less +0 -7
  136. package/src/chrome/regions/PaletteRegion.test.tsx +2 -3
  137. package/src/chrome/regions/PaletteRegion.tsx +3 -4
  138. package/src/chrome/regions/SidebarRegion.less +12 -2
  139. package/src/chrome/regions/ViewportRegion.browser.test.less +6 -0
  140. package/src/chrome/regions/ViewportRegion.browser.test.tsx +38 -0
  141. package/src/chrome/regions/ViewportRegion.less +6 -0
  142. package/src/chrome/regions/ViewportRegion.tsx +10 -4
  143. package/src/chrome/types.ts +4 -2
  144. package/src/config/auto.ts +1 -0
  145. package/src/config/builder.test.ts +2 -1
  146. package/src/config/builder.ts +18 -5
  147. package/src/config/fromConfigField.test.ts +14 -0
  148. package/src/config/fromConfigField.ts +18 -4
  149. package/src/config/path.ts +2 -5
  150. package/src/config/resolve.ts +4 -0
  151. package/src/config/types.ts +14 -9
  152. package/src/controls/ControlPanel.less +1 -1
  153. package/src/controls/ControlPanel.stories.tsx +3 -2
  154. package/src/controls/ControlPanel.test.tsx +98 -11
  155. package/src/controls/ControlPanel.tsx +61 -2
  156. package/src/dragdrop/DragDropRuntime.tsx +22 -13
  157. package/src/dragdrop/DragGhost.tsx +3 -0
  158. package/src/dragdrop/Palette.tsx +2 -0
  159. package/src/index.test.ts +5 -0
  160. package/src/index.ts +16 -2
  161. package/src/instrument/types.ts +0 -6
  162. package/src/job/useJob.ts +14 -9
  163. package/src/lab/Lab.tsx +88 -73
  164. package/src/lab/{LabFit.stories.less → LabFit.browser.test.less} +1 -1
  165. package/src/lab/{LabFit.stories.tsx → LabFit.browser.test.tsx} +41 -63
  166. package/src/lab/LabFullChrome.stories.tsx +1 -0
  167. package/src/lab/LabHeader.test.tsx +24 -14
  168. package/src/lab/LabHeader.tsx +11 -12
  169. package/src/lab/LabShell.less +9 -17
  170. package/src/lab/LabSwitcher.browser.test.tsx +27 -0
  171. package/src/lab/LabSwitcher.less +8 -6
  172. package/src/lab/LabZoom.test.tsx +223 -0
  173. package/src/lab/LabZoom.tsx +170 -0
  174. package/src/lab/Workspace.tsx +48 -52
  175. package/src/lab/index.ts +1 -0
  176. package/src/loupe/AGENTS.md +28 -15
  177. package/src/loupe/CanvasLoupe.tsx +2 -3
  178. package/src/loupe/DomLoupe.tsx +1 -7
  179. package/src/loupe/Loupe.less +16 -0
  180. package/src/loupe/LoupeGestures.tsx +1 -1
  181. package/src/loupe/TrialLoupe.tsx +120 -57
  182. package/src/loupe/index.ts +1 -6
  183. package/src/loupe/types.test.ts +3 -3
  184. package/src/loupe/types.ts +24 -27
  185. package/src/loupe/useLoupe.test.tsx +29 -4
  186. package/src/loupe/useLoupe.ts +20 -17
  187. package/src/overview/OverviewMarks.tsx +12 -4
  188. package/src/overview/TrialOverview.tsx +10 -8
  189. package/src/passthrough/weasel-ui.test.ts +2 -0
  190. package/src/passthrough/weasel-ui.ts +17 -5
  191. package/src/primitives/FloatingPanel.test.tsx +11 -0
  192. package/src/primitives/FloatingPanel.tsx +8 -7
  193. package/src/primitives/JobProgress.less +2 -1
  194. package/src/primitives/Legend.stories.tsx +1 -0
  195. package/src/primitives/Readout.browser.test.tsx +137 -0
  196. package/src/primitives/Readout.less +12 -0
  197. package/src/primitives/Readout.stories.tsx +58 -0
  198. package/src/primitives/Readout.test.tsx +93 -0
  199. package/src/primitives/Readout.tsx +56 -0
  200. package/src/primitives/Split.tsx +69 -41
  201. package/src/primitives/Toolbar.tsx +3 -3
  202. package/src/primitives/ZoomControl.less +1 -2
  203. package/src/primitives/index.ts +2 -0
  204. package/src/specimen/Specimen.stories.tsx +1 -0
  205. package/src/specimen/Specimen.tsx +2 -3
  206. package/src/state/Persistence.browser.test.tsx +47 -0
  207. package/src/state/store.test.ts +5 -1
  208. package/src/state/undock.ts +3 -0
  209. package/src/state/useOpenOnce.ts +20 -16
  210. package/src/state/usePersistedState.test.tsx +1 -1
  211. package/src/state/view.ts +1 -0
  212. package/src/styles.less +1 -0
  213. package/src/surface/index.ts +1 -1
  214. package/src/surface/useTiledSurface.test.tsx +14 -0
  215. package/src/surface/useTiledSurface.ts +11 -3
  216. package/src/theme/Interstellar.stories.tsx +3 -1
  217. package/src/theme/base.less +21 -47
  218. package/src/theme/interstellar.test.ts +1 -1
  219. package/src/theme/interstellar.theme.json +10 -3
  220. package/src/trial/Trial.annotations.test.tsx +73 -0
  221. package/src/trial/Trial.less +0 -29
  222. package/src/trial/Trial.loupe.test.tsx +28 -11
  223. package/src/trial/Trial.tsx +85 -67
  224. package/src/trial/TrialChrome.tsx +27 -6
  225. package/src/trial/index.ts +2 -0
  226. package/src/trial/loupeSwitch.ts +15 -0
  227. package/dist/_dts/LayerList.d-BoELAMsM.d.ts +0 -83
  228. package/dist/_dts/PrefsRow.d-DFpPIeIC.d.ts +0 -39
  229. package/dist/_dts/PropertyField.d-D0ndfIIt.d.ts +0 -404
  230. package/dist/_dts/Select.d-Cr8-zoup.d.ts +0 -569
  231. package/dist/chunk-4TT3PNPR.js.map +0 -1
  232. package/dist/chunk-6FLXL3I7.js.map +0 -1
  233. package/dist/chunk-ASOK76ZH.js.map +0 -1
  234. package/dist/chunk-CGHF6RZV.js.map +0 -1
  235. package/dist/chunk-CQLQPQ4P.js.map +0 -1
  236. package/dist/chunk-DWMSB4CA.js.map +0 -1
  237. package/dist/chunk-HXXTULDN.js.map +0 -1
  238. package/dist/chunk-IJJUK4FU.js.map +0 -1
  239. package/dist/chunk-IRTGUQ5O.js.map +0 -1
  240. package/dist/chunk-KHK5FGER.js +0 -145
  241. package/dist/chunk-KHK5FGER.js.map +0 -1
  242. package/dist/chunk-NEXR37YJ.js.map +0 -1
  243. package/dist/chunk-O6FFFOHS.js.map +0 -1
  244. package/dist/chunk-QGMOA2YE.js.map +0 -1
  245. package/dist/chunk-RWNDDKOH.js.map +0 -1
  246. package/dist/chunk-S4NCI32V.js +0 -549
  247. package/dist/chunk-S4NCI32V.js.map +0 -1
  248. package/dist/chunk-SBXNQW4G.js +0 -3
  249. package/dist/chunk-SLAQPUI4.js.map +0 -1
  250. package/dist/chunk-TJ77A4A3.js.map +0 -1
  251. package/dist/chunk-TYN6MLXX.js +0 -13864
  252. package/dist/chunk-TYN6MLXX.js.map +0 -1
  253. package/dist/chunk-WPLAANH4.js.map +0 -1
  254. package/src/primitives/useRovingTabIndex.test.ts +0 -48
  255. package/src/primitives/useRovingTabIndex.ts +0 -88
  256. package/src/state/Persistence.stories.tsx +0 -75
package/dist/index.js CHANGED
@@ -1,66 +1,67 @@
1
- import { resolveLoupe, TrialLoupe } from './chunk-S4NCI32V.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-S4NCI32V.js';
1
+ import { LoupeSwitchContext } from './chunk-2ES4W47N.js';
2
+ export { LoupeSwitchContext } from './chunk-2ES4W47N.js';
3
3
  import { createEventBus, pushSnapshot, redo, undo } from './chunk-RL2LOLNI.js';
4
4
  export { clearUndo, createEventBus, emptyStack, pushSnapshot, redo, undo } from './chunk-RL2LOLNI.js';
5
- import { useDragDrop, DragOverlay, Palette } from './chunk-WPLAANH4.js';
6
- import { useTiledSurface } from './chunk-IJJUK4FU.js';
7
- export { composeRects, rectsEqual, toDeviceRect, useTiledSurface } from './chunk-IJJUK4FU.js';
8
- import { useJob } from './chunk-HXXTULDN.js';
9
- export { useJob } from './chunk-HXXTULDN.js';
10
- import { markCommands, markSvgNodes } from './chunk-KHK5FGER.js';
11
- export { markCommands, markSvgNodes } from './chunk-KHK5FGER.js';
12
- import { ANNOTATION_WEASEL_TOOLS, annotationToolInfo, suppressContributions, mergeContributions, builtinContributions, TitleBarRegion, ToolbarRegion, PaletteRegion, ViewportRegion, SidebarRegion, StatusRegion, resolveLabTool, useLabChromeContext, labContributions, labAnnotationTools, LabHeaderRegion, LabFooterRegion, LabAsideRegion, LabSidebarRegion } from './chunk-4TT3PNPR.js';
13
- export { ANNOTATION_TOOLS, ANNOTATION_WEASEL_TOOLS, ExportMenu, LabAsideRegion, LabFooterRegion, LabHeaderRegion, LabSidebarRegion, MarkList, PaletteRegion, SidebarRegion, StatusRegion, TitleBarRegion, ToolbarRegion, ViewportRegion, annotationToolInfo, builtinContributions, contributionsIn, labContributions, mergeContributions, suppressContributions, toolContributions, useLabChromeContext } from './chunk-4TT3PNPR.js';
5
+ import { useDragDrop, DragOverlay, Palette } from './chunk-2N7UBSS2.js';
6
+ import { useTiledSurface } from './chunk-EESJUSYR.js';
7
+ export { composeRects, toDeviceRect, useTiledSurface } from './chunk-EESJUSYR.js';
8
+ import { useJob } from './chunk-EFXRM6XQ.js';
9
+ export { useJob } from './chunk-EFXRM6XQ.js';
10
+ import { markCommands, boundsOf, roundFrac, worldToFrac, fracToWorld, fracEncloses, fracContains, POINT_MARK_SHAPE, roundPoints, fromShape, markSvgNodes, toShape } from './chunk-F4ACIK7Z.js';
11
+ export { fracContains, fracEncloses, fracToWorld, markCommands, markSvgNodes, roundFrac, worldToFrac } from './chunk-F4ACIK7Z.js';
12
+ import { ANNOTATION_WEASEL_TOOLS, annotationToolInfo, suppressContributions, mergeContributions, builtinContributions, TRANSIENT_BUILTINS, TitleBarRegion, ToolbarRegion, PaletteRegion, ViewportRegion, SidebarRegion, StatusRegion, resolveLabTool, useLabChromeContext, labContributions, labAnnotationTools, LabHeaderRegion, LabFooterRegion, LabAsideRegion, LabSidebarRegion } from './chunk-GAHELLJT.js';
13
+ export { ANNOTATION_TOOLS, ANNOTATION_WEASEL_TOOLS, ExportMenu, LabAsideRegion, LabFooterRegion, LabHeaderRegion, LabSidebarRegion, MarkList, PaletteRegion, SidebarRegion, StatusRegion, TRANSIENT_BUILTINS, TitleBarRegion, ToolbarRegion, ViewportRegion, annotationToolInfo, builtinContributions, contributionsIn, labContributions, mergeContributions, suppressContributions, toolContributions, useLabChromeContext } from './chunk-GAHELLJT.js';
14
14
  import { AnnotationsContext } from './chunk-3RY6WL5T.js';
15
15
  export { AnnotationsContext, useAnnotations, useAnnotationsOptional } from './chunk-3RY6WL5T.js';
16
- import { Split, JobProgress, Legend } from './chunk-6FLXL3I7.js';
17
- export { JobProgress, Legend, Split } from './chunk-6FLXL3I7.js';
18
- import { FloatingPanel } from './chunk-NEXR37YJ.js';
19
- export { FloatingPanel } from './chunk-NEXR37YJ.js';
20
- import { Toolbar, StatusBar, ScaleIndicator, ZoomControl } from './chunk-SLAQPUI4.js';
21
- export { FpsMeter, ScaleIndicator, StatusBar, Toolbar, ZoomControl } from './chunk-SLAQPUI4.js';
22
- import './chunk-SBXNQW4G.js';
23
- import { unruledConfigSchema, createLabStore, openLabStore, panelKey } from './chunk-HKV7LRHY.js';
24
- export { CURRENT_DOCUMENT_VERSION, dockPanel, labDocumentKey, openLabStore, panelKey, quarantineKey, undockPanel, useTrialState } from './chunk-HKV7LRHY.js';
25
- import { newId, useWarnIgnoredChange, useOpenOnce, PersistenceContext, createRecordCache, noneAdapter, defaultStorage, usePersistedState } from './chunk-RWNDDKOH.js';
26
- export { Persistence, createIndexedDbAdapter, createMemoryAdapter, indexedDbAdapter, localStorageAdapter, noneAdapter, sessionStorageAdapter, urlHashAdapter, usePersistedState } from './chunk-RWNDDKOH.js';
27
- export { ControlPanel, inDialog, summarizeValue } from './chunk-ASOK76ZH.js';
28
- import { useConfigSchema } from './chunk-GHGO52SO.js';
29
- export { sectionTree, useConfigSchema } from './chunk-GHGO52SO.js';
16
+ import { Split, JobProgress, Legend } from './chunk-FB4MIZOO.js';
17
+ export { JobProgress, Legend, Readout, Split } from './chunk-FB4MIZOO.js';
18
+ import { FloatingPanel } from './chunk-XE5HJOPL.js';
19
+ export { FloatingPanel } from './chunk-XE5HJOPL.js';
20
+ import { Toolbar, StatusBar, ScaleIndicator, ZoomControl } from './chunk-CEGFXTBD.js';
21
+ export { FpsMeter, ScaleIndicator, StatusBar, Toolbar, ZoomControl } from './chunk-CEGFXTBD.js';
22
+ import './chunk-CZZPXUOU.js';
23
+ import { unruledConfigSchema, createLabStore, openLabStore, panelKey } from './chunk-IBVRMHW5.js';
24
+ export { CURRENT_DOCUMENT_VERSION, dockPanel, labDocumentKey, openLabStore, panelKey, quarantineKey, undockPanel, useTrialState } from './chunk-IBVRMHW5.js';
25
+ import { newId, useWarnIgnoredChange, useOpenOnce, PersistenceContext, createRecordCache, noneAdapter, defaultStorage, usePersistedState } from './chunk-PMLZP7RT.js';
26
+ export { Persistence, createIndexedDbAdapter, createMemoryAdapter, indexedDbAdapter, localStorageAdapter, noneAdapter, sessionStorageAdapter, urlHashAdapter, usePersistedState } from './chunk-PMLZP7RT.js';
27
+ export { ControlPanel, inDialog, summarizeValue } from './chunk-JFOJESJF.js';
28
+ import { useConfigSchema } from './chunk-E6NARGU7.js';
29
+ export { sectionTree, useConfigSchema } from './chunk-E6NARGU7.js';
30
30
  import { useLabContext, LabContext } from './chunk-Z76KEZAX.js';
31
31
  export { LabContext, useLabContext } from './chunk-Z76KEZAX.js';
32
- import { useResolvedConfig } from './chunk-O6FFFOHS.js';
33
- export { BaseNode, BooleanNode, ColorNode, CustomNode, EnumNode, GroupNode, ListNode, NumberNode, StringNode, ValueNode, applyRules, builtinRules, f, isConfigBranch, resolveConfigSchema, titleCase, useResolvedConfig } from './chunk-O6FFFOHS.js';
34
- export { fromConfigFields, isLeafVisible } from './chunk-IRTGUQ5O.js';
35
- import { hasConfigPath, schemaNodeAtPath, applyConfigWrite, resolveAutoConfig, isAuto, isRecord, autoPathsOf, fillConfigDefaults } from './chunk-TJ77A4A3.js';
36
- export { auto, autoPathsOf, fillConfigDefaults, hasConfigPath, isAuto, resolveAutoConfig, schemaNodeAtPath, valueAtPath, withValueAtPath } from './chunk-TJ77A4A3.js';
37
- import { CanvasStack, Stage, fitStage } from './chunk-CGHF6RZV.js';
38
- export { CanvasStack, Stage, fitStage } from './chunk-CGHF6RZV.js';
39
- export { centerOn, zoomAt } from './chunk-JVBYV6TL.js';
40
- export { screenToWorld, worldToScreen } from './chunk-AWQG7FCC.js';
32
+ import { useResolvedConfig } from './chunk-YCFKW5RX.js';
33
+ export { BaseNode, BooleanNode, ColorNode, CustomNode, EnumNode, GroupNode, ListNode, NumberNode, StringNode, ValueNode, applyRules, builtinRules, f, isConfigBranch, resolveConfigSchema, titleCase, useResolvedConfig } from './chunk-YCFKW5RX.js';
34
+ export { fromConfigFields, isLeafVisible } from './chunk-WLMDTI2Q.js';
35
+ import { hasConfigPath, schemaNodeAtPath, applyConfigWrite, resolveAutoConfig, isAuto, isRecord, autoPathsOf, fillConfigDefaults } from './chunk-READTDQX.js';
36
+ export { auto, autoPathsOf, fillConfigDefaults, hasConfigPath, isAuto, resolveAutoConfig, schemaNodeAtPath, valueAtPath, withValueAtPath } from './chunk-READTDQX.js';
37
+ import { CanvasStack, Stage, fitStage } from './chunk-T6DLGEJB.js';
38
+ export { CanvasStack, Stage, fitStage } from './chunk-T6DLGEJB.js';
39
+ export { centerOn, zoomAt } from './chunk-ROGJWDQA.js';
40
+ export { screenToWorld, worldToScreen } from './chunk-FG3EXZDV.js';
41
41
  import { useSurfaceOptional, useSurfaceCanvas, useTileId, SurfaceContext, SurfaceCanvasContext } from './chunk-WIG6XHJ7.js';
42
42
  export { SurfaceCanvasContext, SurfaceContext, useSurface, useSurfaceCanvas, useSurfaceOptional, useSurfaceTile, useTileId } from './chunk-WIG6XHJ7.js';
43
- import { CameraWheelContext, as2DView, DEFAULT_VIEW, CameraScopeContext, withZoom } from './chunk-DWMSB4CA.js';
44
- export { CameraContext, CameraInput, CameraScope, DEFAULT_VIEW, as2DView } from './chunk-DWMSB4CA.js';
45
- import { applyCamera } from './chunk-24W5VEJW.js';
46
- export { DEFAULT_FRAME, applyCamera, resolveFrame } from './chunk-24W5VEJW.js';
47
- import { CanvasStackContext } from './chunk-CQLQPQ4P.js';
48
- export { CanvasStackContext } from './chunk-CQLQPQ4P.js';
43
+ import { CameraWheelContext, as2DView, CameraRegistryContext, DEFAULT_VIEW, CameraPublishContext, createCameraRegistry, withZoom } from './chunk-VYVGVBGY.js';
44
+ export { CameraContext, CameraInput, CameraPublishContext, CameraRegistryContext, CameraScope, DEFAULT_VIEW, as2DView, createCameraRegistry, usePublishCamera } from './chunk-VYVGVBGY.js';
45
+ import { applyCamera } from './chunk-ULYYNWGP.js';
46
+ export { DEFAULT_FRAME, applyCamera, resolveFrame } from './chunk-ULYYNWGP.js';
47
+ import { CanvasStackContext } from './chunk-ZQIHJXP4.js';
48
+ export { CanvasStackContext } from './chunk-ZQIHJXP4.js';
49
49
  import { LabStoreContext, TrialIdProvider, TrialIdContext } from './chunk-2YILQ7Y5.js';
50
50
  export { LabStoreContext, LabStoreProvider, TrialIdContext, TrialIdProvider, useLabStore, useTrialId } from './chunk-2YILQ7Y5.js';
51
51
  import './chunk-2RVCB2PT.js';
52
- import { D, S2, G, d2, s3, c, Ht, o3, a6, l5, u5, a5, r3, a7, i2, o5, s6, c5, l7, p, f2, u4, g, T, y2, l8, y, c6, i3, l4, C2, S, ae2, oe, B2, b2, A2, n6, s5, m3, l2, u, g2, u3, S4, r4, n7, r5, i4, p2, f, n8, o4, c3, c4, w, L, n9, k2, $2 } from './chunk-TYN6MLXX.js';
53
- export { c as Button, V as CloseIcon, xe as CollapseIcon, _ as CurveField, Se as ExpandIcon, l as Icon, u4 as Input, l9 as Jog, D as LayerList, re as PauseIcon, ne as PlayIcon, V2 as PropertyControl, B2 as PropertyField, b2 as PropertyGroup, oe as PropertyList, ae2 as PropertyPanel, L3 as PropertyRow, se as PropertySpan, B as ResetIcon, T as Select, ae as StepBackIcon, $ as StepForwardIcon, ie as StopIcon, x as Subpanel, a5 as ToggleBar, oe2 as TokenPanel, H2 as inferTokenType, S2 as moveLayers, O2 as refitScale } from './chunk-TYN6MLXX.js';
54
- import { serializeSvg } from './chunk-QGMOA2YE.js';
55
- import { createScene, asNodeId as asNodeId$1, WeaselProvider, SceneCanvas, createPointerStore, PointerContextProvider, sceneFromJSON, renderSceneToPixels, useActiveToolContext } from '@weasel-js/core';
56
- import { createContext, useState, useRef, useEffect, useCallback, useContext, useMemo, useId, Children, useLayoutEffect, useSyncExternalStore } from 'react';
52
+ import { D2, S2, X, U, G, p4, o3, c, Xt, c5, a7, l4, u3, a6, r3, a9, i2, o5, s3, c6, l8, p, f2, u2, y, O2, b4, l9, S, c7, i4, h2, j, k2, oe, se, R, b3, j2, n6, a10, C3, l2, u, v2, d3, j3, r4, n7, r5, i5, p2, f, n8, o4, c4, l7, w, L, n9, j4, be, n2 } from './chunk-4KSC7NXL.js';
53
+ export { c as Button, V as CloseIcon, xe as CollapseIcon, _ as CurveField, i3 as DetailList, a4 as DetailRow, Se as ExpandIcon, l as Icon, u2 as Input, l10 as Jog, D2 as LayerList, re as PauseIcon, ne as PlayIcon, z2 as PropertyControl, R as PropertyField, b3 as PropertyGroup, se as PropertyList, oe as PropertyPanel, F2 as PropertyRow, ce as PropertySpan, B as ResetIcon, O2 as Select, ae as StepBackIcon, $ as StepForwardIcon, ie as StopIcon, x2 as Subpanel, a6 as ToggleBar, oe2 as TokenPanel, H2 as inferTokenType, S2 as moveLayers, O3 as refitScale } from './chunk-4KSC7NXL.js';
54
+ import { serializeSvg } from './chunk-E23Q6A3Y.js';
55
+ import './chunk-XVUKDSYV.js';
56
+ import { makeViewportZoomAction, createScene, asNodeId as asNodeId$1, registerNodeShape, useLatest, WeaselProvider, SceneCanvas, createPointerStore, PointerContextProvider, DepRegistryProvider, ActionsProvider, ActiveToolContextProvider, useActionsRegistry, useDepSource, ActionDisabledReason, useGestureDispatcher, sceneFromJSON, warmRender, renderSceneToPixels, useActiveToolContext } from '@weasel-js/core';
57
+ import { createContext, useState, useRef, useEffect, useInsertionEffect, useCallback, useContext, useMemo, useSyncExternalStore, useLayoutEffect, useId, Children } from 'react';
57
58
  import { flushSync, createPortal } from 'react-dom';
58
59
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
59
60
  import { defineTheme, weaselTheme, colorCount, resolveTheme, colorAt } from '@weasel-js/theme';
60
61
  import { useResolvedColorMode, useThemeOptional, ThemeProvider, ThemeContext } from '@weasel-js/theme/react';
61
62
  import { useStore } from 'zustand/react';
62
63
  import { Provider, StrategyRegistryProvider, DragProvider, Container, useDragHandle } from 'windease/react';
63
- import { asNodeId, Store, createNode, floatingStrategy, gridStrategy } from 'windease';
64
+ import { asNodeId, createNode, floatingStrategy, gridStrategy, Store } from 'windease';
64
65
  import { Focusable } from 'react-aria-components';
65
66
 
66
67
  // src/annotations/staleness.ts
@@ -86,11 +87,11 @@ function isStale(seen, config, keys) {
86
87
 
87
88
  // src/annotations/drawOne.ts
88
89
  function createMarkDrawOne(opts) {
89
- return (node, pose) => markCommands({ pose, data: node.data }, opts.content, resolveMarkStyle(node.data, opts));
90
+ return (node, pose, view) => markCommands({ pose, data: node.data }, resolveMarkStyle(node.data, opts), view.scale);
90
91
  }
91
92
  function resolveMarkStyle(data, opts) {
92
93
  return {
93
- color: opts.meaning?.statuses?.find((s4) => s4.id === data.status)?.color,
94
+ color: opts.meaning?.statuses?.find((s2) => s2.id === data.status)?.color,
94
95
  // Read off the node rather than through the store's `isStale`, which
95
96
  // wants a projected Annotation this path does not have.
96
97
  stale: isStale(data.seen, opts.config, opts.positionDependsOn ?? [])
@@ -103,7 +104,7 @@ function capturePlan(base, format) {
103
104
  if (format === "svg") return "svg-document";
104
105
  return base?.kind === "svg" ? "svg-document" : "raster-stack";
105
106
  }
106
- function nestBaseSvg(markup, w2, h) {
107
+ function nestBaseSvg(markup, w2, h2) {
107
108
  const doc = new DOMParser().parseFromString(markup, "image/svg+xml");
108
109
  const root = doc.documentElement;
109
110
  if (doc.getElementsByTagName("parsererror").length > 0 || root.localName !== "svg") {
@@ -112,40 +113,40 @@ function nestBaseSvg(markup, w2, h) {
112
113
  root.setAttribute("x", "0");
113
114
  root.setAttribute("y", "0");
114
115
  root.setAttribute("width", String(w2));
115
- root.setAttribute("height", String(h));
116
+ root.setAttribute("height", String(h2));
116
117
  return new XMLSerializer().serializeToString(root);
117
118
  }
118
- function embedRasterBase(base, w2, h, onWarn) {
119
+ function embedRasterBase(base, w2, h2, onWarn) {
119
120
  const href = base.kind === "canvas" ? base.canvas.toDataURL() : base.kind === "image" ? base.src : "";
120
121
  if (base.kind === "image" && !href.startsWith("data:")) {
121
122
  onWarn?.(`base image '${href}' is a reference, so this SVG is not self-contained`);
122
123
  }
123
- return serializeSvg([{ kind: "image", href, x: 0, y: 0, width: w2, height: h }], {
124
- viewBox: { x: 0, y: 0, width: w2, height: h }
124
+ return serializeSvg([{ kind: "image", href, x: 0, y: 0, width: w2, height: h2 }], {
125
+ viewBox: { x: 0, y: 0, width: w2, height: h2 }
125
126
  });
126
127
  }
127
- function markNodesOf(scene, draw) {
128
+ function markNodesOf(scene, draw, scale) {
128
129
  return [...scene.renderOrder()].flatMap((id) => {
129
130
  const node = scene.get(asNodeId$1(String(id)));
130
131
  if (!node) return [];
131
132
  return markSvgNodes(
132
133
  { pose: node.pose, data: node.data },
133
- draw.content,
134
- resolveMarkStyle(node.data, draw)
134
+ resolveMarkStyle(node.data, draw),
135
+ { x: scale, y: scale }
135
136
  );
136
137
  });
137
138
  }
138
139
  function composeCaptureSvg(args) {
139
140
  const { base, scene, draw, scale, onWarn } = args;
140
- const { w: w2, h } = draw.content;
141
- const baseXml = base === void 0 ? "" : base.kind === "svg" ? nestBaseSvg(base.markup, w2, h) : embedRasterBase(base, w2, h, onWarn);
142
- const marksXml = serializeSvg(markNodesOf(scene, draw), {
143
- viewBox: { x: 0, y: 0, width: w2, height: h },
141
+ const { w: w2, h: h2 } = draw.content;
142
+ const baseXml = base === void 0 ? "" : base.kind === "svg" ? nestBaseSvg(base.markup, w2, h2) : embedRasterBase(base, w2, h2, onWarn);
143
+ const marksXml = serializeSvg(markNodesOf(scene, draw, scale), {
144
+ viewBox: { x: 0, y: 0, width: w2, height: h2 },
144
145
  onWarn
145
146
  });
146
147
  const outW = Math.max(1, Math.round(w2 * scale));
147
- const outH = Math.max(1, Math.round(h * scale));
148
- return `<svg xmlns="${SVG_NS}" width="${outW}" height="${outH}" viewBox="0 0 ${w2} ${h}">${baseXml}${marksXml}</svg>`;
148
+ const outH = Math.max(1, Math.round(h2 * scale));
149
+ return `<svg xmlns="${SVG_NS}" width="${outW}" height="${outH}" viewBox="0 0 ${w2} ${h2}">${baseXml}${marksXml}</svg>`;
149
150
  }
150
151
  function context2d(width, height) {
151
152
  const canvas = document.createElement("canvas");
@@ -195,12 +196,14 @@ async function stackRaster(args) {
195
196
  const ctx = context2d(width, height);
196
197
  if (base?.kind === "image") ctx.drawImage(await loadImage(base.src), 0, 0, width, height);
197
198
  else if (base?.kind === "canvas") ctx.drawImage(base.canvas, 0, 0, width, height);
198
- const raster = renderSceneToPixels({
199
+ const render = {
199
200
  scene,
200
201
  sourceRect: { x: 0, y: 0, width: draw.content.w, height: draw.content.h },
201
202
  scale: { x: scale, y: scale },
202
203
  drawOne: createMarkDrawOne(draw)
203
- });
204
+ };
205
+ await warmRender({ render });
206
+ const raster = renderSceneToPixels(render);
204
207
  const marks = context2d(raster.width, raster.height);
205
208
  marks.putImageData(
206
209
  new ImageData(Uint8ClampedArray.from(raster.data), raster.width, raster.height),
@@ -238,36 +241,6 @@ async function captureTarget(args, opts = {}) {
238
241
  return { target: args.target, blob, format, width, height };
239
242
  }
240
243
 
241
- // src/annotations/frac.ts
242
- function fracToWorld(f4, content) {
243
- return {
244
- x: f4.x * content.w,
245
- y: f4.y * content.h,
246
- width: f4.w * content.w,
247
- height: f4.h * content.h
248
- };
249
- }
250
- function worldToFrac(r4, content) {
251
- const sx = content.w === 0 ? 0 : 1 / content.w;
252
- const sy = content.h === 0 ? 0 : 1 / content.h;
253
- return { x: r4.x * sx, y: r4.y * sy, w: r4.width * sx, h: r4.height * sy };
254
- }
255
- var DP = 1e4;
256
- function roundFrac(f4) {
257
- return {
258
- x: Math.round(f4.x * DP) / DP,
259
- y: Math.round(f4.y * DP) / DP,
260
- w: Math.round(f4.w * DP) / DP,
261
- h: Math.round(f4.h * DP) / DP
262
- };
263
- }
264
- function fracContains(box, pt, tol = 0) {
265
- return pt.x >= box.x - tol && pt.x <= box.x + box.w + tol && pt.y >= box.y - tol && pt.y <= box.y + box.h + tol;
266
- }
267
- function fracEncloses(outer, inner) {
268
- return inner.x >= outer.x && inner.y >= outer.y && inner.x + inner.w <= outer.x + outer.w && inner.y + inner.h <= outer.y + outer.h;
269
- }
270
-
271
244
  // src/annotations/history.ts
272
245
  var MarkHistory = class {
273
246
  depth = /* @__PURE__ */ new Map();
@@ -300,8 +273,8 @@ var MarkHistory = class {
300
273
  /** The target `undo` would act on, skipping any whose scene has nothing
301
274
  * left — a scene can drop entries this ordering still lists. */
302
275
  next(stack, can, at) {
303
- for (let i4 = stack.length - 1; i4 >= 0; i4--) {
304
- const target = stack[i4];
276
+ for (let i5 = stack.length - 1; i5 >= 0; i5--) {
277
+ const target = stack[i5];
305
278
  if (target === void 0) continue;
306
279
  const scene = at(target);
307
280
  if (scene && can(scene)) return target;
@@ -309,13 +282,13 @@ var MarkHistory = class {
309
282
  return null;
310
283
  }
311
284
  canUndo(at) {
312
- return this.next(this.undoable, (s4) => s4.canUndo(), at) !== null;
285
+ return this.next(this.undoable, (s2) => s2.canUndo(), at) !== null;
313
286
  }
314
287
  canRedo(at) {
315
- return this.next(this.redoable, (s4) => s4.canRedo(), at) !== null;
288
+ return this.next(this.redoable, (s2) => s2.canRedo(), at) !== null;
316
289
  }
317
290
  undo(at) {
318
- const target = this.next(this.undoable, (s4) => s4.canUndo(), at);
291
+ const target = this.next(this.undoable, (s2) => s2.canUndo(), at);
319
292
  const scene = target === null ? void 0 : at(target);
320
293
  if (!scene) return false;
321
294
  this.replaying = "undo";
@@ -326,7 +299,7 @@ var MarkHistory = class {
326
299
  }
327
300
  }
328
301
  redo(at) {
329
- const target = this.next(this.redoable, (s4) => s4.canRedo(), at);
302
+ const target = this.next(this.redoable, (s2) => s2.canRedo(), at);
330
303
  const scene = target === null ? void 0 : at(target);
331
304
  if (!scene) return false;
332
305
  this.replaying = "redo";
@@ -338,9 +311,9 @@ var MarkHistory = class {
338
311
  }
339
312
  };
340
313
  function drop(stack, value) {
341
- for (let i4 = stack.length - 1; i4 >= 0; i4--) {
342
- if (stack[i4] === value) {
343
- stack.splice(i4, 1);
314
+ for (let i5 = stack.length - 1; i5 >= 0; i5--) {
315
+ if (stack[i5] === value) {
316
+ stack.splice(i5, 1);
344
317
  return;
345
318
  }
346
319
  }
@@ -349,6 +322,19 @@ function createAnnotationScene() {
349
322
  return createScene({ systemLayers: [{ id: "marks" }] });
350
323
  }
351
324
  var NO_CONTENT = { w: 0, h: 0 };
325
+ function shapeFor(kind, points, frac) {
326
+ if (!points || kind === "point") return void 0;
327
+ return roundPoints(toShape(points, frac));
328
+ }
329
+ function migrateLegacyPoints(raw, content) {
330
+ const nodes = raw.nodes.map((n6) => {
331
+ const { points, ...data } = n6.data;
332
+ if (!points || data.shape) return n6;
333
+ const shape = shapeFor(data.kind, points, worldToFrac(n6.pose, content));
334
+ return { ...n6, data: { ...data, ...shape ? { shape } : {} } };
335
+ });
336
+ return { ...raw, nodes };
337
+ }
352
338
  function splitId(id) {
353
339
  const at = id.lastIndexOf("/");
354
340
  return at <= 0 ? void 0 : { target: id.slice(0, at), node: id.slice(at + 1) };
@@ -361,26 +347,63 @@ function createAnnotationStore(opts) {
361
347
  const notify = () => {
362
348
  for (const fn of subs) fn();
363
349
  };
350
+ let holding = 0;
351
+ let heldDirty = false;
352
+ const hold = (fn) => {
353
+ holding += 1;
354
+ try {
355
+ fn();
356
+ } finally {
357
+ holding -= 1;
358
+ }
359
+ if (holding === 0 && heldDirty) {
360
+ heldDirty = false;
361
+ notify();
362
+ }
363
+ };
364
+ const exclusive = () => (typeof opts.selection === "function" ? opts.selection() : opts.selection) !== "per-target";
365
+ const lastSelection = /* @__PURE__ */ new Map();
366
+ const selectionKey = (scene) => scene.getSelection().map(String).join(" ");
367
+ const clearOthers = (keep) => {
368
+ for (const [target, scene] of scenes) {
369
+ if (target !== keep && scene.getSelection().length > 0) scene.setSelection([]);
370
+ }
371
+ };
372
+ const targetOf = (id) => targets().find((t) => t.id === id);
373
+ const contentOf = (id) => targetOf(id)?.content ?? NO_CONTENT;
364
374
  const sceneFor = (target) => {
365
375
  const known = scenes.get(target);
366
376
  if (known) return known;
367
377
  const raw = restore?.[target];
368
378
  const scene = raw ? sceneFromJSON(
369
- raw,
379
+ migrateLegacyPoints(raw, contentOf(target)),
370
380
  {}
371
381
  ) : createAnnotationScene();
372
382
  scenes.set(target, scene);
373
383
  history.track(target, scene);
384
+ lastSelection.set(target, selectionKey(scene));
374
385
  scene.subscribe(() => {
375
386
  history.observe(target, scene);
376
- notify();
387
+ const selected = selectionKey(scene);
388
+ const newlySelected = selected !== "" && selected !== lastSelection.get(target);
389
+ lastSelection.set(target, selected);
390
+ if (holding > 0) {
391
+ heldDirty = true;
392
+ return;
393
+ }
394
+ if (newlySelected && exclusive()) {
395
+ hold(() => {
396
+ heldDirty = true;
397
+ clearOthers(target);
398
+ });
399
+ } else {
400
+ notify();
401
+ }
377
402
  });
378
403
  return scene;
379
404
  };
380
405
  const historySceneAt = (target) => scenes.get(target);
381
406
  for (const target of Object.keys(restore ?? {})) sceneFor(target);
382
- const targetOf = (id) => targets().find((t) => t.id === id);
383
- const contentOf = (id) => targetOf(id)?.content ?? NO_CONTENT;
384
407
  const drawOptionsFor = (info) => ({
385
408
  content: info.content,
386
409
  positionDependsOn: info.positionDependsOn,
@@ -390,12 +413,10 @@ function createAnnotationStore(opts) {
390
413
  const project = (target, node) => {
391
414
  const found = scenes.get(target)?.get(asNodeId$1(node));
392
415
  if (!found) return void 0;
393
- return {
394
- ...found.data,
395
- target,
396
- id: `${target}/${node}`,
397
- frac: roundFrac(worldToFrac(found.pose, contentOf(target)))
398
- };
416
+ const { shape, ...data } = found.data;
417
+ const frac = roundFrac(worldToFrac(found.pose, contentOf(target)));
418
+ const points = data.kind === "point" ? [{ x: frac.x, y: frac.y }] : shape && roundPoints(fromShape(shape, frac));
419
+ return { ...data, target, id: `${target}/${node}`, frac, ...points ? { points } : {} };
399
420
  };
400
421
  const liveTargets = () => {
401
422
  const declared = targets().map((t) => t.id);
@@ -407,21 +428,21 @@ function createAnnotationStore(opts) {
407
428
  if (!scene) return [];
408
429
  const out = [];
409
430
  for (const id of scene.renderOrder()) {
410
- const a4 = project(target, String(id));
411
- if (a4) out.push(a4);
431
+ const a6 = project(target, String(id));
432
+ if (a6) out.push(a6);
412
433
  }
413
434
  return out;
414
435
  };
415
436
  const all = () => liveTargets().flatMap(marksOn);
416
- const matches = (a4, q) => {
417
- if (q.target !== void 0 && a4.target !== q.target) return false;
418
- if (q.kind !== void 0 && a4.kind !== q.kind) return false;
419
- if (q.status !== void 0 && a4.status !== q.status) return false;
437
+ const matches = (a6, q) => {
438
+ if (q.target !== void 0 && a6.target !== q.target) return false;
439
+ if (q.kind !== void 0 && a6.kind !== q.kind) return false;
440
+ if (q.status !== void 0 && a6.status !== q.status) return false;
420
441
  if (q.tags !== void 0) {
421
- const have = a4.tags ?? [];
442
+ const have = a6.tags ?? [];
422
443
  for (const t of q.tags) if (!have.includes(t)) return false;
423
444
  }
424
- if (q.where && !q.where(a4)) return false;
445
+ if (q.where && !q.where(a6)) return false;
425
446
  return true;
426
447
  };
427
448
  const nodeOf = (id) => {
@@ -439,17 +460,17 @@ function createAnnotationStore(opts) {
439
460
  return parts ? project(parts.target, parts.node) : void 0;
440
461
  },
441
462
  query(q) {
442
- return q ? all().filter((a4) => matches(a4, q)) : all();
463
+ return q ? all().filter((a6) => matches(a6, q)) : all();
443
464
  },
444
465
  hitTest(target, pt, tol = 0) {
445
- return marksOn(target).filter((a4) => fracContains(a4.frac, pt, tol)).reverse();
466
+ return marksOn(target).filter((a6) => fracContains(a6.frac, pt, tol)).reverse();
446
467
  },
447
468
  within(target, box) {
448
- return marksOn(target).filter((a4) => fracEncloses(box, a4.frac));
469
+ return marksOn(target).filter((a6) => fracEncloses(box, a6.frac));
449
470
  },
450
- isStale(a4, config) {
451
- const keys = targetOf(a4.target)?.positionDependsOn ?? [];
452
- return isStale(a4.seen, config, keys);
471
+ isStale(a6, config) {
472
+ const keys = targetOf(a6.target)?.positionDependsOn ?? [];
473
+ return isStale(a6.seen, config, keys);
453
474
  },
454
475
  canUndo() {
455
476
  return history.canUndo(historySceneAt);
@@ -476,20 +497,24 @@ function createAnnotationStore(opts) {
476
497
  },
477
498
  setSelection(ids) {
478
499
  const wanted = /* @__PURE__ */ new Map();
500
+ const only = exclusive();
479
501
  for (const id of ids) {
480
502
  const found = nodeOf(id);
481
503
  if (!found) continue;
504
+ if (only && wanted.size > 0 && !wanted.has(found.target)) continue;
482
505
  const node = String(found.node.id);
483
506
  const on = wanted.get(found.target);
484
507
  if (!on) wanted.set(found.target, [node]);
485
508
  else if (!on.includes(node)) on.push(node);
486
509
  }
487
- for (const [target, scene] of scenes) {
488
- const next = wanted.get(target) ?? [];
489
- const now = scene.getSelection();
490
- if (now.length === next.length && next.every((n5, i4) => String(now[i4]) === n5)) continue;
491
- scene.setSelection(next.map(asNodeId$1));
492
- }
510
+ hold(() => {
511
+ for (const [target, scene] of scenes) {
512
+ const next = wanted.get(target) ?? [];
513
+ const now = scene.getSelection();
514
+ if (now.length === next.length && next.every((n6, i5) => String(now[i5]) === n6)) continue;
515
+ scene.setSelection(next.map(asNodeId$1));
516
+ }
517
+ });
493
518
  },
494
519
  subscribe(fn) {
495
520
  subs.add(fn);
@@ -499,10 +524,12 @@ function createAnnotationStore(opts) {
499
524
  },
500
525
  add(init, config) {
501
526
  const target = targetOf(init.target);
527
+ const frac = roundFrac(init.frac);
528
+ const shape = shapeFor(init.kind, init.points, frac);
502
529
  const data = {
503
530
  target: init.target,
504
531
  kind: init.kind,
505
- ...init.points ? { points: init.points } : {},
532
+ ...shape ? { shape } : {},
506
533
  ...init.title !== void 0 ? { title: init.title } : {},
507
534
  ...init.status !== void 0 ? { status: init.status } : {},
508
535
  ...init.tags !== void 0 ? { tags: init.tags } : {},
@@ -512,7 +539,7 @@ function createAnnotationStore(opts) {
512
539
  const node = sceneFor(init.target).add({
513
540
  kind: "leaf",
514
541
  layer: "marks",
515
- pose: fracToWorld(roundFrac(init.frac), target?.content ?? NO_CONTENT),
542
+ pose: fracToWorld(frac, target?.content ?? NO_CONTENT),
516
543
  data
517
544
  });
518
545
  return `${init.target}/${String(node)}`;
@@ -520,14 +547,18 @@ function createAnnotationStore(opts) {
520
547
  update(id, patch) {
521
548
  const found = nodeOf(id);
522
549
  if (!found) return;
523
- const { frac, ...meaning } = patch;
550
+ const { frac: fracPatch, points, ...meaning } = patch;
524
551
  const nid = asNodeId$1(found.node.id);
525
- if (Object.keys(meaning).length > 0) {
526
- found.scene.update(nid, { data: { ...found.node.data, ...meaning } });
527
- }
528
- if (frac) {
529
- found.scene.setPose(nid, fracToWorld(roundFrac(frac), contentOf(found.target)));
552
+ const content = contentOf(found.target);
553
+ const frac = fracPatch ?? (points ? boundsOf(points) : null);
554
+ const bounds = roundFrac(frac ?? worldToFrac(found.node.pose, content));
555
+ const shape = points ? shapeFor(found.node.data.kind, points, bounds) : void 0;
556
+ if (Object.keys(meaning).length > 0 || shape) {
557
+ found.scene.update(nid, {
558
+ data: { ...found.node.data, ...meaning, ...shape ? { shape } : {} }
559
+ });
530
560
  }
561
+ if (frac) found.scene.setPose(nid, fracToWorld(bounds, content));
531
562
  },
532
563
  setMeta(id, meta) {
533
564
  const found = nodeOf(id);
@@ -582,17 +613,17 @@ function annotationsFromJSON(raw, targets, rest = {}) {
582
613
  }
583
614
 
584
615
  // src/annotations/view.ts
585
- function toWeaselView(v) {
586
- return { x: v.pan.x, y: v.pan.y, scale: { x: v.zoom, y: v.zoom } };
616
+ function toWeaselView(v2) {
617
+ return { x: v2.pan.x, y: v2.pan.y, scale: { x: v2.zoom, y: v2.zoom } };
587
618
  }
588
- function fromWeaselView(v) {
589
- return { zoom: v.scale.x, pan: { x: v.x, y: v.y } };
619
+ function fromWeaselView(v2) {
620
+ return { zoom: v2.scale.x, pan: { x: v2.x, y: v2.y } };
590
621
  }
591
622
  function fitView(content, pane) {
592
623
  const zoom = content.w > 0 && content.h > 0 ? Math.min(pane.w / content.w, pane.h / content.h) : 1;
593
624
  return { zoom: Number.isFinite(zoom) && zoom > 0 ? zoom : 1, pan: { x: 0, y: 0 } };
594
625
  }
595
- var sameRect = (a4, b2) => a4 !== null && a4.x === b2.x && a4.y === b2.y && a4.w === b2.w && a4.h === b2.h;
626
+ var sameRect = (a6, b3) => a6 !== null && a6.x === b3.x && a6.y === b3.y && a6.w === b3.w && a6.h === b3.h;
596
627
  function ToolBridge({ toolId }) {
597
628
  const tools = useActiveToolContext();
598
629
  const setActive = tools.setActive;
@@ -601,19 +632,24 @@ function ToolBridge({ toolId }) {
601
632
  }, [toolId, setActive]);
602
633
  return null;
603
634
  }
604
- function pointsOf(extras, content) {
605
- const toFrac = (p3) => ({
606
- x: content.w === 0 ? 0 : p3.x / content.w,
607
- y: content.h === 0 ? 0 : p3.y / content.h
608
- });
609
- if (extras.kind === "line") {
610
- return [toFrac(extras.a), toFrac(extras.b)];
611
- }
635
+ function verticesOf(extras) {
636
+ if (extras.kind === "line") return [extras.a, extras.b];
612
637
  if (extras.kind === "pencil") {
613
- return extras.samples.map(toFrac);
638
+ const samples = extras.samples;
639
+ return samples.length > 0 ? samples : void 0;
614
640
  }
615
641
  return void 0;
616
642
  }
643
+ function insertedGeometry(extras) {
644
+ const vertices = verticesOf(extras);
645
+ if (!vertices) return void 0;
646
+ const box = boundsOf(vertices);
647
+ if (!box) return void 0;
648
+ return {
649
+ pose: { x: box.x, y: box.y, width: box.w, height: box.h },
650
+ shape: roundPoints(toShape(vertices, box))
651
+ };
652
+ }
617
653
  function AnnotationOverlay({
618
654
  target,
619
655
  scene,
@@ -626,10 +662,9 @@ function AnnotationOverlay({
626
662
  const [rect, setRect] = useState(null);
627
663
  const [input, setInput] = useState(null);
628
664
  const sceneCanvas = useRef(null);
629
- const toolRef = useRef(activeToolId);
630
- toolRef.current = activeToolId;
631
- const configRef = useRef(config);
632
- configRef.current = config;
665
+ useEffect(() => registerNodeShape(POINT_MARK_SHAPE, { priority: "high" }), []);
666
+ const toolRef = useLatest(activeToolId);
667
+ const configRef = useLatest(config);
633
668
  const id = target.id;
634
669
  const tileId = useTileId(id);
635
670
  const lastTile = useRef(null);
@@ -647,9 +682,10 @@ function AnnotationOverlay({
647
682
  [surface, tileId]
648
683
  );
649
684
  const painted = useRef(false);
650
- const canvasRef = useRef(canvas);
651
- if (canvasRef.current !== canvas) painted.current = false;
652
- canvasRef.current = canvas;
685
+ const canvasRef = useLatest(canvas);
686
+ useInsertionEffect(() => {
687
+ painted.current = false;
688
+ }, [canvas]);
653
689
  const unsubscribeFrame = useRef(null);
654
690
  const attachSceneCanvas = useCallback((api) => {
655
691
  sceneCanvas.current = api;
@@ -669,7 +705,7 @@ function AnnotationOverlay({
669
705
  gl.clearColor(0, 0, 0, 0);
670
706
  gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
671
707
  });
672
- }, [surface, tileId]);
708
+ }, [canvasRef, surface, tileId]);
673
709
  const wheelSlot = useContext(CameraWheelContext);
674
710
  useEffect(() => {
675
711
  if (!input || !wheelSlot) return;
@@ -699,14 +735,11 @@ function AnnotationOverlay({
699
735
  const info = annotationToolInfo(toolRef.current);
700
736
  const kind = info?.kind;
701
737
  if (!kind) return null;
702
- const points = pointsOf(extras, target.content);
703
- const data = {
704
- target: id,
705
- kind,
706
- ...points ? { points } : {},
707
- seen: seenFrom(configRef.current, target.positionDependsOn ?? [])
708
- };
709
- return { data };
738
+ const seen = seenFrom(configRef.current, target.positionDependsOn ?? []);
739
+ const geometry = insertedGeometry(extras);
740
+ if (!geometry) return { data: { target: id, kind, seen } };
741
+ const data = { target: id, kind, shape: geometry.shape, seen };
742
+ return { data, pose: geometry.pose };
710
743
  };
711
744
  }
712
745
  const box = {
@@ -731,13 +764,13 @@ function AnnotationOverlay({
731
764
  }
732
765
  ),
733
766
  canvas && input ? (
734
- // One scope per pane: a shared <ActionsProvider> lets only the newest
735
- // canvas under it respond to input, and the rest go silently dead.
767
+ // Isolated so each pane keeps its own selection and tool.
736
768
  /* @__PURE__ */ jsxs(WeaselProvider, { isolate: true, children: [
737
769
  /* @__PURE__ */ jsx(ToolBridge, { toolId: annotationToolInfo(activeToolId)?.weaselTool ?? "select" }),
738
770
  /* @__PURE__ */ jsx(
739
771
  SceneCanvas,
740
772
  {
773
+ features: ["pick", "move", "transform", "edit"],
741
774
  ref: attachSceneCanvas,
742
775
  scene,
743
776
  width: rect.w,
@@ -921,21 +954,28 @@ var interstellar_theme_default = {
921
954
  accent: {
922
955
  by: "mode",
923
956
  dark: {
924
- value: "#b08adb",
957
+ value: "#4f46e5",
925
958
  type: "color"
926
959
  }
927
960
  },
928
961
  "accent-hover": {
929
962
  by: "mode",
930
963
  dark: {
931
- value: "#c6a3ee",
964
+ value: "#6d5dfc",
965
+ type: "color"
966
+ }
967
+ },
968
+ "accent-strong": {
969
+ by: "mode",
970
+ dark: {
971
+ value: "#7474fe",
932
972
  type: "color"
933
973
  }
934
974
  },
935
975
  "focus-ring": {
936
976
  by: "mode",
937
977
  dark: {
938
- value: "rgba(176, 138, 219, 0.45)",
978
+ value: "rgba(116, 116, 254, 0.5)",
939
979
  type: "color"
940
980
  }
941
981
  },
@@ -968,6 +1008,24 @@ var interstellarTheme = defineTheme({
968
1008
  ...interstellar_theme_default,
969
1009
  extends: weaselTheme
970
1010
  });
1011
+
1012
+ // src/canvas/cameraZoom.ts
1013
+ function atScale(view, scale, at) {
1014
+ return {
1015
+ x: view.x + at.x / view.scale.x - at.x / scale,
1016
+ y: view.y + at.y / view.scale.y - at.y / scale,
1017
+ scale: { x: scale, y: scale }
1018
+ };
1019
+ }
1020
+ function viewMiddle(camera) {
1021
+ const size = camera.hostSize?.();
1022
+ return size ? { x: size.width / 2, y: size.height / 2 } : { x: 0, y: 0 };
1023
+ }
1024
+ function zoomCameraTo(camera, zoom) {
1025
+ const { min, max } = camera.zoomRange();
1026
+ const scale = Math.min(max, Math.max(min, zoom));
1027
+ camera.set(atScale(camera.get(), scale, viewMiddle(camera)));
1028
+ }
971
1029
  function TrialBody({ sidebar, children, contentClassName, ...rest }) {
972
1030
  return /* @__PURE__ */ jsx(
973
1031
  Split,
@@ -1069,6 +1127,7 @@ function UndockedSections({ trialId, sections, onDock }) {
1069
1127
  var NO_OP = () => {
1070
1128
  };
1071
1129
  var NO_SECTIONS = Object.freeze({});
1130
+ var noSubscribe = () => NO_OP;
1072
1131
  function TrialChrome({
1073
1132
  trialId,
1074
1133
  record,
@@ -1087,17 +1146,17 @@ function TrialChrome({
1087
1146
  const lab = useLabContext();
1088
1147
  const storeCtx = useContext(LabStoreContext);
1089
1148
  if (!storeCtx) throw new Error("[labkit] TrialChrome requires <LabStoreProvider>");
1090
- const updateTrialView = useStore(storeCtx.store, (s4) => s4.updateTrialView);
1091
- const updateTrialSidebarWidth = useStore(storeCtx.store, (s4) => s4.updateTrialSidebarWidth);
1092
- const setTrialTitle = useStore(storeCtx.store, (s4) => s4.setTrialTitle);
1093
- const setTrialSectionCollapsed = useStore(storeCtx.store, (s4) => s4.setTrialSectionCollapsed);
1094
- const updateTrialConfig = useStore(storeCtx.store, (s4) => s4.updateTrialConfig);
1095
- const updateTrialState = useStore(storeCtx.store, (s4) => s4.updateTrialState);
1096
- const undockPanelAction = useStore(storeCtx.store, (s4) => s4.undockPanel);
1097
- const dockPanelAction = useStore(storeCtx.store, (s4) => s4.dockPanel);
1098
- const undockedPanels = useStore(storeCtx.store, (s4) => s4.undockedPanels);
1149
+ const updateTrialView = useStore(storeCtx.store, (s2) => s2.updateTrialView);
1150
+ const updateTrialSidebarWidth = useStore(storeCtx.store, (s2) => s2.updateTrialSidebarWidth);
1151
+ const setTrialTitle = useStore(storeCtx.store, (s2) => s2.setTrialTitle);
1152
+ const setTrialSectionCollapsed = useStore(storeCtx.store, (s2) => s2.setTrialSectionCollapsed);
1153
+ const updateTrialConfig = useStore(storeCtx.store, (s2) => s2.updateTrialConfig);
1154
+ const updateTrialState = useStore(storeCtx.store, (s2) => s2.updateTrialState);
1155
+ const undockPanelAction = useStore(storeCtx.store, (s2) => s2.undockPanel);
1156
+ const dockPanelAction = useStore(storeCtx.store, (s2) => s2.dockPanel);
1157
+ const undockedPanels = useStore(storeCtx.store, (s2) => s2.undockedPanels);
1099
1158
  const undockedIds = useMemo(
1100
- () => Object.values(undockedPanels).filter((p3) => p3.trialId === trialId).map((p3) => p3.sectionId),
1159
+ () => Object.values(undockedPanels).filter((p4) => p4.trialId === trialId).map((p4) => p4.sectionId),
1101
1160
  [undockedPanels, trialId]
1102
1161
  );
1103
1162
  const view2d = as2DView(record.view);
@@ -1109,10 +1168,15 @@ function TrialChrome({
1109
1168
  );
1110
1169
  const title = record.title ?? instrument.title ?? instrument.name;
1111
1170
  const collapsedSections = record.collapsedSections ?? NO_SECTIONS;
1171
+ const cameras = useContext(CameraRegistryContext);
1172
+ const camera = useSyncExternalStore(
1173
+ cameras?.subscribe ?? noSubscribe,
1174
+ () => cameras?.get(trialId) ?? null
1175
+ );
1112
1176
  const ctx = useMemo(() => {
1113
- const setZoom = (z) => {
1114
- if (!view2d) return;
1115
- updateTrialView(trialId, withZoom(view2d, z));
1177
+ const setZoom = (z2) => {
1178
+ if (camera) zoomCameraTo(camera, z2);
1179
+ else if (view2d) updateTrialView(trialId, withZoom(view2d, z2));
1116
1180
  };
1117
1181
  return {
1118
1182
  trialId,
@@ -1126,6 +1190,7 @@ function TrialChrome({
1126
1190
  canRedo: undoBindings?.canRedo ?? false,
1127
1191
  undo: undoBindings?.undo ?? NO_OP,
1128
1192
  redo: undoBindings?.redo ?? NO_OP,
1193
+ hasLoupe: loupe != null,
1129
1194
  loupeOn: loupe?.on ?? false,
1130
1195
  toggleLoupe: loupe?.toggle ?? NO_OP,
1131
1196
  configSchema,
@@ -1152,7 +1217,7 @@ function TrialChrome({
1152
1217
  undockedPanels: undockedIds,
1153
1218
  undockPanel: (sectionId, as) => undockPanelAction(trialId, sectionId, as),
1154
1219
  dockPanel: (sectionId) => dockPanelAction(trialId, sectionId),
1155
- savedSnapshots: lab.savedSnapshots.filter((s4) => s4.trialId === trialId),
1220
+ savedSnapshots: lab.savedSnapshots.filter((s2) => s2.trialId === trialId),
1156
1221
  saveSnapshot: (name) => lab.saveSnapshot(trialId, name),
1157
1222
  loadSnapshot: (snapshotId) => lab.loadSnapshot(trialId, snapshotId),
1158
1223
  clone: () => lab.cloneTrial(trialId),
@@ -1184,7 +1249,8 @@ function TrialChrome({
1184
1249
  dockPanelAction,
1185
1250
  setTrialTitle,
1186
1251
  setTrialSectionCollapsed,
1187
- collapsedSections
1252
+ collapsedSections,
1253
+ camera
1188
1254
  ]);
1189
1255
  const contributions = useMemo(
1190
1256
  () => suppressContributions(
@@ -1194,7 +1260,8 @@ function TrialChrome({
1194
1260
  [...trialChrome ?? []],
1195
1261
  [...chrome ?? []]
1196
1262
  ),
1197
- suppress ?? []
1263
+ suppress ?? [],
1264
+ TRANSIENT_BUILTINS
1198
1265
  ),
1199
1266
  [instrument, ctx, trialChrome, chrome, suppress, lab.controls]
1200
1267
  );
@@ -1268,9 +1335,9 @@ function Trial({ id, chrome, suppress }) {
1268
1335
  const lab = useLabContext();
1269
1336
  const storeCtx = useContext(LabStoreContext);
1270
1337
  if (!storeCtx) throw new Error("[labkit] <Trial> requires <LabStoreProvider>");
1271
- const instruments = useStore(storeCtx.store, (s4) => s4.instruments) ?? lab.instruments;
1272
- const record = useStore(storeCtx.store, (s4) => s4.trials.find((w2) => w2.id === id));
1273
- const instrument = record ? instruments.find((i4) => i4.name === record.instrumentName) : void 0;
1338
+ const instruments = useStore(storeCtx.store, (s2) => s2.instruments) ?? lab.instruments;
1339
+ const record = useStore(storeCtx.store, (s2) => s2.trials.find((w2) => w2.id === id));
1340
+ const instrument = record ? instruments.find((i5) => i5.name === record.instrumentName) : void 0;
1274
1341
  const kept = useKeptMarks(instrument?.annotations?.storage, id);
1275
1342
  if (!record) {
1276
1343
  return /* @__PURE__ */ jsxs("div", { className: "lk-trial lk-trial--unknown", children: [
@@ -1334,17 +1401,22 @@ function TrialRuntime({
1334
1401
  suppress
1335
1402
  }) {
1336
1403
  const wheelSlot = useRef(null);
1404
+ const cameras = useContext(CameraRegistryContext);
1405
+ const trialId = record.id;
1406
+ const publishCamera = useMemo(
1407
+ () => cameras ? (camera) => cameras.register(trialId, camera) : null,
1408
+ [cameras, trialId]
1409
+ );
1337
1410
  const [pointer] = useState(createPointerStore);
1338
1411
  const canvasContainerRef = useRef(null);
1339
- const loupeHostRef = useRef(null);
1340
- const updateTrialState = useStore(store, (s4) => s4.updateTrialState);
1341
- const updateTrialConfig = useStore(store, (s4) => s4.updateTrialConfig);
1342
- const updateTrialView = useStore(store, (s4) => s4.updateTrialView);
1343
- const updateTrialUndoStack = useStore(store, (s4) => s4.updateTrialUndoStack);
1344
- const updateTrialAnnotations = useStore(store, (s4) => s4.updateTrialAnnotations);
1345
- const labToolId = useStore(store, (s4) => s4.activeToolId);
1346
- const setLabTool = useStore(store, (s4) => s4.setLabTool);
1347
- const setTrialTool = useStore(store, (s4) => s4.setTrialTool);
1412
+ const updateTrialState = useStore(store, (s2) => s2.updateTrialState);
1413
+ const updateTrialConfig = useStore(store, (s2) => s2.updateTrialConfig);
1414
+ const updateTrialView = useStore(store, (s2) => s2.updateTrialView);
1415
+ const updateTrialUndoStack = useStore(store, (s2) => s2.updateTrialUndoStack);
1416
+ const updateTrialAnnotations = useStore(store, (s2) => s2.updateTrialAnnotations);
1417
+ const labToolId = useStore(store, (s2) => s2.activeToolId);
1418
+ const setLabTool = useStore(store, (s2) => s2.setLabTool);
1419
+ const setTrialTool = useStore(store, (s2) => s2.setTrialTool);
1348
1420
  const schema = useConfigSchema(instrument);
1349
1421
  const { config } = useResolvedConfig(schema, record.config, record.auto);
1350
1422
  const busRef = useRef(null);
@@ -1353,6 +1425,12 @@ function TrialRuntime({
1353
1425
  const [layerVisibility, setLayerVisibility] = useState({});
1354
1426
  const [layerOrder, setLayerOrder] = useState(null);
1355
1427
  const [loupeOn, setLoupeOn] = useState(false);
1428
+ const [lenses, setLenses] = useState(0);
1429
+ const mountLens = useCallback(() => {
1430
+ setLenses((n6) => n6 + 1);
1431
+ return () => setLenses((n6) => n6 - 1);
1432
+ }, []);
1433
+ const loupeSwitch = useMemo(() => ({ on: loupeOn, mount: mountLens }), [loupeOn, mountLens]);
1356
1434
  const visibleLayers = useMemo(
1357
1435
  () => (instrument.canvas?.layers ?? []).filter((l8) => layerVisibility[l8.id] !== false).map((l8) => l8.id),
1358
1436
  [instrument.canvas, layerVisibility]
@@ -1367,7 +1445,7 @@ function TrialRuntime({
1367
1445
  const snap = structuredClone(current.state);
1368
1446
  updateTrialUndoStack(record.id, (prev) => pushSnapshot(prev, snap, maxDepth));
1369
1447
  };
1370
- const setView = (v) => updateTrialView(record.id, v);
1448
+ const setView = (v2) => updateTrialView(record.id, v2);
1371
1449
  const stage = instrument.canvas ? void 0 : instrument.stage;
1372
1450
  const placeView = (size) => {
1373
1451
  const declared = instrument.canvas?.initialView ?? (stage ? stage.initialView ?? ((vp) => fitStage(stage.size, vp)) : void 0);
@@ -1376,29 +1454,48 @@ function TrialRuntime({
1376
1454
  setView(declared(size));
1377
1455
  };
1378
1456
  const annotationsCap = instrument.annotations;
1379
- const targetsRef = useRef(() => []);
1380
- targetsRef.current = () => annotationsCap ? annotationsCap.targets(record.state, config, { id: record.id, view: record.view }) : [];
1457
+ const targetsRef = useLatest(
1458
+ () => annotationsCap ? annotationsCap.targets(record.state, config, { id: record.id, view: record.view }) : []
1459
+ );
1381
1460
  const annotationsRef = useRef(null);
1382
- const configRef = useRef(config);
1383
- configRef.current = config;
1384
- const capRef = useRef(annotationsCap);
1385
- capRef.current = annotationsCap;
1461
+ const configRef = useLatest(config);
1462
+ const capRef = useLatest(annotationsCap);
1386
1463
  if (annotationsRef.current === null) {
1387
- const kept = annotationsCap?.storage ? keptMarks : record.annotations;
1388
- annotationsRef.current = annotationsFromJSON(kept, () => targetsRef.current(), {
1389
- meaning: () => capRef.current?.meaning,
1390
- config: () => configRef.current,
1391
- onCapture: (result) => capRef.current?.onCapture?.(result)
1392
- });
1464
+ annotationsRef.current = annotationsFromJSON(
1465
+ annotationsCap?.storage ? keptMarks : record.annotations,
1466
+ () => targetsRef.current(),
1467
+ {
1468
+ meaning: () => capRef.current?.meaning,
1469
+ config: () => configRef.current,
1470
+ onCapture: (result) => capRef.current?.onCapture?.(result),
1471
+ selection: () => capRef.current?.selection
1472
+ }
1473
+ );
1393
1474
  }
1394
1475
  const annotations = annotationsRef.current;
1476
+ const inputsCommitted = useRef(/* @__PURE__ */ new Set());
1477
+ useLayoutEffect(() => {
1478
+ for (const fn of inputsCommitted.current) fn();
1479
+ }, [record.state, record.view, config, annotationsCap]);
1480
+ const provided = useMemo(
1481
+ () => ({
1482
+ ...annotations,
1483
+ subscribe: (fn) => {
1484
+ const off = annotations.subscribe(fn);
1485
+ inputsCommitted.current.add(fn);
1486
+ return () => {
1487
+ off();
1488
+ inputsCommitted.current.delete(fn);
1489
+ };
1490
+ }
1491
+ }),
1492
+ [annotations]
1493
+ );
1395
1494
  const saveCap = annotationsCap?.storage;
1396
- const saveRef = useRef(() => {
1397
- });
1398
- saveRef.current = (doc) => {
1495
+ const saveRef = useLatest((doc) => {
1399
1496
  if (saveCap) saveCap.save(doc);
1400
1497
  else updateTrialAnnotations(record.id, doc);
1401
- };
1498
+ });
1402
1499
  const [markMoves, setMarkMoves] = useState({ undo: false, redo: false });
1403
1500
  useEffect(() => {
1404
1501
  if (!annotationsCap) return;
@@ -1431,7 +1528,7 @@ function TrialRuntime({
1431
1528
  if (timer) clearTimeout(timer);
1432
1529
  flush();
1433
1530
  };
1434
- }, [annotations, annotationsCap]);
1531
+ }, [annotations, annotationsCap, saveRef]);
1435
1532
  const declaresTools = instrument.tools != null;
1436
1533
  const labTool = resolveLabTool(labToolId, instruments);
1437
1534
  const resolvedToolId = declaresTools ? record.activeToolId ?? instrument.tools?.initial ?? instrument.tools?.tools[0]?.id ?? null : labTool;
@@ -1469,9 +1566,9 @@ function TrialRuntime({
1469
1566
  view: record.view,
1470
1567
  setView,
1471
1568
  zoom: view2d?.zoom ?? 1,
1472
- setZoom: (z) => {
1569
+ setZoom: (z2) => {
1473
1570
  if (!view2d) return;
1474
- updateTrialView(record.id, withZoom(view2d, z));
1571
+ updateTrialView(record.id, withZoom(view2d, z2));
1475
1572
  },
1476
1573
  activeToolId: resolvedToolId,
1477
1574
  visibleLayers,
@@ -1507,16 +1604,11 @@ function TrialRuntime({
1507
1604
  if (undoCap) redoState();
1508
1605
  }
1509
1606
  } : void 0;
1510
- const loupeCap = useMemo(() => {
1511
- const declared = instrument.loupe;
1512
- if (declared == null) return null;
1513
- return resolveLoupe(typeof declared === "function" ? declared(config) : declared);
1514
- }, [instrument.loupe, config]);
1515
- const loupeBindings = loupeCap ? { on: loupeOn, toggle: () => setLoupeOn((v) => !v) } : void 0;
1607
+ const loupeBindings = lenses > 0 ? { on: loupeOn, toggle: () => setLoupeOn((v2) => !v2) } : void 0;
1516
1608
  const canvasLayers = useMemo(() => {
1517
1609
  if (!instrument.canvas) return [];
1518
1610
  const baseLayers = instrument.canvas.layers;
1519
- const ordered = layerOrder ? [...baseLayers].sort((a4, b2) => layerOrder.indexOf(a4.id) - layerOrder.indexOf(b2.id)) : baseLayers;
1611
+ const ordered = layerOrder ? [...baseLayers].sort((a6, b3) => layerOrder.indexOf(a6.id) - layerOrder.indexOf(b3.id)) : baseLayers;
1520
1612
  return ordered.map((layer) => ({
1521
1613
  id: layer.id,
1522
1614
  visible: layerVisibility[layer.id] !== false,
@@ -1531,7 +1623,7 @@ function TrialRuntime({
1531
1623
  return instrument.layers.ids.map((l8) => typeof l8 === "string" ? { id: l8, label: l8 } : l8);
1532
1624
  }, [instrument.layers]);
1533
1625
  const layerItems = useMemo(() => {
1534
- const ordered = layerOrder ? [...layerDescriptors].sort((a4, b2) => layerOrder.indexOf(a4.id) - layerOrder.indexOf(b2.id)) : layerDescriptors;
1626
+ const ordered = layerOrder ? [...layerDescriptors].sort((a6, b3) => layerOrder.indexOf(a6.id) - layerOrder.indexOf(b3.id)) : layerDescriptors;
1535
1627
  const pinnedLast = [
1536
1628
  ...ordered.filter((l8) => !l8.alwaysOn),
1537
1629
  ...ordered.filter((l8) => l8.alwaysOn)
@@ -1544,7 +1636,7 @@ function TrialRuntime({
1544
1636
  }));
1545
1637
  }, [layerDescriptors, layerOrder, layerVisibility]);
1546
1638
  const dragDropResult = useDragDrop({
1547
- capability: instrument.dragDrop ?? { palette: [], onDrop: (_p, _i, s4) => s4 },
1639
+ capability: instrument.dragDrop ?? { palette: [], onDrop: (_p, _i, s2) => s2 },
1548
1640
  canvasContainerRef,
1549
1641
  view: view2d ?? DEFAULT_VIEW,
1550
1642
  worldSpec: instrument.canvas?.worldSpec,
@@ -1561,8 +1653,8 @@ function TrialRuntime({
1561
1653
  });
1562
1654
  const paletteItems = useMemo(() => {
1563
1655
  if (!instrument.dragDrop) return [];
1564
- const p3 = instrument.dragDrop.palette;
1565
- return typeof p3 === "function" ? p3(record.state, config) : p3;
1656
+ const p4 = instrument.dragDrop.palette;
1657
+ return typeof p4 === "function" ? p4(record.state, config) : p4;
1566
1658
  }, [instrument.dragDrop, record.state, config]);
1567
1659
  const layersWithFeedback = useMemo(() => {
1568
1660
  if (!dragDropResult.drag?.feedback) return canvasLayers;
@@ -1588,22 +1680,10 @@ function TrialRuntime({
1588
1680
  }
1589
1681
  ];
1590
1682
  }, [canvasLayers, dragDropResult.drag]);
1591
- const lens = loupeCap ? /* @__PURE__ */ jsx(
1592
- TrialLoupe,
1593
- {
1594
- capability: loupeCap,
1595
- enabled: loupeOn,
1596
- state: record.state,
1597
- config,
1598
- view: view2d ?? DEFAULT_VIEW,
1599
- worldSpec: instrument.canvas?.worldSpec,
1600
- hostRef: loupeHostRef
1601
- }
1602
- ) : null;
1603
1683
  let body;
1604
1684
  if (instrument.canvas) {
1605
1685
  body = /* @__PURE__ */ jsxs("div", { ref: canvasContainerRef, className: "lk-trial__canvas-host", children: [
1606
- /* @__PURE__ */ jsxs(
1686
+ /* @__PURE__ */ jsx(
1607
1687
  CanvasStack,
1608
1688
  {
1609
1689
  layers: layersWithFeedback,
@@ -1613,10 +1693,7 @@ function TrialRuntime({
1613
1693
  onResize: placeView,
1614
1694
  minZoom: instrument.canvas.minZoom,
1615
1695
  maxZoom: instrument.canvas.maxZoom,
1616
- children: [
1617
- instrument.render(renderCtx),
1618
- lens
1619
- ]
1696
+ children: instrument.render(renderCtx)
1620
1697
  }
1621
1698
  ),
1622
1699
  /* @__PURE__ */ jsx(DragOverlay, { drag: dragDropResult.drag })
@@ -1631,19 +1708,10 @@ function TrialRuntime({
1631
1708
  onResize: placeView,
1632
1709
  minZoom: stage.minZoom,
1633
1710
  maxZoom: stage.maxZoom,
1634
- hostRef: loupeHostRef,
1635
- overlay: /* @__PURE__ */ jsxs(Fragment, { children: [
1636
- stage.overlay?.(renderCtx),
1637
- lens
1638
- ] }),
1711
+ overlay: stage.overlay?.(renderCtx),
1639
1712
  children: instrument.render(renderCtx)
1640
1713
  }
1641
1714
  );
1642
- } else if (lens) {
1643
- body = /* @__PURE__ */ jsxs("div", { ref: loupeHostRef, className: "lk-trial__loupe-host", children: [
1644
- instrument.render(renderCtx),
1645
- lens
1646
- ] });
1647
1715
  } else {
1648
1716
  body = instrument.render(renderCtx);
1649
1717
  }
@@ -1654,7 +1722,7 @@ function TrialRuntime({
1654
1722
  );
1655
1723
  const layerListNode = useMemo(
1656
1724
  () => instrument.layers && layerDescriptors.length > 0 ? /* @__PURE__ */ jsx(
1657
- D,
1725
+ D2,
1658
1726
  {
1659
1727
  items: layerItems,
1660
1728
  onReorder: (move) => {
@@ -1698,8 +1766,8 @@ function TrialRuntime({
1698
1766
  activeToolId: labTool
1699
1767
  }
1700
1768
  ) : null;
1701
- const scopedBody = /* @__PURE__ */ jsx(WeaselProvider, { isolate: true, children: /* @__PURE__ */ jsx(PointerContextProvider, { store: pointer, children: /* @__PURE__ */ jsx(CameraScopeContext.Provider, { value: true, children: body }) }) });
1702
- 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(
1769
+ const scopedBody = /* @__PURE__ */ jsx(WeaselProvider, { isolate: true, children: /* @__PURE__ */ jsx(PointerContextProvider, { store: pointer, children: /* @__PURE__ */ jsx(CameraPublishContext.Provider, { value: publishCamera, children: /* @__PURE__ */ jsx(LoupeSwitchContext.Provider, { value: loupeSwitch, children: body }) }) }) });
1770
+ return /* @__PURE__ */ jsx(TrialIdProvider, { trialId: record.id, children: /* @__PURE__ */ jsx(AnnotationsContext.Provider, { value: annotationsCap ? provided : null, children: /* @__PURE__ */ jsx(CameraWheelContext.Provider, { value: wheelSlot, children: /* @__PURE__ */ jsxs(
1703
1771
  TrialChrome,
1704
1772
  {
1705
1773
  job: jobCap ? job : void 0,
@@ -1725,7 +1793,7 @@ function TrialRuntime({
1725
1793
  // src/trial/trialOps.ts
1726
1794
  var DEFAULT_VIEW2 = { zoom: 1, pan: { x: 0, y: 0 } };
1727
1795
  function findInstrument(instruments, name) {
1728
- const found = instruments.find((i4) => i4.name === name);
1796
+ const found = instruments.find((i5) => i5.name === name);
1729
1797
  if (!found) {
1730
1798
  throw new Error(`[labkit] Unknown instrument: "${name}"`);
1731
1799
  }
@@ -1835,11 +1903,11 @@ function reorderTrials(trials, ids) {
1835
1903
  function findOvershoot(bound, items, tolerance = 1) {
1836
1904
  let worst = null;
1837
1905
  for (const { item, edges } of items) {
1838
- const y3 = edges.bottom - bound.bottom;
1906
+ const y2 = edges.bottom - bound.bottom;
1839
1907
  const x2 = edges.right - bound.right;
1840
- const by = Math.max(x2, y3);
1908
+ const by = Math.max(x2, y2);
1841
1909
  if (by <= tolerance) continue;
1842
- if (!worst || by > worst.by) worst = { item, by, axis: y3 >= x2 ? "y" : "x" };
1910
+ if (!worst || by > worst.by) worst = { item, by, axis: y2 >= x2 ? "y" : "x" };
1843
1911
  }
1844
1912
  return worst;
1845
1913
  }
@@ -1915,37 +1983,30 @@ function useLabFitWarning(labBody) {
1915
1983
  function LabHeader({ addTrial: addTrial2 = true }) {
1916
1984
  const lab = useLabContext();
1917
1985
  const only = lab.instruments.length === 1 ? lab.instruments[0] : null;
1918
- return /* @__PURE__ */ jsxs(Fragment, { children: [
1919
- !addTrial2 ? null : only ? /* @__PURE__ */ jsxs(
1920
- "button",
1921
- {
1922
- type: "button",
1923
- className: "lk-lab-header__add",
1924
- onClick: () => lab.addTrial(only.name),
1925
- children: [
1926
- /* @__PURE__ */ jsx(G, { size: 16 }),
1927
- /* @__PURE__ */ jsx("span", { children: "Add trial" })
1928
- ]
1929
- }
1930
- ) : /* @__PURE__ */ jsx(
1931
- d2,
1932
- {
1933
- "aria-label": "Add trial",
1934
- label: "Add trial\u2026",
1935
- items: lab.instruments.map((i4) => ({ value: i4.name, label: i4.title ?? i4.name })),
1936
- onAction: (name) => lab.addTrial(name)
1937
- }
1938
- ),
1939
- /* @__PURE__ */ jsx(
1940
- s3,
1941
- {
1942
- className: "lk-lab-header__mode",
1943
- variant: "flat",
1944
- value: lab.mode,
1945
- onChange: lab.setMode
1946
- }
1947
- )
1948
- ] });
1986
+ return /* @__PURE__ */ jsx(Fragment, { children: !addTrial2 ? null : only ? /* @__PURE__ */ jsxs(
1987
+ "button",
1988
+ {
1989
+ type: "button",
1990
+ className: "lk-lab-header__add",
1991
+ onClick: () => lab.addTrial(only.name),
1992
+ children: [
1993
+ /* @__PURE__ */ jsx(G, { size: 16 }),
1994
+ /* @__PURE__ */ jsx("span", { children: "Add trial" })
1995
+ ]
1996
+ }
1997
+ ) : /* @__PURE__ */ jsx(
1998
+ p4,
1999
+ {
2000
+ "aria-label": "Add trial",
2001
+ label: "Add trial\u2026",
2002
+ items: lab.instruments.map((i5) => ({ value: i5.name, label: i5.title ?? i5.name })),
2003
+ onAction: (name) => lab.addTrial(name)
2004
+ }
2005
+ ) });
2006
+ }
2007
+ function LabThemeSwitcher() {
2008
+ const lab = useLabContext();
2009
+ return /* @__PURE__ */ jsx(o3, { value: lab.mode, onChange: lab.setMode });
1949
2010
  }
1950
2011
  function LabPalette({ tools, contributions }) {
1951
2012
  const ctx = useLabChromeContext();
@@ -1969,13 +2030,13 @@ function locate(url) {
1969
2030
  function currentPage(path, pages) {
1970
2031
  const here = locate(path);
1971
2032
  const onDoc = (doc) => here.doc.endsWith(doc);
1972
- const routed = pages.findIndex((p3) => {
1973
- if (!p3.href.includes("#")) return false;
1974
- const page = locate(p3.href);
2033
+ const routed = pages.findIndex((p4) => {
2034
+ if (!p4.href.includes("#")) return false;
2035
+ const page = locate(p4.href);
1975
2036
  return onDoc(page.doc) && (here.route === page.route || here.route.startsWith(`${page.route}/`));
1976
2037
  });
1977
2038
  if (routed >= 0) return routed;
1978
- return pages.findIndex((p3) => !p3.href.includes("#") && onDoc(locate(p3.href).doc));
2039
+ return pages.findIndex((p4) => !p4.href.includes("#") && onDoc(locate(p4.href).doc));
1979
2040
  }
1980
2041
  function LabSwitcher({ title, pages, path, className }) {
1981
2042
  const [open, setOpen] = useState(false);
@@ -2023,13 +2084,13 @@ function LabSwitcher({ title, pages, path, className }) {
2023
2084
  ]
2024
2085
  }
2025
2086
  ) }),
2026
- open && /* @__PURE__ */ jsx("div", { className: "lk-switcher-menu", id: menuId, role: "menu", children: pages.map((page, i4) => /* @__PURE__ */ jsx(
2087
+ open && /* @__PURE__ */ jsx("div", { className: "lk-switcher-menu", id: menuId, role: "menu", children: pages.map((page, i5) => /* @__PURE__ */ jsx(
2027
2088
  "a",
2028
2089
  {
2029
2090
  href: page.href,
2030
2091
  role: "menuitem",
2031
2092
  className: "lk-switcher-item",
2032
- "aria-current": i4 === here ? "page" : void 0,
2093
+ "aria-current": i5 === here ? "page" : void 0,
2033
2094
  onClick: close,
2034
2095
  children: page.label
2035
2096
  },
@@ -2064,17 +2125,138 @@ function LabShell({
2064
2125
  footer && /* @__PURE__ */ jsx("footer", { className: "lk-shell-footer", children: footer })
2065
2126
  ] });
2066
2127
  }
2128
+ var ZOOM_ID = "viewport.zoom";
2129
+ var NO_TOOLS = /* @__PURE__ */ new Map();
2130
+ var CHANNELS = { contextMenu: false, ingest: false };
2131
+ var NO_ELEMENT = { current: null };
2132
+ var BASE = makeViewportZoomAction({ min: 1e-9, max: Number.POSITIVE_INFINITY });
2133
+ var KEY_BINDINGS = BASE.defaultBinding.filter((b3) => b3.spec.kind === "key");
2134
+ function shortcutFor(kind) {
2135
+ const spec = KEY_BINDINGS.find((b3) => b3.opts?.params?.kind === kind)?.spec;
2136
+ return spec?.key ? n2({ key: spec.key, mod: spec.mods?.mod }) : void 0;
2137
+ }
2138
+ function forZoom(camera) {
2139
+ return {
2140
+ get: camera.get,
2141
+ set: camera.set,
2142
+ hostSize: camera.hostSize,
2143
+ recenter: () => atScale(camera.get(), 1, viewMiddle(camera))
2144
+ };
2145
+ }
2146
+ var percent = new Intl.NumberFormat(void 0, { style: "percent", maximumFractionDigits: 0 });
2147
+ function LabZoom() {
2148
+ return /* @__PURE__ */ jsx(DepRegistryProvider, { children: /* @__PURE__ */ jsx(ActionsProvider, { children: /* @__PURE__ */ jsx(ActiveToolContextProvider, { children: /* @__PURE__ */ jsx(LabZoomControls, {}) }) }) });
2149
+ }
2150
+ function LabZoomControls() {
2151
+ const registry = useActionsRegistry();
2152
+ const { focusedTrialId } = useLabContext();
2153
+ const cameras = useContext(CameraRegistryContext);
2154
+ const storeCtx = useContext(LabStoreContext);
2155
+ if (!storeCtx) throw new Error("[labkit] <LabZoom> requires <LabStoreProvider>");
2156
+ const camera = useSyncExternalStore(
2157
+ cameras?.subscribe ?? noSubscribe2,
2158
+ () => cameras && focusedTrialId ? cameras.get(focusedTrialId) : null
2159
+ );
2160
+ const zoom = useStore(storeCtx.store, (s2) => {
2161
+ const record = s2.trials.find((t) => t.id === focusedTrialId);
2162
+ return as2DView(record?.view)?.zoom ?? null;
2163
+ });
2164
+ const live = zoom !== null && camera !== null;
2165
+ const view = useMemo(() => camera ? forZoom(camera) : null, [camera]);
2166
+ const viewRef = useLatest(live ? view : null);
2167
+ useDepSource("view", () => viewRef.current);
2168
+ useEffect(() => {
2169
+ const action = {
2170
+ ...BASE,
2171
+ defaultBinding: KEY_BINDINGS,
2172
+ enabled: () => viewRef.current ? true : ActionDisabledReason.NotApplicable
2173
+ };
2174
+ return registry.register(action);
2175
+ }, [registry, viewRef]);
2176
+ useGestureDispatcher({
2177
+ canvasRef: NO_ELEMENT,
2178
+ actions: registry,
2179
+ entriesById: NO_TOOLS,
2180
+ channels: CHANNELS,
2181
+ keyboard: "first"
2182
+ });
2183
+ const range = camera?.zoomRange();
2184
+ const canIn = zoom !== null && range !== void 0 && zoom < range.max * (1 - 1e-9);
2185
+ const canOut = zoom !== null && range !== void 0 && zoom > range.min * (1 + 1e-9);
2186
+ const run = (kind) => () => {
2187
+ registry.trigger(ZOOM_ID, { kind });
2188
+ };
2189
+ const hint = (label, kind) => {
2190
+ const keys = shortcutFor(kind);
2191
+ return keys ? `${label} (${keys})` : label;
2192
+ };
2193
+ const shown = live && zoom !== null ? percent.format(zoom) : null;
2194
+ return /* @__PURE__ */ jsx(Toolbar, { "aria-label": "Zoom", children: /* @__PURE__ */ jsxs(Toolbar.Group, { "aria-label": "Zoom", children: [
2195
+ /* @__PURE__ */ jsx(
2196
+ Toolbar.Button,
2197
+ {
2198
+ iconOnly: true,
2199
+ "aria-label": "Zoom out",
2200
+ title: hint("Zoom out", "out"),
2201
+ disabled: !canOut,
2202
+ onClick: run("out"),
2203
+ children: /* @__PURE__ */ jsx(X, { size: 16 })
2204
+ }
2205
+ ),
2206
+ /* @__PURE__ */ jsx(
2207
+ Toolbar.Button,
2208
+ {
2209
+ "aria-label": shown ? `Zoom ${shown} \u2014 reset to actual size` : "Zoom unavailable",
2210
+ title: live ? hint("Actual size", "reset") : "This trial has no camera",
2211
+ disabled: !live,
2212
+ onClick: run("reset"),
2213
+ children: /* @__PURE__ */ jsx("span", { className: "lk-lab-zoom__readout", children: shown ?? "\u2013" })
2214
+ }
2215
+ ),
2216
+ /* @__PURE__ */ jsx(
2217
+ Toolbar.Button,
2218
+ {
2219
+ iconOnly: true,
2220
+ "aria-label": "Zoom in",
2221
+ title: hint("Zoom in", "in"),
2222
+ disabled: !canIn,
2223
+ onClick: run("in"),
2224
+ children: /* @__PURE__ */ jsx(U, { size: 16 })
2225
+ }
2226
+ )
2227
+ ] }) });
2228
+ }
2229
+ var noSubscribe2 = () => () => {
2230
+ };
2067
2231
  var ZONE_ID = asNodeId("lk-workspace");
2068
2232
  var STRATEGIES = { grid: gridStrategy, floating: floatingStrategy };
2069
2233
  var FLOAT_ZONE_ID = asNodeId("lk-workspace-floating");
2070
2234
  var PANEL_KIND = "panel";
2071
2235
  var KIND = "trial";
2236
+ function createWorkspaceStore(grid) {
2237
+ const store = new Store();
2238
+ store.registerNode(
2239
+ createNode({
2240
+ kind: "zone",
2241
+ id: ZONE_ID,
2242
+ container: { strategyId: "grid", config: grid }
2243
+ })
2244
+ );
2245
+ store.registerNode(
2246
+ createNode({
2247
+ kind: "zone",
2248
+ id: FLOAT_ZONE_ID,
2249
+ container: { strategyId: "floating", config: {} }
2250
+ })
2251
+ );
2252
+ return store;
2253
+ }
2072
2254
  function extentOf(store, id) {
2073
- const p3 = store.getNode(id)?.membership?.placement;
2074
- if (!p3) return null;
2255
+ const p4 = store.getNode(id)?.membership?.placement;
2256
+ if (!p4) return null;
2075
2257
  const out = {};
2076
- if (p3.size) out.size = p3.size;
2077
- if (p3.span) out.span = p3.span;
2258
+ if (p4.size) out.size = p4.size;
2259
+ if (p4.span) out.span = p4.span;
2078
2260
  return Object.keys(out).length > 0 ? out : null;
2079
2261
  }
2080
2262
  function PanelFrame({ panel, floating = false }) {
@@ -2095,43 +2277,18 @@ function Workspace({
2095
2277
  viewport
2096
2278
  }) {
2097
2279
  const items = Children.toArray(children);
2098
- const tilePanels = useMemo(() => (panels ?? []).filter((p3) => p3.as === "tile"), [panels]);
2099
- const floatPanels = useMemo(() => (panels ?? []).filter((p3) => p3.as === "floating"), [panels]);
2100
- const wantedIds = [
2101
- ...items.map((_2, i4) => ids?.[i4] ?? `lk-ws-${i4}`),
2102
- ...tilePanels.map((p3) => `lk-panel-${p3.key}`)
2103
- ];
2104
- const idKey = wantedIds.join(",");
2105
- const idKeyRef = useRef(null);
2106
- const nodeIdsRef = useRef([]);
2107
- if (idKeyRef.current !== idKey) {
2108
- idKeyRef.current = idKey;
2109
- nodeIdsRef.current = wantedIds.map(asNodeId);
2110
- }
2111
- const nodeIds = nodeIdsRef.current;
2112
- const layoutRef = useRef(layout);
2113
- layoutRef.current = layout;
2114
- const onLayoutChangeRef = useRef(onLayoutChange);
2115
- onLayoutChangeRef.current = onLayoutChange;
2280
+ const tilePanels = useMemo(() => (panels ?? []).filter((p4) => p4.as === "tile"), [panels]);
2281
+ const floatPanels = useMemo(() => (panels ?? []).filter((p4) => p4.as === "floating"), [panels]);
2282
+ const idKey = JSON.stringify([
2283
+ ...items.map((_2, i5) => ids?.[i5] ?? `lk-ws-${i5}`),
2284
+ ...tilePanels.map((p4) => `lk-panel-${p4.key}`)
2285
+ ]);
2286
+ const nodeIds = useMemo(() => JSON.parse(idKey).map(asNodeId), [idKey]);
2287
+ const layoutRef = useLatest(layout);
2288
+ const onLayoutChangeRef = useLatest(onLayoutChange);
2116
2289
  const storeRef = useRef(null);
2117
- if (storeRef.current === null) {
2118
- const store2 = new Store();
2119
- store2.registerNode(
2120
- createNode({
2121
- kind: "zone",
2122
- id: ZONE_ID,
2123
- container: { strategyId: "grid", config: { resizable, gap, padding } }
2124
- })
2125
- );
2126
- store2.registerNode(
2127
- createNode({
2128
- kind: "zone",
2129
- id: FLOAT_ZONE_ID,
2130
- container: { strategyId: "floating", config: {} }
2131
- })
2132
- );
2133
- storeRef.current = store2;
2134
- }
2290
+ if (storeRef.current === null)
2291
+ storeRef.current = createWorkspaceStore({ resizable, gap, padding });
2135
2292
  const store = storeRef.current;
2136
2293
  const surface = useSurfaceOptional();
2137
2294
  useLayoutEffect(() => {
@@ -2152,7 +2309,7 @@ function Workspace({
2152
2309
  if (saved) store.patchPlacement(id, saved);
2153
2310
  }
2154
2311
  store.setChildOrder(ZONE_ID, [...nodeIds]);
2155
- }, [store, nodeIds]);
2312
+ }, [store, nodeIds, layoutRef]);
2156
2313
  useEffect(() => {
2157
2314
  if (!surface) return;
2158
2315
  surface.invalidateRects();
@@ -2168,15 +2325,15 @@ function Workspace({
2168
2325
  }
2169
2326
  onLayoutChangeRef.current?.(next);
2170
2327
  });
2171
- }, [store, onLayoutChange]);
2328
+ }, [store, onLayoutChange, onLayoutChangeRef]);
2172
2329
  const commitOrder = useCallback(
2173
2330
  (nextIds) => onReorder?.(nextIds.map(String)),
2174
2331
  [onReorder]
2175
2332
  );
2176
2333
  const chrome = useMemo(() => {
2177
- const byId = new Map(nodeIds.map((id, i4) => [id, items[i4]]));
2334
+ const byId = new Map(nodeIds.map((id, i5) => [id, items[i5]]));
2178
2335
  const panelByNode = new Map(
2179
- tilePanels.map((p3) => [`lk-panel-${p3.key}`, p3])
2336
+ tilePanels.map((p4) => [`lk-panel-${p4.key}`, p4])
2180
2337
  );
2181
2338
  return {
2182
2339
  [PANEL_KIND]: ({ node }) => {
@@ -2194,7 +2351,7 @@ function Workspace({
2194
2351
  }
2195
2352
  };
2196
2353
  }, [nodeIds, items, reorderable, tilePanels]);
2197
- const floatingLayer = floatPanels.length === 0 ? null : /* @__PURE__ */ jsx("div", { className: "lk-workspace__floating", children: floatPanels.map((p3) => /* @__PURE__ */ jsx(PanelFrame, { panel: p3, floating: true }, p3.key)) });
2354
+ const floatingLayer = floatPanels.length === 0 ? null : /* @__PURE__ */ jsx("div", { className: "lk-workspace__floating", children: floatPanels.map((p4) => /* @__PURE__ */ jsx(PanelFrame, { panel: p4, floating: true }, p4.key)) });
2198
2355
  const grid = /* @__PURE__ */ jsx(
2199
2356
  Container,
2200
2357
  {
@@ -2240,7 +2397,7 @@ async function openStoredLab({ instruments, defaultInstrument, mode }, storageKe
2240
2397
  const marks = /* @__PURE__ */ new Map();
2241
2398
  await Promise.all(
2242
2399
  opened.store.getState().trials.map(async (trial) => {
2243
- const kept = instruments.find((i4) => i4.name === trial.instrumentName)?.annotations?.storage;
2400
+ const kept = instruments.find((i5) => i5.name === trial.instrumentName)?.annotations?.storage;
2244
2401
  if (!kept) return;
2245
2402
  try {
2246
2403
  marks.set(trial.id, await kept.load());
@@ -2289,11 +2446,11 @@ var NEBULA_SLOTS = [
2289
2446
  ];
2290
2447
  function buildNebula(list, theme, mode, density) {
2291
2448
  const ctx = { theme, resolved: resolveTheme(theme, { mode, density }) };
2292
- const n5 = colorCount(list, ctx) ?? NEBULA_SLOTS.length;
2293
- const colors = Array.from({ length: n5 }, (_2, i4) => colorAt(list, i4, ctx));
2294
- const blobs = colors.map((c6, i4) => {
2295
- const p3 = NEBULA_SLOTS[i4 % NEBULA_SLOTS.length];
2296
- return `radial-gradient(ellipse ${p3.sx} ${p3.sy} at ${p3.cx} ${p3.cy}, color-mix(in srgb, ${c6} 22%, transparent), transparent ${p3.stop})`;
2449
+ const n6 = colorCount(list, ctx) ?? NEBULA_SLOTS.length;
2450
+ const colors = Array.from({ length: n6 }, (_2, i5) => colorAt(list, i5, ctx));
2451
+ const blobs = colors.map((c6, i5) => {
2452
+ const p4 = NEBULA_SLOTS[i5 % NEBULA_SLOTS.length];
2453
+ return `radial-gradient(ellipse ${p4.sx} ${p4.sy} at ${p4.cx} ${p4.cy}, color-mix(in srgb, ${c6} 22%, transparent), transparent ${p4.stop})`;
2297
2454
  });
2298
2455
  blobs.push("radial-gradient(ellipse at center, #0a0a18 0%, #02020a 100%)");
2299
2456
  return blobs.join(", ");
@@ -2389,6 +2546,7 @@ function LabRuntime({
2389
2546
  labChrome,
2390
2547
  suppress,
2391
2548
  addTrial: addTrial2,
2549
+ zoom = true,
2392
2550
  tools,
2393
2551
  configRules,
2394
2552
  controls,
@@ -2399,13 +2557,14 @@ function LabRuntime({
2399
2557
  useLayoutEffect(() => {
2400
2558
  store.getState().setInstruments(instruments);
2401
2559
  }, [instruments, store]);
2402
- const trials = useStore(store, (s4) => s4.trials);
2560
+ const trials = useStore(store, (s2) => s2.trials);
2561
+ const [cameras] = useState(createCameraRegistry);
2403
2562
  const [focusPick, setFocusPick] = useState(null);
2404
2563
  const focusedTrialId = trials.some((t) => t.id === focusPick) ? focusPick : trials[0]?.id ?? null;
2405
- const savedSnapshots = useStore(store, (s4) => s4.savedSnapshots);
2406
- const modeValue = useStore(store, (s4) => s4.mode);
2407
- const layout = useStore(store, (s4) => s4.layout);
2408
- const undockedPanels = useStore(store, (s4) => s4.undockedPanels);
2564
+ const savedSnapshots = useStore(store, (s2) => s2.savedSnapshots);
2565
+ const modeValue = useStore(store, (s2) => s2.mode);
2566
+ const layout = useStore(store, (s2) => s2.layout);
2567
+ const undockedPanels = useStore(store, (s2) => s2.undockedPanels);
2409
2568
  const panelHostsRef = useRef(null);
2410
2569
  if (panelHostsRef.current === null) panelHostsRef.current = createPanelHostRegistry();
2411
2570
  const outerSurface = useSurfaceOptional();
@@ -2421,12 +2580,12 @@ function LabRuntime({
2421
2580
  const canvases = [ownUnderRef.current, ownOverRef.current].filter((c6) => c6 !== null);
2422
2581
  if (canvases.length === 0) return;
2423
2582
  const w2 = Math.round(frame.size.width * frame.dpr);
2424
- const h = Math.round(frame.size.height * frame.dpr);
2425
- if (bufferRef.current.w !== w2 || bufferRef.current.h !== h) {
2426
- bufferRef.current = { w: w2, h };
2583
+ const h2 = Math.round(frame.size.height * frame.dpr);
2584
+ if (bufferRef.current.w !== w2 || bufferRef.current.h !== h2) {
2585
+ bufferRef.current = { w: w2, h: h2 };
2427
2586
  for (const c6 of canvases) {
2428
2587
  c6.width = w2;
2429
- c6.height = h;
2588
+ c6.height = h2;
2430
2589
  c6.style.width = `${frame.size.width}px`;
2431
2590
  c6.style.height = `${frame.size.height}px`;
2432
2591
  }
@@ -2435,7 +2594,9 @@ function LabRuntime({
2435
2594
  }
2436
2595
  }, []);
2437
2596
  const ownSurface = useTiledSurface({ onFrame });
2438
- surfaceRef.current = ownSurface;
2597
+ useInsertionEffect(() => {
2598
+ surfaceRef.current = ownSurface;
2599
+ }, [ownSurface]);
2439
2600
  const [labBody, setLabBody] = useState(null);
2440
2601
  const attachOwnSurface = ownSurface.containerRef;
2441
2602
  const labBodyRef = useCallback(
@@ -2545,15 +2706,15 @@ function LabRuntime({
2545
2706
  const next = resetTrial(store.getState().trials, id, instruments);
2546
2707
  const record = next.find((w2) => w2.id === id);
2547
2708
  if (!record) return;
2548
- store.setState((s4) => ({
2549
- trials: s4.trials.map(
2709
+ store.setState((s2) => ({
2710
+ trials: s2.trials.map(
2550
2711
  (w2) => w2.id === id ? { ...w2, config: record.config, state: record.state, view: record.view } : w2
2551
2712
  )
2552
2713
  }));
2553
2714
  },
2554
2715
  savedSnapshots,
2555
2716
  saveSnapshot: (trialId, name) => {
2556
- const taken = store.getState().savedSnapshots.filter((s4) => s4.trialId === trialId).length;
2717
+ const taken = store.getState().savedSnapshots.filter((s2) => s2.trialId === trialId).length;
2557
2718
  store.getState().saveSnapshot(trialId, name ?? `Snapshot ${taken + 1}`);
2558
2719
  },
2559
2720
  loadSnapshot: (trialId, snapshotId) => {
@@ -2563,8 +2724,8 @@ function LabRuntime({
2563
2724
  store.getState().deleteSnapshot(snapshotId);
2564
2725
  },
2565
2726
  mode: modeValue,
2566
- setMode: (m2) => {
2567
- store.getState().setMode(m2);
2727
+ setMode: (m) => {
2728
+ store.getState().setMode(m);
2568
2729
  },
2569
2730
  configRules,
2570
2731
  controls
@@ -2600,7 +2761,7 @@ function LabRuntime({
2600
2761
  children: trials.map((w2) => /* @__PURE__ */ jsx(Trial, { id: w2.id, chrome, suppress }, w2.id))
2601
2762
  }
2602
2763
  );
2603
- 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(
2764
+ 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(CameraRegistryContext.Provider, { value: cameras, children: /* @__PURE__ */ jsx(
2604
2765
  ThemeProvider,
2605
2766
  {
2606
2767
  theme,
@@ -2620,8 +2781,10 @@ function LabRuntime({
2620
2781
  ] }) : footer,
2621
2782
  header: /* @__PURE__ */ jsxs(Fragment, { children: [
2622
2783
  /* @__PURE__ */ jsx(LabHeader, { ...addTrial2 !== void 0 ? { addTrial: addTrial2 } : {} }),
2784
+ zoom ? /* @__PURE__ */ jsx(LabZoom, {}) : null,
2623
2785
  children,
2624
- /* @__PURE__ */ jsx(LabHeaderRegion, { contributions: labChromeAll })
2786
+ /* @__PURE__ */ jsx(LabHeaderRegion, { contributions: labChromeAll }),
2787
+ /* @__PURE__ */ jsx(LabThemeSwitcher, {})
2625
2788
  ] }),
2626
2789
  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: [
2627
2790
  outerSurface ? null : (
@@ -2664,7 +2827,7 @@ function LabRuntime({
2664
2827
  }
2665
2828
  )
2666
2829
  }
2667
- ) }) }) }) });
2830
+ ) }) }) }) }) });
2668
2831
  }
2669
2832
  function LabBoundary({ children }) {
2670
2833
  return /* @__PURE__ */ jsx(LabStoreContext.Provider, { value: null, children: /* @__PURE__ */ jsx(PersistenceContext.Provider, { value: null, children: /* @__PURE__ */ jsx(AnnotationPreloadContext.Provider, { value: null, children: /* @__PURE__ */ jsx(LabContext.Provider, { value: null, children: /* @__PURE__ */ jsx(PanelHostContext.Provider, { value: null, children: /* @__PURE__ */ jsx(SurfaceContext.Provider, { value: null, children: /* @__PURE__ */ jsx(SurfaceCanvasContext.Provider, { value: { over: null, under: null }, children: /* @__PURE__ */ jsx(TrialIdContext.Provider, { value: null, children: /* @__PURE__ */ jsx(AnnotationsContext.Provider, { value: null, children: /* @__PURE__ */ jsx(CameraWheelContext.Provider, { value: null, children: /* @__PURE__ */ jsx(CanvasStackContext.Provider, { value: null, children: /* @__PURE__ */ jsx(TrialDragContext.Provider, { value: null, children: /* @__PURE__ */ jsx(ThemeContext.Provider, { value: null, children }) }) }) }) }) }) }) }) }) }) }) }) });
@@ -2769,25 +2932,25 @@ function ButtonsAndToggles() {
2769
2932
  /* @__PURE__ */ jsx(c, { variant: "secondary", iconOnly: true, ariaLabel: "Add", children: /* @__PURE__ */ jsx(Dot, {}) })
2770
2933
  ] }) }),
2771
2934
  /* @__PURE__ */ jsx(Cell, { label: "Badge", children: /* @__PURE__ */ jsxs("div", { className: "lk-specimen__row", children: [
2772
- /* @__PURE__ */ jsx(Ht, { shape: "pill", status: "accent", variant: "outline", size: "sm", children: "ACCENT" }),
2773
- /* @__PURE__ */ jsx(Ht, { status: "info", variant: "subtle", children: "INFO" }),
2774
- /* @__PURE__ */ jsx(Ht, { status: "warn", variant: "solid", children: "WARN" }),
2775
- /* @__PURE__ */ jsx(Ht, { status: "danger", variant: "solid", children: "DANGER" }),
2776
- /* @__PURE__ */ jsx(Ht, { status: "muted", children: "MUTED" })
2935
+ /* @__PURE__ */ jsx(Xt, { shape: "pill", status: "accent", variant: "outline", size: "sm", children: "ACCENT" }),
2936
+ /* @__PURE__ */ jsx(Xt, { status: "info", variant: "subtle", children: "INFO" }),
2937
+ /* @__PURE__ */ jsx(Xt, { status: "warn", variant: "solid", children: "WARN" }),
2938
+ /* @__PURE__ */ jsx(Xt, { status: "danger", variant: "solid", children: "DANGER" }),
2939
+ /* @__PURE__ */ jsx(Xt, { status: "muted", children: "MUTED" })
2777
2940
  ] }) }),
2778
2941
  /* @__PURE__ */ jsx(Cell, { label: "Checkbox, Switch", children: /* @__PURE__ */ jsxs("div", { className: "lk-specimen__row", children: [
2779
- /* @__PURE__ */ jsx(o3, { isSelected: snap, onChange: setSnap, children: "Snap to grid" }),
2780
- /* @__PURE__ */ jsx(o3, { isIndeterminate: true, children: "Mixed" }),
2781
- /* @__PURE__ */ jsx(a6, { isSelected: wifi, onChange: setWifi, children: "Wifi" })
2942
+ /* @__PURE__ */ jsx(c5, { isSelected: snap, onChange: setSnap, children: "Snap to grid" }),
2943
+ /* @__PURE__ */ jsx(c5, { isIndeterminate: true, children: "Mixed" }),
2944
+ /* @__PURE__ */ jsx(a7, { isSelected: wifi, onChange: setWifi, children: "Wifi" })
2782
2945
  ] }) }),
2783
- /* @__PURE__ */ jsx(Cell, { label: "RadioGroup", children: /* @__PURE__ */ jsxs(l5, { label: "Snap mode", orientation: "horizontal", defaultValue: "grid", children: [
2784
- /* @__PURE__ */ jsx(u5, { value: "off", children: "Off" }),
2785
- /* @__PURE__ */ jsx(u5, { value: "grid", children: "Grid" }),
2786
- /* @__PURE__ */ jsx(u5, { value: "pixel", children: "Pixel" })
2946
+ /* @__PURE__ */ jsx(Cell, { label: "RadioGroup", children: /* @__PURE__ */ jsxs(l4, { label: "Snap mode", orientation: "horizontal", defaultValue: "grid", children: [
2947
+ /* @__PURE__ */ jsx(u3, { value: "off", children: "Off" }),
2948
+ /* @__PURE__ */ jsx(u3, { value: "grid", children: "Grid" }),
2949
+ /* @__PURE__ */ jsx(u3, { value: "pixel", children: "Pixel" })
2787
2950
  ] }) }),
2788
2951
  /* @__PURE__ */ jsxs(Cell, { label: "ToggleBar", children: [
2789
2952
  /* @__PURE__ */ jsx(
2790
- a5,
2953
+ a6,
2791
2954
  {
2792
2955
  ariaLabel: "Align",
2793
2956
  value: align,
@@ -2800,7 +2963,7 @@ function ButtonsAndToggles() {
2800
2963
  }
2801
2964
  ),
2802
2965
  /* @__PURE__ */ jsx(
2803
- a5,
2966
+ a6,
2804
2967
  {
2805
2968
  ariaLabel: "Align, flat",
2806
2969
  variant: "flat",
@@ -2826,7 +2989,7 @@ function ButtonsAndToggles() {
2826
2989
  }
2827
2990
  ),
2828
2991
  /* @__PURE__ */ jsx(
2829
- a7,
2992
+ a9,
2830
2993
  {
2831
2994
  items: [
2832
2995
  { value: "undo", label: "Undo", onAction: () => {
@@ -2839,7 +3002,7 @@ function ButtonsAndToggles() {
2839
3002
  ] }) }),
2840
3003
  /* @__PURE__ */ jsx(Cell, { label: "MenuButton, Disclosure", children: /* @__PURE__ */ jsxs("div", { className: "lk-specimen__row", children: [
2841
3004
  /* @__PURE__ */ jsx(
2842
- d2,
3005
+ p4,
2843
3006
  {
2844
3007
  label: "Add trial\u2026",
2845
3008
  onAction: () => {
@@ -2853,14 +3016,14 @@ function ButtonsAndToggles() {
2853
3016
  /* @__PURE__ */ jsx(i2, { open, onToggle: () => setOpen((o4) => !o4), label: "Shapes" })
2854
3017
  ] }) }),
2855
3018
  /* @__PURE__ */ jsx(Cell, { label: "Tabs", children: /* @__PURE__ */ jsxs(o5, { children: [
2856
- /* @__PURE__ */ jsxs(s6, { "aria-label": "Sections", children: [
2857
- /* @__PURE__ */ jsx(c5, { id: "props", children: "Props" }),
2858
- /* @__PURE__ */ jsx(c5, { id: "events", children: "Events" }),
2859
- /* @__PURE__ */ jsx(c5, { id: "snapshot", children: "Snapshot" })
3019
+ /* @__PURE__ */ jsxs(s3, { "aria-label": "Sections", children: [
3020
+ /* @__PURE__ */ jsx(c6, { id: "props", children: "Props" }),
3021
+ /* @__PURE__ */ jsx(c6, { id: "events", children: "Events" }),
3022
+ /* @__PURE__ */ jsx(c6, { id: "snapshot", children: "Snapshot" })
2860
3023
  ] }),
2861
- /* @__PURE__ */ jsx(l7, { id: "props", children: "Props panel." }),
2862
- /* @__PURE__ */ jsx(l7, { id: "events", children: "Events panel." }),
2863
- /* @__PURE__ */ jsx(l7, { id: "snapshot", children: "Snapshot panel." })
3024
+ /* @__PURE__ */ jsx(l8, { id: "props", children: "Props panel." }),
3025
+ /* @__PURE__ */ jsx(l8, { id: "events", children: "Events panel." }),
3026
+ /* @__PURE__ */ jsx(l8, { id: "snapshot", children: "Snapshot panel." })
2864
3027
  ] }) }),
2865
3028
  /* @__PURE__ */ jsx(Cell, { label: "Keycaps", children: /* @__PURE__ */ jsxs("div", { className: "lk-specimen__row", children: [
2866
3029
  /* @__PURE__ */ jsx(p, { keys: [{ label: "\u2318" }, { label: "\u21E7" }, { label: "K" }], separator: "+" }),
@@ -2886,9 +3049,9 @@ function FieldsAndPickers() {
2886
3049
  const [paint, setPaint] = useState({ fill: "solid", color: "#b08adb" });
2887
3050
  return /* @__PURE__ */ jsxs(Section, { title: "Fields & pickers", children: [
2888
3051
  /* @__PURE__ */ jsxs(Cell, { label: "Input", children: [
2889
- /* @__PURE__ */ jsx(u4, { label: "Name", placeholder: "e.g. Pico" }),
3052
+ /* @__PURE__ */ jsx(u2, { label: "Name", placeholder: "e.g. Pico" }),
2890
3053
  /* @__PURE__ */ jsx(
2891
- u4,
3054
+ u2,
2892
3055
  {
2893
3056
  label: "Size",
2894
3057
  leadingAdornment: /* @__PURE__ */ jsx("span", { children: "W" }),
@@ -2896,12 +3059,12 @@ function FieldsAndPickers() {
2896
3059
  defaultValue: "120"
2897
3060
  }
2898
3061
  ),
2899
- /* @__PURE__ */ jsx(u4, { label: "Required", isInvalid: true, errorMessage: "Name is required." })
3062
+ /* @__PURE__ */ jsx(u2, { label: "Required", isInvalid: true, errorMessage: "Name is required." })
2900
3063
  ] }),
2901
3064
  /* @__PURE__ */ jsxs(Cell, { label: "NumberField, Select, ComboBox", children: [
2902
- /* @__PURE__ */ jsx(g, { label: "Opacity", minValue: 0, maxValue: 100, step: 5, defaultValue: 80 }),
3065
+ /* @__PURE__ */ jsx(y, { label: "Opacity", minValue: 0, maxValue: 100, step: 5, defaultValue: 80 }),
2903
3066
  /* @__PURE__ */ jsx(
2904
- T,
3067
+ O2,
2905
3068
  {
2906
3069
  label: "Color",
2907
3070
  defaultSelectedKey: "g",
@@ -2913,7 +3076,7 @@ function FieldsAndPickers() {
2913
3076
  }
2914
3077
  ),
2915
3078
  /* @__PURE__ */ jsx(
2916
- y2,
3079
+ b4,
2917
3080
  {
2918
3081
  label: "Filter",
2919
3082
  placeholder: "Type to filter\u2026",
@@ -2925,19 +3088,19 @@ function FieldsAndPickers() {
2925
3088
  )
2926
3089
  ] }),
2927
3090
  /* @__PURE__ */ jsxs(Cell, { label: "RangeSlider, Slider", children: [
2928
- /* @__PURE__ */ jsx(l8, { label: "Range", defaultValue: [20, 80], minValue: 0, maxValue: 100 }),
3091
+ /* @__PURE__ */ jsx(l9, { label: "Range", defaultValue: [20, 80], minValue: 0, maxValue: 100 }),
2929
3092
  /* @__PURE__ */ jsx(
2930
- l8,
3093
+ l9,
2931
3094
  {
2932
3095
  label: "Opacity",
2933
3096
  defaultValue: 75,
2934
3097
  minValue: 0,
2935
3098
  maxValue: 100,
2936
- formatOutput: (v) => `${v}%`
3099
+ formatOutput: (v2) => `${v2}%`
2937
3100
  }
2938
3101
  ),
2939
3102
  /* @__PURE__ */ jsx(
2940
- y,
3103
+ S,
2941
3104
  {
2942
3105
  ariaLabel: "Pair",
2943
3106
  min: 0,
@@ -2952,7 +3115,7 @@ function FieldsAndPickers() {
2952
3115
  ] }),
2953
3116
  /* @__PURE__ */ jsxs(Cell, { label: "DetentSlider, InlineRange", children: [
2954
3117
  /* @__PURE__ */ jsx(
2955
- c6,
3118
+ c7,
2956
3119
  {
2957
3120
  items: [0.25, 0.5, 1, 2, 4],
2958
3121
  value: rate,
@@ -2963,7 +3126,7 @@ function FieldsAndPickers() {
2963
3126
  }
2964
3127
  ),
2965
3128
  /* @__PURE__ */ jsx(
2966
- i3,
3129
+ i4,
2967
3130
  {
2968
3131
  "aria-label": "Width",
2969
3132
  min: 0,
@@ -2974,11 +3137,11 @@ function FieldsAndPickers() {
2974
3137
  )
2975
3138
  ] }),
2976
3139
  /* @__PURE__ */ jsxs(Cell, { label: "ColorField, GradientEditor", children: [
2977
- /* @__PURE__ */ jsx(l4, { value: color, alpha: true, onInput: setColor, onChange: setColor, "aria-label": "Color" }),
2978
- /* @__PURE__ */ jsx(C2, { value: gradient, onInput: setGradient, onChange: setGradient, kindSwitch: true })
3140
+ /* @__PURE__ */ jsx(h2, { value: color, alpha: true, onInput: setColor, onChange: setColor, "aria-label": "Color" }),
3141
+ /* @__PURE__ */ jsx(j, { value: gradient, onInput: setGradient, onChange: setGradient, kindSwitch: true })
2979
3142
  ] }),
2980
3143
  /* @__PURE__ */ jsx(Cell, { label: "PaintInput", children: /* @__PURE__ */ jsx(
2981
- S,
3144
+ k2,
2982
3145
  {
2983
3146
  value: paint,
2984
3147
  onInput: setPaint,
@@ -3007,9 +3170,9 @@ function PanelsAndRows() {
3007
3170
  ]);
3008
3171
  const [panelCollapsed, setPanelCollapsed] = useState(false);
3009
3172
  return /* @__PURE__ */ jsxs(Section, { title: "Panels & property rows", children: [
3010
- /* @__PURE__ */ jsx(Cell, { label: "PropertyPanel", children: /* @__PURE__ */ jsx(ae2, { title: "Shape", children: /* @__PURE__ */ jsxs(oe, { children: [
3173
+ /* @__PURE__ */ jsx(Cell, { label: "PropertyPanel", children: /* @__PURE__ */ jsx(oe, { title: "Shape", children: /* @__PURE__ */ jsxs(se, { children: [
3011
3174
  /* @__PURE__ */ jsx(
3012
- B2,
3175
+ R,
3013
3176
  {
3014
3177
  kind: "number",
3015
3178
  control: "slider",
@@ -3022,7 +3185,7 @@ function PanelsAndRows() {
3022
3185
  }
3023
3186
  ),
3024
3187
  /* @__PURE__ */ jsx(
3025
- B2,
3188
+ R,
3026
3189
  {
3027
3190
  kind: "number",
3028
3191
  control: "slider",
@@ -3035,7 +3198,7 @@ function PanelsAndRows() {
3035
3198
  }
3036
3199
  ),
3037
3200
  /* @__PURE__ */ jsx(
3038
- B2,
3201
+ R,
3039
3202
  {
3040
3203
  kind: "color",
3041
3204
  label: "Fill",
@@ -3045,9 +3208,9 @@ function PanelsAndRows() {
3045
3208
  onAlphaChange: setAlpha
3046
3209
  }
3047
3210
  ),
3048
- /* @__PURE__ */ jsx(B2, { kind: "color", label: "Stroke", value: stroke, onChange: setStroke }),
3211
+ /* @__PURE__ */ jsx(R, { kind: "color", label: "Stroke", value: stroke, onChange: setStroke }),
3049
3212
  /* @__PURE__ */ jsx(
3050
- B2,
3213
+ R,
3051
3214
  {
3052
3215
  kind: "number",
3053
3216
  label: "Count",
@@ -3059,7 +3222,7 @@ function PanelsAndRows() {
3059
3222
  }
3060
3223
  ),
3061
3224
  /* @__PURE__ */ jsx(
3062
- B2,
3225
+ R,
3063
3226
  {
3064
3227
  kind: "enum",
3065
3228
  label: "Line cap",
@@ -3072,7 +3235,7 @@ function PanelsAndRows() {
3072
3235
  }
3073
3236
  ),
3074
3237
  /* @__PURE__ */ jsx(
3075
- B2,
3238
+ R,
3076
3239
  {
3077
3240
  kind: "enum",
3078
3241
  control: "toggle",
@@ -3086,10 +3249,10 @@ function PanelsAndRows() {
3086
3249
  ]
3087
3250
  }
3088
3251
  ),
3089
- /* @__PURE__ */ jsx(B2, { kind: "string", label: "Label", value: label, onChange: setLabel }),
3090
- /* @__PURE__ */ jsx(B2, { kind: "boolean", label: "Visible", value: visible, onChange: setVisible }),
3091
- /* @__PURE__ */ jsx(b2, { title: "Bevel", defaultCollapsed: true, children: /* @__PURE__ */ jsx(
3092
- B2,
3252
+ /* @__PURE__ */ jsx(R, { kind: "string", label: "Label", value: label, onChange: setLabel }),
3253
+ /* @__PURE__ */ jsx(R, { kind: "boolean", label: "Visible", value: visible, onChange: setVisible }),
3254
+ /* @__PURE__ */ jsx(b3, { title: "Bevel", defaultCollapsed: true, children: /* @__PURE__ */ jsx(
3255
+ R,
3093
3256
  {
3094
3257
  kind: "number",
3095
3258
  control: "slider",
@@ -3104,7 +3267,7 @@ function PanelsAndRows() {
3104
3267
  ) })
3105
3268
  ] }) }) }),
3106
3269
  /* @__PURE__ */ jsx(Cell, { label: "PrefsForm", children: /* @__PURE__ */ jsx(
3107
- A2,
3270
+ j2,
3108
3271
  {
3109
3272
  schema: PREFS,
3110
3273
  values: prefs,
@@ -3112,7 +3275,7 @@ function PanelsAndRows() {
3112
3275
  }
3113
3276
  ) }),
3114
3277
  /* @__PURE__ */ jsx(Cell, { label: "LayerList", children: /* @__PURE__ */ jsx(
3115
- D,
3278
+ D2,
3116
3279
  {
3117
3280
  items: layers,
3118
3281
  onReorder: (move) => setLayers(S2(layers, move)),
@@ -3123,11 +3286,11 @@ function PanelsAndRows() {
3123
3286
  }
3124
3287
  ) }),
3125
3288
  /* @__PURE__ */ jsx(Cell, { label: "Sidebar, SidebarPanel", children: /* @__PURE__ */ jsx("div", { className: "lk-specimen__box", children: /* @__PURE__ */ jsx(n6, { side: "right", ariaLabel: "Specimen sidebar", children: /* @__PURE__ */ jsx(
3126
- s5,
3289
+ a10,
3127
3290
  {
3128
3291
  title: "Selection",
3129
3292
  collapsed: panelCollapsed,
3130
- onToggleCollapse: () => setPanelCollapsed((v) => !v),
3293
+ onToggleCollapse: () => setPanelCollapsed((v2) => !v2),
3131
3294
  onHide: () => {
3132
3295
  },
3133
3296
  children: /* @__PURE__ */ jsx("div", { className: "lk-specimen__pad", children: "2 items selected" })
@@ -3139,7 +3302,7 @@ function OverlaysAndFeedback() {
3139
3302
  const calloutAnchor = useRef(null);
3140
3303
  const [calloutOpen, setCalloutOpen] = useState(false);
3141
3304
  const [dialogOpen, setDialogOpen] = useState(false);
3142
- const toasts = useMemo(() => m3(), []);
3305
+ const toasts = useMemo(() => C3(), []);
3143
3306
  return /* @__PURE__ */ jsxs(Section, { title: "Overlays & feedback", children: [
3144
3307
  /* @__PURE__ */ jsx(Cell, { label: "Tooltip, Callout, Dialog, Toast", children: /* @__PURE__ */ jsxs("div", { className: "lk-specimen__row", children: [
3145
3308
  /* @__PURE__ */ jsxs(l2, { children: [
@@ -3148,7 +3311,7 @@ function OverlaysAndFeedback() {
3148
3311
  ] }),
3149
3312
  /* @__PURE__ */ jsx("button", { type: "button", ref: calloutAnchor, onClick: () => setCalloutOpen(true), children: "Callout" }),
3150
3313
  /* @__PURE__ */ jsx(
3151
- g2,
3314
+ v2,
3152
3315
  {
3153
3316
  triggerRef: calloutAnchor,
3154
3317
  isOpen: calloutOpen,
@@ -3160,7 +3323,7 @@ function OverlaysAndFeedback() {
3160
3323
  ),
3161
3324
  /* @__PURE__ */ jsx(c, { onClick: () => setDialogOpen(true), children: "Dialog" }),
3162
3325
  /* @__PURE__ */ jsx(
3163
- u3,
3326
+ d3,
3164
3327
  {
3165
3328
  isOpen: dialogOpen,
3166
3329
  onOpenChange: setDialogOpen,
@@ -3176,7 +3339,7 @@ function OverlaysAndFeedback() {
3176
3339
  children: "Toast"
3177
3340
  }
3178
3341
  ),
3179
- /* @__PURE__ */ jsx(S4, { queue: toasts, placement: "bottom-right" })
3342
+ /* @__PURE__ */ jsx(j3, { queue: toasts, placement: "bottom-right" })
3180
3343
  ] }) }),
3181
3344
  /* @__PURE__ */ jsx(Cell, { label: "Powerline", children: /* @__PURE__ */ jsx(
3182
3345
  r4,
@@ -3193,7 +3356,7 @@ function OverlaysAndFeedback() {
3193
3356
  /* @__PURE__ */ jsx(Cell, { label: "StatusBar", children: /* @__PURE__ */ jsxs(n7, { ariaLabel: "Editor status", children: [
3194
3357
  /* @__PURE__ */ jsx(r5, { children: "tool: select" }),
3195
3358
  /* @__PURE__ */ jsx(r5, { children: "sel: 3" }),
3196
- /* @__PURE__ */ jsx(i4, {}),
3359
+ /* @__PURE__ */ jsx(i5, {}),
3197
3360
  /* @__PURE__ */ jsx(r5, { muted: true, children: "0.7.0" })
3198
3361
  ] }) })
3199
3362
  ] });
@@ -3239,19 +3402,18 @@ function ToolChrome() {
3239
3402
  label: id,
3240
3403
  shortcut: id.slice(0, 1).toUpperCase(),
3241
3404
  active: tool === id,
3242
- tabbable: tool === id,
3243
3405
  onClick: () => setTool(id)
3244
3406
  },
3245
3407
  id
3246
3408
  )) }) }),
3247
- /* @__PURE__ */ jsx(Cell, { label: "ToolOptionsBar", children: /* @__PURE__ */ jsxs(c3, { label: "Text", children: [
3409
+ /* @__PURE__ */ jsx(Cell, { label: "ToolOptionsBar", children: /* @__PURE__ */ jsxs(c4, { label: "Text", children: [
3248
3410
  /* @__PURE__ */ jsx(c, { size: "sm", variant: "ghost", iconOnly: true, ariaLabel: "Bold", children: "B" }),
3249
3411
  /* @__PURE__ */ jsx(c, { size: "sm", variant: "ghost", iconOnly: true, ariaLabel: "Italic", children: "I" })
3250
3412
  ] }) }),
3251
3413
  /* @__PURE__ */ jsx(Cell, { label: "ResizeHandle", children: /* @__PURE__ */ jsxs("div", { className: "lk-specimen__box", children: [
3252
3414
  /* @__PURE__ */ jsx("div", { className: "lk-specimen__box-main", children: "content" }),
3253
3415
  /* @__PURE__ */ jsx(
3254
- c4,
3416
+ l7,
3255
3417
  {
3256
3418
  value: paneWidth,
3257
3419
  min: 100,
@@ -3360,7 +3522,7 @@ function EditorsAndPlots() {
3360
3522
  }
3361
3523
  ) }),
3362
3524
  /* @__PURE__ */ jsx(Cell, { label: "BandEditor", wide: true, children: /* @__PURE__ */ jsx(
3363
- k2,
3525
+ j4,
3364
3526
  {
3365
3527
  min: 1 / 64,
3366
3528
  max: 1 / 2,
@@ -3370,12 +3532,12 @@ function EditorsAndPlots() {
3370
3532
  onChange: setBands,
3371
3533
  selectedIndex: band,
3372
3534
  onSelect: setBand,
3373
- renderBand: (b2) => b2.data.name,
3535
+ renderBand: (b3) => b3.data.name,
3374
3536
  ticks: [1 / 24, 1 / 12, 1 / 6].map((at) => ({ at, label: `1/${Math.round(1 / at)}` }))
3375
3537
  }
3376
3538
  ) }),
3377
3539
  /* @__PURE__ */ jsx(Cell, { label: "Timeline", wide: true, children: /* @__PURE__ */ jsx("div", { className: "lk-specimen__timeline", children: /* @__PURE__ */ jsx(
3378
- $2,
3540
+ be,
3379
3541
  {
3380
3542
  tracks,
3381
3543
  duration: 2e3,
@@ -3438,6 +3600,6 @@ function Specimen({ className }) {
3438
3600
  ] });
3439
3601
  }
3440
3602
 
3441
- export { AnnotationOverlay, AnnotationTargets, Lab, LabBoundary, LabPalette, LabRoot, LabShell, LabSwitcher, SPECIMEN_SECTIONS, Specimen, Trial, TrialBody, TrialChrome, Workspace, addTrial, annotationsFromJSON, capturePlan, captureTarget, cloneTrial, closeTrial, composeCaptureSvg, createAnnotationScene, createAnnotationStore, createMarkDrawOne, currentPage, defineInstrument, fitView, fracContains, fracEncloses, fracToWorld, fromWeaselView, interstellarTheme, isStale, reorderTrials, resetTrial, resolveMarkStyle, roundFrac, seenFrom, toWeaselView, validateConfigSchema, worldToFrac };
3603
+ export { AnnotationOverlay, AnnotationTargets, Lab, LabBoundary, LabPalette, LabRoot, LabShell, LabSwitcher, LabZoom, SPECIMEN_SECTIONS, Specimen, Trial, TrialBody, TrialChrome, Workspace, addTrial, annotationsFromJSON, capturePlan, captureTarget, cloneTrial, closeTrial, composeCaptureSvg, createAnnotationScene, createAnnotationStore, createMarkDrawOne, currentPage, defineInstrument, fitView, fromWeaselView, interstellarTheme, isStale, reorderTrials, resetTrial, resolveMarkStyle, seenFrom, toWeaselView, validateConfigSchema };
3442
3604
  //# sourceMappingURL=index.js.map
3443
3605
  //# sourceMappingURL=index.js.map