@weasel-js/labkit 1.4.1 → 1.4.3

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 (145) hide show
  1. package/README.md +28 -2
  2. package/dist/_dts/{CanvasStackContext-BIRUVVGH.d.ts → CanvasStackContext-Dde8_1my.d.ts} +1 -1
  3. package/dist/_dts/{PrefsForm.d-CgxUequc.d.ts → PrefsForm.d-DDHFANkc.d.ts} +1 -1
  4. package/dist/_dts/{frac-X7mWgd7y.d.ts → frac-B5mB8LsJ.d.ts} +33 -13
  5. package/dist/_dts/{index-FEa1MudK.d.ts → index-DelFfeV0.d.ts} +99 -31
  6. package/dist/_dts/types-B9_zrHmb.d.ts +166 -0
  7. package/dist/_dts/{types-ChVJJHvk.d.ts → types-BP2OCcpg.d.ts} +41 -4
  8. package/dist/_dts/types-x92Kfeme.d.ts +62 -0
  9. package/dist/_dts/{useTrialState-Cdm13fvl.d.ts → useTrialState-D6Vb3T-g.d.ts} +11 -7
  10. package/dist/canvas/index.d.ts +9 -8
  11. package/dist/canvas/index.js +1 -2
  12. package/dist/chrome/index.d.ts +16 -9
  13. package/dist/chrome/index.js +5 -5
  14. package/dist/{chunk-MSR7EVS6.js → chunk-2RQPGOQF.js} +6 -5
  15. package/dist/chunk-2RQPGOQF.js.map +1 -0
  16. package/dist/{chunk-LJSIUFYD.js → chunk-5TROQVQ2.js} +46 -21
  17. package/dist/chunk-5TROQVQ2.js.map +1 -0
  18. package/dist/{chunk-53AZ2O2N.js → chunk-ASWKLRKJ.js} +92 -45
  19. package/dist/chunk-ASWKLRKJ.js.map +1 -0
  20. package/dist/{chunk-BMQWLKOL.js → chunk-C42T6DJT.js} +94 -83
  21. package/dist/chunk-C42T6DJT.js.map +1 -0
  22. package/dist/chunk-E44SU6XS.js +50 -0
  23. package/dist/chunk-E44SU6XS.js.map +1 -0
  24. package/dist/{chunk-OFCBEZMU.js → chunk-FQMJUVHJ.js} +3 -3
  25. package/dist/{chunk-OFCBEZMU.js.map → chunk-FQMJUVHJ.js.map} +1 -1
  26. package/dist/{chunk-PP7ZWAMV.js → chunk-I6JCVE24.js} +3 -3
  27. package/dist/chunk-I6JCVE24.js.map +1 -0
  28. package/dist/chunk-ISCOFAUO.js +170 -0
  29. package/dist/chunk-ISCOFAUO.js.map +1 -0
  30. package/dist/chunk-MHAGC6VD.js +7320 -0
  31. package/dist/chunk-MHAGC6VD.js.map +1 -0
  32. package/dist/{chunk-7TRFMIWX.js → chunk-QAGAHJKZ.js} +4 -4
  33. package/dist/{chunk-7TRFMIWX.js.map → chunk-QAGAHJKZ.js.map} +1 -1
  34. package/dist/{chunk-D7QIPGEB.js → chunk-RS3HRQWU.js} +62 -60
  35. package/dist/chunk-RS3HRQWU.js.map +1 -0
  36. package/dist/{chunk-RLOXQZW3.js → chunk-TJ7QY3OC.js} +3 -3
  37. package/dist/{chunk-RLOXQZW3.js.map → chunk-TJ7QY3OC.js.map} +1 -1
  38. package/dist/controls/index.d.ts +5 -4
  39. package/dist/controls/index.js +3 -3
  40. package/dist/dragdrop/index.d.ts +9 -8
  41. package/dist/dragdrop/index.js +1 -2
  42. package/dist/index.d.ts +152 -57
  43. package/dist/index.js +315 -200
  44. package/dist/index.js.map +1 -1
  45. package/dist/layers/index.d.ts +27 -11
  46. package/dist/layers/index.js +2 -3
  47. package/dist/loupe/index.d.ts +8 -7
  48. package/dist/loupe/index.js +1 -2
  49. package/dist/passthrough/weasel-canvas.d.ts +1 -3
  50. package/dist/passthrough/weasel-canvas.js +1 -1
  51. package/dist/passthrough/weasel-canvas.js.map +1 -1
  52. package/dist/passthrough/weasel-ui.d.ts +171 -22
  53. package/dist/passthrough/weasel-ui.js +1 -2
  54. package/dist/primitives/index.js +3 -4
  55. package/dist/state/index.d.ts +6 -3
  56. package/dist/state/index.js +3 -2
  57. package/dist/state/index.js.map +1 -1
  58. package/dist/styles.css +21 -4
  59. package/dist/surface/index.js +1 -2
  60. package/dist/ui/layers/index.d.ts +22 -9
  61. package/dist/ui/layers/index.js +1 -2
  62. package/dist/undo/index.d.ts +6 -6
  63. package/package.json +7 -7
  64. package/src/annotations/AnnotationTargets.tsx +6 -1
  65. package/src/annotations/Annotations.overlay.test.tsx +2 -0
  66. package/src/annotations/ExportMenu.test.tsx +16 -0
  67. package/src/annotations/ExportMenu.tsx +0 -5
  68. package/src/annotations/types.ts +4 -1
  69. package/src/canvas/useOrbit.test.ts +7 -3
  70. package/src/canvas/usePanZoom.test.ts +7 -3
  71. package/src/chrome/ChromeRegions.stories.tsx +42 -1
  72. package/src/chrome/builtins.test.ts +4 -0
  73. package/src/chrome/builtins.tsx +18 -0
  74. package/src/chrome/index.ts +1 -1
  75. package/src/chrome/regions/SidebarRegion.tsx +7 -6
  76. package/src/chrome/regions/TitleBarRegion.test.tsx +64 -0
  77. package/src/chrome/regions/TitleBarRegion.tsx +16 -7
  78. package/src/chrome/regions/ToolbarRegion.test.tsx +14 -0
  79. package/src/chrome/regions/ToolbarRegion.tsx +2 -2
  80. package/src/chrome/regions/ViewportRegion.tsx +2 -2
  81. package/src/chrome/regions/regions.test.tsx +45 -1
  82. package/src/chrome/types.ts +21 -3
  83. package/src/config/builder.test.ts +56 -2
  84. package/src/config/builder.ts +73 -13
  85. package/src/config/index.ts +16 -1
  86. package/src/config/path.test.ts +109 -0
  87. package/src/config/path.ts +76 -0
  88. package/src/config/resolve.test.ts +93 -2
  89. package/src/config/resolve.ts +72 -29
  90. package/src/config/types.ts +99 -9
  91. package/src/config/visible.ts +6 -4
  92. package/src/controls/ControlPanel.stories.tsx +38 -4
  93. package/src/controls/ControlPanel.test.tsx +152 -0
  94. package/src/controls/ControlPanel.tsx +134 -40
  95. package/src/dragdrop/DragDropRuntime.tsx +74 -61
  96. package/src/dragdrop/Palette.tsx +4 -3
  97. package/src/dragdrop/dragDrop.test.tsx +35 -12
  98. package/src/index.ts +18 -2
  99. package/src/instrument/serializers.ts +36 -0
  100. package/src/instrument/types.ts +8 -7
  101. package/src/lab/Lab.test.tsx +36 -1
  102. package/src/lab/Lab.tsx +12 -3
  103. package/src/lab/LabContext.ts +5 -1
  104. package/src/lab/LabShell.less +17 -2
  105. package/src/lab/LabShell.test.tsx +8 -0
  106. package/src/lab/LabShell.tsx +3 -1
  107. package/src/layers/AGENTS.md +29 -7
  108. package/src/layers/LayerList.less +16 -0
  109. package/src/layers/LayerList.stories.tsx +66 -0
  110. package/src/layers/LayerList.test.tsx +117 -5
  111. package/src/layers/LayerList.tsx +192 -53
  112. package/src/layers/index.ts +1 -1
  113. package/src/passthrough/weasel-ui.ts +5 -0
  114. package/src/primitives/FloatingPanel.test.tsx +8 -3
  115. package/src/primitives/ZoomControl.less +6 -2
  116. package/src/primitives/ZoomControl.tsx +1 -0
  117. package/src/state/helpers.ts +1 -1
  118. package/src/state/store.test.ts +233 -1
  119. package/src/state/store.ts +57 -24
  120. package/src/state/types.ts +44 -3
  121. package/src/state/useTrialState.ts +1 -1
  122. package/src/trial/Trial.config.test.tsx +44 -0
  123. package/src/trial/Trial.less +4 -0
  124. package/src/trial/Trial.targets.test.tsx +96 -0
  125. package/src/trial/Trial.test.tsx +171 -14
  126. package/src/trial/Trial.tsx +7 -4
  127. package/src/trial/TrialChrome.tsx +32 -7
  128. package/src/trial/TrialTitleBar.tsx +7 -3
  129. package/src/trial/index.ts +1 -0
  130. package/src/trial/trialOps.test.ts +49 -1
  131. package/src/trial/trialOps.ts +24 -6
  132. package/dist/_dts/types-lg4TSCb2.d.ts +0 -164
  133. package/dist/_dts/weasel-canvas-FJTFi3ZZ.d.ts +0 -5041
  134. package/dist/chunk-53AZ2O2N.js.map +0 -1
  135. package/dist/chunk-BMQWLKOL.js.map +0 -1
  136. package/dist/chunk-D7QIPGEB.js.map +0 -1
  137. package/dist/chunk-DG2IFMLA.js +0 -29970
  138. package/dist/chunk-DG2IFMLA.js.map +0 -1
  139. package/dist/chunk-J6SBFZYT.js +0 -7121
  140. package/dist/chunk-J6SBFZYT.js.map +0 -1
  141. package/dist/chunk-LJSIUFYD.js.map +0 -1
  142. package/dist/chunk-MIT3ISW4.js +0 -100
  143. package/dist/chunk-MIT3ISW4.js.map +0 -1
  144. package/dist/chunk-MSR7EVS6.js.map +0 -1
  145. package/dist/chunk-PP7ZWAMV.js.map +0 -1
