@weasel-js/labkit 1.5.1 → 1.6.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 (160) hide show
  1. package/README.md +3 -7
  2. package/dist/_dts/{CanvasStackContext-CJNMS6mz.d.ts → CanvasStackContext-DQgcH1ny.d.ts} +1 -1
  3. package/dist/_dts/LayerList.d-2c9yup5Y.d.ts +83 -0
  4. package/dist/_dts/{PrefsForm.d-DPy9I8It.d.ts → PrefsRow.d-DFpPIeIC.d.ts} +16 -1
  5. package/dist/_dts/{Select.d-Gwr8fLpK.d.ts → Select.d-BchGMC2Y.d.ts} +129 -12
  6. package/dist/_dts/{frac-B4okC2lX.d.ts → frac-DTRq7Xpb.d.ts} +31 -13
  7. package/dist/_dts/inDialog-D5ZKGjiD.d.ts +23 -0
  8. package/dist/_dts/{index-CSJoj4LC.d.ts → index-B0--8PEM.d.ts} +5 -4
  9. package/dist/_dts/index-DINmCuLA.d.ts +453 -0
  10. package/dist/_dts/number.d-OeQYQoUO.d.ts +28 -0
  11. package/dist/_dts/{types-DzoNY1pO.d.ts → types-B7y8hX1D.d.ts} +6 -6
  12. package/dist/_dts/{usePanZoom-Pzf9f2jh.d.ts → usePanZoom-Bq73pwFe.d.ts} +4 -47
  13. package/dist/_dts/{useTrialState-uLlmaccn.d.ts → useTrialState-CP4jfuKl.d.ts} +1 -1
  14. package/dist/canvas/index.d.ts +6 -5
  15. package/dist/canvas/index.js +1 -1
  16. package/dist/chrome/index.d.ts +5 -4
  17. package/dist/chrome/index.js +7 -7
  18. package/dist/chunk-2RVCB2PT.js +3 -0
  19. package/dist/chunk-2RVCB2PT.js.map +1 -0
  20. package/dist/{chunk-UN4U4P63.js → chunk-3FI7Y4R4.js} +9 -9
  21. package/dist/chunk-3FI7Y4R4.js.map +1 -0
  22. package/dist/{chunk-7PFH3CNM.js → chunk-AWH3SRPW.js} +31 -5
  23. package/dist/chunk-AWH3SRPW.js.map +1 -0
  24. package/dist/{chunk-ZDHY47EV.js → chunk-BVL7Y574.js} +25 -9
  25. package/dist/chunk-BVL7Y574.js.map +1 -0
  26. package/dist/{chunk-IHUVQ3TX.js → chunk-H5VIV7MQ.js} +31 -27
  27. package/dist/chunk-H5VIV7MQ.js.map +1 -0
  28. package/dist/chunk-HFRE4QQV.js +400 -0
  29. package/dist/chunk-HFRE4QQV.js.map +1 -0
  30. package/dist/{chunk-IXTQI7JX.js → chunk-MOLIUNVH.js} +4 -4
  31. package/dist/{chunk-IXTQI7JX.js.map → chunk-MOLIUNVH.js.map} +1 -1
  32. package/dist/{chunk-CEIUBPPW.js → chunk-TT5C5S6R.js} +3 -86
  33. package/dist/chunk-TT5C5S6R.js.map +1 -0
  34. package/dist/chunk-USO6TERP.js +90 -0
  35. package/dist/chunk-USO6TERP.js.map +1 -0
  36. package/dist/{chunk-PHK4W562.js → chunk-VQ47KWD5.js} +3 -3
  37. package/dist/{chunk-PHK4W562.js.map → chunk-VQ47KWD5.js.map} +1 -1
  38. package/dist/chunk-WUJXJC5O.js +14568 -0
  39. package/dist/chunk-WUJXJC5O.js.map +1 -0
  40. package/dist/{chunk-EHGVROVV.js → chunk-ZORVNOKX.js} +6 -25
  41. package/dist/chunk-ZORVNOKX.js.map +1 -0
  42. package/dist/config/index.d.ts +53 -11
  43. package/dist/config/index.js +5 -5
  44. package/dist/controls/index.d.ts +6 -3
  45. package/dist/controls/index.js +4 -4
  46. package/dist/dragdrop/index.d.ts +4 -3
  47. package/dist/index.d.ts +139 -114
  48. package/dist/index.js +470 -479
  49. package/dist/index.js.map +1 -1
  50. package/dist/layers/index.d.ts +6 -35
  51. package/dist/layers/index.js +2 -3
  52. package/dist/loupe/index.d.ts +6 -5
  53. package/dist/passthrough/weasel-ui.d.ts +421 -215
  54. package/dist/passthrough/weasel-ui.js +1 -1
  55. package/dist/primitives/index.d.ts +2 -13
  56. package/dist/primitives/index.js +4 -4
  57. package/dist/state/index.d.ts +6 -5
  58. package/dist/state/index.js +5 -5
  59. package/dist/styles.css +410 -190
  60. package/dist/undo/index.d.ts +4 -3
  61. package/package.json +9 -13
  62. package/src/annotations/frac.test.ts +7 -0
  63. package/src/annotations/paint.test.ts +21 -0
  64. package/src/annotations/paint.ts +12 -0
  65. package/src/annotations/store.test.ts +31 -0
  66. package/src/annotations/types.ts +3 -2
  67. package/src/canvas/Stage.test.tsx +18 -0
  68. package/src/canvas/index.ts +0 -9
  69. package/src/chrome/labTypes.ts +1 -1
  70. package/src/chrome/regions/PaletteRegion.test.tsx +18 -0
  71. package/src/chrome/regions/PaletteRegion.tsx +5 -4
  72. package/src/chrome/regions/SidebarRegion.less +197 -0
  73. package/src/chrome/regions/SidebarRegion.test.tsx +20 -0
  74. package/src/chrome/regions/SidebarRegion.tsx +7 -2
  75. package/src/chrome/regions/TitleBarRegion.tsx +1 -1
  76. package/src/chrome/types.ts +13 -4
  77. package/src/config/autoConfig.ts +2 -2
  78. package/src/config/builder.ts +23 -5
  79. package/src/config/entry.test.ts +8 -0
  80. package/src/config/index.ts +2 -0
  81. package/src/config/rules.ts +2 -0
  82. package/src/config/sectionTree.test.ts +146 -0
  83. package/src/config/sectionTree.ts +124 -0
  84. package/src/config/types.ts +4 -4
  85. package/src/controls/AutoControls.stories.tsx +6 -6
  86. package/src/controls/ControlPanel.less +27 -0
  87. package/src/controls/ControlPanel.stories.tsx +95 -1
  88. package/src/controls/ControlPanel.test.tsx +82 -112
  89. package/src/controls/ControlPanel.tsx +159 -396
  90. package/src/controls/inDialog.test.tsx +82 -0
  91. package/src/controls/inDialog.tsx +46 -0
  92. package/src/controls/index.ts +1 -0
  93. package/src/index.test.ts +0 -2
  94. package/src/index.ts +14 -35
  95. package/src/instrument/types.ts +5 -2
  96. package/src/lab/Lab.nebula.test.tsx +31 -0
  97. package/src/lab/Lab.tsx +57 -18
  98. package/src/lab/LabBoundary.test.tsx +66 -0
  99. package/src/lab/LabBoundary.tsx +56 -0
  100. package/src/lab/LabHeader.tsx +4 -23
  101. package/src/lab/LabRoot.tsx +2 -3
  102. package/src/lab/LabShell.less +0 -1
  103. package/src/lab/LabShell.stories.tsx +15 -0
  104. package/src/lab/LabShell.test.tsx +18 -0
  105. package/src/lab/LabShell.tsx +14 -1
  106. package/src/lab/LabSwitcher.less +1 -1
  107. package/src/lab/LabSwitcher.test.tsx +41 -0
  108. package/src/lab/LabSwitcher.tsx +30 -8
  109. package/src/lab/index.ts +1 -0
  110. package/src/layers/AGENTS.md +18 -74
  111. package/src/layers/index.ts +4 -2
  112. package/src/passthrough/weasel-ui.ts +25 -10
  113. package/src/primitives/FpsMeter.less +6 -1
  114. package/src/primitives/FpsMeter.test.tsx +12 -4
  115. package/src/primitives/FpsMeter.tsx +6 -1
  116. package/src/primitives/JobProgress.stories.tsx +1 -1
  117. package/src/primitives/StatusBar.less +6 -0
  118. package/src/primitives/index.ts +0 -2
  119. package/src/specimen/Specimen.tsx +42 -56
  120. package/src/state/types.ts +8 -2
  121. package/src/state/view.test.ts +4 -4
  122. package/src/styles.less +4 -2
  123. package/src/theme/Interstellar.stories.tsx +8 -9
  124. package/src/theme/base.less +3 -20
  125. package/src/theme/interstellar.test.ts +6 -3
  126. package/src/theme/interstellar.theme.json +0 -56
  127. package/src/theme/interstellar.ts +2 -2
  128. package/src/trial/Trial.canvas.test.tsx +9 -5
  129. package/src/trial/Trial.config.test.tsx +1 -1
  130. package/src/trial/Trial.tsx +30 -8
  131. package/dist/_dts/index-BNdz-tNd.d.ts +0 -423
  132. package/dist/chunk-7PFH3CNM.js.map +0 -1
  133. package/dist/chunk-CEIUBPPW.js.map +0 -1
  134. package/dist/chunk-EH7UEQV7.js +0 -590
  135. package/dist/chunk-EH7UEQV7.js.map +0 -1
  136. package/dist/chunk-EHGVROVV.js.map +0 -1
  137. package/dist/chunk-IHUVQ3TX.js.map +0 -1
  138. package/dist/chunk-MDVTK5U3.js +0 -170
  139. package/dist/chunk-MDVTK5U3.js.map +0 -1
  140. package/dist/chunk-PJ7WEQBJ.js +0 -18
  141. package/dist/chunk-PJ7WEQBJ.js.map +0 -1
  142. package/dist/chunk-T3EC2DXD.js +0 -12628
  143. package/dist/chunk-T3EC2DXD.js.map +0 -1
  144. package/dist/chunk-UN4U4P63.js.map +0 -1
  145. package/dist/chunk-ZDHY47EV.js.map +0 -1
  146. package/dist/ui/layers/index.d.ts +0 -66
  147. package/dist/ui/layers/index.js +0 -3
  148. package/dist/ui/layers/index.js.map +0 -1
  149. package/src/canvas/useOrbit.test.ts +0 -167
  150. package/src/canvas/useOrbit.ts +0 -156
  151. package/src/lab/useSystemMode.ts +0 -30
  152. package/src/layers/LayerList.less +0 -93
  153. package/src/layers/LayerList.stories.tsx +0 -66
  154. package/src/layers/LayerList.test.tsx +0 -232
  155. package/src/layers/LayerList.tsx +0 -268
  156. package/src/primitives/Sidebar.less +0 -112
  157. package/src/primitives/Sidebar.stories.tsx +0 -38
  158. package/src/primitives/Sidebar.test.tsx +0 -74
  159. package/src/primitives/Sidebar.tsx +0 -35
  160. package/src/ui/layers/index.ts +0 -5
