@weasel-js/labkit 1.8.0 → 1.8.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 (189) hide show
  1. package/README.md +24 -0
  2. package/dist/_dts/{CanvasStackContext-C0fzFsQg.d.ts → CanvasStackContext-B05ZM04l.d.ts} +3 -1
  3. package/dist/_dts/{Stage-Cvwxdnfp.d.ts → Stage-CIJlbjrE.d.ts} +5 -3
  4. package/dist/_dts/{index-DRaswz91.d.ts → index-8geQNQet.d.ts} +83 -11
  5. package/dist/_dts/{index-DEHRoTXK.d.ts → index-DRi3V5sN.d.ts} +5 -3
  6. package/dist/_dts/{useTrialState-CS4TdWPW.d.ts → useTrialState-Ar0AFpCe.d.ts} +94 -87
  7. package/dist/canvas/index.d.ts +4 -4
  8. package/dist/canvas/index.js +2 -2
  9. package/dist/chrome/index.d.ts +2 -2
  10. package/dist/chrome/index.js +7 -7
  11. package/dist/{chunk-YX75W6EK.js → chunk-4RCVWX4M.js} +8 -8
  12. package/dist/chunk-4RCVWX4M.js.map +1 -0
  13. package/dist/{chunk-WIG6XHJ7.js → chunk-6IIKLXBZ.js} +13 -4
  14. package/dist/chunk-6IIKLXBZ.js.map +1 -0
  15. package/dist/{chunk-F4ACIK7Z.js → chunk-6Q6ACJ3H.js} +3 -3
  16. package/dist/{chunk-F4ACIK7Z.js.map → chunk-6Q6ACJ3H.js.map} +1 -1
  17. package/dist/{chunk-PMLZP7RT.js → chunk-7ALJOVGP.js} +5 -9
  18. package/dist/chunk-7ALJOVGP.js.map +1 -0
  19. package/dist/{chunk-XE5HJOPL.js → chunk-7FMZT2WL.js} +3 -3
  20. package/dist/{chunk-XE5HJOPL.js.map → chunk-7FMZT2WL.js.map} +1 -1
  21. package/dist/{chunk-XVUKDSYV.js → chunk-AVOILDN2.js} +2 -2
  22. package/dist/{chunk-XVUKDSYV.js.map → chunk-AVOILDN2.js.map} +1 -1
  23. package/dist/{chunk-2N7UBSS2.js → chunk-J5MDJEOZ.js} +3 -3
  24. package/dist/{chunk-2N7UBSS2.js.map → chunk-J5MDJEOZ.js.map} +1 -1
  25. package/dist/{chunk-NVKBL7ZE.js → chunk-KHB3KRRQ.js} +7 -7
  26. package/dist/chunk-KHB3KRRQ.js.map +1 -0
  27. package/dist/{chunk-XXACAPVU.js → chunk-NHIO2O3O.js} +31 -30
  28. package/dist/chunk-NHIO2O3O.js.map +1 -0
  29. package/dist/{chunk-EESJUSYR.js → chunk-QMAY74TP.js} +56 -5
  30. package/dist/chunk-QMAY74TP.js.map +1 -0
  31. package/dist/{chunk-YDE6XATS.js → chunk-RN7T7OBO.js} +44 -22
  32. package/dist/chunk-RN7T7OBO.js.map +1 -0
  33. package/dist/{chunk-3L4MLLQB.js → chunk-RXO5UEGT.js} +4 -11
  34. package/dist/chunk-RXO5UEGT.js.map +1 -0
  35. package/dist/{chunk-MOTDMMMN.js → chunk-SQX35X5V.js} +3 -3
  36. package/dist/{chunk-MOTDMMMN.js.map → chunk-SQX35X5V.js.map} +1 -1
  37. package/dist/{chunk-J5HJJDLF.js → chunk-T5OEUNCB.js} +7 -2
  38. package/dist/chunk-T5OEUNCB.js.map +1 -0
  39. package/dist/{chunk-RL2LOLNI.js → chunk-UX66USTN.js} +2 -2
  40. package/dist/chunk-UX66USTN.js.map +1 -0
  41. package/dist/{chunk-WURZTJGQ.js → chunk-V4SMGEYY.js} +4858 -4806
  42. package/dist/chunk-V4SMGEYY.js.map +1 -0
  43. package/dist/{chunk-E23Q6A3Y.js → chunk-VCK5T6EP.js} +3 -3
  44. package/dist/{chunk-E23Q6A3Y.js.map → chunk-VCK5T6EP.js.map} +1 -1
  45. package/dist/{chunk-T6DLGEJB.js → chunk-XWR2P6MR.js} +23 -6
  46. package/dist/chunk-XWR2P6MR.js.map +1 -0
  47. package/dist/{chunk-7KAETQSS.js → chunk-YZA73U4A.js} +7 -7
  48. package/dist/chunk-YZA73U4A.js.map +1 -0
  49. package/dist/config/index.d.ts +2 -2
  50. package/dist/config/index.js +2 -2
  51. package/dist/controls/index.js +5 -5
  52. package/dist/dragdrop/index.d.ts +1 -1
  53. package/dist/dragdrop/index.js +2 -2
  54. package/dist/index.d.ts +157 -15
  55. package/dist/index.js +893 -258
  56. package/dist/index.js.map +1 -1
  57. package/dist/layers/index.d.ts +1 -1
  58. package/dist/layers/index.js +3 -3
  59. package/dist/loupe/index.d.ts +175 -27
  60. package/dist/loupe/index.js +547 -141
  61. package/dist/loupe/index.js.map +1 -1
  62. package/dist/overview/index.js +5 -5
  63. package/dist/passthrough/weasel-ui.js +3 -3
  64. package/dist/primitives/index.d.ts +4 -19
  65. package/dist/primitives/index.js +7 -7
  66. package/dist/state/index.d.ts +5 -7
  67. package/dist/state/index.js +5 -5
  68. package/dist/styles.css +173 -38
  69. package/dist/surface/index.d.ts +20 -1
  70. package/dist/surface/index.js +2 -2
  71. package/dist/undo/index.d.ts +18 -7
  72. package/dist/undo/index.js +1 -1
  73. package/package.json +11 -11
  74. package/src/annotations/AnnotationOverlay.abandon.test.tsx +4 -0
  75. package/src/annotations/AnnotationOverlay.tsx +10 -1
  76. package/src/annotations/Annotations.less +2 -0
  77. package/src/annotations/Annotations.overlay.test.tsx +29 -0
  78. package/src/canvas/CanvasStack.test.tsx +35 -0
  79. package/src/canvas/CanvasStack.tsx +12 -1
  80. package/src/canvas/useLayerScheduler.ts +14 -0
  81. package/src/chrome/builtins.test.ts +14 -0
  82. package/src/chrome/builtins.tsx +17 -2
  83. package/src/chrome/regions/StatusRegion.tsx +20 -14
  84. package/src/chrome/regions/regions.test.tsx +28 -0
  85. package/src/chrome/types.ts +8 -0
  86. package/src/clock/clock.browser.test.less +5 -0
  87. package/src/clock/clock.browser.test.tsx +77 -0
  88. package/src/clock/clock.integration.test.tsx +95 -0
  89. package/src/clock/clockRegistry.test.ts +33 -0
  90. package/src/clock/clockRegistry.ts +46 -0
  91. package/src/clock/hooks.ts +44 -0
  92. package/src/clock/index.ts +2 -0
  93. package/src/clock/trialClock.test.ts +265 -0
  94. package/src/clock/trialClock.ts +245 -0
  95. package/src/clock/useClockLoop.ts +45 -0
  96. package/src/config/resolve.test.ts +12 -0
  97. package/src/config/resolve.ts +5 -0
  98. package/src/controls/ControlMatrix.tsx +56 -54
  99. package/src/index.ts +3 -2
  100. package/src/instrument/types.ts +21 -1
  101. package/src/lab/Lab.chrome.test.tsx +8 -1
  102. package/src/lab/Lab.presentation.test.tsx +113 -0
  103. package/src/lab/Lab.surface.test.tsx +20 -9
  104. package/src/lab/Lab.tsx +132 -257
  105. package/src/lab/LabLightbox.browser.test.less +17 -0
  106. package/src/lab/LabLightbox.browser.test.tsx +216 -0
  107. package/src/lab/LabShell.less +10 -3
  108. package/src/lab/LabSurface.tsx +129 -0
  109. package/src/lab/Workspace.stories.tsx +18 -0
  110. package/src/lab/Workspace.surface.test.tsx +4 -0
  111. package/src/lab/Workspace.test.tsx +37 -1
  112. package/src/lab/Workspace.tsx +34 -6
  113. package/src/lab/index.ts +2 -0
  114. package/src/lab/nebula.ts +30 -0
  115. package/src/lab/openLab.test.ts +81 -0
  116. package/src/lab/openLab.ts +119 -0
  117. package/src/lab/presentation.browser.test.less +5 -0
  118. package/src/lab/presentation.browser.test.tsx +119 -0
  119. package/src/lab/presentation.less +64 -0
  120. package/src/lab/presentation.tsx +47 -0
  121. package/src/lab/useFocusPick.ts +43 -0
  122. package/src/lightbox/Lightbox.browser.test.less +14 -0
  123. package/src/lightbox/Lightbox.browser.test.tsx +113 -0
  124. package/src/lightbox/Lightbox.less +128 -0
  125. package/src/lightbox/Lightbox.test.tsx +195 -0
  126. package/src/lightbox/Lightbox.tsx +205 -0
  127. package/src/lightbox/LightboxContext.ts +38 -0
  128. package/src/lightbox/LightboxLayers.tsx +40 -0
  129. package/src/lightbox/index.ts +8 -0
  130. package/src/lightbox/modal.ts +94 -0
  131. package/src/loupe/AGENTS.md +40 -2
  132. package/src/loupe/CanvasLoupe.tsx +5 -4
  133. package/src/loupe/DomLoupe.tsx +7 -5
  134. package/src/loupe/Loupe.less +15 -0
  135. package/src/loupe/LoupeBubble.tsx +35 -13
  136. package/src/loupe/SourceLoupe.browser.test.tsx +204 -0
  137. package/src/loupe/SourceLoupe.tsx +60 -0
  138. package/src/loupe/TrialLoupe.browser.test.tsx +342 -0
  139. package/src/loupe/TrialLoupe.tsx +126 -28
  140. package/src/loupe/canvasLens.ts +20 -17
  141. package/src/loupe/index.ts +26 -2
  142. package/src/loupe/sourceLens.test.ts +65 -0
  143. package/src/loupe/sourceLens.ts +97 -0
  144. package/src/loupe/types.test.ts +6 -0
  145. package/src/loupe/types.ts +109 -7
  146. package/src/loupe/useLoupe.test.tsx +188 -3
  147. package/src/loupe/useLoupe.ts +106 -11
  148. package/src/primitives/index.ts +2 -2
  149. package/src/specimen/Specimen.tsx +0 -7
  150. package/src/state/helpers.test.ts +4 -11
  151. package/src/state/helpers.ts +2 -8
  152. package/src/state/index.ts +0 -2
  153. package/src/state/openLabStore.test.ts +10 -8
  154. package/src/state/openLabStore.ts +1 -1
  155. package/src/state/store.test.ts +2 -2
  156. package/src/state/store.ts +15 -16
  157. package/src/state/types.ts +9 -9
  158. package/src/styles.less +2 -1
  159. package/src/surface/AGENTS.md +13 -0
  160. package/src/surface/index.ts +1 -0
  161. package/src/surface/useSurfaceTile.test.tsx +4 -0
  162. package/src/surface/useSurfaceTile.ts +17 -1
  163. package/src/surface/useTiledSurface.test.tsx +65 -0
  164. package/src/surface/useTiledSurface.ts +68 -1
  165. package/src/trial/Trial.stories.tsx +0 -1
  166. package/src/trial/Trial.test.tsx +40 -3
  167. package/src/trial/Trial.tsx +87 -43
  168. package/src/trial/TrialChrome.tsx +20 -2
  169. package/src/trial/trialOps.test.ts +1 -2
  170. package/src/trial/trialOps.ts +2 -3
  171. package/src/undo/stateOp.ts +21 -0
  172. package/src/undo/undoStack.ts +18 -6
  173. package/dist/chunk-3L4MLLQB.js.map +0 -1
  174. package/dist/chunk-7KAETQSS.js.map +0 -1
  175. package/dist/chunk-EESJUSYR.js.map +0 -1
  176. package/dist/chunk-J5HJJDLF.js.map +0 -1
  177. package/dist/chunk-NVKBL7ZE.js.map +0 -1
  178. package/dist/chunk-PMLZP7RT.js.map +0 -1
  179. package/dist/chunk-RL2LOLNI.js.map +0 -1
  180. package/dist/chunk-T6DLGEJB.js.map +0 -1
  181. package/dist/chunk-WIG6XHJ7.js.map +0 -1
  182. package/dist/chunk-WURZTJGQ.js.map +0 -1
  183. package/dist/chunk-XXACAPVU.js.map +0 -1
  184. package/dist/chunk-YDE6XATS.js.map +0 -1
  185. package/dist/chunk-YX75W6EK.js.map +0 -1
  186. package/src/primitives/StatusBar.less +0 -34
  187. package/src/primitives/StatusBar.stories.tsx +0 -24
  188. package/src/primitives/StatusBar.test.tsx +0 -25
  189. package/src/primitives/StatusBar.tsx +0 -28
