@weasel-js/labkit 1.4.4 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/README.md +13 -2
  2. package/dist/_dts/{CanvasStackContext-kTEZvEgE.d.ts → CanvasStackContext-CqkxkFJW.d.ts} +1 -1
  3. package/dist/_dts/ToggleBar.d-DbHoWKIA.d.ts +116 -0
  4. package/dist/_dts/{frac-z7ker2Vx.d.ts → frac-8R6UmlvN.d.ts} +253 -31
  5. package/dist/_dts/index-B8uu2ba7.d.ts +237 -0
  6. package/dist/_dts/{index-Blv9uQzu.d.ts → index-retnAut7.d.ts} +18 -10
  7. package/dist/_dts/{types-B9_zrHmb.d.ts → types-Ca2LCnPq.d.ts} +65 -2
  8. package/dist/_dts/usePanZoom-nS798rOF.d.ts +160 -0
  9. package/dist/_dts/useTrialState-jcx_TdU6.d.ts +212 -0
  10. package/dist/canvas/index.d.ts +17 -128
  11. package/dist/canvas/index.js +4 -2
  12. package/dist/chrome/index.d.ts +4 -92
  13. package/dist/chrome/index.js +9 -5
  14. package/dist/chunk-67SJLMC7.js +704 -0
  15. package/dist/chunk-67SJLMC7.js.map +1 -0
  16. package/dist/chunk-6ZDGOZQV.js +310 -0
  17. package/dist/chunk-6ZDGOZQV.js.map +1 -0
  18. package/dist/{chunk-MOM3GOVY.js → chunk-EKIICY6X.js} +111 -25
  19. package/dist/chunk-EKIICY6X.js.map +1 -0
  20. package/dist/{chunk-CTRKTLYZ.js → chunk-H6ZAOWNE.js} +11 -11
  21. package/dist/chunk-H6ZAOWNE.js.map +1 -0
  22. package/dist/{chunk-SMHP6XZ4.js → chunk-KJALCDWE.js} +9 -7
  23. package/dist/chunk-KJALCDWE.js.map +1 -0
  24. package/dist/{chunk-BKFVHKJH.js → chunk-MUKOW3TC.js} +3 -3
  25. package/dist/{chunk-BKFVHKJH.js.map → chunk-MUKOW3TC.js.map} +1 -1
  26. package/dist/chunk-NDRYLVOW.js +15 -0
  27. package/dist/chunk-NDRYLVOW.js.map +1 -0
  28. package/dist/{chunk-64ZCN3DA.js → chunk-NS64DXMN.js} +226 -159
  29. package/dist/chunk-NS64DXMN.js.map +1 -0
  30. package/dist/{chunk-I6JCVE24.js → chunk-PGETSEDK.js} +135 -13
  31. package/dist/chunk-PGETSEDK.js.map +1 -0
  32. package/dist/{chunk-FQMJUVHJ.js → chunk-QLSV2N3G.js} +32 -37
  33. package/dist/chunk-QLSV2N3G.js.map +1 -0
  34. package/dist/chunk-RWNDDKOH.js +682 -0
  35. package/dist/chunk-RWNDDKOH.js.map +1 -0
  36. package/dist/chunk-S2SHZRD7.js +7681 -0
  37. package/dist/chunk-S2SHZRD7.js.map +1 -0
  38. package/dist/{chunk-U3IYHIAE.js → chunk-TQBBYTAF.js} +19 -82
  39. package/dist/chunk-TQBBYTAF.js.map +1 -0
  40. package/dist/chunk-UU3NJO6N.js +72 -0
  41. package/dist/chunk-UU3NJO6N.js.map +1 -0
  42. package/dist/chunk-WIG6XHJ7.js +35 -0
  43. package/dist/chunk-WIG6XHJ7.js.map +1 -0
  44. package/dist/{chunk-TJ7QY3OC.js → chunk-ZPH5WTWF.js} +21 -15
  45. package/dist/chunk-ZPH5WTWF.js.map +1 -0
  46. package/dist/config/index.d.ts +232 -0
  47. package/dist/config/index.js +6 -0
  48. package/dist/config/index.js.map +1 -0
  49. package/dist/controls/index.d.ts +2 -3
  50. package/dist/controls/index.js +3 -2
  51. package/dist/dragdrop/index.d.ts +2 -4
  52. package/dist/index.d.ts +110 -381
  53. package/dist/index.js +691 -817
  54. package/dist/index.js.map +1 -1
  55. package/dist/layers/index.d.ts +3 -5
  56. package/dist/layers/index.js +2 -2
  57. package/dist/loupe/index.d.ts +6 -8
  58. package/dist/loupe/index.js +2 -2
  59. package/dist/passthrough/weasel-ui.d.ts +164 -122
  60. package/dist/passthrough/weasel-ui.js +1 -1
  61. package/dist/primitives/index.d.ts +53 -6
  62. package/dist/primitives/index.js +6 -4
  63. package/dist/state/index.d.ts +12 -7
  64. package/dist/state/index.js +16 -13
  65. package/dist/state/index.js.map +1 -1
  66. package/dist/styles.css +84 -50
  67. package/dist/surface/index.d.ts +44 -7
  68. package/dist/surface/index.js +2 -1
  69. package/dist/ui/layers/index.js +1 -1
  70. package/dist/undo/index.d.ts +2 -4
  71. package/package.json +15 -8
  72. package/src/annotations/AnnotationOverlay.tsx +53 -5
  73. package/src/annotations/Annotations.overlay.test.tsx +85 -6
  74. package/src/annotations/drawOne.test.ts +1 -1
  75. package/src/annotations/drawOne.ts +2 -2
  76. package/src/annotations/paint.test.ts +2 -2
  77. package/src/annotations/paint.ts +1 -1
  78. package/src/annotations/preload.ts +6 -0
  79. package/src/annotations/store.test.ts +18 -0
  80. package/src/annotations/store.ts +5 -4
  81. package/src/annotations/svgNodes.test.ts +2 -2
  82. package/src/annotations/toolMap.ts +3 -3
  83. package/src/annotations/types.ts +6 -3
  84. package/src/canvas/CameraWheelContext.ts +12 -0
  85. package/src/canvas/CanvasStack.test.tsx +25 -0
  86. package/src/canvas/CanvasStack.tsx +31 -4
  87. package/src/canvas/Stage.less +18 -0
  88. package/src/canvas/Stage.test.tsx +186 -0
  89. package/src/canvas/Stage.tsx +134 -0
  90. package/src/canvas/camera.test.ts +17 -2
  91. package/src/canvas/camera.ts +9 -9
  92. package/src/canvas/index.ts +4 -0
  93. package/src/canvas/usePanZoom.ts +10 -5
  94. package/src/chrome/ChromeRegions.stories.tsx +0 -3
  95. package/src/chrome/LabChrome.tsx +114 -0
  96. package/src/chrome/builtins.tsx +20 -31
  97. package/src/chrome/index.ts +15 -0
  98. package/src/chrome/labRegions.test.tsx +208 -0
  99. package/src/chrome/labTypes.ts +50 -0
  100. package/src/chrome/merge.ts +7 -9
  101. package/src/chrome/regions/PaletteRegion.test.tsx +6 -4
  102. package/src/chrome/regions/PaletteRegion.tsx +21 -9
  103. package/src/chrome/regions/SidebarRegion.tsx +25 -13
  104. package/src/chrome/regions/StatusRegion.tsx +15 -8
  105. package/src/chrome/regions/ToolbarRegion.tsx +30 -16
  106. package/src/chrome/types.ts +47 -20
  107. package/src/config/builder.test.ts +4 -0
  108. package/src/config/builder.ts +16 -10
  109. package/src/config/declarationEmit.fixture.ts +11 -0
  110. package/src/config/declarationEmit.test.ts +51 -0
  111. package/src/config/entry.test.ts +46 -0
  112. package/src/config/index.ts +13 -1
  113. package/src/config/nodeClasses.test.ts +18 -0
  114. package/src/config/types.ts +3 -1
  115. package/src/controls/ControlPanel.test.tsx +16 -0
  116. package/src/controls/ControlPanel.tsx +5 -2
  117. package/src/fake-indexeddb-auto.d.ts +5 -0
  118. package/src/index.test.ts +7 -0
  119. package/src/index.ts +38 -5
  120. package/src/instrument/serializers.test.ts +22 -0
  121. package/src/instrument/serializers.ts +11 -0
  122. package/src/instrument/types.ts +25 -1
  123. package/src/lab/Lab.less +4 -13
  124. package/src/lab/Lab.persist.test.tsx +147 -0
  125. package/src/lab/Lab.stories.tsx +0 -2
  126. package/src/lab/Lab.surface.test.tsx +66 -3
  127. package/src/lab/Lab.test.tsx +91 -17
  128. package/src/lab/Lab.tsx +337 -124
  129. package/src/lab/LabFit.stories.less +18 -0
  130. package/src/lab/LabFit.stories.tsx +219 -0
  131. package/src/lab/LabFullChrome.stories.tsx +0 -3
  132. package/src/lab/LabHeader.test.tsx +40 -9
  133. package/src/lab/LabHeader.tsx +7 -13
  134. package/src/lab/LabPalette.tsx +13 -26
  135. package/src/lab/LabShell.less +49 -12
  136. package/src/lab/LabSwitcher.less +8 -1
  137. package/src/lab/Workspace.surface.test.tsx +2 -1
  138. package/src/lab/fitCheck.test.ts +57 -0
  139. package/src/lab/fitCheck.ts +125 -0
  140. package/src/loupe/AGENTS.md +1 -1
  141. package/src/loupe/Loupe.less +1 -1
  142. package/src/loupe/canvasLens.ts +1 -1
  143. package/src/loupe/types.ts +1 -1
  144. package/src/loupe/useLoupe.test.tsx +1 -1
  145. package/src/loupe/useLoupe.ts +1 -1
  146. package/src/passthrough/weasel-ui.ts +9 -0
  147. package/src/primitives/FloatingPanel.stories.tsx +13 -1
  148. package/src/primitives/FloatingPanel.test.tsx +47 -16
  149. package/src/primitives/FloatingPanel.tsx +11 -25
  150. package/src/primitives/Split.less +14 -0
  151. package/src/primitives/Split.test.tsx +108 -0
  152. package/src/primitives/Split.tsx +159 -0
  153. package/src/primitives/ZoomControl.tsx +5 -2
  154. package/src/primitives/index.ts +2 -0
  155. package/src/state/Persistence.stories.tsx +75 -0
  156. package/src/state/Persistence.tsx +33 -0
  157. package/src/state/SingletonExperiment.test.tsx +47 -53
  158. package/src/state/SingletonExperiment.tsx +24 -13
  159. package/src/state/adapterContract.ts +116 -0
  160. package/src/state/adapters.test.ts +113 -61
  161. package/src/state/adapters.ts +328 -69
  162. package/src/state/document.test.ts +71 -96
  163. package/src/state/document.ts +66 -52
  164. package/src/state/helpers.test.ts +1 -1
  165. package/src/state/index.ts +8 -0
  166. package/src/state/labRecords.test.ts +105 -0
  167. package/src/state/labRecords.ts +160 -0
  168. package/src/state/openLabStore.test.ts +658 -0
  169. package/src/state/openLabStore.ts +403 -0
  170. package/src/state/records.test.ts +234 -0
  171. package/src/state/records.ts +219 -0
  172. package/src/state/store.test.ts +104 -635
  173. package/src/state/store.ts +116 -157
  174. package/src/state/toolSlot.test.ts +1 -1
  175. package/src/state/types.ts +39 -15
  176. package/src/state/useOpenOnce.ts +68 -0
  177. package/src/state/usePersistedState.test.tsx +125 -0
  178. package/src/state/usePersistedState.ts +66 -0
  179. package/src/state/useTrialState.test.tsx +2 -3
  180. package/src/state/view.test.ts +49 -15
  181. package/src/state/view.ts +18 -1
  182. package/src/styles.less +2 -0
  183. package/src/surface/AGENTS.md +39 -7
  184. package/src/surface/SurfaceContext.ts +26 -3
  185. package/src/surface/index.ts +2 -0
  186. package/src/surface/useSurfaceTile.test.tsx +2 -1
  187. package/src/surface/useSurfaceTile.ts +6 -5
  188. package/src/surface/useTiledSurface.test.tsx +175 -0
  189. package/src/surface/useTiledSurface.ts +54 -5
  190. package/src/theme/Interstellar.stories.tsx +7 -7
  191. package/src/theme/interstellar.test.ts +1 -1
  192. package/src/theme/interstellar.tokens.json +2 -2
  193. package/src/tools/labTool.ts +11 -0
  194. package/src/trial/Trial.annotations.persist.test.tsx +100 -58
  195. package/src/trial/Trial.annotations.test.tsx +47 -4
  196. package/src/trial/Trial.job.test.tsx +3 -3
  197. package/src/trial/Trial.less +1 -26
  198. package/src/trial/Trial.stories.tsx +1 -2
  199. package/src/trial/Trial.test.tsx +28 -4
  200. package/src/trial/Trial.trialId.test.tsx +23 -1
  201. package/src/trial/Trial.tsx +118 -38
  202. package/src/trial/TrialBody.tsx +17 -141
  203. package/src/trial/TrialChrome.tsx +7 -4
  204. package/src/trial/trialOps.ts +5 -3
  205. package/dist/_dts/types-BP2OCcpg.d.ts +0 -155
  206. package/dist/_dts/types-x92Kfeme.d.ts +0 -62
  207. package/dist/_dts/useTrialState-D6Vb3T-g.d.ts +0 -99
  208. package/dist/chunk-2P6PP5N4.js +0 -553
  209. package/dist/chunk-2P6PP5N4.js.map +0 -1
  210. package/dist/chunk-64ZCN3DA.js.map +0 -1
  211. package/dist/chunk-CTRKTLYZ.js.map +0 -1
  212. package/dist/chunk-FQMJUVHJ.js.map +0 -1
  213. package/dist/chunk-I6JCVE24.js.map +0 -1
  214. package/dist/chunk-MOM3GOVY.js.map +0 -1
  215. package/dist/chunk-SMHP6XZ4.js.map +0 -1
  216. package/dist/chunk-TJ7QY3OC.js.map +0 -1
  217. package/dist/chunk-U3IYHIAE.js.map +0 -1
  218. package/dist/chunk-W3ECWC2K.js +0 -7335
  219. package/dist/chunk-W3ECWC2K.js.map +0 -1