@@ -218,7 +218,7 @@ describe('a config written as auto', () => {
218
218
  const { container } = render(<Lab instruments={[instrument]} defaultInstrument="Ghosted" />);
219
219
  expect(screen.getByRole('button', { name: /Gap/ })).toHaveAttribute('aria-pressed', 'false');
220
220
  expect(screen.getByRole('button', { name: /Width/ })).toHaveAttribute('aria-pressed', 'true');
221
- // 432 / 24. A ghosted control draws what the resolver decided rather than
221
+ // 432 / 24. An auto row holds what the resolver decided rather than
222
222
  // the pinned 12 underneath it, and un-pinning writes back what it drew.
223
223
  expect((container.querySelector('input[type=range]') as HTMLInputElement).value).toBe('18');
224
224
  });
@@ -24,9 +24,9 @@ import type {
24
24
  } from '../instrument/types';
25
25
  import { useJob } from '../job/useJob';
26
26
  import { useLabContext } from '../lab/LabContext';
27
- import { LayerList } from '../layers/LayerList';
28
27
  import { TrialLoupe } from '../loupe/TrialLoupe';
29
28
  import { resolveLoupe } from '../loupe/types';
29
+ import { LayerList, type LayerListItem, moveLayers } from '../passthrough/weasel-ui';
30
30
  import { LabStoreContext, TrialIdProvider } from '../state/context';