package/README.md CHANGED
@@ -13,10 +13,16 @@ algorithm and would rather not build the page around it.
13
13
  ## Install
14
14
 
15
15
  ```bash
16
- npm i @weasel-js/labkit
16
+ npm i @weasel-js/labkit @weasel-js/core
17
17
  ```
18
18
 
19
- React 19 is a peer dependency (`react` and `react-dom`, `^19.0.0`). One
19
+ `@weasel-js/core` is a peer dependency, pinned to the matching version. labkit
20
+ does not ship its own copy: core keeps its content handlers, paint kinds, shape
21
+ painters and markers in module-global registries, and two copies means anything
22
+ registered through one is invisible to the other — a blank canvas with no error.
23
+ Installing it alongside is what guarantees there is exactly one.
24
+
25
+ React 19 is a peer dependency too (`react` and `react-dom`, `^19.0.0`). One
20
26
  stylesheet import covers everything labkit draws, including the theme tokens and
21
27
  the `@weasel-js/ui` components it passes through:
22
28
 
@@ -70,6 +76,18 @@ renders the settings panel from it. `render` returns the instrument's own DOM;
70
76
  returning `null` alongside `canvas` means the canvas layers are the whole
71
77
  picture. `storage` keeps open trials and their state across reloads.
72
78
 