package/dist/index.js CHANGED
@@ -1,46 +1,46 @@
1
1
  import { LoupeSwitchContext } from './chunk-2ES4W47N.js';
2
2
  export { LoupeSwitchContext } from './chunk-2ES4W47N.js';
3
- import { createEventBus, pushSnapshot, redo, undo } from './chunk-RL2LOLNI.js';
4
- export { clearUndo, createEventBus, emptyStack, pushSnapshot, redo, undo } from './chunk-RL2LOLNI.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';
3
+ import { createEventBus } from './chunk-UX66USTN.js';
4
+ export { clearUndo, createEventBus, emptyStack, pushSnapshot, redo, undo } from './chunk-UX66USTN.js';
5
+ import { useDragDrop, DragOverlay, Palette } from './chunk-J5MDJEOZ.js';
6
+ import { useTiledSurface } from './chunk-QMAY74TP.js';
7
+ export { composeRects, toDeviceRect, useTiledSurface } from './chunk-QMAY74TP.js';
8
8
  import { useJob } from './chunk-EFXRM6XQ.js';
9
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-YDE6XATS.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-YDE6XATS.js';
10
+ import { markCommands, boundsOf, roundFrac, worldToFrac, fracToWorld, fracEncloses, fracContains, POINT_MARK_SHAPE, roundPoints, fromShape, markSvgNodes, toShape } from './chunk-6Q6ACJ3H.js';
11
+ export { fracContains, fracEncloses, fracToWorld, markCommands, markSvgNodes, roundFrac, worldToFrac } from './chunk-6Q6ACJ3H.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-RN7T7OBO.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-RN7T7OBO.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-NVKBL7ZE.js';
17
- export { JobProgress, Legend, Readout, Split } from './chunk-NVKBL7ZE.js';
18
- import { FloatingPanel } from './chunk-XE5HJOPL.js';
19
- export { FloatingPanel } from './chunk-XE5HJOPL.js';
20
- import { Toolbar, StatusBar, ScaleIndicator, ZoomControl } from './chunk-3L4MLLQB.js';
21
- export { FpsMeter, ScaleIndicator, StatusBar, Toolbar, ZoomControl } from './chunk-3L4MLLQB.js';
16
+ import { Split, JobProgress, Legend } from './chunk-KHB3KRRQ.js';
17
+ export { JobProgress, Legend, Readout, Split } from './chunk-KHB3KRRQ.js';
18
+ import { FloatingPanel } from './chunk-7FMZT2WL.js';
19
+ export { FloatingPanel } from './chunk-7FMZT2WL.js';
20
+ import { Toolbar, ScaleIndicator, ZoomControl } from './chunk-RXO5UEGT.js';
21
+ export { FpsMeter, ScaleIndicator, Toolbar, ZoomControl } from './chunk-RXO5UEGT.js';
22
22
  import './chunk-CZZPXUOU.js';
23
- import { unruledConfigSchema, createLabStore, openLabStore, panelKey } from './chunk-XXACAPVU.js';
24
- export { CURRENT_DOCUMENT_VERSION, dockPanel, labDocumentKey, openLabStore, panelKey, quarantineKey, undockPanel, useTrialState } from './chunk-XXACAPVU.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 { ControlMatrix } from './chunk-YX75W6EK.js';
28
- export { ControlPanel, inDialog, summarizeValue } from './chunk-7KAETQSS.js';
29
- import { useConfigSchema } from './chunk-MOTDMMMN.js';
30
- export { sectionTree, useConfigSchema } from './chunk-MOTDMMMN.js';
31
- import { useLabContext, LabContext } from './chunk-Z76KEZAX.js';
23
+ import { unruledConfigSchema, createLabStore, openLabStore, panelKey } from './chunk-NHIO2O3O.js';
24
+ export { CURRENT_DOCUMENT_VERSION, dockPanel, labDocumentKey, openLabStore, panelKey, quarantineKey, undockPanel, useTrialState } from './chunk-NHIO2O3O.js';
25
+ import { valueRecord, newId, useWarnIgnoredChange, useOpenOnce, PersistenceContext, createRecordCache, noneAdapter, defaultStorage, usePersistedState } from './chunk-7ALJOVGP.js';
26
+ export { Persistence, createIndexedDbAdapter, createMemoryAdapter, indexedDbAdapter, localStorageAdapter, noneAdapter, sessionStorageAdapter, urlHashAdapter, usePersistedState } from './chunk-7ALJOVGP.js';
27
+ export { ControlMatrix } from './chunk-4RCVWX4M.js';
28
+ export { ControlPanel, inDialog, summarizeValue } from './chunk-YZA73U4A.js';
29
+ import { useConfigSchema } from './chunk-SQX35X5V.js';
30
+ export { sectionTree, useConfigSchema } from './chunk-SQX35X5V.js';
31
+ import { LabContext, useLabContext } from './chunk-Z76KEZAX.js';
32
32
  export { LabContext, useLabContext } from './chunk-Z76KEZAX.js';
33
- import { useResolvedConfig } from './chunk-J5HJJDLF.js';
34
- export { BaseNode, BooleanNode, ColorNode, CustomNode, EnumNode, GroupNode, ListNode, NumberNode, StringNode, ValueNode, applyRules, builtinRules, f, isConfigBranch, resolveConfigSchema, titleCase, useResolvedConfig } from './chunk-J5HJJDLF.js';
33
+ import { useResolvedConfig } from './chunk-T5OEUNCB.js';
34
+ export { BaseNode, BooleanNode, ColorNode, CustomNode, EnumNode, GroupNode, ListNode, NumberNode, StringNode, ValueNode, applyRules, builtinRules, f, isConfigBranch, resolveConfigSchema, titleCase, useResolvedConfig } from './chunk-T5OEUNCB.js';
35
35
  export { fromConfigFields, isLeafVisible } from './chunk-WLMDTI2Q.js';
36
36
  import { hasConfigPath, schemaNodeAtPath, applyConfigWrite, resolveAutoConfig, isAuto, isRecord, autoPathsOf, fillConfigDefaults } from './chunk-READTDQX.js';
37
37
  export { auto, autoPathsOf, fillConfigDefaults, hasConfigPath, isAuto, resolveAutoConfig, schemaNodeAtPath, valueAtPath, withValueAtPath } from './chunk-READTDQX.js';
38
- import { CanvasStack, Stage, fitStage } from './chunk-T6DLGEJB.js';
39
- export { CanvasStack, Stage, fitStage } from './chunk-T6DLGEJB.js';
38
+ import { CanvasStack, Stage, fitStage } from './chunk-XWR2P6MR.js';
39
+ export { CanvasStack, Stage, fitStage } from './chunk-XWR2P6MR.js';
40
40
  export { centerOn, zoomAt } from './chunk-ROGJWDQA.js';
41
41
  export { screenToWorld, worldToScreen } from './chunk-FG3EXZDV.js';
42
- import { useSurfaceOptional, useSurfaceCanvas, useTileId, SurfaceContext, SurfaceCanvasContext } from './chunk-WIG6XHJ7.js';
43
- export { SurfaceCanvasContext, SurfaceContext, useSurface, useSurfaceCanvas, useSurfaceOptional, useSurfaceTile, useTileId } from './chunk-WIG6XHJ7.js';
42
+ import { useSurfaceOptional, useSurfaceCanvas, useTileId, useTileInScope, SurfaceContext, SurfaceCanvasContext } from './chunk-6IIKLXBZ.js';
43
+ export { SurfaceCanvasContext, SurfaceContext, useSurface, useSurfaceCanvas, useSurfaceOptional, useSurfaceTile, useTileId, useTileInScope } from './chunk-6IIKLXBZ.js';
44
44
  import { CameraWheelContext, as2DView, CameraRegistryContext, DEFAULT_VIEW, CameraPublishContext, createCameraRegistry, withZoom } from './chunk-VYVGVBGY.js';
45
45
  export { CameraContext, CameraInput, CameraPublishContext, CameraRegistryContext, CameraScope, DEFAULT_VIEW, as2DView, createCameraRegistry, usePublishCamera } from './chunk-VYVGVBGY.js';
46
46
  import { applyCamera } from './chunk-ULYYNWGP.js';
@@ -50,12 +50,12 @@ export { CanvasStackContext } from './chunk-ZQIHJXP4.js';
50
50
  import { LabStoreContext, TrialIdProvider, TrialIdContext } from './chunk-2YILQ7Y5.js';
51
51
  export { LabStoreContext, LabStoreProvider, TrialIdContext, TrialIdProvider, useLabStore, useTrialId } from './chunk-2YILQ7Y5.js';
52
52
  import './chunk-2RVCB2PT.js';
53
- import { D2, S, X, U, G, p6, o3, c, Xt, c4, a5, l5, u3, s3, r3, a7, i3, o5, s5, c5, l8, p, f2, u2, y, O2, b4, l9, w2, c6, i4, h2, j, k2, ce, le, ye, b3, j2, n6, a8, C3, l3, u, v3, d3, j3, r4, n7, r5, i5, p2, f, n8, o4, p4, l7, w, L, n9, j4, xe2, n2 } from './chunk-WURZTJGQ.js';
54
- export { c as Button, V as CloseIcon, xe as CollapseIcon, _ as CurveField, s as DetailList, c2 as DetailRow, Se as ExpandIcon, l as Icon, u2 as Input, l10 as Jog, D2 as LayerList, re as PauseIcon, ne as PlayIcon, be as PropertyControl, ye as PropertyField, b3 as PropertyGroup, le as PropertyList, ce as PropertyPanel, L2 as PropertyRow, ue as PropertySpan, B as ResetIcon, O2 as Select, ae as StepBackIcon, $ as StepForwardIcon, ie as StopIcon, x2 as Subpanel, s3 as ToggleBar, oe as TokenPanel, H2 as inferTokenType, S as moveLayers, O3 as refitScale } from './chunk-WURZTJGQ.js';
55
- import { serializeSvg } from './chunk-E23Q6A3Y.js';
56
- import './chunk-XVUKDSYV.js';
57
- 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';
58
- import { createContext, useState, useRef, useEffect, useInsertionEffect, useCallback, useContext, useMemo, useSyncExternalStore, useLayoutEffect, useId, Children } from 'react';
53
+ import { A2, T, Z, U, V, de, G, p6, o3, c, Xt, c4, a5, l5, u3, s3, r3, a7, i3, o5, s5, c5, l8, p, f3, u2, y, O2, b4, l9, w2, c6, i4, h2, j, k2, ce2, le, ye, b3, j2, n6, a8, C3, l3, u, v3, d2, j3, r4, n7, r5, i5, p2, f2, n8, o4, p4, l7, w, L, n9, j4, xe, n2 } from './chunk-V4SMGEYY.js';
54
+ export { c as Button, V as CloseIcon, we as CollapseIcon, _ as CurveField, s as DetailList, c2 as DetailRow, Te as ExpandIcon, l as Icon, u2 as Input, l10 as Jog, A2 as LayerList, oe as PauseIcon, ae as PlayIcon, be as PropertyControl, ye as PropertyField, b3 as PropertyGroup, le as PropertyList, ce2 as PropertyPanel, L2 as PropertyRow, ue as PropertySpan, B as ResetIcon, O2 as Select, n7 as StatusBar, r5 as StatusBarItem, i5 as StatusBarSpacer, ce as StepBackIcon, $ as StepForwardIcon, se as StopIcon, x2 as Subpanel, s3 as ToggleBar, oe2 as TokenPanel, H2 as inferTokenType, T as moveLayers, O3 as refitScale } from './chunk-V4SMGEYY.js';
55
+ import { serializeSvg } from './chunk-VCK5T6EP.js';
56
+ import './chunk-AVOILDN2.js';
57
+ import { makeViewportZoomAction, createScene, asNodeId as asNodeId$1, registerNodeShape, useLatest, WeaselProvider, SceneCanvas, createPointerStore, PointerContextProvider, DepRegistryProvider, ActionsProvider, ActiveToolContextProvider, useActionsRegistry, useDepSource, ActionDisabledReason, useGestureDispatcher, sceneFromJSON, warmRender, renderSceneToPixels, useActiveToolContext, createHistory, useVisibleRaf, stableStringify } from '@weasel-js/core';
58
+ import { createContext, useState, useRef, useEffect, useInsertionEffect, useCallback, useContext, useSyncExternalStore, useReducer, useLayoutEffect, useMemo, useId, Children } from 'react';
59
59
  import { flushSync, createPortal } from 'react-dom';