31
31
  import type { LabStore } from '../state/store';
32
32
  import type { TrialRecord } from '../state/types';
@@ -401,6 +401,24 @@ function TrialRuntime({
401
401
  return instrument.layers.ids.map((l) => (typeof l === 'string' ? { id: l, label: l } : l));
402
402
  }, [instrument.layers]);
403
403
 
404
+ // In the order the canvas draws them, with the always-on layers pinned
405
+ // below the rest, where a drag cannot reach past them.
406
+ const layerItems: LayerListItem[] = useMemo(() => {
407
+ const ordered = layerOrder
408
+ ? [...layerDescriptors].sort((a, b) => layerOrder.indexOf(a.id) - layerOrder.indexOf(b.id))
409
+ : layerDescriptors;
410
+ const pinnedLast = [
411
+ ...ordered.filter((l) => !l.alwaysOn),
412
+ ...ordered.filter((l) => l.alwaysOn),
413
+ ];
414
+ return pinnedLast.map((l) => ({
415
+ id: l.id,
416
+ label: l.label,
417
+ locked: l.alwaysOn === true,
418
+ visible: l.alwaysOn === true || layerVisibility[l.id] !== false,
419
+ }));
420
+ }, [layerDescriptors, layerOrder, layerVisibility]);
421
+
404
422
  const dragDropResult = useDragDrop({
405
423
  capability: instrument.dragDrop ?? { palette: [], onDrop: (_p, _i, s) => s },
406
424
  canvasContainerRef,
@@ -492,7 +510,12 @@ function TrialRuntime({
492
510
  minZoom={stage.minZoom}
493
511
  maxZoom={stage.maxZoom}
494
512
  hostRef={loupeHostRef}
495
- overlay={lens}
513
+ overlay={
514
+ <>
515
+ {stage.overlay?.(renderCtx)}
516
+ {lens}
517
+ </>
518
+ }
496
519
  >
497
520
  {instrument.render(renderCtx)}
498
521
  </Stage>
@@ -519,19 +542,18 @@ function TrialRuntime({
519
542
  () =>
520
543
  instrument.layers && layerDescriptors.length > 0 ? (
521
544
  <LayerList
522
- layers={layerDescriptors}
523
- visibility={layerVisibility}
524
- onReorder={(next) => {
525
- setLayerOrder(next.map((l) => l.id));
545
+ items={layerItems}
546
+ onReorder={(move) => {
547
+ setLayerOrder(moveLayers(layerItems, move).map((l) => l.id));
526
548
  bus.emit('layers.reorder');
527
549
  }}
528
- onToggle={(lid, visible) => {
550
+ onVisibilityChange={(lid, visible) => {
529
551
  setLayerVisibility((prev) => ({ ...prev, [lid]: visible }));
530
552
  bus.emit('layers.toggle');
531
553
  }}
532
554
  />
533
555
  ) : null,
534
- [instrument.layers, layerDescriptors, layerVisibility, bus],
556
+ [instrument.layers, layerDescriptors, layerItems, bus],
535
557
  );
536
558
 
537
559
  const extraChrome = useMemo<TrialContribution[]>(() => {
@@ -1,423 +0,0 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { ReactNode } from 'react';
3
- import { ToolPrefNumberFormat } from '@weasel-js/core';
4
- import { R as ResolvedConfig, a as ConfigField, d as ControlRenderer } from './types-DzoNY1pO.js';
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
- }
30
- /** Props for `<PropertyPanel>`. */
31
- interface PropertyPanelProps extends PropertyMetricProps {
32
- title?: ReactNode;
33
- children: ReactNode;
34
- className?: string;
35
- }
36
- /** A titled panel holding property rows — the sidebar container the rest of
37
- * this module's components fill. */
38
- declare function PropertyPanel({ title, children, className, density, align, }: PropertyPanelProps): react_jsx_runtime.JSX.Element;
39
- /** How a property list packs its rows into two columns. */
40
- type PropertyListPack = 'auto-color' | 'pairs' | 'one-up';
41
- /** Props for `<PropertyList>`. */
42
- interface PropertyListProps extends PropertyMetricProps {
43
- children: ReactNode;
44
- className?: string;
45
- /**
46
- * How rows pack into the 2-column grid.
47
- * - `'auto-color'` (default): only color rows pair side-by-side; everything
48
- * else spans the full width. Right for sparse top-level panels.
49
- * - `'pairs'`: every row auto-places into the 2-column grid two-per-row.
50
- * Headers and subpanels still span the full width; wrap any other
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.
54
- */
55
- pack?: PropertyListPack;
56
- }
57
- /**
58
- * Grid container for PropertyRows. Use standalone for chrome-less layouts, or
59
- * nest inside <PropertyPanel/> for the standard glass card.
60
- */
61
- declare function PropertyList({ children, className, pack, density, align, }: PropertyListProps): react_jsx_runtime.JSX.Element;
62
- /** Props for `<PropertySpan>`. */
63
- interface PropertySpanProps {
64
- children: ReactNode;
65
- className?: string;
66
- }
67
- /**
68
- * Wrapper that makes an arbitrary child span both columns of a
69
- * `<PropertyList pack="pairs">`, a `<PropertyGroup pack="pairs">` body, or a
70
- * `<Subpanel>`. `<PropertyRow span>` covers the row case; this covers
71
- * everything else a consumer puts in the grid.
72
- */
73
- declare function PropertySpan({ children, className }: PropertySpanProps): react_jsx_runtime.JSX.Element;
74
- /** Which control shape a row holds, which decides its intrinsic layout. */
75
- type PropertyRowVariant = 'default' | 'color' | 'checkbox';
76
- /** Whether a row's label sits above its control or beside it. */
77
- type PropertyRowLayout = 'block' | 'inline';
78
- /** Props for `<PropertyRow>`. */
79
- interface PropertyRowProps extends PropertyMetricProps {
80
- label: ReactNode;
81
- /** Right-aligned readout shown next to the label (e.g. current value). */
82
- readout?: ReactNode;
83
- /**
84
- * Help text for the row, shown in a tooltip off an ⓘ affordance beside the
85
- * label. Empty or absent renders no affordance.
86
- */
87
- description?: string;
88
- variant?: PropertyRowVariant;
89
- /**
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.
93
- */
94
- layout?: PropertyRowLayout;
95
- /**
96
- * Take the full width of the enclosing grid. Only has an effect inside a
97
- * `<PropertyList pack="pairs">` or a `<Subpanel>`; elsewhere rows are already
98
- * full width.
99
- */
100
- span?: boolean;
101
- children: ReactNode;
102
- htmlFor?: string;
103
- /** The control is a group of several (a segmented toggle), not one element.
104
- * The row then renders as a `<div>`: a `<label>` hands a click on its text to
105
- * its first control, which in a group means selecting the first option. Each
106
- * member has to carry its own name. */
107
- group?: boolean;
108
- className?: string;
109
- /** The row is auto: its value is not pinned and the owner computes it. Draws
110
- * the control ghosted and colorless. */
111
- auto?: boolean;
112
- /** Given, the row carries a pin dot that toggles `auto`. Omitted, the row can
113
- * show an auto state but not change it. */
114
- onAutoChange?: (next: boolean) => void;
115
- /** Marks the row's element for a panel-level gesture handler. `PropertyRow`
116
- * does not spread unknown props, so this is declared rather than inherited. */
117
- 'data-auto-path'?: string;
118
- }
119
- /** The label-plus-control frame every typed row below is built from. Use it
120
- * directly for a control this module does not cover. */
121
- declare function PropertyRow({ label, readout, description, variant, layout, span, children, htmlFor, group, className, density, align, auto, onAutoChange, 'data-auto-path': autoPath, }: PropertyRowProps): react_jsx_runtime.JSX.Element;
122
- /** Props for `<SliderRow>`. */
123
- interface SliderRowProps extends PropertyMetricProps {
124
- label: ReactNode;
125
- value: number;
126
- min: number;
127
- max: number;
128
- step?: number;
129
- /**
130
- * The committed value. Given `onInput` as well, it fires once a drag ends
131
- * or a typed readout is accepted; on its own it fires on every move, which
132
- * is what a row with one callback has always done.
133
- */
134
- onChange: (next: number) => void;
135
- /**
136
- * The live value, fired continuously through a drag. Pass it alongside
137
- * `onChange` when the write is expensive — cheap state here, the costly
138
- * work there. Mirrors `<Slider>`'s `onInput` / `onChange` pair.
139
- */
140
- onInput?: (next: number) => void;
141
- /** Override how the value is rendered next to the label. Defaults to `value.toString()`. */
142
- format?: (value: number) => ReactNode;
143
- /**
144
- * Static text in place of the editable readout. Use it when the row is not
145
- * showing a number a caller could type back — an auto row's `auto · 18`, say,
146
- * which an editable field would seed a draft with and then fail to parse.
147
- */
148
- readout?: ReactNode;
149
- /** A named display for the value: `compact` reads `2.0M`. `format` wins when both are given. */
150
- notation?: ToolPrefNumberFormat;
151
- /**
152
- * Optional suffix rendered next to the readout. A string becomes a
153
- * baseline-aligned dim "word" unit (e.g. "px"); pass JSX like `<sup>°</sup>`
154
- * to get the browser's native super positioning for symbol units.
155
- */
156
- unit?: ReactNode;
157
- layout?: PropertyRowLayout;
158
- description?: string;
159
- /** Take the full width of the enclosing grid — see `<PropertyRow span>`. */
160
- span?: boolean;
161
- /** The row is auto — see `<PropertyRow auto>`. */
162
- auto?: boolean;
163
- /** Toggles `auto` from a pin dot — see `<PropertyRow onAutoChange>`. */
164
- onAutoChange?: (next: boolean) => void;
165
- /** Marks the row for a panel-level gesture handler — see `<PropertyRow data-auto-path>`. */
166
- 'data-auto-path'?: string;
167
- }
168
- /** A bounded number edited by dragging, with a live readout whose precision
169
- * follows `step`. */
170
- declare function SliderRow({ label, value, min, max, step, onChange, onInput, format, readout, notation, unit, layout, description, span, density, align, auto, onAutoChange, 'data-auto-path': autoPath, }: SliderRowProps): react_jsx_runtime.JSX.Element;
171
- /** Props for `<ColorRow>`. */
172
- interface ColorRowProps extends PropertyMetricProps {
173
- label: ReactNode;
174
- value: string;
175
- /**
176
- * The committed color. Given `onInput` as well, it fires once the picker
177
- * closes; on its own it fires on every move inside it, which is what a row
178
- * with one callback has always done.
179
- */
180
- onChange: (next: string) => void;
181
- /**
182
- * The live color, fired continuously while the picker is open. Pass it
183
- * alongside `onChange` when the write is expensive — cheap state here, the
184
- * costly work there. Mirrors `<Slider>`'s `onInput` / `onChange` pair.
185
- */
186
- onInput?: (next: string) => void;
187
- /** 0..1 alpha. When provided, a translucent slider renders beneath the swatch. */
188
- alpha?: number;
189
- /** The committed alpha, on the same terms as `onChange`. */
190
- onAlphaChange?: (next: number) => void;
191
- /** The live alpha, on the same terms as `onInput`. */
192
- onAlphaInput?: (next: number) => void;
193
- /**
194
- * Render the alpha track as inert (dimmed, no thumb, not-allowed cursor).
195
- * Use when the color's consumer drops alpha so the affordance reads dead.
196
- */
197
- alphaDisabled?: boolean;
198
- /** Label beside the swatch (default) or above it. */
199
- layout?: PropertyRowLayout;
200
- description?: string;
201
- /** Right-aligned readout shown next to the label — see `<PropertyRow readout>`. */
202
- readout?: ReactNode;
203
- /** Take the full width of the enclosing grid — see `<PropertyRow span>`. */
204
- span?: boolean;
205
- /** The row is auto — see `<PropertyRow auto>`. */
206
- auto?: boolean;
207
- /** Toggles `auto` from a pin dot — see `<PropertyRow onAutoChange>`. */
208
- onAutoChange?: (next: boolean) => void;
209
- /** Marks the row for a panel-level gesture handler — see `<PropertyRow data-auto-path>`. */
210
- 'data-auto-path'?: string;
211
- }
212
- /** A color swatch, optionally with an alpha slider beneath it. */
213
- declare function ColorRow({ label, readout, value, onChange, onInput, alpha, onAlphaChange, onAlphaInput, alphaDisabled, layout, description, span, density, align, auto, onAutoChange, 'data-auto-path': autoPath, }: ColorRowProps): react_jsx_runtime.JSX.Element;
214
- /** Props for `<CheckboxRow>`. */
215
- interface CheckboxRowProps extends PropertyMetricProps {
216
- label: ReactNode;
217
- /** Absent reads as unchecked. */
218
- value: boolean | undefined;
219
- onChange: (next: boolean) => void;
220
- /** Label beside the box (default) or above it. */
221
- layout?: PropertyRowLayout;
222
- description?: string;
223
- /** Right-aligned readout shown next to the label — see `<PropertyRow readout>`. */
224
- readout?: ReactNode;
225
- /** Take the full width of the enclosing grid — see `<PropertyRow span>`. */
226
- span?: boolean;
227
- /** The row is auto — see `<PropertyRow auto>`. */
228
- auto?: boolean;
229
- /** Toggles `auto` from a pin dot — see `<PropertyRow onAutoChange>`. */
230
- onAutoChange?: (next: boolean) => void;
231
- /** Marks the row for a panel-level gesture handler — see `<PropertyRow data-auto-path>`. */
232
- 'data-auto-path'?: string;
233
- }
234
- /** A boolean checkbox. */
235
- declare function CheckboxRow({ label, readout, value, onChange, layout, span, description, density, align, auto, onAutoChange, 'data-auto-path': autoPath, }: CheckboxRowProps): react_jsx_runtime.JSX.Element;
236
- /** Props for `<TextRow>`. */
237
- interface TextRowProps extends PropertyMetricProps {
238
- label: ReactNode;
239
- /** Absent reads as an empty field. */
240
- value: string | null | undefined;
241
- onChange: (next: string) => void;
242
- placeholder?: string;
243
- maxLength?: number;
244
- layout?: PropertyRowLayout;
245
- description?: string;
246
- /** Right-aligned readout shown next to the label — see `<PropertyRow readout>`. */
247
- readout?: ReactNode;
248
- /** Take the full width of the enclosing grid — see `<PropertyRow span>`. */
249
- span?: boolean;
250
- /** The row is auto — see `<PropertyRow auto>`. */
251
- auto?: boolean;
252
- /** Toggles `auto` from a pin dot — see `<PropertyRow onAutoChange>`. */
253
- onAutoChange?: (next: boolean) => void;
254
- /** Marks the row for a panel-level gesture handler — see `<PropertyRow data-auto-path>`. */
255
- 'data-auto-path'?: string;
256
- }
257
- /** A single-line text input. */
258
- declare function TextRow({ label, readout, value, onChange, placeholder, maxLength, layout, description, span, density, align, auto, onAutoChange, 'data-auto-path': autoPath, }: TextRowProps): react_jsx_runtime.JSX.Element;
259
- /** Props for `<NumberRow>`. */
260
- interface NumberRowProps extends PropertyMetricProps {
261
- label: ReactNode;
262
- /** Absent reads as an empty field. */
263
- value: number | null | undefined;
264
- /**
265
- * The committed value. Given `onInput` as well, it fires on blur or Enter;
266
- * on its own it fires on every keystroke, which is what a row with one
267
- * callback has always done.
268
- */
269
- onChange: (next: number) => void;
270
- /**
271
- * The live value, fired per keystroke. Pass it alongside `onChange` when the
272
- * write is expensive — cheap state here, the costly work there. Mirrors
273
- * `<Slider>`'s `onInput` / `onChange` pair.
274
- */
275
- onInput?: (next: number) => void;
276
- min?: number;
277
- max?: number;
278
- step?: number;
279
- placeholder?: string;
280
- /**
281
- * Optional suffix rendered after the field. A string becomes a dim "word"
282
- * unit (e.g. "px"); pass JSX like `<sup>°</sup>` for symbol units. Display
283
- * only — it never participates in parsing, and the value stays a number.
284
- */
285
- unit?: ReactNode;
286
- layout?: PropertyRowLayout;
287
- description?: string;
288
- /** Right-aligned readout shown next to the label — see `<PropertyRow readout>`. */
289
- readout?: ReactNode;
290
- /** Take the full width of the enclosing grid — see `<PropertyRow span>`. */
291
- span?: boolean;
292
- /** The row is auto — see `<PropertyRow auto>`. */
293
- auto?: boolean;
294
- /** Toggles `auto` from a pin dot — see `<PropertyRow onAutoChange>`. */
295
- onAutoChange?: (next: boolean) => void;
296
- /** Marks the row for a panel-level gesture handler — see `<PropertyRow data-auto-path>`. */
297
- 'data-auto-path'?: string;
298
- }
299
- /** A number typed directly. Reach for `<SliderRow>` when the range matters
300
- * more than the exact value. */
301
- declare function NumberRow({ label, readout, value, onChange, onInput, min, max, step, placeholder, unit, layout, description, span, density, align, auto, onAutoChange, 'data-auto-path': autoPath, }: NumberRowProps): react_jsx_runtime.JSX.Element;
302
- interface PropertyOption<T extends string> {
303
- value: T;
304
- label: ReactNode;
305
- }
306
- /** Props for `<SelectRow>`. */
307
- interface SelectRowProps<T extends string> extends PropertyMetricProps {
308
- label: ReactNode;
309
- /** Absent, or not one of `options`, shows a placeholder rather than the first option. */
310
- value: T | undefined;
311
- options: ReadonlyArray<PropertyOption<T>>;
312
- onChange: (next: T) => void;
313
- /** Shown while no option is chosen. Defaults to "Choose option…". */
314
- placeholder?: string;
315
- layout?: PropertyRowLayout;
316
- description?: string;
317
- /** Right-aligned readout shown next to the label — see `<PropertyRow readout>`. */
318
- readout?: ReactNode;
319
- /** Take the full width of the enclosing grid — see `<PropertyRow span>`. */
320
- span?: boolean;
321
- /** The row is auto — see `<PropertyRow auto>`. */
322
- auto?: boolean;
323
- /** Toggles `auto` from a pin dot — see `<PropertyRow onAutoChange>`. */
324
- onAutoChange?: (next: boolean) => void;
325
- /** Marks the row for a panel-level gesture handler — see `<PropertyRow data-auto-path>`. */
326
- 'data-auto-path'?: string;
327
- }
328
- /** A dropdown over a fixed set of choices. Prefer `<ToggleRow>` when there
329
- * are only two or three and they should all stay visible. */
330
- declare function SelectRow<T extends string>({ label, readout, value, options, onChange, placeholder, layout, description, span, density, align, auto, onAutoChange, 'data-auto-path': autoPath, }: SelectRowProps<T>): react_jsx_runtime.JSX.Element;
331
- /** Props for `<ToggleRow>`. */
332
- interface ToggleRowProps<T extends string> extends PropertyMetricProps {
333
- label: ReactNode;
334
- value: T;
335
- options: ReadonlyArray<PropertyOption<T>>;
336
- onChange: (next: T) => void;
337
- layout?: PropertyRowLayout;
338
- description?: string;
339
- /** Right-aligned readout shown next to the label — see `<PropertyRow readout>`. */
340
- readout?: ReactNode;
341
- /** Take the full width of the enclosing grid — see `<PropertyRow span>`. */
342
- span?: boolean;
343
- /** The row is auto — see `<PropertyRow auto>`. */
344
- auto?: boolean;
345
- /** Toggles `auto` from a pin dot — see `<PropertyRow onAutoChange>`. */
346
- onAutoChange?: (next: boolean) => void;
347
- /** Marks the row for a panel-level gesture handler — see `<PropertyRow data-auto-path>`. */
348
- 'data-auto-path'?: string;
349
- }
350
- /** A segmented control: the same choice as a select, with every option
351
- * visible at once. */
352
- declare function ToggleRow<T extends string>({ label, readout, value, options, onChange, layout, description, span, density, align, auto, onAutoChange, 'data-auto-path': autoPath, }: ToggleRowProps<T>): react_jsx_runtime.JSX.Element;
353
-
354
- /** How a panel packs its rows into the two-column property grid.
355
- * - `'auto'`: narrow controls (numbers, checkboxes, dropdowns, colors) sit
356
- * two per row; the ones that need the width — text, sliders, segmented
357
- * toggles — span it.
358
- * - `'pairs'`: every row pairs. A control that needs the full width says so
359
- * itself, with `<PropertyRow span>`.
360
- * - `'one-up'`: one control per row, colors excepted.
361
- */
362
- type ControlPack = 'auto' | 'pairs' | 'one-up';
363
- interface ControlPanelProps<TC extends Record<string, unknown>> {
364
- /** The instrument's resolved config schema. */
365
- schema?: ResolvedConfig;
366
- /** @deprecated Pass `schema`. A field list is adapted into one internally. */
367
- fields?: ConfigField[];
368
- config: TC;
369
- /** Writes one value. The path is dotted for a leaf inside an `f.group`, and
370
- * the bare key for one at the root. */
371
- setConfig: (path: string, value: unknown) => void;
372
- /**
373
- * Control overrides and app-defined kinds, PrefsForm-style. Keys are config
374
- * paths (checked first) or leaf kinds. A renderer returning `null` collapses
375
- * the row; entries override the built-in rows on collision.
376
- */
377
- renderers?: Record<string, ControlRenderer>;
378
- /** How rows pack into the two-column grid. Defaults to `'pairs'`. */
379
- pack?: ControlPack;
380
- /**
381
- * Where a row's label sits relative to its control. Unset takes each row's
382
- * own orientation — `block` (label above, which is what leaves a paired row
383
- * room for its value) for most, `inline` for colors and checkboxes.
384
- */
385
- layout?: PropertyRowLayout;
386
- /** Room the rows get. Passed straight to the property list. */
387
- density?: PropertyDensity;
388
- /** Cross-axis alignment of an inline row's label and control. */
389
- align?: PropertyAlign;
390
- /**
391
- * Fold each section away behind a twisty, starting `'open'` or `'closed'`.
392
- * Unset draws the heading alone, as the panel always has — unless a section
393
- * in the schema declares how it opens, which makes that section foldable on
394
- * its own and outranks this for that one section.
395
- */
396
- collapse?: 'open' | 'closed';
397
- /**
398
- * Which sections are folded, keyed as `onCollapse` reports them. Given, the
399
- * panel keeps no state of its own: every toggle arrives at `onCollapse`
400
- * instead, which is where a lab that remembers a trial's sections writes
401
- * them.
402
- */
403
- collapsed?: Readonly<Record<string, boolean>>;
404
- /** A fold moved. The key is a section's label — prefixed by its group's
405
- * dotted path when the section sits inside one — or a group's own path. */
406
- onCollapse?: (key: string, collapsed: boolean) => void;
407
- /** Dotted paths currently unpinned. A row in this set draws ghosted and its
408
- * dot reads as auto, and the dot writes the sentinel back through
409
- * `setConfig`. Omitted, the panel keeps the set itself: the dots still work
410
- * and the sentinel never reaches `setConfig`, which would otherwise store it
411
- * as the row's value. */
412
- auto?: ReadonlySet<string>;
413
- /** Draw leaves marked `hidden`. */
414
- showHidden?: boolean;
415
- className?: string;
416
- }
417
- /** Render an instrument's config schema as a stack of controls, each writing
418
- * back through `setConfig`. Built on the property rows, so a lab's controls
419
- * are the same aligned, themed rows the rest of the kit uses. */
420
- declare function ControlPanel<TC extends Record<string, unknown>>({ schema, fields, config, setConfig, renderers, pack, layout, density, align, collapse, collapsed, onCollapse, auto: given, showHidden, className, }: ControlPanelProps<TC>): react_jsx_runtime.JSX.Element;
421
-
422
- 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 };
423
- 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 };
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/config/auto.ts","../src/config/path.ts","../src/config/autoConfig.ts"],"names":["n"],"mappings":"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","file":"chunk-7PFH3CNM.js","sourcesContent":["/**\n * Written at a config path to mean \"do not pin this — let the instrument\n * decide\". Accepted by `setConfig`, by a trial's `configSeed`, and by\n * `.initial()` in a schema. It is normalized into the trial's set of unpinned\n * paths on the way into the store and is never itself stored, so a serialized\n * trial contains no sentinel and the last pinned value survives.\n */\nexport const auto: unique symbol = Symbol('weasel.auto');\n\n/** The type of the `auto` sentinel, for widening a value parameter. */\nexport type Auto = typeof auto;\n\nexport function isAuto(value: unknown): value is Auto {\n return value === auto;\n}\n","import type { PrefGroup, PrefLeaf } from '@weasel-js/ui';\n\n/**\n * Reading and writing a config tree by dotted path. A schema's leaves address\n * their values this way, so a leaf nested under `f.group` and a flat one are\n * reached by exactly the same call.\n */\n\n/** A plain object a config tree can be walked into. An array, a `Date`, a\n * `Map` or a class instance is a leaf value: a walk that descended into one\n * would rebuild it as a bare `{}`. */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null) return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/** The value at a dotted path. `undefined` when a segment is missing or the\n * walk hits something that is not a record. */\nexport function valueAtPath(config: unknown, path: string): unknown {\n let cur = config;\n for (const segment of path.split('.')) {\n if (!isRecord(cur)) return undefined;\n cur = cur[segment];\n }\n return cur;\n}\n\n/** Whether a dotted path is actually present, which a `undefined` value is\n * not enough to tell you. */\nexport function hasConfigPath(config: unknown, path: string): boolean {\n let cur = config;\n for (const segment of path.split('.')) {\n if (!isRecord(cur) || !(segment in cur)) return false;\n cur = cur[segment];\n }\n return true;\n}\n\nfunction writeIn(node: unknown, segments: readonly string[], value: unknown): unknown {\n const [head, ...rest] = segments;\n if (head === undefined) return value;\n const base = isRecord(node) ? node : {};\n return { ...base, [head]: rest.length === 0 ? value : writeIn(base[head], rest, value) };\n}\n\n/** A copy of `config` with `value` written at a dotted path. Every record on\n * the way down is copied and the input is left alone; a missing — or\n * non-record — intermediate is replaced with the branch the path implies. */\nexport function withValueAtPath<T>(config: T, path: string, value: unknown): T {\n return writeIn(config, path.split('.'), value) as T;\n}\n\n/**\n * `stored` with every gap filled from `defaults`, down the whole tree.\n *\n * This is how a config written before its schema grew a branch still loads:\n * the branch arrives at its defaults instead of `undefined`, and a key the\n * defaults no longer mention is kept rather than dropped, so nothing is lost\n * to a schema change the author has not finished making.\n */\nexport function fillConfigDefaults<T>(stored: unknown, defaults: T): T {\n if (!isRecord(defaults)) return (stored === undefined ? defaults : stored) as T;\n const base = isRecord(stored) ? stored : {};\n const out: Record<string, unknown> = { ...base };\n for (const [key, value] of Object.entries(defaults)) {\n out[key] = fillConfigDefaults(base[key], value);\n }\n return out as T;\n}\n\n/** A node in a resolved schema tree, by dotted path. Structural rather than\n * `isPrefLeaf` so this stays free of a runtime import. */\nexport function schemaNodeAtPath(group: PrefGroup, path: string): PrefLeaf | PrefGroup | undefined {\n let node: PrefLeaf | PrefGroup | undefined = group;\n for (const segment of path.split('.')) {\n if (node === undefined || !('children' in node)) return undefined;\n node = node.children[segment];\n }\n return node;\n}\n","import { isPrefLeaf, type PrefGroup, type PrefLeaf } from '@weasel-js/ui';\nimport { isAuto } from './auto';\nimport { isRecord, withValueAtPath } from './path';\nimport type { ResolvedConfig } from './types';\n\ntype Resolver = (config: Record<string, unknown>) => unknown;\n\n/** Reads a labkit-only extra off a resolved leaf. `PrefLeaf` has no field for\n * these; they ride as extra keys and survive the resolve walk. */\nfunction extra<T>(leaf: PrefLeaf, key: string): T | undefined {\n return (leaf as unknown as Record<string, T | undefined>)[key];\n}\n\n/** Every leaf in a resolved schema, in schema order, with its dotted path. */\nfunction* leaves(group: PrefGroup, at = ''): Generator<[string, PrefLeaf]> {\n for (const [key, child] of Object.entries(group.children)) {\n const path = at === '' ? key : `${at}.${key}`;\n if (isPrefLeaf(child)) yield [path, child];\n else yield* leaves(child, path);\n }\n}\n\n/** The dotted paths a schema declares as starting auto — every leaf that said\n * `.initial(auto)`. This is what seeds a new trial's unpinned set. */\nexport function autoPathsOf(resolved: ResolvedConfig): string[] {\n const out: string[] = [];\n for (const [path, leaf] of leaves(resolved.group)) {\n if (extra<boolean>(leaf, 'unpinned')) out.push(path);\n }\n return out;\n}\n\n/**\n * The config an instrument reads: the stored one, with every unpinned path\n * either computed by its resolver or removed.\n *\n * Resolution is demand-driven — a resolver reading another unpinned path\n * forces that one first, whatever order the schema declared them in — so a\n * cycle is a real cycle and not an ordering accident.\n */\nexport function resolveAutoConfig<TC>(\n resolved: ResolvedConfig,\n config: TC,\n autoPaths: ReadonlySet<string>,\n): TC {\n if (autoPaths.size === 0) return config;\n\n const resolvers = new Map<string, Resolver | undefined>();\n for (const [path, leaf] of leaves(resolved.group)) {\n if (autoPaths.has(path)) resolvers.set(path, extra<Resolver>(leaf, 'autoResolve'));\n }\n if (resolvers.size === 0) return config;\n\n // Drop first, so a resolver reading a still-unresolved auto path sees\n // `undefined` rather than a stale pinned value it would silently believe.\n let out = config as unknown as Record<string, unknown>;\n for (const path of resolvers.keys()) out = dropAtPath(out, path);\n\n const inFlight = new Set<string>();\n const done = new Set<string>();\n\n const need = (path: string): void => {\n if (done.has(path)) return;\n if (inFlight.has(path)) {\n throw new Error(`[labkit] auto resolver cycle at \"${path}\"`);\n }\n const resolve = resolvers.get(path);\n if (!resolve) {\n done.add(path);\n return;\n }\n inFlight.add(path);\n try {\n // Read `out` only after the resolver has run: it forces its own\n // dependencies, and each of those replaced `out`.\n const value = resolve(demand(() => out, resolvers, need));\n out = withValueAtPath(out, path, value);\n } finally {\n inFlight.delete(path);\n }\n done.add(path);\n };\n\n for (const path of resolvers.keys()) need(path);\n return out as unknown as TC;\n}\n\n/**\n * The config a resolver reads. A top-level key that is itself unpinned is\n * forced before it is handed back, which is what makes declaration order\n * irrelevant and turns a genuine loop into a named error.\n *\n * Only top-level keys are trapped: a resolver reaching `c.grid.size` gets the\n * already-dropped branch, and a nested auto path it depends on resolves by\n * schema order alone.\n */\nfunction demand(\n read: () => Record<string, unknown>,\n resolvers: ReadonlyMap<string, Resolver | undefined>,\n need: (path: string) => void,\n): Record<string, unknown> {\n return new Proxy(read(), {\n get(_target, key) {\n if (typeof key === 'string' && resolvers.has(key)) need(key);\n return Reflect.get(read(), key);\n },\n has(_target, key) {\n return Reflect.has(read(), key);\n },\n ownKeys() {\n return Reflect.ownKeys(read());\n },\n getOwnPropertyDescriptor(_target, key) {\n return Reflect.getOwnPropertyDescriptor(read(), key);\n },\n });\n}\n\n/** A copy of `config` with the dotted path removed, copying every record on\n * the way down. A path that is not there comes back unchanged. */\nfunction dropAtPath(config: Record<string, unknown>, path: string): Record<string, unknown> {\n const [head, ...rest] = path.split('.');\n if (head === undefined || !(head in config)) return config;\n if (rest.length === 0) {\n const { [head]: _dropped, ...kept } = config;\n return kept;\n }\n const child = config[head];\n if (!isRecord(child)) return config;\n return { ...config, [head]: dropAtPath(child, rest.join('.')) };\n}\n\n/**\n * What writing `value` at `path` makes of a trial's raw config and its set of\n * unpinned paths. `auto` pins nothing and joins the set; any other value is\n * written and leaves it. Both the store and the chrome go through this, so\n * they cannot disagree about what a write meant.\n */\nexport function applyConfigWrite<TC>(\n config: TC,\n autoPaths: readonly string[],\n path: string,\n value: unknown,\n): { config: TC; autoPaths: readonly string[] } {\n if (isAuto(value)) {\n if (autoPaths.includes(path)) return { config, autoPaths };\n return { config, autoPaths: [...autoPaths, path] };\n }\n return {\n config: withValueAtPath(config, path, value),\n autoPaths: autoPaths.includes(path) ? autoPaths.filter((p) => p !== path) : autoPaths,\n };\n}\n"]}