79
+ A value is addressed by its path: `bins` above sits at `config.bins` and is
80
+ written as `setConfig('bins', 40)`. `f.group` nests one —
81
+ `paper: f.group({ width: f.number(210) })` puts the value at
82
+ `config.paper.width` and addresses it as `'paper.width'`, in `setConfig`, in a
83
+ control override, and anywhere else a path is taken. `.section('Advanced')` is
84
+ the other thing and only looks like it: it puts a heading over sibling rows and
85
+ leaves their paths alone.
86
+
87
+ A stored config missing something its schema has since gained — a whole nested
88
+ group included — is filled from the instrument's defaults as it loads, so
89
+ growing a schema never strands a saved trial or a snapshot.
90
+
73
91
  ## Capabilities
74
92
 
75
93
  A capability is a field on the instrument. Declaring it is what makes the trial
@@ -120,6 +138,14 @@ changed draws dashed and reports `isStale`. `meaning.statuses` is the optional
120
138
  vocabulary a mark can be labelled with — a status carries its own color, which
121
139
  the mark on the canvas follows.
122
140
 
141
+ `targets` is declared once per instrument and called once per trial, with the
142
+ asking trial as its third argument: `targets(state, config, trial)`. `trial.id`
143
+ is the id `useTileId` scopes a surface tile under, so a consumer holding its own
144
+ per-trial refs keys them by it and hands back the ones this trial owns; a
145
+ module-level ref like the one above is one ref shared by every trial of the
146
+ instrument, which only shows once a second trial is open. `trial.view` is that
147
+ trial's camera.
148
+
123
149
  Marks live in the trial's record and survive a reload. An instrument that would
124
150
  rather keep them in a format it already owns declares `annotations.storage` with
125
151
  a `load`/`save` pair, and labkit never writes its own slot.
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { RefObject } from 'react';
3
- import { V as ViewTransform, W as WorldFrame } from './frac-X7mWgd7y.js';
3
+ import { V as ViewTransform, W as WorldFrame } from './frac-B5mB8LsJ.js';
4
4
 
5
5
  /** One layer of a canvas stack: its id, whether it is currently shown, and how
6
6
  * it paints itself. */
@@ -1,5 +1,5 @@
1
1
  import { ReactNode } from 'react';
2
- import { T as ToolPrefLeaf } from './weasel-canvas-FJTFi3ZZ.js';
2
+ import { ToolPrefLeaf } from '@weasel-js/core';
3
3
 
4
4
  /** What a {@link PrefRenderer} is given for the leaf it is rendering. */
5
5
  interface PrefRenderContext {
@@ -1,8 +1,9 @@
1
1
  import { ComponentType, ReactNode, RefObject } from 'react';
2
- import { R as ResolvedConfig, C as ConfigField, a as ConfigSchema } from './types-lg4TSCb2.js';
3
- import { c as SavedSnapshot } from './types-ChVJJHvk.js';
2
+ import { R as ResolvedConfig, a as ConfigSchema, C as ConfigPath } from './types-B9_zrHmb.js';
3
+ import { C as ConfigField } from './types-x92Kfeme.js';
4
+ import { c as SavedSnapshot, i as TrialInfo } from './types-BP2OCcpg.js';
4
5
  import { J as JobHandle, a as JobCapability } from './types-DJ79Tg5J.js';
5
- import { S as Scene } from './weasel-canvas-FJTFi3ZZ.js';
6
+ import { Scene } from '@weasel-js/core';
6
7
 
7
8
  /** How an instrument's world sits on the canvas, independent of the camera.
8
9
  * The defaults reproduce the convention labkit shipped before this existed:
@@ -53,7 +54,10 @@ interface ToolbarItem {
53
54
  pressed?: boolean;
54
55
  /** Render the label beside the glyph rather than only in the tooltip. */
55
56
  showLabel?: boolean;
56
- onActivate: () => void;
57
+ /** Handed the trial's chrome context, so a contribution declared from the
58
+ * lab can reach `ctx.saveSnapshot()` and the rest without the `render`
59
+ * escape. A zero-argument handler stays valid. */
60
+ onActivate: (ctx: TrialChromeContext) => void;
57
61
  }
58
62
  /** A selectable tool in the palette region. */