@@ -1,14 +1,9 @@
1
- import { type ReactNode, useEffect, useMemo, useRef } from 'react';
2
- import { asNodeId, createNode, Store, stripStrategy } from 'windease';
3
- import { type ChromeMap, Container, Provider, StrategyRegistryProvider } from 'windease/react';
4
-
5
- const ZONE_ID = asNodeId('lk-trial-body');
6
- const SIDEBAR_ID = asNodeId('lk-trial-sidebar');
7
- const CONTENT_ID = asNodeId('lk-trial-content');
8
- const STRATEGIES = { strip: stripStrategy as never };
1
+ import type { ReactNode } from 'react';
2
+ import { Split, type SplitProps } from '../primitives/Split';
9
3
 
10
4
  /** Props for `<TrialBody>`. */
11
- export interface TrialBodyProps {
5
+ export interface TrialBodyProps
6
+ extends Omit<SplitProps, 'sidebar' | 'children' | 'className' | 'sidebarClassName' | 'label'> {
12
7
  /** The sidebar region's sections. */
13
8
  sidebar: ReactNode;
14
9
  /** The instrument's own output. */
@@ -16,140 +11,21 @@ export interface TrialBodyProps {
16
11
  /** Extra class on the content pane — `Trial` marks a canvas instrument's
17
12
  * well flush. */
18
13
  contentClassName?: string;
19
- /** The sidebar's current extent in pixels. Omit for an unpersisted lab: the
20
- * seam still works, its result just does not outlive the mount. */
21
- width?: number;
22
- /** Where the sidebar starts before anyone drags it. */
23
- defaultWidth?: number;
24
- minWidth?: number;
25
- maxWidth?: number;
26
- /** How little the instrument may be squeezed to. Reported as the seam's
27
- * reachable maximum, so the drag stops there rather than clamping silently. */
28
- contentMinWidth?: number;
29
- /** Fires through the drag, not on release: the pane is already there. */
30
- onWidthChange?: (width: number) => void;
31
- /**
32
- * Fixed extent. Omit in an app — the strip measures its own box. Supply it
33
- * where nothing measures, notably jsdom, or both panes lay out at zero.
34
- */
35
- viewport?: { w: number; h: number };
36
14
  }
37
15
 
38
- /**
39
- * A trial's sidebar and content as a two-pane strip with a draggable seam.
40
- *
41
- * The seam is `stripStrategy`'s own resize affordance, so it arrives as a
42
- * `role="separator"` carrying the reachable range, operable by pointer and by
43
- * arrows / Home / End, and clamped against the content pane's floor rather
44
- * than against the sidebar's alone.
45
- */
46
- export function TrialBody({
47
- sidebar,
48
- children,
49
- contentClassName,
50
- width,
51
- defaultWidth = 320,
52
- minWidth = 140,
53
- maxWidth = 720,
54
- contentMinWidth = 160,
55
- onWidthChange,
56
- viewport,
57
- }: TrialBodyProps) {
58
- // The cross-axis half of every size hint below is inert: a horizontal strip
59
- // stretches its panes to the container's height and never reads `h`.
60
- const storeRef = useRef<Store | null>(null);
61
- if (storeRef.current === null) {
62
- const store = new Store();
63
- store.registerNode(
64
- createNode({
65
- kind: 'zone',
66
- id: ZONE_ID,
67
- container: { strategyId: 'strip', config: { axis: 'x', resizable: true } },
68
- }),
69
- );
70
- store.registerNode(
71
- createNode({
72
- kind: 'sidebar',
73
- id: SIDEBAR_ID,
74
- parentId: ZONE_ID,
75
- meta: { title: 'Sidebar' },
76
- placement: { size: { w: width ?? defaultWidth } },
77
- hints: { minSize: { w: minWidth, h: 0 }, maxSize: { w: maxWidth, h: 0 } },
78
- }),
79
- );
80
- store.registerNode(
81
- createNode({
82
- kind: 'content',
83
- id: CONTENT_ID,
84
- parentId: ZONE_ID,
85
- meta: { title: 'Content' },
86
- hints: { minSize: { w: contentMinWidth, h: 0 } },
87
- }),
88
- );
89
- store.showNode(SIDEBAR_ID);
90
- store.showNode(CONTENT_ID);
91
- storeRef.current = store;
92
- }
93
- const store = storeRef.current;
94
-
95
- // What the store last held, so the placement event can tell a drag from the
96
- // echo of our own write and a controlled `width` does not fight the seam.
97
- const widthRef = useRef(width ?? defaultWidth);
98
-
99
- useEffect(() => {
100
- store.setHints(SIDEBAR_ID, {
101
- minSize: { w: minWidth, h: 0 },
102
- maxSize: { w: maxWidth, h: 0 },
103
- });
104
- store.setHints(CONTENT_ID, { minSize: { w: contentMinWidth, h: 0 } });
105
- }, [store, minWidth, maxWidth, contentMinWidth]);
106
-
107
- useEffect(() => {
108
- if (width === undefined || width === widthRef.current) return;
109
- widthRef.current = width;
110
- store.patchPlacement(SIDEBAR_ID, { size: { w: width } });
111
- }, [store, width]);
112
-
113
- const onWidthChangeRef = useRef(onWidthChange);
114
- onWidthChangeRef.current = onWidthChange;
115
- useEffect(
116
- () =>
117
- store.events.on('node.placementChanged', () => {
118
- const next = (store.getPlacement(SIDEBAR_ID)?.size as { w?: number } | undefined)?.w;
119
- if (typeof next !== 'number' || next === widthRef.current) return;
120
- widthRef.current = next;
121
- onWidthChangeRef.current?.(next);
122
- }),
123
- [store],
124
- );
125
-
126
- const chrome = useMemo<ChromeMap>(
127
- () => ({
128
- sidebar: () => <div className="lk-trial__sidebar">{sidebar}</div>,
129
- content: () => (
130
- <div
131
- className={
132
- contentClassName ? `lk-trial__content ${contentClassName}` : 'lk-trial__content'
133
- }
134
- >
135
- {children}
136
- </div>
137
- ),
138
- }),
139
- [sidebar, children, contentClassName],
140
- );
141
-
16
+ /** A trial's sidebar and content as a `Split`, wearing the trial's classes. */
17
+ export function TrialBody({ sidebar, children, contentClassName, ...rest }: TrialBodyProps) {
142
18
  return (
143
- <Provider store={store}>
144
- <StrategyRegistryProvider strategies={STRATEGIES}>
145
- <Container
146
- parentId={ZONE_ID}
147
- chrome={chrome}
148
- className="lk-trial__panes windease-zone"
149
- affordances
150
- viewport={viewport}
151
- />
152
- </StrategyRegistryProvider>
153
- </Provider>
19
+ <Split
20
+ {...rest}
21
+ sidebar={sidebar}
22
+ className="lk-trial__panes"
23
+ sidebarClassName="lk-trial__sidebar"
24
+ contentClassName={
25
+ contentClassName ? `lk-trial__content ${contentClassName}` : 'lk-trial__content'
26
+ }
27
+ >
28
+ {children}
29
+ </Split>
154
30
  );
155
31
  }
@@ -17,7 +17,7 @@ import { useLabContext } from '../lab/LabContext';
17
17
  import { JobProgress } from '../primitives/JobProgress';
18
18
  import { LabStoreContext } from '../state/context';
19
19
  import type { TrialRecord } from '../state/types';
20
- import { as2DView } from '../state/view';
20
+ import { as2DView, withZoom } from '../state/view';
21
21
  import { TrialBody } from './TrialBody';
22
22
  import { TrialTitleBar } from './TrialTitleBar';
23
23
  import { UndockedSections } from './UndockedSections';
@@ -111,13 +111,13 @@ export function TrialChrome({
111
111
 
112
112
  const configSchema = useConfigSchema(instrument);
113
113
 
114
- const title = record.title ?? record.instrumentName;
114
+ const title = record.title ?? instrument.title ?? instrument.name;
115
115
  const collapsedSections = record.collapsedSections ?? NO_SECTIONS;
116
116
 
117
117
  const ctx = useMemo<TrialChromeContext>(() => {
118
118
  const setZoom = (z: number): void => {
119
119
  if (!view2d) return;
120
- updateTrialView(trialId, { ...view2d, zoom: z });
120
+ updateTrialView(trialId, withZoom(view2d, z));
121
121
  };
122
122
  return {
123
123
  trialId,
@@ -225,6 +225,7 @@ export function TrialChrome({
225
225
  return (
226
226
  <section
227
227
  className="lk-trial"
228
+ data-trial-id={trialId}
228
229
  aria-label={`Trial ${title}`}
229
230
  tabIndex={-1}
230
231
  onKeyDown={handleKeyDown}
@@ -243,7 +244,9 @@ export function TrialChrome({
243
244
  <TrialBody
244
245
  width={record.sidebarWidth}
245
246
  onWidthChange={(w) => updateTrialSidebarWidth(trialId, w)}
246
- contentClassName={instrument.canvas ? 'lk-trial__content--flush' : undefined}
247
+ contentClassName={
248
+ instrument.canvas || instrument.stage ? 'lk-trial__content--flush' : undefined
249
+ }
247
250
  sidebar={
248
251
  <>
249
252
  <SidebarRegion contributions={inRegion('sidebar')} ctx={ctx} />
@@ -1,7 +1,7 @@
1
1
  import { fillConfigDefaults } from '../config/path';
2
2
  import type { Instrument, InstrumentList } from '../instrument/types';
3
- import type { TrialRecord } from '../state/types';
4
3
  import { newId } from '../state/helpers';
4
+ import type { TrialRecord } from '../state/types';
5
5
 
6
6
  const DEFAULT_VIEW = { zoom: 1, pan: { x: 0, y: 0 } } as const;
7
7
 
@@ -14,10 +14,12 @@ function findInstrument(instruments: InstrumentList, name: string): Instrument {
14
14
  }
15
15
 
16
16
  function initialView(instrument: Instrument): TrialRecord['view'] {
17
- const declared = instrument.canvas?.initialView;
17
+ const stage = instrument.canvas ? undefined : instrument.stage;
18
+ const declared = instrument.canvas?.initialView ?? stage?.initialView;
18
19
  // A function needs the canvas size, which no trial has at creation. `null`
19
20
  // is the unplaced marker; `Trial` resolves it on the first non-empty size.
20
- if (typeof declared === 'function') return null;
21
+ // A stage that names no view is fitted, which needs that size too.
22
+ if (typeof declared === 'function' || (stage && declared === undefined)) return null;
21
23
  return declared ? structuredClone(declared) : { ...DEFAULT_VIEW, pan: { ...DEFAULT_VIEW.pan } };
22
24
  }
23
25
 
@@ -1,155 +0,0 @@
1
- import { C as ConfigPath, V as ValueAtPath } from './types-B9_zrHmb.js';
2
-
3
- /** A sidebar section torn out of its trial, and where it went. */
4
- interface UndockedPanel {
5
- trialId: string;
6
- sectionId: string;
7
- as: 'tile' | 'floating';
8
- }
9
- /** Undocked panels, keyed by {@link panelKey}. */
10
- type UndockedPanels = Record<string, UndockedPanel>;
11
- declare function panelKey(trialId: string, sectionId: string): string;
12
- declare function undockPanel(panels: UndockedPanels, trialId: string, sectionId: string, as?: UndockedPanel['as']): UndockedPanels;
13
- /** Dock one section back, or — with no `sectionId` — every panel the trial
14
- * owns, which is what closing a trial wants. */
15
- declare function dockPanel(panels: UndockedPanels, trialId: string, sectionId?: string): UndockedPanels;
16
-
17
- /** A trial's undo history, as snapshots of its state either side of the
18
- * present. */
19
- interface UndoStack {
20
- past: unknown[];
21
- future: unknown[];
22
- }
23
- /** The trial a per-trial call is coming from. `id` is the id `useTileId`
24
- * scopes a surface tile under, so a consumer keying its own per-trial
25
- * registry and labkit's tiles agree on the key. */
26
- interface TrialInfo<TV = unknown> {
27
- id: string;
28
- /** The trial's view, in whatever shape the instrument keeps it. */
29
- view: TV;
30
- }
31
- /** One trial as the store holds it: which instrument it runs, that
32
- * instrument's config and state, the camera, and the undo history. */
33
- interface TrialRecord<TS = unknown, TC = unknown, TV = unknown> {
34
- id: string;
35
- instrumentName: string;
36
- config: TC;
37
- state: TS;
38
- /** Opaque to labkit: persisted, restored on Reset and handed to the instrument,
39
- * but never read into. A 3D lab puts an orbit here and keeps all three. */
40
- view: TV;
41
- /** The config `addTrial` opened this trial on, overlaid on the instrument's
42
- * defaults. Kept so Reset restores the trial's own subject rather than the
43
- * bare defaults. */
44
- configSeed?: Partial<TC>;
45
- /** This trial's own tool slot. Undefined means it reads the lab's. */
46
- activeToolId?: string | null;
47
- /** The extent the trial's sidebar was last dragged to, in pixels. Undefined
48
- * until someone moves the seam. */
49
- sidebarWidth?: number;
50
- /** What the title bar reads. Undefined — the state a trial opens in, and the
51
- * one `setTitle(null)` returns it to — means the instrument's name. */
52
- title?: string | null;
53
- /** Which of the trial's collapsible sections are folded, keyed as
54
- * `TrialChromeContext.collapsedSections` describes. A key that is absent
55
- * takes the section's own default. */
56
- collapsedSections?: Record<string, boolean>;
57
- /** The marks on this trial's annotation targets, as `AnnotationsApi.toJSON`
58
- * wrote them. Opaque here, and absent for a trial whose instrument declares
59
- * no `annotations` — or declares its own `storage`. Not in `state`, which
60
- * belongs to the instrument and is typed as such. */
61
- annotations?: unknown;
62
- undoStack: UndoStack;
63
- }
64
- /** A named, saved copy of a trial's config and state, restorable into any
65
- * trial running the same instrument. */
66
- interface SavedSnapshot {
67
- id: string;
68
- name: string;
69
- trialId: string;
70
- instrumentName: string;
71
- config: unknown;
72
- state: unknown;
73
- savedAt: number;
74
- }
75
- /** `auto` follows the OS; the other two are an explicit choice. */
76
- type LabMode = 'auto' | 'light' | 'dark';
77
- /** Everything a lab persists: its trials, its saved snapshots, and the
78
- * chosen color mode. */
79
- interface LabStoreState {
80
- trials: TrialRecord[];
81
- savedSnapshots: SavedSnapshot[];
82
- mode: LabMode;
83
- /** The lab's tool slot — what a trial with no slot of its own resolves to. */
84
- activeToolId: string | null;
85
- /** Per-trial tile extents, keyed by trial id. Opaque here — the
86
- * shape belongs to whatever lays the trials out. */
87
- layout: Record<string, unknown>;
88
- /** Sidebar sections torn out of their trial. A panel here is not rendered in
89
- * its trial's sidebar; the workspace renders it instead. */
90
- undockedPanels: UndockedPanels;
91
- }
92
- /** Where a lab persists itself. Implementations are keyed string storage and
93
- * nothing more, so the same store works against localStorage, the URL hash,
94
- * or memory. */
95
- interface StorageAdapter {
96
- read(key: string): string | null;
97
- write(key: string, value: string): void;
98
- delete?(key: string): void;
99
- }
100
- /** What `useTrialState` hands an instrument: its state and config, with
101
- * a setter for each. */
102
- interface TrialStateHandle<TS, TC> {
103
- state: TS;
104
- setState: (next: TS | ((prev: TS) => TS)) => void;
105
- config: TC;
106
- /** Write one config value, by dotted path — `'grid.size'` for a leaf under
107
- * an `f.group`, `'cellSize'` for one at the root. */
108
- setConfig: <P extends ConfigPath<TC> & string>(path: P, value: ValueAtPath<TC, P>) => void;
109
- }
110
- /** Options for `createLabStore`. `storageKey` namespaces the keys written, so
111
- * two labs on one origin do not collide. */
112
- interface CreateLabStoreOptions {
113
- storageKey: string;
114
- storage: StorageAdapter;
115
- initialMode?: LabMode;
116
- /** Each instrument's default config, keyed by instrument name, used to fill
117
- * the gaps in a stored one. A config saved before its schema grew a branch
118
- * arrives holding that branch's defaults rather than `undefined`, and keeps
119
- * whatever keys the schema has since stopped naming. `<Lab>` collects these
120
- * off its `instruments`. */
121
- configDefaults?: Record<string, () => unknown>;
122
- /** How each instrument's state survives a reload. Hydration is the first
123
- * thing `createLabStore` does, so these have to arrive with the store —
124
- * anything registered afterwards is already too late to read the document
125
- * it was built from. `<Lab>` collects them off its `instruments`. */
126
- serializers?: InstrumentSerializers;
127
- }
128
- /** Per-instrument serialize/deserialize hooks, keyed by instrument name. An
129
- * instrument whose state is already JSON-safe needs no entry. `deserialize`
130
- * is handed the config the state was saved against — a trial's own for a
131
- * reload, the snapshot's for a load — since a state rebuilt without it can
132
- * disagree with the settings sitting next to it. */
133
- type InstrumentSerializers = Record<string, {
134
- serialize?: (state: unknown) => unknown;
135
- deserialize?: (data: unknown, config: unknown) => unknown;
136
- } | undefined>;
137
- /** A trial as it is persisted: everything but the undo history, which is
138
- * session-only. */
139
- type SerializedTrial = Omit<TrialRecord, 'undoStack'>;
140
- /** Everything a lab persists, under one key, at a known version. */
141
- interface LabDocument {
142
- version: number;
143
- trials: SerializedTrial[];
144
- saves: SavedSnapshot[];
145
- layout: Record<string, unknown>;
146
- /** Sidebar sections torn out of their trial, keyed by trial and section. */
147
- undockedPanels: UndockedPanels;
148
- mode: LabMode;
149
- }
150
- /** Migrates a document one version forward. Index `i` in the chain takes a
151
- * version-`i` document to version `i + 1`. */
152
- type Migration = (doc: Record<string, unknown>) => Record<string, unknown>;
153
-
154
- export { dockPanel as g, panelKey as p, undockPanel as u };
155
- export type { CreateLabStoreOptions as C, InstrumentSerializers as I, LabDocument as L, Migration as M, SerializedTrial as S, TrialRecord as T, UndoStack as U, StorageAdapter as a, LabStoreState as b, SavedSnapshot as c, TrialStateHandle as d, UndockedPanel as e, UndockedPanels as f, LabMode as h, TrialInfo as i };
@@ -1,62 +0,0 @@
1
- /** Which control a config field is edited with. */
2
- type ConfigFieldType = 'slider' | 'checkbox' | 'select' | 'number' | 'text' | 'color';
3
- /** What every config field carries: the config key it writes, the label shown
4
- * beside it, and which control renders it. */
5
- interface ConfigFieldBase {
6
- key: string;
7
- label: string;
8
- type: ConfigFieldType;
9
- }
10
- /** A bounded number edited by dragging. */
11
- interface SliderField extends ConfigFieldBase {
12
- type: 'slider';
13
- default: number;
14
- min: number;
15
- max: number;
16
- step?: number;
17
- }
18
- /** A boolean. */
19
- interface CheckboxField extends ConfigFieldBase {
20
- type: 'checkbox';
21
- default: boolean;
22
- }
23
- /** One choice in a select field. */
24
- interface SelectOption {
25
- value: string;
26
- label: string;
27
- }
28
- /** A fixed set of labeled choices. */
29
- interface SelectField extends ConfigFieldBase {
30
- type: 'select';
31
- default: string;
32
- options: SelectOption[];
33
- }
34
- /** A number typed directly, optionally bounded. Use a slider field instead
35
- * when the range matters more than the exact value. */
36
- interface NumberField extends ConfigFieldBase {
37
- type: 'number';
38
- default: number;
39
- min?: number;
40
- max?: number;
41
- step?: number;
42
- }
43
- /** A free-text string. Writes are debounced so typing does not re-run the
44
- * instrument on every keystroke. */
45
- interface TextField extends ConfigFieldBase {
46
- type: 'text';
47
- default: string;
48
- placeholder?: string;
49
- maxLength?: number;
50
- /** Milliseconds to debounce live setConfig calls. Default 150 ms. Set to 0 to disable. */
51
- debounceMs?: number;
52
- }
53
- /** A color, as a CSS color string. */
54
- interface ColorField extends ConfigFieldBase {
55
- type: 'color';
56
- default: string;
57
- }
58
- /** One field of an instrument's config schema. The schema is what the control
59
- * panel renders, and what `validateConfigSchema` checks. */
60
- type ConfigField = SliderField | CheckboxField | SelectField | NumberField | TextField | ColorField;
61
-
62
- export type { ConfigField as C, NumberField as N, SelectField as S, TextField as T, CheckboxField as a, ColorField as b, ConfigFieldBase as c, ConfigFieldType as d, SelectOption as e, SliderField as f };
@@ -1,99 +0,0 @@
1
- import { a as StorageAdapter, b as LabStoreState, T as TrialRecord, c as SavedSnapshot, h as LabMode, e as UndockedPanel, C as CreateLabStoreOptions, d as TrialStateHandle } from './types-BP2OCcpg.js';
2
- import * as react from 'react';
3
- import { ReactNode, ReactElement } from 'react';
4
- import { StoreApi } from 'zustand/vanilla';
5
-
6
- /** Persist to `localStorage` — state survives a reload and a new tab. Falls
7
- * back to no-ops when storage is unavailable. */
8
- declare const localStorageAdapter: StorageAdapter;
9
- /** Persist to `sessionStorage` — state survives a reload but not a new tab. */
10
- declare const sessionStorageAdapter: StorageAdapter;
11
- /** Persist into the URL fragment, so the page's link carries its state and
12
- * can be shared or bookmarked. */
13
- declare const urlHashAdapter: StorageAdapter;
14
- /** An in-memory store, discarded on reload. For tests, and for labs that
15
- * should start fresh every time. */
16
- declare function createMemoryAdapter(): StorageAdapter;
17
- /** Persists nothing and reads back nothing. */
18
- declare const noneAdapter: StorageAdapter;
19
-
20
- /** Every mutation a lab store supports: managing trials, saving and
21
- * restoring snapshots, and setting the color mode. */
22
- interface LabStoreActions {
23
- addTrial: (record: Omit<TrialRecord, 'undoStack'>) => void;
24
- removeTrial: (id: string) => void;
25
- updateTrialState: <TS>(id: string, next: TS | ((prev: TS) => TS)) => void;
26
- /** Write one config value. `path` is dotted for a value nested under an
27
- * `f.group`, and the bare key for one at the root. */
28
- updateTrialConfig: (id: string, path: string, value: unknown) => void;
29
- updateTrialView: (id: string, view: unknown) => void;
30
- updateTrialSidebarWidth: (id: string, width: number) => void;
31
- /** Retitle a trial. `null` returns it to its instrument's name. */
32
- setTrialTitle: (id: string, title: string | null) => void;
33
- /** Fold or unfold one of a trial's sections. */
34
- setTrialSectionCollapsed: (id: string, key: string, collapsed: boolean) => void;
35
- updateTrialAnnotations: (id: string, doc: unknown) => void;
36
- updateTrialUndoStack: (id: string, next: TrialRecord['undoStack'] | ((prev: TrialRecord['undoStack']) => TrialRecord['undoStack'])) => void;
37
- setTrialInstrument: (id: string, instrumentName: string) => void;
38
- saveSnapshot: (trialId: string, name: string) => void;
39
- loadSnapshot: (snapshotId: string, trialId: string) => void;
40
- deleteSnapshot: (snapshotId: string) => void;
41
- listSnapshots: (trialId?: string) => SavedSnapshot[];
42
- setMode: (mode: LabMode) => void;
43
- setLabTool: (id: string | null) => void;
44
- setTrialTool: (trialId: string, id: string | null) => void;
45
- setLayout: (layout: Record<string, unknown>) => void;
46
- /** Tear a sidebar section out of its trial. Default target is a workspace tile. */
47
- undockPanel: (trialId: string, sectionId: string, as?: UndockedPanel['as']) => void;
48
- /** Put one section back, or — with no `sectionId` — every panel the trial owns. */
49
- dockPanel: (trialId: string, sectionId?: string) => void;
50
- }
51
- /** A lab's store: its state and its actions. How each instrument's state is
52
- * serialized comes in through `CreateLabStoreOptions.serializers`. */
53
- type LabStore = StoreApi<LabStoreState & LabStoreActions>;
54
- /** Build a lab store, hydrating from storage if anything was saved under the
55
- * same key. Writes back are debounced. */
56
- declare function createLabStore(options: CreateLabStoreOptions): LabStore;
57
-
58
- type LabStoreCtx = {
59
- store: LabStore;
60
- } | null;
61
- /** Context carrying the lab store. Prefer `useLabStore`; this is exported for
62
- * code that needs to read the context without subscribing. */
63
- declare const LabStoreContext: react.Context<LabStoreCtx>;
64
- /** Provides a lab store to its subtree. */
65
- declare function LabStoreProvider({ store, children, }: {
66
- store: LabStore;
67
- children: ReactNode;
68
- }): ReactElement;
69
- /** Subscribe to the whole lab store. Throws outside a `<LabStoreProvider>`. */
70
- declare function useLabStore(): LabStoreState & ReturnType<LabStore['getState']>;
71
- /** Context carrying which trial the subtree belongs to. */
72
- declare const TrialIdContext: react.Context<string | null>;
73
- /** Names the trial its subtree belongs to, so an instrument's hooks can
74
- * find their own record in the store without being passed an id. */
75
- declare function TrialIdProvider({ trialId, children, }: {
76
- trialId: string;
77
- children: ReactNode;
78
- }): ReactElement;
79
- /** The id of the trial this component is inside. Throws outside a
80
- * `<TrialIdProvider>`. */
81
- declare function useTrialId(): string;
82
-
83
- /** Bumped whenever the persisted shape changes; every bump needs a migration. */
84
- declare const CURRENT_DOCUMENT_VERSION = 3;
85
- /** The one key a lab persists under. The `:doc` suffix keeps it out of the
86
- * legacy bucket namespace, where a lab named `a:saves` would otherwise write
87
- * its document over lab `a`'s saves bucket. */
88
- declare function labDocumentKey(storageKey: string): string;
89
- /** Where a document that failed to migrate is set aside, so a bad migration
90
- * loses state loudly rather than silently. */
91
- declare function quarantineKey(storageKey: string): string;
92
-
93
- /** An instrument's own state and config, plus setters. Reads the surrounding
94
- * trial id, so an instrument never has to know which trial it is
95
- * running in. Throws outside a lab store and trial. */
96
- declare function useTrialState<TS = unknown, TC = unknown>(): TrialStateHandle<TS, TC>;
97
-
98
- export { CURRENT_DOCUMENT_VERSION as C, TrialIdContext as T, LabStoreContext as b, LabStoreProvider as c, TrialIdProvider as d, createLabStore as e, createMemoryAdapter as f, localStorageAdapter as g, useLabStore as h, useTrialId as i, useTrialState as j, labDocumentKey as l, noneAdapter as n, quarantineKey as q, sessionStorageAdapter as s, urlHashAdapter as u };
99
- export type { LabStore as L, LabStoreActions as a };