@weasel-js/labkit 1.5.0 → 1.5.2

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