59
63
  interface ToolItem {
@@ -65,7 +69,7 @@ interface ToolItem {
65
69
  /** A titled block in the sidebar. */
66
70
  interface SidebarSection {
67
71
  title: string;
68
- /** Starts collapsed. The open/closed state itself is the region's. */
72
+ /** Starts collapsed, until the trial remembers a fold of its own. */
69
73
  defaultCollapsed?: boolean;
70
74
  /** Offer the tear-out control. On by default; a section that only makes
71
75
  * sense beside its trial sets this false. */
@@ -79,7 +83,8 @@ interface ViewportControl {
79
83
  icon: IconComponent;
80
84
  label: string;
81
85
  disabled?: boolean;
82
- onActivate: () => void;
86
+ /** Handed the trial's chrome context, as `ToolbarItem.onActivate` is. */
87
+ onActivate: (ctx: TrialChromeContext) => void;
83
88
  }
84
89
  /** A readout in the status bar. */
85
90
  interface StatusReadout {
@@ -139,6 +144,12 @@ type TrialContribution = (ContributionBase & {
139
144
  interface TrialChromeContext {
140
145
  trialId: string;
141
146
  instrumentName: string;
147
+ /** What the title bar reads, which is the instrument's name until something
148
+ * calls `setTitle`. */
149
+ title: string;
150
+ /** Retitle this trial; `null` restores the instrument name. Persisted with
151
+ * the trial, so a title survives a reload. */
152
+ setTitle: (title: string | null) => void;
142
153
  isLastTrial: boolean;
143
154
  /** Null when the trial holds a view that is not the 2D one. */
144
155
  zoom: number | null;
@@ -159,6 +170,13 @@ interface TrialChromeContext {
159
170
  configFields: ConfigField[];
160
171
  config: unknown;
161
172
  setConfig: (key: string, value: unknown) => void;
173
+ /** Which of this trial's collapsible sections are folded. A sidebar
174
+ * section's key is its contribution id; a section *inside* a contribution —
175
+ * a control panel's property group — is keyed `<contribution id>/<label>`.
176
+ * A section absent here is at its own default. Persisted with the trial. */
177
+ collapsedSections: Readonly<Record<string, boolean>>;
178
+ /** Fold or unfold one section, keyed as `collapsedSections` is. */
179
+ setSectionCollapsed: (key: string, collapsed: boolean) => void;
162
180
  /** Section ids this trial currently has torn out of its sidebar. */
163
181
  undockedPanels: readonly string[];
164
182
  /** Tear a sidebar section out into the workspace. */
@@ -282,12 +300,12 @@ interface RenderContext<TS = unknown, TC = unknown> {
282
300
  state: TS;
283
301
  config: TC;
284
302
  setState: (next: TS | ((prev: TS) => TS)) => void;
285
- setConfig: (key: keyof TC, value: unknown) => void;
286
- trial: {
287
- id: string;
288
- /** The trial's view, in whatever shape this instrument chose. labkit persists
289
- * it and restores it on Reset without ever reading into it. */
290
- view: unknown;
303
+ /** Write one config value, by dotted path — `'grid.size'` for a leaf under
304
+ * an `f.group`, `'cellSize'` for one at the root. */
305
+ setConfig: (path: ConfigPath<TC>, value: unknown) => void;
306
+ /** labkit persists `view` and restores it on Reset without ever reading
307
+ * into it. */
308
+ trial: TrialInfo & {
291
309
  setView: (next: unknown) => void;
292
310
  /** 2D convenience over `view`. Reads 1 and writes nothing when the trial holds
293
311
  * a view that is not the 2D one. */
@@ -641,7 +659,9 @@ interface AnnotationStorage {
641
659
  /** Declares that an instrument accepts marks: which regions take them,
642
660
  * optionally what a mark is allowed to mean, and optionally where they live. */
643
661
  interface AnnotationsCapability<TS = unknown, TC = unknown> {
644
- targets: (state: TS, config: TC) => readonly AnnotationTarget[];
662
+ /** `trial` is which trial is asking: a declaration made once per instrument
663
+ * is called once per trial, and its targets are that trial's own. */
664
+ targets: (state: TS, config: TC, trial: TrialInfo) => readonly AnnotationTarget[];
645
665
  meaning?: AnnotationMeaning;
646
666
  storage?: AnnotationStorage;
647
667
  /** Fires after every finished export, labkit's own chrome included. A
@@ -1,20 +1,45 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode } from 'react';
3
- import { R as ResolvedConfig, C as ConfigField, c as ControlRenderer } from './types-lg4TSCb2.js';
3
+ import { R as ResolvedConfig, c as ControlRenderer } from './types-B9_zrHmb.js';
4
+ import { C as ConfigField } from './types-x92Kfeme.js';
4
5
 
6
+ /**
7
+ * How much room a container gives its rows — gaps, padding, and field height,
8
+ * moved together. Set on any container in the family; it reaches every
9
+ * descendant, so an inner group can differ from the panel around it.
10
+ */
11
+ type PropertyDensity = 'tight' | 'normal' | 'roomy';
12
+ /** Where an inline row's label and control sit on the row's cross axis. Set on
13
+ * a container to line a whole column of them up. */
14
+ type PropertyAlign = 'start' | 'center' | 'end' | 'baseline';
15
+ /** Metric props every container in the family takes. Both reach descendants,
16
+ * so the nearest container that states one wins. */
17
+ interface PropertyMetricProps {
18
+ /** Room the rows get. Unset inherits from an outer container, else `normal`. */
19
+ density?: PropertyDensity;
20
+ /**
21
+ * Cross-axis alignment of an inline row's label and control. `baseline` sits
22
+ * the control on the label's first-line baseline, which is what lines a
23
+ * column of swatches up against labels of different heights. Unset keeps each
24
+ * variant's own alignment — a color row centers its label and swatch and
25
+ * sinks the pair to the row's bottom edge, which keeps a paired alpha track
26
+ * level with its neighbour.
27
+ */
28
+ align?: PropertyAlign;
29
+ }
5
30
  /** Props for `<PropertyPanel>`. */
6
- interface PropertyPanelProps {
31
+ interface PropertyPanelProps extends PropertyMetricProps {
7
32
  title?: ReactNode;
8
33
  children: ReactNode;
9
34
  className?: string;
10
35
  }
11
36
  /** A titled panel holding property rows — the sidebar container the rest of
12
37
  * this module's components fill. */
13
- declare function PropertyPanel({ title, children, className }: PropertyPanelProps): react_jsx_runtime.JSX.Element;
38
+ declare function PropertyPanel({ title, children, className, density, align, }: PropertyPanelProps): react_jsx_runtime.JSX.Element;
14
39
  /** How a property list packs its rows into two columns. */
15
- type PropertyListPack = 'auto-color' | 'pairs';
40
+ type PropertyListPack = 'auto-color' | 'pairs' | 'one-up';
16
41
  /** Props for `<PropertyList>`. */
17
- interface PropertyListProps {
42
+ interface PropertyListProps extends PropertyMetricProps {
18
43
  children: ReactNode;
19
44
  className?: string;
20
45
  /**
@@ -24,6 +49,8 @@ interface PropertyListProps {
24
49
  * - `'pairs'`: every row auto-places into the 2-column grid two-per-row.
25
50
  * Headers and subpanels still span the full width; wrap any other
26
51
  * full-width child in `<PropertySpan>`. Right for dense effect bodies.
52
+ * - `'one-up'`: every row spans the full width, color rows included. Right
53
+ * for a palette — a column of swatches read as a group.
27
54
  */
28
55
  pack?: PropertyListPack;
29
56
  }
@@ -31,7 +58,7 @@ interface PropertyListProps {
31
58
  * Grid container for PropertyRows. Use standalone for chrome-less layouts, or
32
59
  * nest inside <PropertyPanel/> for the standard glass card.
33
60
  */
34
- declare function PropertyList({ children, className, pack }: PropertyListProps): react_jsx_runtime.JSX.Element;
61
+ declare function PropertyList({ children, className, pack, density, align, }: PropertyListProps): react_jsx_runtime.JSX.Element;
35
62
  /** Props for `<PropertySpan>`. */
36
63
  interface PropertySpanProps {
37
64
  children: ReactNode;
@@ -49,7 +76,7 @@ type PropertyRowVariant = 'default' | 'color' | 'checkbox';
49
76
  /** Whether a row's label sits above its control or beside it. */
50
77
  type PropertyRowLayout = 'block' | 'inline';
51
78
  /** Props for `<PropertyRow>`. */
52
- interface PropertyRowProps {
79
+ interface PropertyRowProps extends PropertyMetricProps {
53
80
  label: ReactNode;
54
81
  /** Right-aligned readout shown next to the label (e.g. current value). */
55
82
  readout?: ReactNode;
@@ -60,9 +87,9 @@ interface PropertyRowProps {
60
87
  description?: string;
61
88
  variant?: PropertyRowVariant;
62
89
  /**
63
- * Label position relative to the control. "block" (default) stacks
64
- * label above control; "inline" places the label to the left.
65
- * Color and checkbox variants are always inline by their own nature.
90
+ * Label position relative to the control. Unset takes the variant's own
91
+ * orientation: `block` — label above control — for the default variant, and
92
+ * `inline` for the color and checkbox variants, which read as a row.
66
93
  */
67
94
  layout?: PropertyRowLayout;
68
95
  /**
@@ -77,15 +104,26 @@ interface PropertyRowProps {
77
104
  }
78
105
  /** The label-plus-control frame every typed row below is built from. Use it
79
106
  * directly for a control this module does not cover. */
80
- declare function PropertyRow({ label, readout, description, variant, layout, span, children, htmlFor, className, }: PropertyRowProps): react_jsx_runtime.JSX.Element;
107
+ declare function PropertyRow({ label, readout, description, variant, layout, span, children, htmlFor, className, density, align, }: PropertyRowProps): react_jsx_runtime.JSX.Element;
81
108
  /** Props for `<SliderRow>`. */
82
- interface SliderRowProps {
109
+ interface SliderRowProps extends PropertyMetricProps {
83
110
  label: ReactNode;
84
111
  value: number;
85
112
  min: number;
86
113
  max: number;
87
114
  step?: number;
115
+ /**
116
+ * The committed value. Given `onInput` as well, it fires once a drag ends
117
+ * or a typed readout is accepted; on its own it fires on every move, which
118
+ * is what a row with one callback has always done.
119
+ */
88
120
  onChange: (next: number) => void;
121
+ /**
122
+ * The live value, fired continuously through a drag. Pass it alongside
123
+ * `onChange` when the write is expensive — cheap state here, the costly
124
+ * work there. Mirrors `<Slider>`'s `onInput` / `onChange` pair.
125
+ */
126
+ onInput?: (next: number) => void;
89
127
  /** Override how the value is rendered next to the label. Defaults to `value.toString()`. */
90
128
  format?: (value: number) => ReactNode;
91
129
  /**
@@ -101,9 +139,9 @@ interface SliderRowProps {
101
139
  }
102
140
  /** A bounded number edited by dragging, with a live readout whose precision
103
141
  * follows `step`. */
104
- declare function SliderRow({ label, value, min, max, step, onChange, format, unit, layout, description, span, }: SliderRowProps): react_jsx_runtime.JSX.Element;
142
+ declare function SliderRow({ label, value, min, max, step, onChange, onInput, format, unit, layout, description, span, density, align, }: SliderRowProps): react_jsx_runtime.JSX.Element;
105
143
  /** Props for `<ColorRow>`. */
106
- interface ColorRowProps {
144
+ interface ColorRowProps extends PropertyMetricProps {
107
145
  label: ReactNode;
108
146
  value: string;
109
147
  onChange: (next: string) => void;
@@ -115,25 +153,29 @@ interface ColorRowProps {
115
153
  * Use when the color's consumer drops alpha so the affordance reads dead.
116
154
  */
117
155
  alphaDisabled?: boolean;
156
+ /** Label beside the swatch (default) or above it. */
157
+ layout?: PropertyRowLayout;
118
158
  description?: string;
119
159
  /** Take the full width of the enclosing grid — see `<PropertyRow span>`. */
120
160
  span?: boolean;
121
161
  }
122
162
  /** A color swatch, optionally with an alpha slider beneath it. */
123
- declare function ColorRow({ label, value, onChange, alpha, onAlphaChange, alphaDisabled, description, span, }: ColorRowProps): react_jsx_runtime.JSX.Element;
163
+ declare function ColorRow({ label, value, onChange, alpha, onAlphaChange, alphaDisabled, layout, description, span, density, align, }: ColorRowProps): react_jsx_runtime.JSX.Element;
124
164
  /** Props for `<CheckboxRow>`. */
125
- interface CheckboxRowProps {
165
+ interface CheckboxRowProps extends PropertyMetricProps {
126
166
  label: ReactNode;
127
167
  value: boolean;
128
168
  onChange: (next: boolean) => void;
169
+ /** Label beside the box (default) or above it. */
170
+ layout?: PropertyRowLayout;
129
171
  description?: string;
130
172
  /** Take the full width of the enclosing grid — see `<PropertyRow span>`. */
131
173
  span?: boolean;
132
174
  }
133
175
  /** A boolean checkbox. */
134
- declare function CheckboxRow({ label, value, onChange, span, description }: CheckboxRowProps): react_jsx_runtime.JSX.Element;
176
+ declare function CheckboxRow({ label, value, onChange, layout, span, description, density, align, }: CheckboxRowProps): react_jsx_runtime.JSX.Element;
135
177
  /** Props for `<TextRow>`. */
136
- interface TextRowProps {
178
+ interface TextRowProps extends PropertyMetricProps {
137
179
  label: ReactNode;
138
180
  value: string;
139
181
  onChange: (next: string) => void;
@@ -145,9 +187,9 @@ interface TextRowProps {
145
187
  span?: boolean;
146
188
  }
147
189
  /** A single-line text input. */
148
- declare function TextRow({ label, value, onChange, placeholder, maxLength, layout, description, span, }: TextRowProps): react_jsx_runtime.JSX.Element;
190
+ declare function TextRow({ label, value, onChange, placeholder, maxLength, layout, description, span, density, align, }: TextRowProps): react_jsx_runtime.JSX.Element;
149
191
  /** Props for `<NumberRow>`. */
150
- interface NumberRowProps {
192
+ interface NumberRowProps extends PropertyMetricProps {
151
193
  label: ReactNode;
152
194
  value: number;
153
195
  onChange: (next: number) => void;
@@ -168,13 +210,13 @@ interface NumberRowProps {
168
210
  }
169
211
  /** A number typed directly. Reach for `<SliderRow>` when the range matters
170
212
  * more than the exact value. */
171
- declare function NumberRow({ label, value, onChange, min, max, step, placeholder, unit, layout, description, span, }: NumberRowProps): react_jsx_runtime.JSX.Element;
213
+ declare function NumberRow({ label, value, onChange, min, max, step, placeholder, unit, layout, description, span, density, align, }: NumberRowProps): react_jsx_runtime.JSX.Element;
172
214
  interface PropertyOption<T extends string> {
173
215
  value: T;
174
216
  label: ReactNode;
175
217
  }
176
218
  /** Props for `<SelectRow>`. */
177
- interface SelectRowProps<T extends string> {
219
+ interface SelectRowProps<T extends string> extends PropertyMetricProps {
178
220
  label: ReactNode;
179
221
  value: T;
180
222
  options: ReadonlyArray<PropertyOption<T>>;
@@ -186,9 +228,9 @@ interface SelectRowProps<T extends string> {
186
228
  }
187
229
  /** A dropdown over a fixed set of choices. Prefer `<ToggleRow>` when there
188
230
  * are only two or three and they should all stay visible. */
189
- declare function SelectRow<T extends string>({ label, value, options, onChange, layout, description, span, }: SelectRowProps<T>): react_jsx_runtime.JSX.Element;
231
+ declare function SelectRow<T extends string>({ label, value, options, onChange, layout, description, span, density, align, }: SelectRowProps<T>): react_jsx_runtime.JSX.Element;
190
232
  /** Props for `<ToggleRow>`. */
191
- interface ToggleRowProps<T extends string> {
233
+ interface ToggleRowProps<T extends string> extends PropertyMetricProps {
192
234
  label: ReactNode;
193
235
  value: T;
194
236
  options: ReadonlyArray<PropertyOption<T>>;
@@ -200,7 +242,7 @@ interface ToggleRowProps<T extends string> {
200
242
  }
201
243
  /** A segmented control: the same choice as a select, with every option
202
244
  * visible at once. */
203
- declare function ToggleRow<T extends string>({ label, value, options, onChange, layout, description, span, }: ToggleRowProps<T>): react_jsx_runtime.JSX.Element;
245
+ declare function ToggleRow<T extends string>({ label, value, options, onChange, layout, description, span, density, align, }: ToggleRowProps<T>): react_jsx_runtime.JSX.Element;
204
246
 
205
247
  /** How a panel packs its rows into the two-column property grid.
206
248
  * - `'auto'`: narrow controls (numbers, checkboxes, dropdowns, colours) sit
@@ -217,7 +259,9 @@ interface ControlPanelProps<TC extends Record<string, unknown>> {
217
259
  /** @deprecated Pass `schema`. A field list is adapted into one internally. */
218
260
  fields?: ConfigField[];
219
261
  config: TC;
220
- setConfig: (key: keyof TC, value: unknown) => void;
262
+ /** Writes one value. The path is dotted for a leaf inside an `f.group`, and
263
+ * the bare key for one at the root. */
264
+ setConfig: (path: string, value: unknown) => void;
221
265
  /**
222
266
  * Control overrides and app-defined kinds, PrefsForm-style. Keys are config
223
267
  * paths (checked first) or leaf kinds. A renderer returning `null` collapses
@@ -226,9 +270,33 @@ interface ControlPanelProps<TC extends Record<string, unknown>> {
226
270
  renderers?: Record<string, ControlRenderer>;
227
271
  /** How rows pack into the two-column grid. Defaults to `'pairs'`. */
228
272
  pack?: ControlPack;
229
- /** Where a row's label sits relative to its control. Defaults to `'block'`
230
- * — above it, which is what leaves a paired row room for its value. */
273
+ /**
274
+ * Where a row's label sits relative to its control. Unset takes each row's
275
+ * own orientation — `block` (label above, which is what leaves a paired row
276
+ * room for its value) for most, `inline` for colors and checkboxes.
277
+ */
231
278
  layout?: PropertyRowLayout;
279
+ /** Room the rows get. Passed straight to the property list. */
280
+ density?: PropertyDensity;
281
+ /** Cross-axis alignment of an inline row's label and control. */
282
+ align?: PropertyAlign;
283
+ /**
284
+ * Fold each section away behind a twisty, starting `'open'` or `'closed'`.
285
+ * Unset draws the heading alone, as the panel always has — unless a section
286
+ * in the schema declares how it opens, which makes that section foldable on
287
+ * its own and outranks this for that one section.
288
+ */
289
+ collapse?: 'open' | 'closed';
290
+ /**
291
+ * Which sections are folded, keyed as `onCollapse` reports them. Given, the
292
+ * panel keeps no state of its own: every toggle arrives at `onCollapse`
293
+ * instead, which is where a lab that remembers a trial's sections writes
294
+ * them.
295
+ */
296
+ collapsed?: Readonly<Record<string, boolean>>;
297
+ /** A fold moved. The key is a section's label — prefixed by its group's
298
+ * dotted path when the section sits inside one — or a group's own path. */
299
+ onCollapse?: (key: string, collapsed: boolean) => void;
232
300
  /** Draw leaves marked `hidden`. */
233
301
  showHidden?: boolean;
234
302
  className?: string;
@@ -236,7 +304,7 @@ interface ControlPanelProps<TC extends Record<string, unknown>> {
236
304
  /** Render an instrument's config schema as a stack of controls, each writing
237
305
  * back through `setConfig`. Built on the property rows, so a lab's controls
238
306
  * are the same aligned, themed rows the rest of the kit uses. */
239
- declare function ControlPanel<TC extends Record<string, unknown>>({ schema, fields, config, setConfig, renderers, pack, layout, showHidden, className, }: ControlPanelProps<TC>): react_jsx_runtime.JSX.Element;
307
+ declare function ControlPanel<TC extends Record<string, unknown>>({ schema, fields, config, setConfig, renderers, pack, layout, density, align, collapse, collapsed, onCollapse, showHidden, className, }: ControlPanelProps<TC>): react_jsx_runtime.JSX.Element;
240
308
 
241
- export { CheckboxRow as C, NumberRow as N, PropertyList as P, SelectRow as S, TextRow as T, ColorRow as b, ControlPanel as d, PropertyPanel as i, PropertyRow as k, PropertySpan as o, SliderRow as r, ToggleRow as u };
242
- export type { CheckboxRowProps as a, ColorRowProps as c, NumberRowProps as e, PropertyListPack as f, PropertyListProps as g, PropertyOption as h, PropertyPanelProps as j, PropertyRowLayout as l, PropertyRowProps as m, PropertyRowVariant as n, PropertySpanProps as p, SelectRowProps as q, SliderRowProps as s, TextRowProps as t, ToggleRowProps as v };
309
+ export { CheckboxRow as C, NumberRow as N, SelectRow as S, TextRow as T, ColorRow as c, ControlPanel as e, PropertyList as g, PropertyPanel as j, PropertyRow as l, PropertySpan as p, SliderRow as s, ToggleRow as v };
310
+ export type { PropertyMetricProps as P, PropertyListPack as a, CheckboxRowProps as b, ColorRowProps as d, NumberRowProps as f, PropertyListProps as h, PropertyOption as i, PropertyPanelProps as k, PropertyRowLayout as m, PropertyRowProps as n, PropertyRowVariant as o, PropertySpanProps as q, SelectRowProps as r, SliderRowProps as t, TextRowProps as u, ToggleRowProps as w };
@@ -0,0 +1,166 @@
1
+ import { ToolPrefLeaf, ToolPrefGroup } from '@weasel-js/core';
2
+ import { P as PrefRenderer } from './PrefsForm.d-DDHFANkc.js';
3
+
4
+ /**
5
+ * Renders the control cell for one config leaf. Identical to weasel-ui's
6
+ * `PrefRenderer` on purpose — aliased rather than redeclared so the two
7
+ * cannot drift.
8
+ */
9
+ type ControlRenderer = PrefRenderer;
10
+ /** One labeled choice in an `enum` leaf. */
11
+ interface ConfigOption {
12
+ value: string;
13
+ label: string;
14
+ }
15
+ /** Everything a leaf can carry beyond its kind and default. A flat union of
16
+ * every `Pref*` leaf's extras, plus labkit's own `debounceMs`. */
17
+ interface Annotations {
18
+ name?: string;
19
+ description?: string;
20
+ hidden?: boolean;
21
+ block?: boolean;
22
+ pair?: string;
23
+ min?: number;
24
+ max?: number;
25
+ step?: number;
26
+ suffix?: string;
27
+ control?: string;
28
+ options?: readonly ConfigOption[];
29
+ placeholder?: string;
30
+ maxLength?: number;
31
+ /** Milliseconds to debounce a string leaf's live writes. Default 150. */
32
+ debounceMs?: number;
33
+ }
34
+ /** What a rule contributes. `kind` is honored only while it is still unset,
35
+ * which is what makes `f.value` claimable and a kinded factory final. */
36
+ type LeafPatch = Annotations & {
37
+ kind?: string;
38
+ };
39
+ /** What a rule is given for the leaf it is deciding about. */
40
+ interface ConfigRuleContext {
41
+ /** The leaf's own key — its last path segment, and what a label is titled
42
+ * from. */
43
+ key: string;
44
+ /** Dotted path within the schema, which is the path the value is written
45
+ * at. Equal to `key` for a leaf sitting at the root. */
46
+ path: string;
47
+ /** The leaf's default value. A rule may read it but never change it. */
48
+ default: unknown;
49
+ /** What earlier rules and the author's own annotations have settled. */
50
+ leaf: Readonly<LeafPatch>;
51
+ }
52
+ /**
53
+ * Decides part of how a leaf is presented. Returns a patch, or null to
54
+ * abstain. Merging is gap-filling: a property already settled is never
55
+ * overwritten, so the author's annotations beat every rule and a consumer's
56
+ * rules beat labkit's built-ins.
57
+ */
58
+ type ConfigRule = (ctx: ConfigRuleContext) => LeafPatch | null;
59
+ /** A presentational bucket of nodes, rendered under one heading. Buckets
60
+ * siblings: a section never nests a value, which is what separates it from
61
+ * `f.group`. */
62
+ interface SectionSpec {
63
+ /** Dotted path of the group whose children this buckets. `''` is the root,
64
+ * which is where every section of a flat schema sits. */
65
+ at: string;
66
+ label: string;
67
+ /** Full dotted paths, so a section under a group names its children the way
68
+ * everything else does. */
69
+ paths: readonly string[];
70
+ /** Whether the section opens folded. Set, the panel folds this section
71
+ * whether or not it was given a panel-wide `collapse`; a fold the reader
72
+ * has since toggled outranks it. */
73
+ collapsed?: boolean;
74
+ }
75
+ /** A schema resolved against a set of rules: the vocabulary weasel-ui renders,
76
+ * plus the three things labkit keeps on the side because `PrefLeaf` has no
77
+ * field for them. */
78
+ interface ResolvedConfig {
79
+ /** The schema as a `PrefGroup` tree: an `f.group` is a nested group, so a
80
+ * leaf's dotted path within it is the path its value is written at. */
81
+ group: ToolPrefGroup;
82
+ sections: readonly SectionSpec[];
83
+ showIf: ReadonlyMap<string, (config: Record<string, unknown>) => boolean>;
84
+ /** Node-level `.render` overrides, keyed by path. */
85
+ renderers: Readonly<Record<string, ControlRenderer>>;
86
+ }
87
+ /** How a node names the section it belongs to: the heading, and how that
88
+ * section opens. Every node in one section repeats the heading; only one of
89
+ * them has to say `collapsed`. */
90
+ interface SectionOption {
91
+ label: string;
92
+ collapsed?: boolean;
93
+ }
94
+ /** Per-node extras that do not belong on a `PrefLeaf`. */
95
+ interface NodeOptions extends BranchOptions {
96
+ render?: ControlRenderer;
97
+ validate?: (leaf: ToolPrefLeaf) => string[];
98
+ }
99
+ /** What a branch can say about itself, beyond its children. */
100
+ interface BranchOptions {
101
+ section?: SectionOption;
102
+ /** Show this node only while the predicate holds. On a group it hides the
103
+ * whole subtree; the values stay in config either way. */
104
+ showIf?: (config: Record<string, unknown>) => boolean;
105
+ }
106
+ /** A group's own annotations: what it is called and, optionally, why. */
107
+ interface BranchAnnotations {
108
+ name?: string;
109
+ description?: string;
110
+ }
111
+ /** The builder's leaf: a kind (or null, to be decided by rules), a default,
112
+ * an annotation bag, and the extras above. */
113
+ interface ConfigNode<T = unknown> {
114
+ readonly kind: string | null;
115
+ readonly default: T;
116
+ readonly annotations: Readonly<Annotations>;
117
+ readonly options: Readonly<NodeOptions>;
118
+ }
119
+ /** The builder's branch: named children, nested as deeply as the schema
120
+ * wants. A branch nests the value too — `grid: f.group({ size })` puts the
121
+ * value at `grid.size`, where `.section('Grid')` would have left it at
122
+ * `size`. */
123
+ interface ConfigBranch<S extends ConfigShape = ConfigShape> {
124
+ readonly children: S;
125
+ readonly annotations: Readonly<BranchAnnotations>;
126
+ readonly options: Readonly<BranchOptions>;
127
+ }
128
+ /** Either half of a schema tree. */
129
+ type ConfigEntry = ConfigNode | ConfigBranch;
130
+ /** The children of a schema or a group. */
131
+ type ConfigShape = {
132
+ readonly [key: string]: ConfigEntry;
133
+ };
134
+ /** The value type a node produces. */
135
+ type NodeValue<N> = N extends ConfigNode<infer T> ? T : never;
136
+ /** The value type a schema entry produces — a leaf's own, or a branch's
137
+ * nested record. */
138
+ type EntryValue<E> = E extends ConfigBranch<infer S> ? InferConfig<S> : E extends ConfigNode<infer T> ? T : never;
139
+ /** The config type a builder shape produces. */
140
+ type InferConfig<S> = {
141
+ [K in keyof S]: EntryValue<S[K]>;
142
+ };
143
+ /** Whether a config value has children a path can descend into. Arrays and
144
+ * functions are values, not branches. */
145
+ type Branching<V> = V extends readonly unknown[] ? false : V extends (...args: never[]) => unknown ? false : V extends object ? true : false;
146
+ /**
147
+ * Every dotted path a config offers, a group's own path included. A config
148
+ * whose shape is not known — `unknown`, or a bare record — gives `string`,
149
+ * which is what keeps a generic instrument writable.
150
+ */
151
+ type ConfigPath<T> = unknown extends T ? string : T extends object ? {
152
+ [K in keyof T & string]: Branching<T[K]> extends true ? K | `${K}.${ConfigPath<T[K]>}` : K;
153
+ }[keyof T & string] : never;
154
+ /** The type at a dotted path, or `unknown` where the path is not one the
155
+ * config's type spells out. */
156
+ type ValueAtPath<T, P extends string> = unknown extends T ? unknown : P extends `${infer Head}.${infer Rest}` ? Head extends keyof T ? ValueAtPath<T[Head], Rest> : unknown : P extends keyof T ? T[P] : unknown;
157
+ /** An instrument's config, declared once. */
158
+ interface ConfigSchema<TC> {
159
+ readonly nodes: ConfigShape;
160
+ /** The starting config — what `defaultConfig()` would have returned. */
161
+ defaults(): TC;
162
+ }
163
+ /** The config type behind a schema: `ConfigOf<typeof sceneConfig>`. */
164
+ type ConfigOf<S> = S extends ConfigSchema<infer TC> ? TC : never;
165
+
166
+ export type { Annotations as A, BranchAnnotations as B, ConfigPath as C, EntryValue as E, InferConfig as I, LeafPatch as L, NodeOptions as N, ResolvedConfig as R, SectionSpec as S, ValueAtPath as V, ConfigSchema as a, ConfigNode as b, ControlRenderer as c, ConfigOption as d, ConfigShape as e, ConfigBranch as f, BranchOptions as g, ConfigEntry as h, ConfigRule as i, ConfigRuleContext as j, ConfigOf as k, NodeValue as l };