60
60
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
61
61
  import { defineTheme, weaselTheme, colorCount, resolveTheme, colorAt } from '@weasel-js/theme';
@@ -668,6 +668,7 @@ function AnnotationOverlay({
668
668
  const configRef = useLatest(config);
669
669
  const id = target.id;
670
670
  const tileId = useTileId(id);
671
+ const inScope = useTileInScope(tileId);
671
672
  const lastTile = useRef(null);
672
673
  useEffect(() => {
673
674
  const el = target.ref.current ?? null;
@@ -758,6 +759,7 @@ function AnnotationOverlay({
758
759
  className: "lk-annotate__input",
759
760
  "data-annotation-target": id,
760
761
  "data-idle": annotationToolInfo(activeToolId) ? void 0 : "",
762
+ hidden: !inScope,
761
763
  role: "application",
762
764
  "aria-label": `Annotations on ${id}`,
763
765
  tabIndex: 0,
@@ -811,6 +813,62 @@ function AnnotationTargets({
811
813
  t.id
812
814
  )) });
813
815
  }
816
+ function createClockRegistry() {
817
+ const handles = /* @__PURE__ */ new Map();
818
+ const listeners = /* @__PURE__ */ new Set();
819
+ const notify = () => {
820
+ for (const l8 of [...listeners]) l8();
821
+ };
822
+ return {
823
+ register(trialId, handle) {
824
+ handles.set(trialId, handle);
825
+ notify();
826
+ return () => {
827
+ if (handles.get(trialId) !== handle) return;
828
+ handles.delete(trialId);
829
+ notify();
830
+ };
831
+ },
832
+ get: (trialId) => handles.get(trialId) ?? null,
833
+ all: () => handles.values(),
834
+ subscribe(listener) {
835
+ listeners.add(listener);
836
+ return () => {
837
+ listeners.delete(listener);
838
+ };
839
+ }
840
+ };
841
+ }
842
+ var ClockRegistryContext = createContext(null);
843
+ var TrialClockContext = createContext(null);
844
+
845
+ // src/clock/hooks.ts
846
+ var NO_SUBSCRIBE = () => () => {
847
+ };
848
+ function useTrialClock(trialId) {
849
+ const registry = useContext(ClockRegistryContext);
850
+ const own = useContext(TrialClockContext);
851
+ const lab = useContext(LabContext);
852
+ useSyncExternalStore(registry?.subscribe ?? NO_SUBSCRIBE, () => registry ? resolve() : own);
853
+ function resolve() {
854
+ if (trialId !== void 0) return registry?.get(trialId) ?? null;
855
+ if (own) return own;
856
+ const focused = lab?.focusedTrialId;
857
+ return focused ? registry?.get(focused) ?? null : null;
858
+ }
859
+ const clock = resolve()?.clock ?? null;
860
+ const [, bump] = useReducer((n6) => n6 + 1, 0);
861
+ useEffect(() => clock?.subscribe(bump), [clock]);
862
+ return clock;
863
+ }
864
+ function useClockFrame(fn) {
865
+ const handle = useContext(TrialClockContext);
866
+ const latest = useLatest(fn);
867
+ useEffect(
868
+ () => handle?.onFrame((elapsed, pass) => latest.current(elapsed, pass)),
869
+ [handle, latest]
870
+ );
871
+ }
814
872
 
815
873
  // src/instrument/defineInstrument.ts
816
874
  function defineInstrument(spec) {
@@ -870,6 +928,41 @@ function validateConfigSchema(fields) {
870
928
  return { valid: errors.length === 0, errors };
871
929
  }
872
930
  var AnnotationPreloadContext = createContext(null);
931
+ function useClockLoop(registry) {
932
+ const loop = useVisibleRaf(
933
+ (timestamp) => {
934
+ let live = false;
935
+ for (const { clock } of registry.all()) {
936
+ if (clock.inert) continue;
937
+ clock.sync(timestamp);
938
+ if (!clock.inert) live = true;
939
+ }
940
+ if (live) loop.request();
941
+ },
942
+ {
943
+ onResume: () => {
944
+ for (const { clock } of registry.all()) clock.rebase();
945
+ }
946
+ }
947
+ );
948
+ useEffect(() => {
949
+ let unwakes = [];
950
+ const attach = () => {
951
+ for (const off2 of unwakes) off2();
952
+ unwakes = [];
953
+ for (const { clock } of registry.all()) {
954
+ unwakes.push(clock.onWake(() => loop.request()));
955
+ if (!clock.inert) loop.request();
956
+ }
957
+ };
958
+ attach();
959
+ const off = registry.subscribe(attach);
960
+ return () => {
961
+ off();
962
+ for (const unwake of unwakes) unwake();
963
+ };
964
+ }, [registry, loop]);
965
+ }
873
966
 
874
967
  // src/theme/interstellar.theme.json
875
968
  var interstellar_theme_default = {
@@ -996,6 +1089,180 @@ var interstellarTheme = defineTheme({
996
1089
  extends: weaselTheme
997
1090
  });
998
1091
 
1092
+ // src/clock/trialClock.ts
1093
+ var passesOf = (loop) => loop === true ? Number.POSITIVE_INFINITY : loop === false ? 1 : Math.max(1, loop);
1094
+ var checkRate = (rate, seekable) => {
1095
+ if (!Number.isFinite(rate)) throw new RangeError(`labkit: a clock's rate is finite, not ${rate}`);
1096
+ if (rate < 0 && !seekable)
1097
+ throw new RangeError("labkit: a clock that is not seekable cannot run backward");
1098
+ };
1099
+ function createTrialClock(spec, start) {
1100
+ const duration = spec.duration ?? Number.POSITIVE_INFINITY;
1101
+ const seekable = spec.seekable ?? true;
1102
+ const openRate = spec.rate ?? 0;
1103
+ checkRate(openRate, seekable);
1104
+ let loop = spec.loop ?? false;
1105
+ let span = duration * passesOf(loop);
1106
+ let rate = start ? start.rate : openRate;
1107
+ let elapsed = start ? Math.min(Math.max(0, start.elapsed), span) : 0;
1108
+ let ramp = null;
1109
+ let last = Number.NaN;
1110
+ let changed = false;
1111
+ let slept = false;
1112
+ let wakeFired = false;
1113
+ const wakes = /* @__PURE__ */ new Set();
1114
+ const subs = /* @__PURE__ */ new Set();
1115
+ const frames = /* @__PURE__ */ new Set();
1116
+ const isInert = () => rate === 0 && ramp === null && !changed;
1117
+ const notify = () => {
1118
+ for (const fn of [...subs]) fn();
1119
+ };
1120
+ const change = () => {
1121
+ changed = true;
1122
+ if (wakeFired) return;
1123
+ wakeFired = true;
1124
+ for (const fn of [...wakes]) fn();
1125
+ };
1126
+ const passAt = () => duration > 0 && Number.isFinite(duration) ? elapsed >= span ? passesOf(loop) - 1 : Math.floor(elapsed / duration) : 0;
1127
+ const phaseAt = () => duration > 0 && Number.isFinite(duration) ? elapsed >= span ? 1 : elapsed % duration / duration : 0;
1128
+ const advance = (dt) => {
1129
+ if (!ramp) return rate * dt;
1130
+ if (dt >= ramp.left) {
1131
+ const area2 = (rate + ramp.to) / 2 * ramp.left + ramp.to * (dt - ramp.left);
1132
+ rate = ramp.to;
1133
+ ramp = null;
1134
+ notify();
1135
+ return area2;
1136
+ }
1137
+ const next = rate + (ramp.to - rate) * dt / ramp.left;
1138
+ const area = (rate + next) / 2 * dt;
1139
+ rate = next;
1140
+ ramp.left -= dt;
1141
+ return area;
1142
+ };
1143
+ const clock = {
1144
+ get elapsed() {
1145
+ return elapsed;
1146
+ },
1147
+ get pass() {
1148
+ return passAt();
1149
+ },
1150
+ get phase() {
1151
+ return phaseAt();
1152
+ },
1153
+ get rate() {
1154
+ return rate;
1155
+ },
1156
+ set rate(next) {
1157
+ checkRate(next, seekable);
1158
+ rate = next;
1159
+ ramp = null;
1160
+ change();
1161
+ notify();
1162
+ },
1163
+ get loop() {
1164
+ return loop;
1165
+ },
1166
+ set loop(next) {
1167
+ loop = next;
1168
+ span = duration * passesOf(loop);
1169
+ elapsed = Math.min(elapsed, span);
1170
+ change();
1171
+ notify();
1172
+ },
1173
+ ramp(to, over) {
1174
+ checkRate(to, seekable);
1175
+ if (!(over > 0)) {
1176
+ clock.rate = to;
1177
+ return;
1178
+ }
1179
+ ramp = { to, left: over };
1180
+ change();
1181
+ notify();
1182
+ },
1183
+ seek(target) {
1184
+ if (!seekable) throw new Error("labkit: this clock is not seekable");
1185
+ elapsed = Math.min(Math.max(0, target), span);
1186
+ change();
1187
+ notify();
1188
+ },
1189
+ sync(timestamp) {
1190
+ const wasChanged = changed;
1191
+ const before = elapsed;
1192
+ if (!Number.isNaN(last) && !slept) {
1193
+ elapsed += advance(Math.max(0, timestamp - last));
1194
+ if (elapsed >= span && rate > 0) {
1195
+ elapsed = span;
1196
+ rate = 0;
1197
+ ramp = null;
1198
+ notify();
1199
+ } else if (elapsed <= 0 && rate < 0) {
1200
+ elapsed = 0;
1201
+ rate = 0;
1202
+ ramp = null;
1203
+ notify();
1204
+ }
1205
+ }
1206
+ last = timestamp;
1207
+ changed = false;
1208
+ wakeFired = false;
1209
+ slept = isInert();
1210
+ if (elapsed !== before || wasChanged) {
1211
+ const pass = passAt();
1212
+ for (const fn of [...frames]) fn(elapsed, pass);
1213
+ }
1214
+ },
1215
+ rebase() {
1216
+ last = Number.NaN;
1217
+ },
1218
+ get inert() {
1219
+ return isInert();
1220
+ },
1221
+ onWake(fn) {
1222
+ wakes.add(fn);
1223
+ return () => {
1224
+ wakes.delete(fn);
1225
+ };
1226
+ },
1227
+ subscribe(fn) {
1228
+ subs.add(fn);
1229
+ return () => {
1230
+ subs.delete(fn);
1231
+ };
1232
+ }
1233
+ };
1234
+ return {
1235
+ clock,
1236
+ reset() {
1237
+ elapsed = 0;
1238
+ rate = openRate;
1239
+ ramp = null;
1240
+ change();
1241
+ notify();
1242
+ },
1243
+ onFrame(fn) {
1244
+ frames.add(fn);
1245
+ return () => {
1246
+ frames.delete(fn);
1247
+ };
1248
+ }
1249
+ };
1250
+ }
1251
+
1252
+ // src/undo/stateOp.ts
1253
+ function stateOp(before, read, write) {
1254
+ let after;
1255
+ const forward = {
1256
+ label: "state",
1257
+ apply: () => write(structuredClone(after)),
1258
+ invert: () => {
1259
+ after = structuredClone(read());
1260
+ return { label: "state", apply: () => write(structuredClone(before)), invert: () => forward };
1261
+ }
1262
+ };
1263
+ return forward;
1264
+ }
1265
+
999
1266
  // src/canvas/cameraZoom.ts
1000
1267
  function atScale(view, scale, at) {
1001
1268
  return {
@@ -1013,6 +1280,36 @@ function zoomCameraTo(camera, zoom) {
1013
1280
  const scale = Math.min(max, Math.max(min, zoom));
1014
1281
  camera.set(atScale(camera.get(), scale, viewMiddle(camera)));
1015
1282
  }
1283
+ var PresentationContext = createContext(null);
1284
+ function usePresentation() {
1285
+ const value = useContext(PresentationContext);
1286
+ if (!value) throw new Error("[labkit] usePresentation() must be called inside <Lab>");
1287
+ return value;
1288
+ }
1289
+ function usePresentedTrial(trialId) {
1290
+ const presented = useContext(PresentationContext)?.trialId === trialId;
1291
+ const surface = useSurfaceOptional();
1292
+ const [el, ref] = useState(null);
1293
+ useLayoutEffect(() => {
1294
+ if (!presented || !el || !surface) return;
1295
+ surface.scope(el);
1296
+ return () => surface.scope(null);
1297
+ }, [presented, el, surface]);
1298
+ return { presented, ref };
1299
+ }
1300
+ function hasPresentParam() {
1301
+ return typeof location !== "undefined" && new URLSearchParams(location.search).has("present");
1302
+ }
1303
+ var LightboxContext = createContext(null);
1304
+ function useLightbox() {
1305
+ return useContext(LightboxContext);
1306
+ }
1307
+ function useLightboxControl() {
1308
+ const ctx = useContext(LightboxContext);
1309
+ const claim = ctx?.claimControl;
1310
+ useLayoutEffect(() => claim?.(), [claim]);
1311
+ return ctx;
1312
+ }
1016
1313
  function TrialBody({ sidebar, children, contentClassName, ...rest }) {
1017
1314
  return /* @__PURE__ */ jsx(
1018
1315
  Split,
@@ -1131,6 +1428,7 @@ function TrialChrome({
1131
1428
  children
1132
1429
  }) {
1133
1430
  const lab = useLabContext();
1431
+ const { presented, ref: presentedRef } = usePresentedTrial(trialId);
1134
1432
  const storeCtx = useContext(LabStoreContext);
1135
1433
  if (!storeCtx) throw new Error("[labkit] TrialChrome requires <LabStoreProvider>");
1136
1434
  const updateTrialView = useStore(storeCtx.store, (s4) => s4.updateTrialView);
@@ -1160,6 +1458,7 @@ function TrialChrome({
1160
1458
  cameras?.subscribe ?? noSubscribe,
1161
1459
  () => cameras?.get(trialId) ?? null
1162
1460
  );
1461
+ const lightbox = useLightboxControl();
1163
1462
  const ctx = useMemo(() => {
1164
1463
  const setZoom = (z) => {
1165
1464
  if (camera) zoomCameraTo(camera, z);
@@ -1180,6 +1479,9 @@ function TrialChrome({
1180
1479
  hasLoupe: loupe != null,
1181
1480
  loupeOn: loupe?.on ?? false,
1182
1481
  toggleLoupe: loupe?.toggle ?? NO_OP,
1482
+ canExpand: lightbox != null,
1483
+ expanded: lightbox?.expanded ?? false,
1484
+ toggleExpanded: lightbox?.toggle ?? NO_OP,
1183
1485
  configSchema,
1184
1486
  configFields: instrument.config ? [] : instrument.configSchema?.() ?? [],
1185
1487
  config: record.config,
@@ -1225,6 +1527,7 @@ function TrialChrome({
1225
1527
  updateTrialState,
1226
1528
  undoBindings,
1227
1529
  loupe,
1530
+ lightbox,
1228
1531
  view2d,
1229
1532
  activeToolId,
1230
1533
  setActiveTool,
@@ -1255,7 +1558,7 @@ function TrialChrome({
1255
1558
  const inRegion = (region) => contributions.filter((c6) => c6.region === region);
1256
1559
  const handleKeyDown = (e) => {
1257
1560
  const mod = e.metaKey || e.ctrlKey;
1258
- if (!mod) return;
1561
+ if (!mod || presented) return;
1259
1562
  if (e.key === "z" || e.key === "Z") {
1260
1563
  e.preventDefault();
1261
1564
  if (e.shiftKey) ctx.redo();
@@ -1268,9 +1571,11 @@ function TrialChrome({
1268
1571
  return /* @__PURE__ */ jsxs(
1269
1572
  "section",
1270
1573
  {
1574
+ ref: presentedRef,
1271
1575
  className: "lk-trial",
1272
1576
  "data-trial-id": trialId,
1273
1577
  "data-focused": lab.trials.length > 1 && lab.focusedTrialId === trialId ? "true" : void 0,
1578
+ "data-lk-presented": presented ? "" : void 0,
1274
1579
  "aria-label": `Trial ${title}`,
1275
1580
  tabIndex: -1,
1276
1581
  onKeyDown: handleKeyDown,
@@ -1291,7 +1596,10 @@ function TrialChrome({
1291
1596
  {
1292
1597
  width: record.sidebarWidth,
1293
1598
  onWidthChange: (w3) => updateTrialSidebarWidth(trialId, w3),
1294
- contentClassName: instrument.canvas || instrument.stage ? "lk-trial__content--flush" : void 0,
1599
+ contentClassName: [
1600
+ instrument.canvas || instrument.stage ? "lk-trial__content--flush" : "",
1601
+ presented ? "lk-trial__stage" : ""
1602
+ ].filter(Boolean).join(" ") || void 0,
1295
1603
  sidebar: /* @__PURE__ */ jsxs(Fragment, { children: [
1296
1604
  /* @__PURE__ */ jsx(SidebarRegion, { contributions: inRegion("sidebar"), ctx }),
1297
1605
  /* @__PURE__ */ jsx(
@@ -1399,7 +1707,8 @@ function TrialRuntime({
1399
1707
  const updateTrialState = useStore(store, (s4) => s4.updateTrialState);
1400
1708
  const updateTrialConfig = useStore(store, (s4) => s4.updateTrialConfig);
1401
1709
  const updateTrialView = useStore(store, (s4) => s4.updateTrialView);
1402
- const updateTrialUndoStack = useStore(store, (s4) => s4.updateTrialUndoStack);
1710
+ const setTrialHistory = useStore(store, (s4) => s4.setTrialHistory);
1711
+ const updateTrialClock = useStore(store, (s4) => s4.updateTrialClock);
1403
1712
  const updateTrialAnnotations = useStore(store, (s4) => s4.updateTrialAnnotations);
1404
1713
  const labToolId = useStore(store, (s4) => s4.activeToolId);
1405
1714
  const setLabTool = useStore(store, (s4) => s4.setLabTool);
@@ -1422,6 +1731,25 @@ function TrialRuntime({
1422
1731
  () => (instrument.canvas?.layers ?? []).filter((l8) => layerVisibility[l8.id] !== false).map((l8) => l8.id),
1423
1732
  [instrument.canvas, layerVisibility]
1424
1733
  );
1734
+ const [clockHandle] = useState(
1735
+ () => instrument.clock ? createTrialClock(instrument.clock, record.clock) : null
1736
+ );
1737
+ const clocks = useContext(ClockRegistryContext);
1738
+ useEffect(() => {
1739
+ if (!clockHandle || !clocks) return;
1740
+ return clocks.register(record.id, clockHandle);
1741
+ }, [clocks, clockHandle, record.id]);
1742
+ useEffect(() => {
1743
+ if (!clockHandle) return;
1744
+ const { clock } = clockHandle;
1745
+ return clock.subscribe(
1746
+ () => updateTrialClock(record.id, { elapsed: clock.elapsed, rate: clock.rate })
1747
+ );
1748
+ }, [clockHandle, record.id, updateTrialClock]);
1749
+ const ticks = useMemo(
1750
+ () => clockHandle ? (fn) => clockHandle.onFrame(() => fn()) : void 0,
1751
+ [clockHandle]
1752
+ );
1425
1753
  const undoCap = instrument.undo;
1426
1754
  const undoEvents = useMemo(() => new Set(undoCap?.snapshotOn ?? ["state.change"]), [undoCap]);
1427
1755
  const maxDepth = undoCap?.maxDepth ?? 50;
@@ -1429,8 +1757,14 @@ function TrialRuntime({
1429
1757
  if (!undoCap || !undoEvents.has(event)) return;
1430
1758
  const current = store.getState().trials.find((w3) => w3.id === record.id);
1431
1759
  if (!current) return;
1432
- const snap = structuredClone(current.state);
1433
- updateTrialUndoStack(record.id, (prev) => pushSnapshot(prev, snap, maxDepth));
1760
+ let history = current.history;
1761
+ if (!history) {
1762
+ history = createHistory(null, { historyLimit: maxDepth });
1763
+ setTrialHistory(record.id, history);
1764
+ }
1765
+ const read = () => store.getState().trials.find((w3) => w3.id === record.id)?.state;
1766
+ const write = (state) => updateTrialState(record.id, state);
1767
+ history.recordEntry([stateOp(structuredClone(current.state), read, write)], event);
1434
1768
  };
1435
1769
  const setView = (v2) => updateTrialView(record.id, v2);
1436
1770
  const stage = instrument.canvas ? void 0 : instrument.stage;
@@ -1559,7 +1893,8 @@ function TrialRuntime({
1559
1893
  },
1560
1894
  activeToolId: resolvedToolId,
1561
1895
  visibleLayers,
1562
- pointer
1896
+ pointer,
1897
+ ...clockHandle ? { clock: clockHandle.clock } : {}
1563
1898
  },
1564
1899
  emit: (event) => {
1565
1900
  snapshotIfNeeded(event);
@@ -1567,28 +1902,17 @@ function TrialRuntime({
1567
1902
  },
1568
1903
  job: jobCap ? job : void 0
1569
1904
  };
1570
- const undoState = () => {
1571
- const result = undo(record.undoStack, structuredClone(record.state));
1572
- if (!result) return;
1573
- updateTrialState(record.id, result.snapshot);
1574
- updateTrialUndoStack(record.id, result.stack);
1575
- };
1576
- const redoState = () => {
1577
- const result = redo(record.undoStack, structuredClone(record.state));
1578
- if (!result) return;
1579
- updateTrialState(record.id, result.snapshot);
1580
- updateTrialUndoStack(record.id, result.stack);
1581
- };
1905
+ const stateHistory = useHistoryVersion(record.history);
1582
1906
  const undoBindings = undoCap || annotationsCap ? {
1583
- canUndo: markMoves.undo || (undoCap ? record.undoStack.past.length > 0 : false),
1584
- canRedo: markMoves.redo || (undoCap ? record.undoStack.future.length > 0 : false),
1907
+ canUndo: markMoves.undo || (undoCap ? stateHistory?.canUndo() ?? false : false),
1908
+ canRedo: markMoves.redo || (undoCap ? stateHistory?.canRedo() ?? false : false),
1585
1909
  undo: () => {
1586
1910
  if (annotationsCap && annotations.undo()) return;
1587
- if (undoCap) undoState();
1911
+ if (undoCap) stateHistory?.undo();
1588
1912
  },
1589
1913
  redo: () => {
1590
1914
  if (annotationsCap && annotations.redo()) return;
1591
- if (undoCap) redoState();
1915
+ if (undoCap) stateHistory?.redo();
1592
1916
  }
1593
1917
  } : void 0;
1594
1918
  const loupeBindings = lenses > 0 ? { on: loupeOn, toggle: () => setLoupeOn((v2) => !v2) } : void 0;
@@ -1596,15 +1920,24 @@ function TrialRuntime({
1596
1920
  if (!instrument.canvas) return [];
1597
1921
  const baseLayers = instrument.canvas.layers;
1598
1922
  const ordered = layerOrder ? [...baseLayers].sort((a4, b3) => layerOrder.indexOf(a4.id) - layerOrder.indexOf(b3.id)) : baseLayers;
1923
+ const clock = clockHandle?.clock;
1599
1924
  return ordered.map((layer) => ({
1600
1925
  id: layer.id,
1601
1926
  visible: layerVisibility[layer.id] !== false,
1927
+ timed: layer.timed === true,
1602
1928
  render: (ctx, view, frame) => {
1603
1929
  applyCamera(ctx, view, frame);
1604
- layer.draw(ctx, { state: record.state, config, zoom: view.zoom });
1930
+ layer.draw(ctx, {
1931
+ state: record.state,
1932
+ config,
1933
+ zoom: view.zoom,
1934
+ elapsed: clock?.elapsed ?? 0,
1935
+ pass: clock?.pass ?? 0,
1936
+ phase: clock?.phase ?? 0
1937
+ });
1605
1938
  }
1606
1939
  }));
1607
- }, [instrument.canvas, record.state, config, layerVisibility, layerOrder]);
1940
+ }, [instrument.canvas, record.state, config, layerVisibility, layerOrder, clockHandle]);
1608
1941
  const layerDescriptors = useMemo(() => {
1609
1942
  if (!instrument.layers) return [];
1610
1943
  return instrument.layers.ids.map((l8) => typeof l8 === "string" ? { id: l8, label: l8 } : l8);
@@ -1680,6 +2013,7 @@ function TrialRuntime({
1680
2013
  onResize: placeView,
1681
2014
  minZoom: instrument.canvas.minZoom,
1682
2015
  maxZoom: instrument.canvas.maxZoom,
2016
+ ticks,
1683
2017
  children: instrument.render(renderCtx)
1684
2018
  }
1685
2019
  ),
@@ -1709,11 +2043,11 @@ function TrialRuntime({
1709
2043
  );
1710
2044
  const layerListNode = useMemo(
1711
2045
  () => instrument.layers && layerDescriptors.length > 0 ? /* @__PURE__ */ jsx(
1712
- D2,
2046
+ A2,
1713
2047
  {
1714
2048
  items: layerItems,
1715
2049
  onReorder: (move) => {
1716
- setLayerOrder(S(layerItems, move).map((l8) => l8.id));
2050
+ setLayerOrder(T(layerItems, move).map((l8) => l8.id));
1717
2051
  bus.emit("layers.reorder");
1718
2052
  },
1719
2053
  onVisibilityChange: (lid, visible) => {
@@ -1754,7 +2088,7 @@ function TrialRuntime({
1754
2088
  }
1755
2089
  ) : null;
1756
2090
  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 }) }) }) });
1757
- 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(
2091
+ return /* @__PURE__ */ jsx(TrialIdProvider, { trialId: record.id, children: /* @__PURE__ */ jsx(TrialClockContext.Provider, { value: clockHandle, children: /* @__PURE__ */ jsx(AnnotationsContext.Provider, { value: annotationsCap ? provided : null, children: /* @__PURE__ */ jsx(CameraWheelContext.Provider, { value: wheelSlot, children: /* @__PURE__ */ jsxs(
1758
2092
  TrialChrome,
1759
2093
  {
1760
2094
  job: jobCap ? job : void 0,
@@ -1774,7 +2108,13 @@ function TrialRuntime({
1774
2108
  annotationOverlays
1775
2109
  ]
1776
2110
  }
1777
- ) }) }) });
2111
+ ) }) }) }) });
2112
+ }
2113
+ var NO_SUBSCRIBE2 = () => () => {
2114
+ };
2115
+ function useHistoryVersion(history) {
2116
+ useSyncExternalStore(history?.subscribe ?? NO_SUBSCRIBE2, () => history?.getVersion() ?? 0);
2117
+ return history;
1778
2118
  }
1779
2119
 
1780
2120
  // src/trial/trialOps.ts
@@ -1827,8 +2167,7 @@ function addTrial(trials, instruments, instrumentName, options = {}) {
1827
2167
  instrumentName,
1828
2168
  config,
1829
2169
  state,
1830
- view: initialView(instrument),
1831
- undoStack: { past: [], future: [] }
2170
+ view: initialView(instrument)
1832
2171
  };
1833
2172
  if (options.config) record.configSeed = seeded.config;
1834
2173
  if (seeded.autoPaths.length > 0) record.autoSeed = seeded.autoPaths;
@@ -1839,13 +2178,13 @@ function cloneTrial(trials, id) {
1839
2178
  const sourceIdx = trials.findIndex((w3) => w3.id === id);
1840
2179
  const source = trials[sourceIdx];
1841
2180
  if (!source) return trials;
2181
+ const { history: _history, ...rest } = source;
1842
2182
  const clone = {
1843
- ...source,
2183
+ ...rest,
1844
2184
  id: newId(),
1845
2185
  config: structuredClone(source.config),
1846
2186
  state: structuredClone(source.state),
1847
- view: structuredClone(source.view),
1848
- undoStack: { past: [], future: [] }
2187
+ view: structuredClone(source.view)
1849
2188
  };
1850
2189
  return [...trials.slice(0, sourceIdx + 1), clone, ...trials.slice(sourceIdx + 1)];
1851
2190
  }
@@ -2112,6 +2451,91 @@ function LabShell({
2112
2451
  footer && /* @__PURE__ */ jsx("footer", { className: "lk-shell-footer", children: footer })
2113
2452
  ] });
2114
2453
  }
2454
+ var NONE = { below: [], above: [] };
2455
+ var LightboxLayersContext = createContext(NONE);
2456
+ function LightboxLayers({ below, above, children }) {
2457
+ const value = useMemo(
2458
+ () => ({ below: below ?? NONE.below, above: above ?? NONE.above }),
2459
+ [below, above]
2460
+ );
2461
+ return /* @__PURE__ */ jsx(LightboxLayersContext.Provider, { value, children });
2462
+ }
2463
+ function useLightboxLayers() {
2464
+ return useContext(LightboxLayersContext);
2465
+ }
2466
+ function LabSurface({ bodyRef, children }) {
2467
+ const outerSurface = useSurfaceOptional();
2468
+ const outerOver = useSurfaceCanvas("over");
2469
+ const outerUnder = useSurfaceCanvas("under");
2470
+ const [ownOver, setOwnOver] = useState(null);
2471
+ const [ownUnder, setOwnUnder] = useState(null);
2472
+ const [underLayer, setUnderLayer] = useState(null);
2473
+ const [overLayer, setOverLayer] = useState(null);
2474
+ const ownOverRef = useRef(null);
2475
+ const ownUnderRef = useRef(null);
2476
+ const bufferRef = useRef({ w: 0, h: 0 });
2477
+ const surfaceRef = useRef(null);
2478
+ const onFrame = useCallback((frame) => {
2479
+ const canvases2 = [ownUnderRef.current, ownOverRef.current].filter((c6) => c6 !== null);
2480
+ if (canvases2.length === 0) return;
2481
+ const w3 = Math.round(frame.size.width * frame.dpr);
2482
+ const h2 = Math.round(frame.size.height * frame.dpr);
2483
+ if (bufferRef.current.w !== w3 || bufferRef.current.h !== h2) {
2484
+ bufferRef.current = { w: w3, h: h2 };
2485
+ for (const c6 of canvases2) {
2486
+ c6.width = w3;
2487
+ c6.height = h2;
2488
+ c6.style.width = `${frame.size.width}px`;
2489
+ c6.style.height = `${frame.size.height}px`;
2490
+ }
2491
+ surfaceRef.current?.invalidateAll();
2492
+ return;
2493
+ }
2494
+ }, []);
2495
+ const ownSurface = useTiledSurface({ onFrame });
2496
+ useInsertionEffect(() => {
2497
+ surfaceRef.current = ownSurface;
2498
+ }, [ownSurface]);
2499
+ const attachOwnSurface = ownSurface.containerRef;
2500
+ const overLayerRef = useCallback(
2501
+ (el) => {
2502
+ setOverLayer(el);
2503
+ attachOwnSurface(el);
2504
+ },
2505
+ [attachOwnSurface]
2506
+ );
2507
+ const surface = outerSurface ?? ownSurface;
2508
+ const canvases = useMemo(
2509
+ () => outerSurface ? { over: outerOver, under: outerUnder } : { over: ownOver, under: ownUnder },
2510
+ [outerSurface, outerOver, outerUnder, ownOver, ownUnder]
2511
+ );
2512
+ const below = useMemo(() => [underLayer], [underLayer]);
2513
+ const above = useMemo(() => [overLayer], [overLayer]);
2514
+ const body = /* @__PURE__ */ jsxs("div", { className: "lk-lab__body", ref: bodyRef, children: [
2515
+ outerSurface ? null : /* @__PURE__ */ jsx("div", { className: "lk-lab__layer lk-lab__layer--under", ref: setUnderLayer, children: /* @__PURE__ */ jsx(
2516
+ "canvas",
2517
+ {
2518
+ className: "lk-lab__surface",
2519
+ ref: (el) => {
2520
+ ownUnderRef.current = el;
2521
+ setOwnUnder(el);
2522
+ }
2523
+ }
2524
+ ) }),
2525
+ children,
2526
+ outerSurface ? null : /* @__PURE__ */ jsx("div", { className: "lk-lab__layer lk-lab__layer--over", ref: overLayerRef, children: /* @__PURE__ */ jsx(
2527
+ "canvas",
2528
+ {
2529
+ className: "lk-lab__surface",
2530
+ ref: (el) => {
2531
+ ownOverRef.current = el;
2532
+ setOwnOver(el);
2533
+ }
2534
+ }
2535
+ ) })
2536
+ ] });
2537
+ return /* @__PURE__ */ jsx(SurfaceContext.Provider, { value: surface, children: /* @__PURE__ */ jsx(SurfaceCanvasContext.Provider, { value: canvases, children: /* @__PURE__ */ jsx(LightboxLayers, { below, above, children: body }) }) });
2538
+ }
2115
2539
  var ZOOM_ID = "viewport.zoom";
2116
2540
  var NO_TOOLS = /* @__PURE__ */ new Map();
2117
2541
  var CHANNELS = { contextMenu: false, ingest: false };
@@ -2187,7 +2611,7 @@ function LabZoomControls() {
2187
2611
  title: hint("Zoom out", "out"),
2188
2612
  disabled: !canOut,
2189
2613
  onClick: run("out"),
2190
- children: /* @__PURE__ */ jsx(X, { size: 16 })
2614
+ children: /* @__PURE__ */ jsx(Z, { size: 16 })
2191
2615
  }
2192
2616
  ),
2193
2617
  /* @__PURE__ */ jsx(
@@ -2215,6 +2639,311 @@ function LabZoomControls() {
2215
2639
  }
2216
2640
  var noSubscribe2 = () => () => {
2217
2641
  };
2642
+ var NEBULA_SLOTS = [
2643
+ { cx: "18%", cy: "30%", sx: "60%", sy: "80%", stop: "70%" },
2644
+ { cx: "78%", cy: "70%", sx: "55%", sy: "90%", stop: "65%" },
2645
+ { cx: "50%", cy: "50%", sx: "50%", sy: "70%", stop: "75%" },
2646
+ { cx: "12%", cy: "82%", sx: "50%", sy: "70%", stop: "70%" },
2647
+ { cx: "85%", cy: "18%", sx: "55%", sy: "80%", stop: "70%" }
2648
+ ];
2649
+ function buildNebula(list, theme, mode, density) {
2650
+ const ctx = { theme, resolved: resolveTheme(theme, { mode, density }) };
2651
+ const n6 = colorCount(list, ctx) ?? NEBULA_SLOTS.length;
2652
+ const colors = Array.from({ length: n6 }, (_2, i4) => colorAt(list, i4, ctx));
2653
+ const blobs = colors.map((c6, i4) => {
2654
+ const p5 = NEBULA_SLOTS[i4 % NEBULA_SLOTS.length];
2655
+ return `radial-gradient(ellipse ${p5.sx} ${p5.sy} at ${p5.cx} ${p5.cy}, color-mix(in srgb, ${c6} 22%, transparent), transparent ${p5.stop})`;
2656
+ });
2657
+ blobs.push("radial-gradient(ellipse at center, #0a0a18 0%, #02020a 100%)");
2658
+ return blobs.join(", ");
2659
+ }
2660
+ function presentStorageKey(storageKey) {
2661
+ return `${storageKey}:present`;
2662
+ }
2663
+ var SEED_RECORD = valueRecord(null, "lk-present-seed");
2664
+ function seedPresentedTrial(store, records, { instruments, defaultInstrument }, seed) {
2665
+ const instrument = seed.instrument ?? defaultInstrument;
2666
+ const fingerprint = stableStringify({ ...seed, instrument });
2667
+ const trials = store.getState().trials;
2668
+ if (trials.length > 0 && records.get(SEED_RECORD) === fingerprint) return;
2669
+ const record = addTrial([], instruments, instrument, { config: seed.config })[0];
2670
+ if (!record) return;
2671
+ store.setState({ trials: [] });
2672
+ store.getState().addTrial({
2673
+ ...record,
2674
+ ..."state" in seed ? { state: seed.state } : {},
2675
+ ..."view" in seed ? { view: seed.view } : {}
2676
+ });
2677
+ records.set(SEED_RECORD, fingerprint);
2678
+ }
2679
+ function seedDefaultTrial(store, instruments, defaultInstrument) {
2680
+ if (store.getState().trials.length > 0) return;
2681
+ const record = addTrial([], instruments, defaultInstrument)[0];
2682
+ if (!record) return;
2683
+ store.getState().addTrial(record);
2684
+ }
2685
+ function openUnstoredLab(options, seed) {
2686
+ const { instruments, defaultInstrument, mode } = options;
2687
+ const store = createLabStore({ initialMode: mode ?? "auto", instruments });
2688
+ const records = createRecordCache({ storage: noneAdapter, prefix: "", writable: false });
2689
+ if (seed) seedPresentedTrial(store, records, options, seed);
2690
+ else seedDefaultTrial(store, instruments, defaultInstrument);
2691
+ return { store, records, close: () => records.close(), marks: /* @__PURE__ */ new Map() };
2692
+ }
2693
+ async function openStoredLab(options, storageKey, storage, seed) {
2694
+ const { instruments, defaultInstrument, mode } = options;
2695
+ const opened = await openLabStore({
2696
+ storageKey,
2697
+ storage: storage ?? await defaultStorage(),
2698
+ initialMode: mode ?? "auto",
2699
+ instruments
2700
+ });
2701
+ if (seed) seedPresentedTrial(opened.store, opened.records, options, seed);
2702
+ else seedDefaultTrial(opened.store, instruments, defaultInstrument);
2703
+ const marks = /* @__PURE__ */ new Map();
2704
+ await Promise.all(
2705
+ opened.store.getState().trials.map(async (trial) => {
2706
+ const kept = instruments.find((i4) => i4.name === trial.instrumentName)?.annotations?.storage;
2707
+ if (!kept) return;
2708
+ try {
2709
+ marks.set(trial.id, await kept.load());
2710
+ } catch (error) {
2711
+ console.warn(`[labkit] could not load the marks of trial "${trial.id}"`, error);
2712
+ marks.set(trial.id, null);
2713
+ }
2714
+ })
2715
+ );
2716
+ return { ...opened, marks };
2717
+ }
2718
+ function useFocusPick(labBody, store, pick) {
2719
+ useEffect(() => {
2720
+ if (!labBody) return;
2721
+ const pickFrom = (node) => {
2722
+ const owned = new Set(store.getState().trials.map((t) => t.id));
2723
+ const nearest = (from) => from?.closest(".lk-trial[data-trial-id]") ?? null;
2724
+ let trial = typeof node?.closest === "function" ? nearest(node) : null;
2725
+ while (trial && !owned.has(trial.dataset.trialId ?? "")) trial = nearest(trial.parentElement);
2726
+ if (trial?.dataset.trialId) pick(trial.dataset.trialId);
2727
+ };
2728
+ const onInput = (event) => pickFrom(event.target);
2729
+ let pending;
2730
+ const doc = labBody.ownerDocument;
2731
+ const win = doc.defaultView;
2732
+ const onBlur = () => {
2733
+ clearTimeout(pending);
2734
+ pending = setTimeout(() => pickFrom(doc.activeElement), 0);
2735
+ };
2736
+ labBody.addEventListener("pointerdown", onInput, true);
2737
+ labBody.addEventListener("focusin", onInput, true);
2738
+ win?.addEventListener("blur", onBlur);
2739
+ return () => {
2740
+ clearTimeout(pending);
2741
+ labBody.removeEventListener("pointerdown", onInput, true);
2742
+ labBody.removeEventListener("focusin", onInput, true);
2743
+ win?.removeEventListener("blur", onBlur);
2744
+ };
2745
+ }, [labBody, store, pick]);
2746
+ }
2747
+
2748
+ // src/lightbox/modal.ts
2749
+ var OWN_DOUBLE_CLICK = [
2750
+ "button",
2751
+ "a[href]",
2752
+ "input",
2753
+ "select",
2754
+ "textarea",
2755
+ "label",
2756
+ "summary",
2757
+ '[contenteditable]:not([contenteditable="false"])',
2758
+ '[role="button"]',
2759
+ '[role="checkbox"]',
2760
+ '[role="combobox"]',
2761
+ '[role="link"]',
2762
+ '[role="menuitem"]',
2763
+ '[role="option"]',
2764
+ '[role="radio"]',
2765
+ '[role="separator"]',
2766
+ '[role="slider"]',
2767
+ '[role="spinbutton"]',
2768
+ '[role="switch"]',
2769
+ '[role="tab"]',
2770
+ '[role="textbox"]',
2771
+ "[data-lk-lightbox-ignore]"
2772
+ ].join(",");
2773
+ function opensLightbox(event, root) {
2774
+ if (event.defaultPrevented) return false;
2775
+ const target = event.target;
2776
+ if (!(target instanceof Element)) return true;
2777
+ const owner = target.closest(OWN_DOUBLE_CLICK);
2778
+ return owner === null || !root.contains(owner);
2779
+ }
2780
+ function inertOutside(keep) {
2781
+ const path = /* @__PURE__ */ new Set();
2782
+ for (const el of keep) {
2783
+ for (let node = el; node && node !== document.body; node = node.parentElement) {
2784
+ path.add(node);
2785
+ }
2786
+ }
2787
+ const marked = [];
2788
+ for (const node of path) {
2789
+ const parent = node.parentElement;
2790
+ if (!parent) continue;
2791
+ for (const sibling of Array.from(parent.children)) {
2792
+ if (path.has(sibling) || sibling.hasAttribute("inert")) continue;
2793
+ sibling.setAttribute("inert", "");
2794
+ marked.push(sibling);
2795
+ }
2796
+ }
2797
+ return () => {
2798
+ for (const sibling of marked) sibling.removeAttribute("inert");
2799
+ };
2800
+ }
2801
+ function liftStack(stack, self) {
2802
+ if (typeof self.showPopover !== "function") return () => {
2803
+ };
2804
+ stack.forEach((el, i4) => {
2805
+ el.setAttribute("popover", "manual");
2806
+ if (el !== self) el.setAttribute("data-lk-lightbox-layer", "");
2807
+ if (i4 === 0) el.setAttribute("data-lk-lightbox-scrim", "");
2808
+ el.showPopover();
2809
+ });
2810
+ return () => {
2811
+ for (const el of [...stack].reverse()) {
2812
+ if (el.matches(":popover-open")) el.hidePopover();
2813
+ el.removeAttribute("popover");
2814
+ el.removeAttribute("data-lk-lightbox-layer");
2815
+ el.removeAttribute("data-lk-lightbox-scrim");
2816
+ }
2817
+ };
2818
+ }
2819
+ function dialogProps(label) {
2820
+ return {
2821
+ role: "dialog",
2822
+ "aria-modal": true,
2823
+ "aria-label": label,
2824
+ "data-wzl-portal-host": ""
2825
+ };
2826
+ }
2827
+ function Lightbox({
2828
+ children,
2829
+ label = "Expanded view",
2830
+ disabled = false,
2831
+ expandOnDoubleClick = false,
2832
+ expanded: expandedProp,
2833
+ onExpandedChange,
2834
+ className
2835
+ }) {
2836
+ const [own, setOwn] = useState(false);
2837
+ const expanded = !disabled && (expandedProp ?? own);
2838
+ const onChangeRef = useLatest(onExpandedChange);
2839
+ const expandedRef = useLatest(expanded);
2840
+ const rootRef = useRef(null);
2841
+ const closeRef = useRef(null);
2842
+ const [claims, setClaims] = useState(0);
2843
+ const layers = useLightboxLayers();
2844
+ const surface = useSurfaceOptional();
2845
+ const set = useCallback(
2846
+ (next) => {
2847
+ if (next === expandedRef.current) return;
2848
+ setOwn(next);
2849
+ onChangeRef.current?.(next);
2850
+ },
2851
+ [expandedRef, onChangeRef]
2852
+ );
2853
+ const ctx = useMemo(
2854
+ () => disabled ? null : {
2855
+ expanded,
2856
+ open: () => set(true),
2857
+ close: () => set(false),
2858
+ toggle: () => set(!expandedRef.current),
2859
+ claimControl: () => {
2860
+ setClaims((n6) => n6 + 1);
2861
+ return () => setClaims((n6) => n6 - 1);
2862
+ }
2863
+ },
2864
+ [disabled, expanded, set, expandedRef]
2865
+ );
2866
+ useLayoutEffect(() => {
2867
+ const el = rootRef.current;
2868
+ if (!expanded || !el) return;
2869
+ const returnTo = document.activeElement;
2870
+ const stack = [...layers.below, el, ...layers.above].filter(
2871
+ (layer) => layer !== null
2872
+ );
2873
+ const unlift = liftStack(stack, el);
2874
+ const release = inertOutside(stack);
2875
+ surface?.scope(el);
2876
+ closeRef.current?.focus({ preventScroll: true });
2877
+ return () => {
2878
+ surface?.scope(null);
2879
+ release();
2880
+ unlift();
2881
+ if (returnTo?.isConnected) returnTo.focus({ preventScroll: true });
2882
+ };
2883
+ }, [expanded, layers, surface]);
2884
+ useEffect(() => {
2885
+ if (!expanded) return;
2886
+ const onKey = (e) => {
2887
+ if (e.key !== "Escape" || e.defaultPrevented) return;
2888
+ e.preventDefault();
2889
+ set(false);
2890
+ };
2891
+ document.addEventListener("keydown", onKey);
2892
+ return () => document.removeEventListener("keydown", onKey);
2893
+ }, [expanded, set]);
2894
+ const onDoubleClick = (e) => {
2895
+ if (disabled || !expandOnDoubleClick || !opensLightbox(e, e.currentTarget)) return;
2896
+ e.preventDefault();
2897
+ set(!expanded);
2898
+ };
2899
+ const onClick = (e) => {
2900
+ if (expanded && e.target === e.currentTarget) set(false);
2901
+ };
2902
+ const classes = ["lk-lightbox"];
2903
+ if (expanded) classes.push("lk-lightbox--expanded");
2904
+ if (className) classes.push(className);
2905
+ let control = null;
2906
+ if (expanded) {
2907
+ control = /* @__PURE__ */ jsx(
2908
+ "button",
2909
+ {
2910
+ ref: closeRef,
2911
+ type: "button",
2912
+ className: "lk-lightbox__button lk-lightbox__close",
2913
+ "aria-label": "Close expanded view",
2914
+ title: "Close (Esc)",
2915
+ onClick: () => set(false),
2916
+ children: /* @__PURE__ */ jsx(V, { size: 16 })
2917
+ }
2918
+ );
2919
+ } else if (!disabled && claims === 0) {
2920
+ control = /* @__PURE__ */ jsx(
2921
+ "button",
2922
+ {
2923
+ type: "button",
2924
+ className: "lk-lightbox__button lk-lightbox__open",
2925
+ "aria-label": "Expand",
2926
+ title: "Expand",
2927
+ onClick: () => set(true),
2928
+ children: /* @__PURE__ */ jsx(de, { size: 16 })
2929
+ }
2930
+ );
2931
+ }
2932
+ return /* @__PURE__ */ jsx(LightboxContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxs(
2933
+ "div",
2934
+ {
2935
+ ref: rootRef,
2936
+ className: classes.join(" "),
2937
+ ...expanded ? dialogProps(label) : null,
2938
+ onDoubleClick,
2939
+ onClick,
2940
+ children: [
2941
+ /* @__PURE__ */ jsx("div", { className: "lk-lightbox__frame", children }),
2942
+ control
2943
+ ]
2944
+ }
2945
+ ) });
2946
+ }
2218
2947
  var ZONE_ID = asNodeId("lk-workspace");
2219
2948
  var STRATEGIES = { grid: gridStrategy, floating: floatingStrategy };
2220
2949
  var FLOAT_ZONE_ID = asNodeId("lk-workspace-floating");
@@ -2261,7 +2990,9 @@ function Workspace({
2261
2990
  onLayoutChange,
2262
2991
  gap = 12,
2263
2992
  padding = 0,
2264
- viewport
2993
+ viewport,
2994
+ lightbox = true,
2995
+ expandOnDoubleClick = false
2265
2996
  }) {
2266
2997
  const items = Children.toArray(children);
2267
2998
  const tilePanels = useMemo(() => (panels ?? []).filter((p5) => p5.as === "tile"), [panels]);
@@ -2318,6 +3049,8 @@ function Workspace({
2318
3049
  [onReorder]
2319
3050
  );
2320
3051
  const chrome = useMemo(() => {
3052
+ const lightboxFor = typeof lightbox === "function" ? lightbox : () => lightbox;
3053
+ const doubleClickFor = typeof expandOnDoubleClick === "function" ? expandOnDoubleClick : () => expandOnDoubleClick;
2321
3054
  const byId = new Map(nodeIds.map((id, i4) => [id, items[i4]]));
2322
3055
  const panelByNode = new Map(
2323
3056
  tilePanels.map((p5) => [`lk-panel-${p5.key}`, p5])
@@ -2329,15 +3062,28 @@ function Workspace({
2329
3062
  return /* @__PURE__ */ jsx(PanelFrame, { panel });
2330
3063
  },
2331
3064
  [KIND]: ({ node }) => {
2332
- const content = byId.get(node.id) ?? null;
2333
- if (!reorderable) return content;
3065
+ const id = String(node.id);
3066
+ const tile = (
3067
+ // `disabled` rather than leaving the wrapper out, so turning the
3068
+ // lightbox off cannot remount the tile.
3069
+ /* @__PURE__ */ jsx(
3070
+ Lightbox,
3071
+ {
3072
+ className: "lk-trial-tile",
3073
+ disabled: !lightboxFor(id),
3074
+ expandOnDoubleClick: doubleClickFor(id),
3075
+ children: byId.get(node.id) ?? null
3076
+ }
3077
+ )
3078
+ );
3079
+ if (!reorderable) return tile;
2334
3080
  return (
2335
3081
  // No grip strip: the trial's own title bar is the drag surface.
2336
- /* @__PURE__ */ jsx(TrialDragContext.Provider, { value: { nodeId: node.id }, children: /* @__PURE__ */ jsx("div", { className: "lk-trial-tile", children: content }) })
3082
+ /* @__PURE__ */ jsx(TrialDragContext.Provider, { value: { nodeId: node.id }, children: tile })
2337
3083
  );
2338
3084
  }
2339
3085
  };
2340
- }, [nodeIds, items, reorderable, tilePanels]);
3086
+ }, [nodeIds, items, reorderable, tilePanels, lightbox, expandOnDoubleClick]);
2341
3087
  const floatingLayer = floatPanels.length === 0 ? null : /* @__PURE__ */ jsx("div", { className: "lk-workspace__floating", children: floatPanels.map((p5) => /* @__PURE__ */ jsx(PanelFrame, { panel: p5, floating: true }, p5.key)) });
2342
3088
  const grid = /* @__PURE__ */ jsx(
2343
3089
  Container,
@@ -2360,42 +3106,6 @@ function Workspace({
2360
3106
  ] })
2361
3107
  ) : reorderable ? /* @__PURE__ */ jsx(DragProvider, { children: grid }) : grid }) });
2362
3108
  }
2363
- function seedDefaultTrial(store, instruments, defaultInstrument) {
2364
- if (store.getState().trials.length > 0) return;
2365
- const record = addTrial([], instruments, defaultInstrument)[0];
2366
- if (!record) return;
2367
- const { undoStack: _undoStack, ...rest } = record;
2368
- store.getState().addTrial(rest);
2369
- }
2370
- function openUnstoredLab({ instruments, defaultInstrument, mode }) {
2371
- const store = createLabStore({ initialMode: mode ?? "auto", instruments });
2372
- seedDefaultTrial(store, instruments, defaultInstrument);
2373
- const records = createRecordCache({ storage: noneAdapter, prefix: "", writable: false });
2374
- return { store, records, close: () => records.close(), marks: /* @__PURE__ */ new Map() };
2375
- }
2376
- async function openStoredLab({ instruments, defaultInstrument, mode }, storageKey, storage) {
2377
- const opened = await openLabStore({
2378
- storageKey,
2379
- storage: storage ?? await defaultStorage(),
2380
- initialMode: mode ?? "auto",
2381
- instruments
2382
- });
2383
- seedDefaultTrial(opened.store, instruments, defaultInstrument);
2384
- const marks = /* @__PURE__ */ new Map();
2385
- await Promise.all(
2386
- opened.store.getState().trials.map(async (trial) => {
2387
- const kept = instruments.find((i4) => i4.name === trial.instrumentName)?.annotations?.storage;
2388
- if (!kept) return;
2389
- try {
2390
- marks.set(trial.id, await kept.load());
2391
- } catch (error) {
2392
- console.warn(`[labkit] could not load the marks of trial "${trial.id}"`, error);
2393
- marks.set(trial.id, null);
2394
- }
2395
- })
2396
- );
2397
- return { ...opened, marks };
2398
- }
2399
3109
  function LabFallback({
2400
3110
  title,
2401
3111
  mode,
@@ -2424,24 +3134,6 @@ function LabFallback({
2424
3134
  }
2425
3135
  );
2426
3136
  }
2427
- var NEBULA_SLOTS = [
2428
- { cx: "18%", cy: "30%", sx: "60%", sy: "80%", stop: "70%" },
2429
- { cx: "78%", cy: "70%", sx: "55%", sy: "90%", stop: "65%" },
2430
- { cx: "50%", cy: "50%", sx: "50%", sy: "70%", stop: "75%" },
2431
- { cx: "12%", cy: "82%", sx: "50%", sy: "70%", stop: "70%" },
2432
- { cx: "85%", cy: "18%", sx: "55%", sy: "80%", stop: "70%" }
2433
- ];
2434
- function buildNebula(list, theme, mode, density) {
2435
- const ctx = { theme, resolved: resolveTheme(theme, { mode, density }) };
2436
- const n6 = colorCount(list, ctx) ?? NEBULA_SLOTS.length;
2437
- const colors = Array.from({ length: n6 }, (_2, i4) => colorAt(list, i4, ctx));
2438
- const blobs = colors.map((c6, i4) => {
2439
- const p5 = NEBULA_SLOTS[i4 % NEBULA_SLOTS.length];
2440
- return `radial-gradient(ellipse ${p5.sx} ${p5.sy} at ${p5.cx} ${p5.cy}, color-mix(in srgb, ${c6} 22%, transparent), transparent ${p5.stop})`;
2441
- });
2442
- blobs.push("radial-gradient(ellipse at center, #0a0a18 0%, #02020a 100%)");
2443
- return blobs.join(", ");
2444
- }
2445
3137
  function LabPanes({
2446
3138
  contributions,
2447
3139
  children
@@ -2498,13 +3190,21 @@ function Lab(props) {
2498
3190
  if (process.env.NODE_ENV !== "production" && props.instruments.length === 0) {
2499
3191
  throw new Error("[labkit] <Lab> requires a non-empty `instruments` array");
2500
3192
  }
2501
- const { storageKey, storage } = props;
2502
- useWarnIgnoredChange("<Lab>", { storageKey, storage });
2503
- const opened = useOpenOnce(
2504
- () => storageKey === void 0 ? openUnstoredLab(props) : openStoredLab(props, storageKey, storage)
2505
- );
3193
+ const { storageKey, storage, present } = props;
3194
+ useWarnIgnoredChange("<Lab>", { storageKey, storage, present });
3195
+ const [startsPresenting] = useState(() => present === true || hasPresentParam());
3196
+ if (process.env.NODE_ENV !== "production" && props.seed && !startsPresenting) {
3197
+ warnSeedIgnored();
3198
+ }
3199
+ const opened = useOpenOnce(() => {
3200
+ const seed = startsPresenting ? props.seed ?? {} : void 0;
3201
+ if (storageKey === void 0) return openUnstoredLab(props, seed);
3202
+ const key = startsPresenting ? presentStorageKey(storageKey) : storageKey;
3203
+ return openStoredLab(props, key, storage, seed);
3204
+ });
2506
3205
  if (!opened) {
2507
3206
  if (props.fallback !== void 0) return props.fallback;
3207
+ if (startsPresenting) return null;
2508
3208
  return /* @__PURE__ */ jsx(
2509
3209
  LabFallback,
2510
3210
  {
@@ -2517,7 +3217,13 @@ function Lab(props) {
2517
3217
  }
2518
3218
  );
2519
3219
  }
2520
- return /* @__PURE__ */ jsx(LabRuntime, { ...props, opened });
3220
+ return /* @__PURE__ */ jsx(LabRuntime, { ...props, opened, startsPresenting });
3221
+ }
3222
+ var seedWarned = false;
3223
+ function warnSeedIgnored() {
3224
+ if (seedWarned) return;
3225
+ seedWarned = true;
3226
+ console.warn("[labkit] <Lab> reads `seed` only when it starts presenting; it is ignored here");
2521
3227
  }
2522
3228
  function LabRuntime({
2523
3229
  instruments,
@@ -2532,13 +3238,15 @@ function LabRuntime({
2532
3238
  chrome,
2533
3239
  labChrome,
2534
3240
  suppress,
3241
+ expandOnDoubleClick = false,
2535
3242
  addTrial: addTrial2,
2536
3243
  zoom = true,
2537
3244
  tools,
2538
3245
  configRules,
2539
3246
  controls,
2540
3247
  children,
2541
- opened
3248
+ opened,
3249
+ startsPresenting
2542
3250
  }) {
2543
3251
  const { store } = opened;
2544
3252
  useLayoutEffect(() => {
@@ -2546,86 +3254,42 @@ function LabRuntime({
2546
3254
  }, [instruments, store]);
2547
3255
  const trials = useStore(store, (s4) => s4.trials);
2548
3256
  const [cameras] = useState(createCameraRegistry);
3257
+ const [clocks] = useState(createClockRegistry);
3258
+ useClockLoop(clocks);
2549
3259
  const [focusPick, setFocusPick] = useState(null);
2550
3260
  const focusedTrialId = trials.some((t) => t.id === focusPick) ? focusPick : trials[0]?.id ?? null;
3261
+ const [presentedBy, setPresentedBy] = useState(
3262
+ startsPresenting ? "mount" : null
3263
+ );
3264
+ const presenting = presentedBy !== null;
3265
+ const presentation = useMemo(
3266
+ () => ({
3267
+ active: presenting,
3268
+ enter: () => setPresentedBy((by) => by ?? "enter"),
3269
+ exit: () => setPresentedBy(null),
3270
+ trialId: presenting ? focusedTrialId : null
3271
+ }),
3272
+ [presenting, focusedTrialId]
3273
+ );
2551
3274
  const savedSnapshots = useStore(store, (s4) => s4.savedSnapshots);
2552
3275
  const modeValue = useStore(store, (s4) => s4.mode);
2553
3276
  const layout = useStore(store, (s4) => s4.layout);
2554
3277
  const undockedPanels = useStore(store, (s4) => s4.undockedPanels);
2555
3278
  const panelHostsRef = useRef(null);
2556
3279
  if (panelHostsRef.current === null) panelHostsRef.current = createPanelHostRegistry();
2557
- const outerSurface = useSurfaceOptional();
2558
- const outerOver = useSurfaceCanvas("over");
2559
- const outerUnder = useSurfaceCanvas("under");
2560
- const [ownOver, setOwnOver] = useState(null);
2561
- const [ownUnder, setOwnUnder] = useState(null);
2562
- const ownOverRef = useRef(null);
2563
- const ownUnderRef = useRef(null);
2564
- const bufferRef = useRef({ w: 0, h: 0 });
2565
- const surfaceRef = useRef(null);
2566
- const onFrame = useCallback((frame) => {
2567
- const canvases = [ownUnderRef.current, ownOverRef.current].filter((c6) => c6 !== null);
2568
- if (canvases.length === 0) return;
2569
- const w3 = Math.round(frame.size.width * frame.dpr);
2570
- const h2 = Math.round(frame.size.height * frame.dpr);
2571
- if (bufferRef.current.w !== w3 || bufferRef.current.h !== h2) {
2572
- bufferRef.current = { w: w3, h: h2 };
2573
- for (const c6 of canvases) {
2574
- c6.width = w3;
2575
- c6.height = h2;
2576
- c6.style.width = `${frame.size.width}px`;
2577
- c6.style.height = `${frame.size.height}px`;
2578
- }
2579
- surfaceRef.current?.invalidateAll();
2580
- return;
2581
- }
2582
- }, []);
2583
- const ownSurface = useTiledSurface({ onFrame });
2584
- useInsertionEffect(() => {
2585
- surfaceRef.current = ownSurface;
2586
- }, [ownSurface]);
2587
3280
  const [labBody, setLabBody] = useState(null);
2588
- const attachOwnSurface = ownSurface.containerRef;
2589
- const labBodyRef = useCallback(
2590
- (el) => {
2591
- setLabBody(el);
2592
- if (!outerSurface) attachOwnSurface(el);
2593
- },
2594
- [outerSurface, attachOwnSurface]
2595
- );
2596
3281
  useLabFitWarning(labBody);
3282
+ useFocusPick(labBody, store, setFocusPick);
2597
3283
  useEffect(() => {
2598
- if (!labBody) return;
2599
- const pick = (node) => {
2600
- const owned = new Set(store.getState().trials.map((t) => t.id));
2601
- const nearest = (from) => from?.closest(".lk-trial[data-trial-id]") ?? null;
2602
- let trial = typeof node?.closest === "function" ? nearest(node) : null;
2603
- while (trial && !owned.has(trial.dataset.trialId ?? "")) trial = nearest(trial.parentElement);
2604
- if (trial?.dataset.trialId) setFocusPick(trial.dataset.trialId);
2605
- };
2606
- const onInput = (event) => pick(event.target);
2607
- let pending;
3284
+ if (presentedBy !== "enter" || !labBody) return;
2608
3285
  const doc = labBody.ownerDocument;
2609
- const win = doc.defaultView;
2610
- const onBlur = () => {
2611
- clearTimeout(pending);
2612
- pending = setTimeout(() => pick(doc.activeElement), 0);
3286
+ const onKeyDown = (event) => {
3287
+ if (event.key !== "Escape" || event.defaultPrevented) return;
3288
+ setPresentedBy(null);
2613
3289
  };
2614
- labBody.addEventListener("pointerdown", onInput, true);
2615
- labBody.addEventListener("focusin", onInput, true);
2616
- win?.addEventListener("blur", onBlur);
2617
- return () => {
2618
- clearTimeout(pending);
2619
- labBody.removeEventListener("pointerdown", onInput, true);
2620
- labBody.removeEventListener("focusin", onInput, true);
2621
- win?.removeEventListener("blur", onBlur);
2622
- };
2623
- }, [labBody, store]);
2624
- const surface = outerSurface ?? ownSurface;
2625
- const surfaceCanvases = useMemo(
2626
- () => outerSurface ? { over: outerOver, under: outerUnder } : { over: ownOver, under: ownUnder },
2627
- [outerSurface, outerOver, outerUnder, ownOver, ownUnder]
2628
- );
3290
+ doc.addEventListener("keydown", onKeyDown);
3291
+ return () => doc.removeEventListener("keydown", onKeyDown);
3292
+ }, [presentedBy, labBody]);
2629
3293
  const workspacePanels = useMemo(
2630
3294
  () => Object.entries(undockedPanels).map(([key, panel]) => ({
2631
3295
  key,
@@ -2698,6 +3362,7 @@ function LabRuntime({
2698
3362
  (w3) => w3.id === id ? { ...w3, config: record.config, state: record.state, view: record.view } : w3
2699
3363
  )
2700
3364
  }));
3365
+ clocks.get(id)?.reset();
2701
3366
  },
2702
3367
  savedSnapshots,
2703
3368
  saveSnapshot: (trialId, name) => {
@@ -2718,6 +3383,7 @@ function LabRuntime({
2718
3383
  controls
2719
3384
  };
2720
3385
  }, [
3386
+ clocks,
2721
3387
  instruments,
2722
3388
  trials,
2723
3389
  focusedTrialId,
@@ -2745,15 +3411,19 @@ function LabRuntime({
2745
3411
  onReorder: (ids) => contextValue.reorderTrials(ids),
2746
3412
  layout,
2747
3413
  onLayoutChange: (next) => store.getState().setLayout(next),
3414
+ expandOnDoubleClick: typeof expandOnDoubleClick === "function" ? (id) => {
3415
+ const trial = trials.find((t) => t.id === id);
3416
+ return trial ? expandOnDoubleClick(trial) : false;
3417
+ } : expandOnDoubleClick,
2748
3418
  children: trials.map((w3) => /* @__PURE__ */ jsx(Trial, { id: w3.id, chrome, suppress }, w3.id))
2749
3419
  }
2750
3420
  );
2751
- 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(
3421
+ 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(PresentationContext.Provider, { value: presentation, children: /* @__PURE__ */ jsx(CameraRegistryContext.Provider, { value: cameras, children: /* @__PURE__ */ jsx(ClockRegistryContext.Provider, { value: clocks, children: /* @__PURE__ */ jsx(
2752
3422
  ThemeProvider,
2753
3423
  {
2754
3424
  theme,
2755
3425
  selection: { mode: resolvedMode, density: density ?? "comfortable" },
2756
- className: "lk-lab",
3426
+ className: presenting ? "lk-lab lk-lab--present" : "lk-lab",
2757
3427
  style: backdropStyle,
2758
3428
  children: /* @__PURE__ */ jsx(
2759
3429
  LabShell,
@@ -2768,53 +3438,22 @@ function LabRuntime({
2768
3438
  ] }) : footer,
2769
3439
  header: /* @__PURE__ */ jsxs(Fragment, { children: [
2770
3440
  /* @__PURE__ */ jsx(LabHeader, { ...addTrial2 !== void 0 ? { addTrial: addTrial2 } : {} }),
2771
- zoom ? /* @__PURE__ */ jsx(LabZoom, {}) : null,
3441
+ zoom && !presenting ? /* @__PURE__ */ jsx(LabZoom, {}) : null,
2772
3442
  children,
2773
3443
  /* @__PURE__ */ jsx(LabHeaderRegion, { contributions: labChromeAll }),
2774
3444
  /* @__PURE__ */ jsx(LabThemeSwitcher, {})
2775
3445
  ] }),
2776
- 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: [
2777
- outerSurface ? null : (
2778
- // Two buffers stacked around the trials, both inert —
2779
- // each tile takes input from its own box. A tile's marks
2780
- // annotate the instrument's DOM from over it; an opaque
2781
- // renderer sits under it, so the pane can still hold a
2782
- // label. The under one is first so it paints first.
2783
- /* @__PURE__ */ jsxs(Fragment, { children: [
2784
- /* @__PURE__ */ jsx(
2785
- "canvas",
2786
- {
2787
- className: "lk-lab__surface lk-lab__surface--under",
2788
- ref: (el) => {
2789
- ownUnderRef.current = el;
2790
- setOwnUnder(el);
2791
- }
2792
- }
2793
- ),
2794
- /* @__PURE__ */ jsx(
2795
- "canvas",
2796
- {
2797
- className: "lk-lab__surface lk-lab__surface--over",
2798
- ref: (el) => {
2799
- ownOverRef.current = el;
2800
- setOwnOver(el);
2801
- }
2802
- }
2803
- )
2804
- ] })
2805
- ),
2806
- hasPaneChrome ? /* @__PURE__ */ jsxs(LabPanes, { contributions: labChromeAll, children: [
2807
- /* @__PURE__ */ jsx(LabPalette, { contributions: labChromeAll }),
2808
- workspace
2809
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2810
- /* @__PURE__ */ jsx(LabPalette, { contributions: labChromeAll }),
2811
- workspace
2812
- ] })
2813
- ] }) }) }) })
3446
+ children: /* @__PURE__ */ jsx(PanelHostContext.Provider, { value: panelHostsRef.current, children: /* @__PURE__ */ jsx(LabSurface, { bodyRef: setLabBody, children: hasPaneChrome ? /* @__PURE__ */ jsxs(LabPanes, { contributions: labChromeAll, children: [
3447
+ /* @__PURE__ */ jsx(LabPalette, { contributions: labChromeAll }),
3448
+ workspace
3449
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
3450
+ /* @__PURE__ */ jsx(LabPalette, { contributions: labChromeAll }),
3451
+ workspace
3452
+ ] }) }) })
2814
3453
  }
2815
3454
  )
2816
3455
  }
2817
- ) }) }) }) }) });
3456
+ ) }) }) }) }) }) }) });
2818
3457
  }
2819
3458
  function LabBoundary({ children }) {
2820
3459
  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 }) }) }) }) }) }) }) }) }) }) }) }) });
@@ -3014,8 +3653,8 @@ function ButtonsAndToggles() {
3014
3653
  ] }) }),
3015
3654
  /* @__PURE__ */ jsx(Cell, { label: "Keycaps", children: /* @__PURE__ */ jsxs("div", { className: "lk-specimen__row", children: [
3016
3655
  /* @__PURE__ */ jsx(p, { keys: [{ label: "\u2318" }, { label: "\u21E7" }, { label: "K" }], separator: "+" }),
3017
- /* @__PURE__ */ jsx(f2, { label: "K" }),
3018
- /* @__PURE__ */ jsx(f2, { label: "\u2325", variant: "minimal" })
3656
+ /* @__PURE__ */ jsx(f3, { label: "K" }),
3657
+ /* @__PURE__ */ jsx(f3, { label: "\u2325", variant: "minimal" })
3019
3658
  ] }) })
3020
3659
  ] });
3021
3660
  }
@@ -3157,7 +3796,7 @@ function PanelsAndRows() {
3157
3796
  ]);
3158
3797
  const [panelCollapsed, setPanelCollapsed] = useState(false);
3159
3798
  return /* @__PURE__ */ jsxs(Section, { title: "Panels & property rows", children: [
3160
- /* @__PURE__ */ jsx(Cell, { label: "PropertyPanel", children: /* @__PURE__ */ jsx(ce, { title: "Shape", children: /* @__PURE__ */ jsxs(le, { children: [
3799
+ /* @__PURE__ */ jsx(Cell, { label: "PropertyPanel", children: /* @__PURE__ */ jsx(ce2, { title: "Shape", children: /* @__PURE__ */ jsxs(le, { children: [
3161
3800
  /* @__PURE__ */ jsx(
3162
3801
  ye,
3163
3802
  {
@@ -3262,10 +3901,10 @@ function PanelsAndRows() {
3262
3901
  }
3263
3902
  ) }),
3264
3903
  /* @__PURE__ */ jsx(Cell, { label: "LayerList", children: /* @__PURE__ */ jsx(
3265
- D2,
3904
+ A2,
3266
3905
  {
3267
3906
  items: layers,
3268
- onReorder: (move) => setLayers(S(layers, move)),
3907
+ onReorder: (move) => setLayers(T(layers, move)),
3269
3908
  renderBody: (item) => /* @__PURE__ */ jsxs("div", { children: [
3270
3909
  "settings for ",
3271
3910
  item.label
@@ -3310,7 +3949,7 @@ function OverlaysAndFeedback() {
3310
3949
  ),
3311
3950
  /* @__PURE__ */ jsx(c, { onClick: () => setDialogOpen(true), children: "Dialog" }),
3312
3951
  /* @__PURE__ */ jsx(
3313
- d3,
3952
+ d2,
3314
3953
  {
3315
3954
  isOpen: dialogOpen,
3316
3955
  onOpenChange: setDialogOpen,
@@ -3362,7 +4001,7 @@ function ListsAndData() {
3362
4001
  }
3363
4002
  ) }),
3364
4003
  /* @__PURE__ */ jsx(Cell, { label: "DataGrid", children: /* @__PURE__ */ jsx(
3365
- f,
4004
+ f2,
3366
4005
  {
3367
4006
  rows: [
3368
4007
  { id: "1", name: "accent", count: 3 },
@@ -3524,7 +4163,7 @@ function EditorsAndPlots() {
3524
4163
  }
3525
4164
  ) }),
3526
4165
  /* @__PURE__ */ jsx(Cell, { label: "Timeline", wide: true, children: /* @__PURE__ */ jsx("div", { className: "lk-specimen__timeline", children: /* @__PURE__ */ jsx(
3527
- xe2,
4166
+ xe,
3528
4167
  {
3529
4168
  tracks,
3530
4169
  duration: 2e3,
@@ -3549,10 +4188,6 @@ function LabChrome() {
3549
4188
  /* @__PURE__ */ jsx(Toolbar.Group, { end: true, children: /* @__PURE__ */ jsx(Toolbar.Button, { onClick: () => {
3550
4189
  }, children: "Save" }) })
3551
4190
  ] }) }),
3552
- /* @__PURE__ */ jsx(Cell, { label: "StatusBar", children: /* @__PURE__ */ jsxs(StatusBar, { children: [
3553
- /* @__PURE__ */ jsx(StatusBar.Section, { children: "Items: 12" }),
3554
- /* @__PURE__ */ jsx(StatusBar.Section, { children: "Zoom: 100%" })
3555
- ] }) }),
3556
4191
  /* @__PURE__ */ jsxs(Cell, { label: "Legend, JobProgress", children: [
3557
4192
  /* @__PURE__ */ jsx(Legend, { entries: LEGEND }),
3558
4193
  /* @__PURE__ */ jsx(JobProgress, { job: JOB })
@@ -3587,6 +4222,6 @@ function Specimen({ className }) {
3587
4222
  ] });
3588
4223
  }
3589
4224
 
3590
- 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 };
4225
+ export { AnnotationOverlay, AnnotationTargets, Lab, LabBoundary, LabPalette, LabRoot, LabShell, LabSwitcher, LabZoom, Lightbox, LightboxLayers, 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, useClockFrame, useLightbox, useLightboxControl, useLightboxLayers, usePresentation, useTrialClock, validateConfigSchema };
3591
4226
  //# sourceMappingURL=index.js.map
3592
4227
  //# sourceMappingURL=index.js.map