@weasel-js/labkit 1.0.2 → 1.0.4

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 (111) hide show
  1. package/dist/_dts/{DrawCommand-CitWPxMY.d.ts → DrawCommand-BkZztJsW.d.ts} +59 -1
  2. package/dist/_dts/{index-C6Yze7sQ.d.ts → index-CFlDPeZh.d.ts} +30 -1
  3. package/dist/_dts/{types-Si4Fw-1F.d.ts → types-x92Kfeme.d.ts} +14 -0
  4. package/dist/_dts/{useExperimentState-CJn2hHzd.d.ts → useExperimentState-D7EQnnwJ.d.ts} +47 -0
  5. package/dist/canvas/index.d.ts +13 -2
  6. package/dist/canvas/index.js +2 -2
  7. package/dist/{chunk-54IQ2DX7.js → chunk-574LJAV4.js} +3 -3
  8. package/dist/chunk-574LJAV4.js.map +1 -0
  9. package/dist/{chunk-VLAHRJOC.js → chunk-5R2ATYPJ.js} +2 -2
  10. package/dist/{chunk-VLAHRJOC.js.map → chunk-5R2ATYPJ.js.map} +1 -1
  11. package/dist/{chunk-R4TACNW7.js → chunk-BAPZPDDA.js} +631 -591
  12. package/dist/chunk-BAPZPDDA.js.map +1 -0
  13. package/dist/{chunk-PWC7AQZM.js → chunk-CPUJ3QXL.js} +2 -2
  14. package/dist/chunk-CPUJ3QXL.js.map +1 -0
  15. package/dist/{chunk-2ZRE7WGQ.js → chunk-DEWXYFEU.js} +6 -6
  16. package/dist/chunk-DEWXYFEU.js.map +1 -0
  17. package/dist/{chunk-3P56ZCCJ.js → chunk-DWV7SFKR.js} +601 -269
  18. package/dist/chunk-DWV7SFKR.js.map +1 -0
  19. package/dist/{chunk-53XSBIUK.js → chunk-KSTEW2AF.js} +18 -2
  20. package/dist/chunk-KSTEW2AF.js.map +1 -0
  21. package/dist/{chunk-HXZHVU4G.js → chunk-PMAU3SEE.js} +3 -3
  22. package/dist/chunk-PMAU3SEE.js.map +1 -0
  23. package/dist/{chunk-7BKDG73Z.js → chunk-RL2LOLNI.js} +2 -2
  24. package/dist/chunk-RL2LOLNI.js.map +1 -0
  25. package/dist/{chunk-2QNYYL3V.js → chunk-SFL7NFKN.js} +2 -2
  26. package/dist/chunk-SFL7NFKN.js.map +1 -0
  27. package/dist/controls/index.d.ts +4 -2
  28. package/dist/controls/index.js +1 -1
  29. package/dist/dragdrop/index.d.ts +2 -2
  30. package/dist/index.d.ts +180 -15
  31. package/dist/index.js +146 -37
  32. package/dist/index.js.map +1 -1
  33. package/dist/layers/index.d.ts +6 -3
  34. package/dist/layers/index.js +1 -1
  35. package/dist/passthrough/weasel-canvas.d.ts +5 -1
  36. package/dist/passthrough/weasel-canvas.js +1 -1
  37. package/dist/passthrough/weasel-ui.d.ts +444 -7
  38. package/dist/passthrough/weasel-ui.js +2 -2
  39. package/dist/primitives/index.d.ts +13 -0
  40. package/dist/primitives/index.js +2 -2
  41. package/dist/state/index.d.ts +16 -3
  42. package/dist/state/index.js +2 -2
  43. package/dist/state/index.js.map +1 -1
  44. package/dist/styles.css +163 -6
  45. package/dist/ui/layers/index.d.ts +5 -0
  46. package/dist/ui/layers/index.js +3 -3
  47. package/dist/undo/index.d.ts +17 -2
  48. package/dist/undo/index.js +1 -1
  49. package/package.json +2 -1
  50. package/src/canvas/CanvasStack.tsx +4 -0
  51. package/src/canvas/CanvasStackContext.ts +3 -0
  52. package/src/canvas/canvasCoords.ts +2 -0
  53. package/src/canvas/useLayerScheduler.ts +2 -0
  54. package/src/controls/ControlPanel.tsx +2 -0
  55. package/src/controls/types.ts +14 -0
  56. package/src/instrument/capabilityDetector.ts +3 -0
  57. package/src/instrument/defineInstrument.ts +2 -0
  58. package/src/instrument/types.ts +29 -0
  59. package/src/instrument/validateConfigSchema.ts +5 -0
  60. package/src/lab/Lab.tsx +17 -2
  61. package/src/lab/LabContext.ts +6 -0
  62. package/src/lab/LabShell.tsx +4 -0
  63. package/src/lab/WorkspaceGrid.less +37 -5
  64. package/src/lab/WorkspaceGrid.stories.tsx +16 -0
  65. package/src/lab/WorkspaceGrid.test.tsx +84 -10
  66. package/src/lab/WorkspaceGrid.tsx +188 -12
  67. package/src/lab/index.ts +0 -2
  68. package/src/layers/LayerList.tsx +3 -0
  69. package/src/primitives/FpsMeter.tsx +1 -0
  70. package/src/primitives/ScaleIndicator.tsx +4 -0
  71. package/src/primitives/Sidebar.tsx +3 -0
  72. package/src/primitives/StatusBar.tsx +3 -0
  73. package/src/primitives/Toolbar.tsx +6 -0
  74. package/src/state/SingletonExperiment.tsx +1 -0
  75. package/src/state/adapters.ts +8 -0
  76. package/src/state/context.tsx +9 -0
  77. package/src/state/helpers.ts +13 -1
  78. package/src/state/store.test.ts +2 -1
  79. package/src/state/store.ts +25 -0
  80. package/src/state/types.ts +20 -0
  81. package/src/state/useExperimentState.ts +3 -0
  82. package/src/test-setup.ts +19 -0
  83. package/src/ui/layers/LayerStack.tsx +5 -0
  84. package/src/ui/properties/CurveField.tsx +4 -1
  85. package/src/ui/properties/EffectCard.tsx +12 -0
  86. package/src/ui/properties/PropertyGroup.tsx +1 -0
  87. package/src/ui/properties/PropertyPanel.tsx +28 -0
  88. package/src/undo/eventBus.ts +4 -0
  89. package/src/undo/undoStack.ts +11 -0
  90. package/src/workspace/DefaultSidebar.tsx +3 -0
  91. package/src/workspace/DefaultStatusBar.tsx +3 -0
  92. package/src/workspace/DefaultToolbar.tsx +3 -0
  93. package/src/workspace/Workspace.stories.tsx +1 -0
  94. package/src/workspace/Workspace.test.tsx +1 -0
  95. package/src/workspace/Workspace.tsx +3 -0
  96. package/src/workspace/WorkspaceChrome.tsx +4 -0
  97. package/src/workspace/index.ts +1 -0
  98. package/src/workspace/slotTypes.ts +9 -0
  99. package/src/workspace/workspaceOps.test.ts +34 -1
  100. package/src/workspace/workspaceOps.ts +24 -0
  101. package/dist/chunk-2QNYYL3V.js.map +0 -1
  102. package/dist/chunk-2ZRE7WGQ.js.map +0 -1
  103. package/dist/chunk-3P56ZCCJ.js.map +0 -1
  104. package/dist/chunk-53XSBIUK.js.map +0 -1
  105. package/dist/chunk-54IQ2DX7.js.map +0 -1
  106. package/dist/chunk-7BKDG73Z.js.map +0 -1
  107. package/dist/chunk-HXZHVU4G.js.map +0 -1
  108. package/dist/chunk-PWC7AQZM.js.map +0 -1
  109. package/dist/chunk-R4TACNW7.js.map +0 -1
  110. package/src/lab/gridDims.test.ts +0 -35
  111. package/src/lab/gridDims.ts +0 -11
@@ -32,10 +32,30 @@ interface Op {
32
32
  args?: unknown;
33
33
  }
34
34
 
35
+ /** Options for `history.beginJournal()`. */
35
36
  interface BeginJournalOptions {
37
+ /** Label for the single parent-history entry the journal flushes on commit. */
36
38
  label: string;
39
+ /** Caller-supplied tag naming what this journal is scoped to — typically the
40
+ * id of the node being edited. The history layer only carries it; callers
41
+ * read it back off the journal to decide whether a suspended journal
42
+ * matches what they are about to edit. */
37
43
  targetId?: string;
38
44
  }
45
+ /**
46
+ * A scoped sub-history forked from a `History`, opened by
47
+ * `history.beginJournal()`. Applies, undoes and redoes against the same
48
+ * adapter as its parent, but keeps its entries to itself: `commit` flushes the
49
+ * journal's net forward ops to the parent as one entry, `cancel` rewinds them
50
+ * and contributes nothing. Use it when a self-contained editing session (a
51
+ * text edit, a modal drag) should collapse to a single step in the parent's
52
+ * undo stack while still offering undo *within* the session.
53
+ *
54
+ * A journal is active, suspended or closed. `commit` and `cancel` are
55
+ * terminal; `suspend` lets the parent be used again and can be reversed with
56
+ * `history.resumeJournal()`. Every mutating method throws when the journal is
57
+ * not active.
58
+ */
39
59
  interface Journal {
40
60
  readonly targetId: string | undefined;
41
61
  readonly forkedAtEntryId: number;
@@ -165,7 +185,9 @@ interface History {
165
185
  * 2026-05-24-modality-design.md for the full lifecycle. */
166
186
  beginJournal(opts: BeginJournalOptions): Journal;
167
187
  /** Re-activate a suspended journal. Throws if the journal was committed or
168
- * cancelled (those are terminal). Staleness checking is the caller's
188
+ * cancelled (those are terminal), or if a different journal is currently
189
+ * active — at most one journal writes to the adapter at a time, on resume
190
+ * as well as on open. Staleness checking is the caller's
169
191
  * responsibility — consult `journal.forkedAtEntryId` against
170
192
  * `currentEntryId()` and your own op-semantic rules to decide whether
171
193
  * to resume or discard before calling this. */
@@ -277,9 +299,13 @@ interface NodeBase<TData, TLayer extends string, TPose> {
277
299
  data: TData;
278
300
  parent: NodeId | null;
279
301
  }
302
+ /** A node with no children — a shape, a label, an image. */
280
303
  interface LeafNode<TData, TLayer extends string, TPose = RectPose> extends NodeBase<TData, TLayer, TPose> {
281
304
  kind: 'leaf';
282
305
  }
306
+ /** A node with an ordered list of children. This is the real group: what
307
+ * Cmd+G creates, what SVG `<g>` round-trips to. A container has its own pose,
308
+ * which its children's poses are relative to, and may optionally clip them. */
283
309
  interface ContainerNode<TData, TLayer extends string, TPose = RectPose> extends NodeBase<TData, TLayer, TPose> {
284
310
  kind: 'container';
285
311
  children: NodeId[];
@@ -290,20 +316,30 @@ interface ContainerNode<TData, TLayer extends string, TPose = RectPose> extends
290
316
  * paints descendants only where it covers. */
291
317
  clipFromPose?: (pose: TPose) => Path | null;
292
318
  }
319
+ /** A node in the scene tree: either a leaf or a container. Re-exported
320
+ * publicly as `SceneNode`, to avoid colliding with the DOM's `Node`. */
293
321
  type Node<TData, TLayer extends string, TPose = RectPose> = LeafNode<TData, TLayer, TPose> | ContainerNode<TData, TLayer, TPose>;
294
322
  interface LayerRecordBase<TLayer extends string> {
295
323
  id: TLayer;
296
324
  visible: boolean;
297
325
  locked: boolean;
298
326
  }
327
+ /** A layer declared when the scene was created. Fixed set, no display name —
328
+ * these are the kit's own render bands, not something a user manages. */
299
329
  interface SystemLayerRecord<TLayer extends string> extends LayerRecordBase<TLayer> {
300
330
  kind: 'system';
301
331
  }
332
+ /** A layer the user created and can rename, reorder or delete. */
302
333
  interface UserLayerRecord<TLayer extends string> extends LayerRecordBase<TLayer> {
303
334
  kind: 'user';
304
335
  name: string;
305
336
  }
337
+ /** Per-layer metadata held by the scene: whether it is visible and locked,
338
+ * and where it sits in the render stack. Distinct from a node's `layer` tag,
339
+ * which merely names one of these. */
306
340
  type LayerRecord<TLayer extends string> = SystemLayerRecord<TLayer> | UserLayerRecord<TLayer>;
341
+ /** What `Scene.add` needs to mint a node. Everything except the id is
342
+ * required; the id is generated unless one is supplied. */
307
343
  interface AddNodeSpec<TData, TLayer extends string, TPose = RectPose> {
308
344
  kind: 'leaf' | 'container';
309
345
  layer: TLayer;
@@ -317,10 +353,13 @@ interface AddNodeSpec<TData, TLayer extends string, TPose = RectPose> {
317
353
  * to the node; ignored for leaves. Mirrors `ContainerNode.clipFromPose`. */
318
354
  clipFromPose?: (pose: TPose) => Path | null;
319
355
  }
356
+ /** A custom scene mutation registered with `Scene.registerOp`: how to apply
357
+ * it and how to undo it. The pair is what makes it participate in history. */
320
358
  interface RegisteredOp<P> {
321
359
  apply: (payload: P) => void;
322
360
  revert: (payload: P) => void;
323
361
  }
362
+ /** One of the layers a scene is created with. */
324
363
  interface SystemLayerSpec<TLayer extends string> {
325
364
  id: TLayer;
326
365
  visible?: boolean;
@@ -361,6 +400,18 @@ interface SerializedNode<TData, TLayer extends string, TPose> {
361
400
  * Containers only; omitted when the container has no clip. */
362
401
  clipFromPoseKey?: string;
363
402
  }
403
+ /**
404
+ * The kit-owned scene tree: nodes, layers, and the undo history over both.
405
+ *
406
+ * A scene is logical, not visual — it says what exists and where, and nothing
407
+ * about how it is painted. Every mutating method is undoable, and reads are
408
+ * snapshots rather than live views. Nodes are addressed by `NodeId`; hold ids
409
+ * across mutations, not node objects.
410
+ *
411
+ * Three type parameters keep it domain-agnostic: `TData` is the app's payload,
412
+ * which the kit never inspects; `TPose` is the transform shape, `RectPose` by
413
+ * default; `TLayer` is the union of layer names.
414
+ */
364
415
  interface Scene<TData, TLayer extends string, TPose = RectPose> {
365
416
  readonly nodes: ReadonlyMap<NodeId, Node<TData, TLayer, TPose>>;
366
417
  readonly roots: readonly NodeId[];
@@ -865,6 +916,8 @@ interface TextStyle {
865
916
  * `runs/rangeStyle.ts` for why the model collapses the tri-state.
866
917
  */
867
918
 
919
+ /** A run with every style resolved against the node's text style — no
920
+ * optional inheritance left. This is what layout and painting consume. */
868
921
  interface ResolvedRun {
869
922
  text: string;
870
923
  fontFamily: string;
@@ -926,6 +979,8 @@ type ShaderUniform = number | [number, number] | [number, number, number] | [num
926
979
 
927
980
  /** DrawCommand variants implemented through step 6. */
928
981
  type DrawCommand = PathDrawCommand | GroupDrawCommand | TextDrawCommand | ImageDrawCommand | ShaderDrawCommand;
982
+ /** Draw a path, filled and/or stroked. The workhorse command: every shape the
983
+ * kit draws that is not text, an image, or a custom shader is one of these. */
929
984
  interface PathDrawCommand {
930
985
  kind: 'path';
931
986
  path: Path;
@@ -948,6 +1003,9 @@ interface PathDrawCommand {
948
1003
  */
949
1004
  vertexColors?: number[];
950
1005
  }
1006
+ /** Draw a list of commands under a shared transform, opacity, color matrix
1007
+ * and clip. Groups nest, and their effects accumulate down the stack — this
1008
+ * is how a container node's transform reaches its descendants. */
951
1009
  interface GroupDrawCommand {
952
1010
  kind: 'group';
953
1011
  transform?: Mat3;
@@ -1,6 +1,8 @@
1
1
  import { ReactNode } from 'react';
2
- import { C as ConfigField } from './types-Si4Fw-1F.js';
2
+ import { C as ConfigField } from './types-x92Kfeme.js';
3
3
 
4
+ /** What an instrument's `render` is handed: its state and config, the setters
5
+ * for both, the workspace it is mounted in, and a way to emit named events. */
4
6
  interface RenderContext<TS = unknown, TC = unknown> {
5
7
  state: TS;
6
8
  config: TC;
@@ -13,6 +15,7 @@ interface RenderContext<TS = unknown, TC = unknown> {
13
15
  };
14
16
  emit: (event: string) => void;
15
17
  }
18
+ /** One 2D canvas layer of an instrument, drawn in declaration order. */
16
19
  interface CanvasLayer<TS = unknown, TC = unknown> {
17
20
  id: string;
18
21
  draw: (ctx: CanvasRenderingContext2D, args: {
@@ -21,6 +24,8 @@ interface CanvasLayer<TS = unknown, TC = unknown> {
21
24
  zoom: number;
22
25
  }) => void;
23
26
  }
27
+ /** Declares that an instrument draws to a canvas: its layers, and where the
28
+ * view starts. */
24
29
  interface CanvasCapability<TS = unknown, TC = unknown> {
25
30
  layers: CanvasLayer<TS, TC>[];
26
31
  initialView?: {
@@ -31,9 +36,14 @@ interface CanvasCapability<TS = unknown, TC = unknown> {
31
36
  };
32
37
  };
33
38
  }
39
+ /** Declares which of an instrument's layers the workspace should offer
40
+ * show/hide controls for. */
34
41
  interface LayerCapability {
35
42
  ids: string[];
36
43
  }
44
+ /** Declares that an instrument accepts items dragged from a palette: what the
45
+ * palette offers, what a drop does to the state, and — optionally — live
46
+ * feedback during the drag and the ability to drag existing items back out. */
37
47
  interface DragDropCapability<TS = unknown, TC = unknown> {
38
48
  palette: PaletteItem[] | ((state: TS, config: TC) => PaletteItem[]);
39
49
  onDrop: (worldPos: Point, item: PaletteItem, state: TS, config: TC) => TS;
@@ -43,38 +53,57 @@ interface DragDropCapability<TS = unknown, TC = unknown> {
43
53
  state: TS;
44
54
  } | null;
45
55
  }
56
+ /** Declares that an instrument's state is undoable: which emitted events
57
+ * snapshot it, and how many snapshots to keep. */
46
58
  interface UndoCapability {
47
59
  snapshotOn?: string[];
48
60
  maxDepth?: number;
49
61
  }
62
+ /** The name of an event an instrument emits through `RenderContext.emit`. */
50
63
  type SystemEvent = string;
64
+ /** A point in world coordinates. */
51
65
  type Point = {
52
66
  x: number;
53
67
  y: number;
54
68
  };
69
+ /** What a hit-test found, and where. */
55
70
  type HitResult = {
56
71
  hit: boolean;
57
72
  layerId?: string;
58
73
  pointId?: string;
59
74
  };
75
+ /** A workspace's camera. */
60
76
  type ViewTransform = {
61
77
  zoom: number;
62
78
  pan: Point;
63
79
  };
80
+ /** A layer as the layer list shows it. `alwaysOn` layers cannot be hidden. */
64
81
  type LayerDescriptor = {
65
82
  id: string;
66
83
  label: string;
67
84
  alwaysOn?: boolean;
68
85
  };
86
+ /** One draggable entry in an instrument's palette. */
69
87
  type PaletteItem = {
70
88
  id: string;
71
89
  label: string;
72
90
  data?: unknown;
73
91
  };
92
+ /** Whether a drop would be accepted at the current position, and why not if
93
+ * it would not. */
74
94
  type DragFeedback = {
75
95
  ok: boolean;
76
96
  reason?: string;
77
97
  };
98
+ /**
99
+ * An instrument: one self-contained interactive experiment a lab can host.
100
+ *
101
+ * It owns two pieces of data — `config`, the settings the control panel edits,
102
+ * and `state`, what the experiment is currently doing — and renders from both.
103
+ * The optional capability fields declare what else it wants from the runtime:
104
+ * a canvas, a layer list, palette drag-and-drop, undo. Declaring a capability
105
+ * is what makes the workspace provide the corresponding chrome.
106
+ */
78
107
  interface Instrument<TS = unknown, TC = unknown> {
79
108
  name: string;
80
109
  defaultConfig: () => TC;
@@ -1,9 +1,13 @@
1
+ /** Which control a config field is edited with. */
1
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. */
2
5
  interface ConfigFieldBase {
3
6
  key: string;
4
7
  label: string;
5
8
  type: ConfigFieldType;
6
9
  }
10
+ /** A bounded number edited by dragging. */
7
11
  interface SliderField extends ConfigFieldBase {
8
12
  type: 'slider';
9
13
  default: number;
@@ -11,19 +15,24 @@ interface SliderField extends ConfigFieldBase {
11
15
  max: number;
12
16
  step?: number;
13
17
  }
18
+ /** A boolean. */
14
19
  interface CheckboxField extends ConfigFieldBase {
15
20
  type: 'checkbox';
16
21
  default: boolean;
17
22
  }
23
+ /** One choice in a select field. */
18
24
  interface SelectOption {
19
25
  value: string;
20
26
  label: string;
21
27
  }
28
+ /** A fixed set of labeled choices. */
22
29
  interface SelectField extends ConfigFieldBase {
23
30
  type: 'select';
24
31
  default: string;
25
32
  options: SelectOption[];
26
33
  }
34
+ /** A number typed directly, optionally bounded. Use a slider field instead
35
+ * when the range matters more than the exact value. */
27
36
  interface NumberField extends ConfigFieldBase {
28
37
  type: 'number';
29
38
  default: number;
@@ -31,6 +40,8 @@ interface NumberField extends ConfigFieldBase {
31
40
  max?: number;
32
41
  step?: number;
33
42
  }
43
+ /** A free-text string. Writes are debounced so typing does not re-run the
44
+ * instrument on every keystroke. */
34
45
  interface TextField extends ConfigFieldBase {
35
46
  type: 'text';
36
47
  default: string;
@@ -39,10 +50,13 @@ interface TextField extends ConfigFieldBase {
39
50
  /** Milliseconds to debounce live setConfig calls. Default 150 ms. Set to 0 to disable. */
40
51
  debounceMs?: number;
41
52
  }
53
+ /** A color, as a CSS color string. */
42
54
  interface ColorField extends ConfigFieldBase {
43
55
  type: 'color';
44
56
  default: string;
45
57
  }
58
+ /** One field of an instrument's config schema. The schema is what the control
59
+ * panel renders, and what `validateConfigSchema` checks. */
46
60
  type ConfigField = SliderField | CheckboxField | SelectField | NumberField | TextField | ColorField;
47
61
 
48
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 };
@@ -2,10 +2,14 @@ import * as react from 'react';
2
2
  import { ReactNode, ReactElement } from 'react';
3
3
  import { StoreApi } from 'zustand/vanilla';
4
4
 
5
+ /** A workspace's undo history, as snapshots of its state either side of the
6
+ * present. */
5
7
  interface UndoStack {
6
8
  past: unknown[];
7
9
  future: unknown[];
8
10
  }
11
+ /** One workspace as the store holds it: which instrument it runs, that
12
+ * instrument's config and state, the camera, and the undo history. */
9
13
  interface WorkspaceRecord<TS = unknown, TC = unknown> {
10
14
  id: string;
11
15
  instrumentName: string;
@@ -20,6 +24,8 @@ interface WorkspaceRecord<TS = unknown, TC = unknown> {
20
24
  };
21
25
  undoStack: UndoStack;
22
26
  }
27
+ /** A named, saved copy of a workspace's config and state, restorable into any
28
+ * workspace running the same instrument. */
23
29
  interface SavedSnapshot {
24
30
  id: string;
25
31
  name: string;
@@ -31,38 +37,62 @@ interface SavedSnapshot {
31
37
  }
32
38
  /** `auto` follows the OS; the other two are an explicit choice. */
33
39
  type LabMode = 'auto' | 'light' | 'dark';
40
+ /** Everything a lab persists: its workspaces, its saved snapshots, and the
41
+ * chosen color mode. */
34
42
  interface LabStoreState {
35
43
  workspaces: WorkspaceRecord[];
36
44
  savedSnapshots: SavedSnapshot[];
37
45
  mode: LabMode;
46
+ /** Per-workspace tile extents, keyed by workspace id. Opaque here — the
47
+ * shape belongs to whatever lays the workspaces out. */
48
+ layout: Record<string, unknown>;
38
49
  }
50
+ /** Where a lab persists itself. Implementations are keyed string storage and
51
+ * nothing more, so the same store works against localStorage, the URL hash,
52
+ * or memory. */
39
53
  interface StorageAdapter {
40
54
  read(key: string): string | null;
41
55
  write(key: string, value: string): void;
42
56
  delete?(key: string): void;
43
57
  }
58
+ /** What `useExperimentState` hands an instrument: its state and config, with
59
+ * a setter for each. */
44
60
  interface ExperimentStateHandle<TS, TC> {
45
61
  state: TS;
46
62
  setState: (next: TS | ((prev: TS) => TS)) => void;
47
63
  config: TC;
48
64
  setConfig: (key: keyof TC, value: TC[keyof TC]) => void;
49
65
  }
66
+ /** Options for `createLabStore`. `storageKey` namespaces the keys written, so
67
+ * two labs on one origin do not collide. */
50
68
  interface CreateLabStoreOptions {
51
69
  storageKey: string;
52
70
  storage: StorageAdapter;
53
71
  initialMode?: LabMode;
54
72
  }
73
+ /** Per-instrument serialize/deserialize hooks, keyed by instrument name. An
74
+ * instrument whose state is already JSON-safe needs no entry. */
55
75
  type InstrumentSerializers = Record<string, {
56
76
  serialize?: (state: unknown) => unknown;
57
77
  deserialize?: (data: unknown) => unknown;
58
78
  } | undefined>;
59
79
 
80
+ /** Persist to `localStorage` — state survives a reload and a new tab. Falls
81
+ * back to no-ops when storage is unavailable. */
60
82
  declare const localStorageAdapter: StorageAdapter;
83
+ /** Persist to `sessionStorage` — state survives a reload but not a new tab. */
61
84
  declare const sessionStorageAdapter: StorageAdapter;
85
+ /** Persist into the URL fragment, so the page's link carries its state and
86
+ * can be shared or bookmarked. */
62
87
  declare const urlHashAdapter: StorageAdapter;
88
+ /** An in-memory store, discarded on reload. For tests, and for labs that
89
+ * should start fresh every time. */
63
90
  declare function createMemoryAdapter(): StorageAdapter;
91
+ /** Persists nothing and reads back nothing. */
64
92
  declare const noneAdapter: StorageAdapter;
65
93
 
94
+ /** Every mutation a lab store supports: managing workspaces, saving and
95
+ * restoring snapshots, and setting the color mode. */
66
96
  interface LabStoreActions {
67
97
  addWorkspace: (record: Omit<WorkspaceRecord, 'undoStack'>) => void;
68
98
  removeWorkspace: (id: string) => void;
@@ -76,28 +106,45 @@ interface LabStoreActions {
76
106
  deleteSnapshot: (snapshotId: string) => void;
77
107
  listSnapshots: (workspaceId?: string) => SavedSnapshot[];
78
108
  setMode: (mode: LabMode) => void;
109
+ setLayout: (layout: Record<string, unknown>) => void;
79
110
  }
111
+ /** A lab's store: its state and actions, plus the hook instruments use to
112
+ * register how their state is serialized. */
80
113
  type LabStore = StoreApi<LabStoreState & LabStoreActions> & {
81
114
  registerSerializers: (s: InstrumentSerializers) => void;
82
115
  };
116
+ /** Build a lab store, hydrating from storage if anything was saved under the
117
+ * same key. Writes back are debounced. */
83
118
  declare function createLabStore(options: CreateLabStoreOptions): LabStore;
84
119
 
85
120
  type LabStoreCtx = {
86
121
  store: LabStore;
87
122
  } | null;
123
+ /** Context carrying the lab store. Prefer `useLabStore`; this is exported for
124
+ * code that needs to read the context without subscribing. */
88
125
  declare const LabStoreContext: react.Context<LabStoreCtx>;
126
+ /** Provides a lab store to its subtree. */
89
127
  declare function LabStoreProvider({ store, children, }: {
90
128
  store: LabStore;
91
129
  children: ReactNode;
92
130
  }): ReactElement;
131
+ /** Subscribe to the whole lab store. Throws outside a `<LabStoreProvider>`. */
93
132
  declare function useLabStore(): LabStoreState & ReturnType<LabStore['getState']>;
133
+ /** Context carrying which workspace the subtree belongs to. */
94
134
  declare const WorkspaceIdContext: react.Context<string | null>;
135
+ /** Names the workspace its subtree belongs to, so an instrument's hooks can
136
+ * find their own record in the store without being passed an id. */
95
137
  declare function WorkspaceIdProvider({ workspaceId, children, }: {
96
138
  workspaceId: string;
97
139
  children: ReactNode;
98
140
  }): ReactElement;
141
+ /** The id of the workspace this component is inside. Throws outside a
142
+ * `<WorkspaceIdProvider>`. */
99
143
  declare function useWorkspaceId(): string;
100
144
 
145
+ /** An instrument's own state and config, plus setters. Reads the surrounding
146
+ * workspace id, so an instrument never has to know which workspace it is
147
+ * running in. Throws outside a lab store and workspace. */
101
148
  declare function useExperimentState<TS = unknown, TC = unknown>(): ExperimentStateHandle<TS, TC>;
102
149
 
103
150
  export { LabStoreContext as b, LabStoreProvider as c, WorkspaceIdContext as f, WorkspaceIdProvider as g, createLabStore as h, createMemoryAdapter as i, useExperimentState as j, useLabStore as k, localStorageAdapter as l, useWorkspaceId as m, noneAdapter as n, sessionStorageAdapter as s, urlHashAdapter as u };
@@ -1,15 +1,18 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as react from 'react';
3
3
  import { ReactNode } from 'react';
4
- import { V as ViewTransform, d as Point } from '../_dts/index-C6Yze7sQ.js';
5
- import '../_dts/types-Si4Fw-1F.js';
4
+ import { V as ViewTransform, d as Point } from '../_dts/index-CFlDPeZh.js';
5
+ import '../_dts/types-x92Kfeme.js';
6
6
 
7
+ /** One layer of a canvas stack: its id, whether it is currently shown, and how
8
+ * it paints itself. */
7
9
  interface CanvasLayerDescriptor {
8
10
  id: string;
9
11
  visible: boolean;
10
12
  render: (ctx: CanvasRenderingContext2D, view: ViewTransform) => void;
11
13
  }
12
14
 
15
+ /** Props for `<CanvasStack>`. */
13
16
  interface CanvasStackProps {
14
17
  layers: CanvasLayerDescriptor[];
15
18
  view: ViewTransform;
@@ -20,14 +23,22 @@ interface CanvasStackProps {
20
23
  onHitTest?: (worldPos: Point) => void;
21
24
  children?: ReactNode;
22
25
  }
26
+ /** Stacks one `<canvas>` per layer and drives them from a shared view, so a
27
+ * layer that changes rarely is not redrawn with one that changes every frame.
28
+ * Handles sizing, device pixel ratio, and pan/zoom. */
23
29
  declare function CanvasStack({ layers, view, onViewChange, width, height, className, onHitTest, children, }: CanvasStackProps): react_jsx_runtime.JSX.Element;
24
30
 
31
+ /** What a canvas stack publishes to its children — currently the view, so
32
+ * DOM overlays can position themselves in the same coordinates. */
25
33
  interface CanvasStackContextValue {
26
34
  view: ViewTransform;
27
35
  }
36
+ /** Context carrying the surrounding canvas stack's view. */
28
37
  declare const CanvasStackContext: react.Context<CanvasStackContextValue | null>;
29
38
 
39
+ /** Project a world point into screen coordinates under a view. */
30
40
  declare function worldToScreen(world: Point, view: ViewTransform): Point;
41
+ /** Unproject a screen point back to world coordinates under a view. */
31
42
  declare function screenToWorld(screen: Point, view: ViewTransform): Point;
32
43
 
33
44
  export { CanvasStack, CanvasStackContext, screenToWorld, worldToScreen };
@@ -1,4 +1,4 @@
1
- export { CanvasStack, screenToWorld, worldToScreen } from '../chunk-54IQ2DX7.js';
2
- export { CanvasStackContext } from '../chunk-PWC7AQZM.js';
1
+ export { CanvasStack, screenToWorld, worldToScreen } from '../chunk-574LJAV4.js';
2
+ export { CanvasStackContext } from '../chunk-CPUJ3QXL.js';
3
3
  //# sourceMappingURL=index.js.map
4
4
  //# sourceMappingURL=index.js.map
@@ -1,4 +1,4 @@
1
- import { CanvasStackContext } from './chunk-PWC7AQZM.js';
1
+ import { CanvasStackContext } from './chunk-CPUJ3QXL.js';
2
2
  import { useRef, useState, useEffect, useMemo, useCallback } from 'react';
3
3
  import { jsx, jsxs } from 'react/jsx-runtime';
4
4
 
@@ -218,5 +218,5 @@ function CanvasStack({
218
218
  }
219
219
 
220
220
  export { CanvasStack, screenToWorld, worldToScreen };
221
- //# sourceMappingURL=chunk-54IQ2DX7.js.map
222
- //# sourceMappingURL=chunk-54IQ2DX7.js.map
221
+ //# sourceMappingURL=chunk-574LJAV4.js.map
222
+ //# sourceMappingURL=chunk-574LJAV4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/canvas/canvasCoords.ts","../src/canvas/useLayerScheduler.ts","../src/canvas/usePanZoom.ts","../src/canvas/CanvasStack.tsx"],"names":["useRef","useEffect"],"mappings":"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","file":"chunk-574LJAV4.js","sourcesContent":["import type { Point, ViewTransform } from '../instrument/types';\n\n/** Project a world point into screen coordinates under a view. */\nexport function worldToScreen(world: Point, view: ViewTransform): Point {\n return {\n x: world.x * view.zoom + view.pan.x,\n y: world.y * view.zoom + view.pan.y,\n };\n}\n\n/** Unproject a screen point back to world coordinates under a view. */\nexport function screenToWorld(screen: Point, view: ViewTransform): Point {\n return {\n x: (screen.x - view.pan.x) / view.zoom,\n y: (screen.y - view.pan.y) / view.zoom,\n };\n}\n","import { type RefObject, useEffect, useRef } from 'react';\nimport type { ViewTransform } from '../instrument/types';\n\n/** One layer of a canvas stack: its id, whether it is currently shown, and how\n * it paints itself. */\nexport interface CanvasLayerDescriptor {\n id: string;\n visible: boolean;\n render: (ctx: CanvasRenderingContext2D, view: ViewTransform) => void;\n}\n\ninterface SchedulerOptions {\n layers: CanvasLayerDescriptor[];\n view: ViewTransform;\n canvasRefs: RefObject<Map<string, HTMLCanvasElement>>;\n size: { width: number; height: number; dpr: number };\n}\n\nexport function useLayerScheduler({ layers, view, canvasRefs, size }: SchedulerOptions): void {\n const dirty = useRef<Set<string>>(new Set());\n const rafId = useRef<number | null>(null);\n const lastRenderRef = useRef<Map<string, CanvasLayerDescriptor['render']>>(new Map());\n\n useEffect(() => {\n for (const layer of layers) {\n const prev = lastRenderRef.current.get(layer.id);\n if (prev !== layer.render) dirty.current.add(layer.id);\n lastRenderRef.current.set(layer.id, layer.render);\n }\n for (const id of [...lastRenderRef.current.keys()]) {\n if (!layers.find((l) => l.id === id)) lastRenderRef.current.delete(id);\n }\n }, [layers]);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: re-mark dirty when view or size changes\n useEffect(() => {\n for (const layer of layers) dirty.current.add(layer.id);\n }, [view, size, layers]);\n\n useEffect(() => {\n const tick = () => {\n rafId.current = null;\n if (dirty.current.size === 0) return;\n const map = canvasRefs.current;\n if (!map) {\n dirty.current.clear();\n return;\n }\n for (const layer of layers) {\n if (!dirty.current.has(layer.id)) continue;\n if (!layer.visible) continue;\n const canvas = map.get(layer.id);\n if (!canvas) continue;\n const ctx = canvas.getContext('2d');\n if (!ctx) continue;\n ctx.save();\n ctx.setTransform(size.dpr, 0, 0, size.dpr, 0, 0);\n ctx.clearRect(0, 0, size.width, size.height);\n layer.render(ctx, view);\n ctx.restore();\n }\n dirty.current.clear();\n };\n if (dirty.current.size > 0 && rafId.current === null) {\n rafId.current = requestAnimationFrame(tick);\n }\n return () => {\n if (rafId.current !== null) {\n cancelAnimationFrame(rafId.current);\n rafId.current = null;\n }\n };\n });\n}\n","import { type PointerEvent, useCallback, useRef, type WheelEvent } from 'react';\nimport type { ViewTransform } from '../instrument/types';\n\nexport interface UsePanZoomOptions {\n view: ViewTransform;\n onViewChange: (v: ViewTransform) => void;\n minZoom?: number;\n maxZoom?: number;\n}\n\nexport interface PanZoomHandlers {\n onWheel: (e: WheelEvent<HTMLElement>) => void;\n onPointerDown: (e: PointerEvent<HTMLElement>) => void;\n onPointerMove: (e: PointerEvent<HTMLElement>) => void;\n onPointerUp: (e: PointerEvent<HTMLElement>) => void;\n isDragging: () => boolean;\n}\n\ninterface DragState {\n pointerId: number;\n startScreenX: number;\n startScreenY: number;\n startPan: { x: number; y: number };\n moved: boolean;\n}\n\nconst DRAG_THRESHOLD = 3;\n\nexport function usePanZoom({\n view,\n onViewChange,\n minZoom = 0.1,\n maxZoom = 32,\n}: UsePanZoomOptions): PanZoomHandlers {\n const dragRef = useRef<DragState | null>(null);\n const viewRef = useRef(view);\n viewRef.current = view;\n\n const onWheel = useCallback(\n (e: WheelEvent<HTMLElement>) => {\n e.preventDefault();\n const rect = e.currentTarget.getBoundingClientRect();\n const cursorX = e.clientX - rect.left;\n const cursorY = e.clientY - rect.top;\n const v = viewRef.current;\n const factor = Math.exp(-e.deltaY * 0.001);\n const nextZoom = Math.min(maxZoom, Math.max(minZoom, v.zoom * factor));\n const ratio = nextZoom / v.zoom;\n const nextPan = {\n x: cursorX - (cursorX - v.pan.x) * ratio,\n y: cursorY - (cursorY - v.pan.y) * ratio,\n };\n onViewChange({ zoom: nextZoom, pan: nextPan });\n },\n [onViewChange, minZoom, maxZoom],\n );\n\n const onPointerDown = useCallback((e: PointerEvent<HTMLElement>) => {\n if (e.button !== 0) return;\n e.currentTarget.setPointerCapture(e.pointerId);\n dragRef.current = {\n pointerId: e.pointerId,\n startScreenX: e.clientX,\n startScreenY: e.clientY,\n startPan: { ...viewRef.current.pan },\n moved: false,\n };\n }, []);\n\n const onPointerMove = useCallback(\n (e: PointerEvent<HTMLElement>) => {\n const drag = dragRef.current;\n if (!drag || drag.pointerId !== e.pointerId) return;\n const dx = e.clientX - drag.startScreenX;\n const dy = e.clientY - drag.startScreenY;\n if (!drag.moved && Math.hypot(dx, dy) < DRAG_THRESHOLD) return;\n drag.moved = true;\n onViewChange({\n zoom: viewRef.current.zoom,\n pan: { x: drag.startPan.x + dx, y: drag.startPan.y + dy },\n });\n },\n [onViewChange],\n );\n\n const onPointerUp = useCallback((e: PointerEvent<HTMLElement>) => {\n const drag = dragRef.current;\n if (!drag || drag.pointerId !== e.pointerId) return;\n if (e.currentTarget.hasPointerCapture(e.pointerId)) {\n e.currentTarget.releasePointerCapture(e.pointerId);\n }\n dragRef.current = null;\n }, []);\n\n const isDragging = useCallback(() => dragRef.current?.moved === true, []);\n\n return { onWheel, onPointerDown, onPointerMove, onPointerUp, isDragging };\n}\n","import { type CSSProperties, type ReactNode, useEffect, useMemo, useRef, useState } from 'react';\nimport type { Point, ViewTransform } from '../instrument/types';\nimport { CanvasStackContext } from './CanvasStackContext';\nimport { screenToWorld } from './canvasCoords';\nimport { type CanvasLayerDescriptor, useLayerScheduler } from './useLayerScheduler';\nimport { usePanZoom } from './usePanZoom';\n\n/** Props for `<CanvasStack>`. */\nexport interface CanvasStackProps {\n layers: CanvasLayerDescriptor[];\n view: ViewTransform;\n onViewChange: (v: ViewTransform) => void;\n width?: number | string;\n height?: number | string;\n className?: string;\n onHitTest?: (worldPos: Point) => void;\n children?: ReactNode;\n}\n\n/** Stacks one `<canvas>` per layer and drives them from a shared view, so a\n * layer that changes rarely is not redrawn with one that changes every frame.\n * Handles sizing, device pixel ratio, and pan/zoom. */\nexport function CanvasStack({\n layers,\n view,\n onViewChange,\n width = '100%',\n height = '100%',\n className,\n onHitTest,\n children,\n}: CanvasStackProps) {\n const containerRef = useRef<HTMLDivElement | null>(null);\n const canvasMap = useRef<Map<string, HTMLCanvasElement>>(new Map());\n const [size, setSize] = useState({ width: 0, height: 0, dpr: 1 });\n\n const setCanvasRef = (id: string, el: HTMLCanvasElement | null): void => {\n if (el) canvasMap.current.set(id, el);\n else canvasMap.current.delete(id);\n };\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n const update = () => {\n const rect = el.getBoundingClientRect();\n const dpr = window.devicePixelRatio ?? 1;\n setSize((prev) => {\n if (prev.width === rect.width && prev.height === rect.height && prev.dpr === dpr) {\n return prev;\n }\n return { width: rect.width, height: rect.height, dpr };\n });\n };\n update();\n if (typeof ResizeObserver === 'undefined') return;\n const ro = new ResizeObserver(update);\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n\n const handlers = usePanZoom({ view, onViewChange });\n useLayerScheduler({ layers, view, canvasRefs: canvasMap, size });\n\n const ctxValue = useMemo(() => ({ view }), [view]);\n\n const containerStyle: CSSProperties = { width, height };\n const canvasPx = {\n width: Math.max(0, Math.round(size.width * size.dpr)),\n height: Math.max(0, Math.round(size.height * size.dpr)),\n };\n const canvasCss: CSSProperties = {\n width: size.width,\n height: size.height,\n };\n\n const handlePointerUp = (e: React.PointerEvent<HTMLDivElement>) => {\n const wasDragging = handlers.isDragging();\n handlers.onPointerUp(e);\n if (!wasDragging && onHitTest && containerRef.current) {\n const rect = containerRef.current.getBoundingClientRect();\n const screen = { x: e.clientX - rect.left, y: e.clientY - rect.top };\n onHitTest(screenToWorld(screen, view));\n }\n };\n\n return (\n <CanvasStackContext.Provider value={ctxValue}>\n <div\n ref={containerRef}\n className={className ? `lk-canvas-stack ${className}` : 'lk-canvas-stack'}\n style={containerStyle}\n onWheel={handlers.onWheel}\n onPointerDown={handlers.onPointerDown}\n onPointerMove={handlers.onPointerMove}\n onPointerUp={handlePointerUp}\n >\n {layers.map((layer) => (\n <canvas\n key={layer.id}\n ref={(el) => setCanvasRef(layer.id, el)}\n className=\"lk-canvas-stack__canvas\"\n width={canvasPx.width}\n height={canvasPx.height}\n style={{ ...canvasCss, display: layer.visible ? 'block' : 'none' }}\n />\n ))}\n <div className=\"lk-canvas-stack__overlay\">{children}</div>\n </div>\n </CanvasStackContext.Provider>\n );\n}\n"]}
@@ -197,5 +197,5 @@ function ColorRow({
197
197
  }
198
198
 
199
199
  export { ControlPanel };
200
- //# sourceMappingURL=chunk-VLAHRJOC.js.map
201
- //# sourceMappingURL=chunk-VLAHRJOC.js.map
200
+ //# sourceMappingURL=chunk-5R2ATYPJ.js.map
201
+ //# sourceMappingURL=chunk-5R2ATYPJ.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/controls/ControlPanel.tsx"],"names":[],"mappings":"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","file":"chunk-VLAHRJOC.js","sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport type {\n CheckboxField,\n ColorField,\n ConfigField,\n NumberField,\n SelectField,\n SliderField,\n TextField,\n} from './types';\n\nexport interface ControlPanelProps<TC extends Record<string, unknown>> {\n fields: ConfigField[];\n config: TC;\n setConfig: (key: keyof TC, value: unknown) => void;\n className?: string;\n}\n\nexport function ControlPanel<TC extends Record<string, unknown>>({\n fields,\n config,\n setConfig,\n className,\n}: ControlPanelProps<TC>) {\n return (\n <div className={className ? `lk-control-panel ${className}` : 'lk-control-panel'}>\n {fields.map((field) => (\n <ControlRow key={field.key} field={field} config={config} setConfig={setConfig} />\n ))}\n </div>\n );\n}\n\ninterface ControlRowProps<TC extends Record<string, unknown>> {\n field: ConfigField;\n config: TC;\n setConfig: (key: keyof TC, value: unknown) => void;\n}\n\nfunction ControlRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: ControlRowProps<TC>) {\n switch (field.type) {\n case 'slider':\n return <SliderRow field={field} config={config} setConfig={setConfig} />;\n case 'checkbox':\n return <CheckboxRow field={field} config={config} setConfig={setConfig} />;\n case 'select':\n return <SelectRow field={field} config={config} setConfig={setConfig} />;\n case 'number':\n return <NumberRow field={field} config={config} setConfig={setConfig} />;\n case 'text':\n return <TextRow field={field} config={config} setConfig={setConfig} />;\n case 'color':\n return <ColorRow field={field} config={config} setConfig={setConfig} />;\n default:\n return null;\n }\n}\n\ntype RowProps<F extends ConfigField, TC extends Record<string, unknown>> = {\n field: F;\n config: TC;\n setConfig: (key: keyof TC, value: unknown) => void;\n};\n\nfunction SliderRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: RowProps<SliderField, TC>) {\n const value = (config[field.key] as number | undefined) ?? field.default;\n return (\n <div className=\"lk-control-row\">\n <label className=\"lk-control-label\" htmlFor={`lk-ctrl-${field.key}`}>\n {field.label}\n </label>\n <div className=\"lk-control-slider-row\">\n <input\n id={`lk-ctrl-${field.key}`}\n className=\"lk-control-input lk-control-input--range\"\n type=\"range\"\n min={field.min}\n max={field.max}\n step={field.step ?? 1}\n value={value}\n onChange={(e) => setConfig(field.key as keyof TC, Number(e.target.value))}\n />\n <span className=\"lk-control-slider-value\">{value}</span>\n </div>\n </div>\n );\n}\n\nfunction CheckboxRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: RowProps<CheckboxField, TC>) {\n const value = (config[field.key] as boolean | undefined) ?? field.default;\n return (\n <div className=\"lk-control-row lk-control-row--inline\">\n <input\n id={`lk-ctrl-${field.key}`}\n className=\"lk-control-input lk-control-input--checkbox\"\n type=\"checkbox\"\n checked={value}\n onChange={(e) => setConfig(field.key as keyof TC, e.target.checked)}\n />\n <label className=\"lk-control-label\" htmlFor={`lk-ctrl-${field.key}`}>\n {field.label}\n </label>\n </div>\n );\n}\n\nfunction SelectRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: RowProps<SelectField, TC>) {\n const value = (config[field.key] as string | undefined) ?? field.default;\n return (\n <div className=\"lk-control-row\">\n <label className=\"lk-control-label\" htmlFor={`lk-ctrl-${field.key}`}>\n {field.label}\n </label>\n <select\n id={`lk-ctrl-${field.key}`}\n className=\"lk-control-input\"\n value={value}\n onChange={(e) => setConfig(field.key as keyof TC, e.target.value)}\n >\n {field.options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </div>\n );\n}\n\nfunction NumberRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: RowProps<NumberField, TC>) {\n const value = (config[field.key] as number | undefined) ?? field.default;\n return (\n <div className=\"lk-control-row\">\n <label className=\"lk-control-label\" htmlFor={`lk-ctrl-${field.key}`}>\n {field.label}\n </label>\n <input\n id={`lk-ctrl-${field.key}`}\n className=\"lk-control-input\"\n type=\"number\"\n value={value}\n min={field.min}\n max={field.max}\n step={field.step}\n onChange={(e) => setConfig(field.key as keyof TC, Number(e.target.value))}\n onBlur={(e) => {\n const n = Number(e.target.value);\n if (!Number.isFinite(n)) return;\n let clamped = n;\n if (field.min !== undefined && clamped < field.min) clamped = field.min;\n if (field.max !== undefined && clamped > field.max) clamped = field.max;\n if (clamped !== n) setConfig(field.key as keyof TC, clamped);\n }}\n />\n </div>\n );\n}\n\nfunction TextRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: RowProps<TextField, TC>) {\n const debounceMs = field.debounceMs ?? 150;\n const initial = (config[field.key] as string | undefined) ?? field.default;\n const [local, setLocal] = useState(initial);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const lastExternal = useRef(initial);\n\n useEffect(() => {\n const ext = (config[field.key] as string | undefined) ?? field.default;\n if (ext !== lastExternal.current) {\n lastExternal.current = ext;\n setLocal(ext);\n }\n }, [config, field.key, field.default]);\n\n return (\n <div className=\"lk-control-row\">\n <label className=\"lk-control-label\" htmlFor={`lk-ctrl-${field.key}`}>\n {field.label}\n </label>\n <input\n id={`lk-ctrl-${field.key}`}\n className=\"lk-control-input\"\n type=\"text\"\n value={local}\n placeholder={field.placeholder}\n maxLength={field.maxLength}\n onChange={(e) => {\n const next = e.target.value;\n setLocal(next);\n if (timer.current) clearTimeout(timer.current);\n if (debounceMs === 0) {\n lastExternal.current = next;\n setConfig(field.key as keyof TC, next);\n } else {\n timer.current = setTimeout(() => {\n lastExternal.current = next;\n setConfig(field.key as keyof TC, next);\n }, debounceMs);\n }\n }}\n />\n </div>\n );\n}\n\nfunction ColorRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: RowProps<ColorField, TC>) {\n const value = (config[field.key] as string | undefined) ?? field.default;\n return (\n <div className=\"lk-control-row lk-control-row--inline\">\n <label className=\"lk-control-label\" htmlFor={`lk-ctrl-${field.key}`}>\n {field.label}\n </label>\n <input\n id={`lk-ctrl-${field.key}`}\n className=\"lk-control-input lk-control-input--color\"\n type=\"color\"\n value={value}\n onChange={(e) => setConfig(field.key as keyof TC, e.target.value)}\n />\n </div>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/controls/ControlPanel.tsx"],"names":[],"mappings":"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","file":"chunk-5R2ATYPJ.js","sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport type {\n CheckboxField,\n ColorField,\n ConfigField,\n NumberField,\n SelectField,\n SliderField,\n TextField,\n} from './types';\n\nexport interface ControlPanelProps<TC extends Record<string, unknown>> {\n fields: ConfigField[];\n config: TC;\n setConfig: (key: keyof TC, value: unknown) => void;\n className?: string;\n}\n\n/** Render an instrument's config schema as a stack of controls, each writing\n * back through `setConfig`. */\nexport function ControlPanel<TC extends Record<string, unknown>>({\n fields,\n config,\n setConfig,\n className,\n}: ControlPanelProps<TC>) {\n return (\n <div className={className ? `lk-control-panel ${className}` : 'lk-control-panel'}>\n {fields.map((field) => (\n <ControlRow key={field.key} field={field} config={config} setConfig={setConfig} />\n ))}\n </div>\n );\n}\n\ninterface ControlRowProps<TC extends Record<string, unknown>> {\n field: ConfigField;\n config: TC;\n setConfig: (key: keyof TC, value: unknown) => void;\n}\n\nfunction ControlRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: ControlRowProps<TC>) {\n switch (field.type) {\n case 'slider':\n return <SliderRow field={field} config={config} setConfig={setConfig} />;\n case 'checkbox':\n return <CheckboxRow field={field} config={config} setConfig={setConfig} />;\n case 'select':\n return <SelectRow field={field} config={config} setConfig={setConfig} />;\n case 'number':\n return <NumberRow field={field} config={config} setConfig={setConfig} />;\n case 'text':\n return <TextRow field={field} config={config} setConfig={setConfig} />;\n case 'color':\n return <ColorRow field={field} config={config} setConfig={setConfig} />;\n default:\n return null;\n }\n}\n\ntype RowProps<F extends ConfigField, TC extends Record<string, unknown>> = {\n field: F;\n config: TC;\n setConfig: (key: keyof TC, value: unknown) => void;\n};\n\nfunction SliderRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: RowProps<SliderField, TC>) {\n const value = (config[field.key] as number | undefined) ?? field.default;\n return (\n <div className=\"lk-control-row\">\n <label className=\"lk-control-label\" htmlFor={`lk-ctrl-${field.key}`}>\n {field.label}\n </label>\n <div className=\"lk-control-slider-row\">\n <input\n id={`lk-ctrl-${field.key}`}\n className=\"lk-control-input lk-control-input--range\"\n type=\"range\"\n min={field.min}\n max={field.max}\n step={field.step ?? 1}\n value={value}\n onChange={(e) => setConfig(field.key as keyof TC, Number(e.target.value))}\n />\n <span className=\"lk-control-slider-value\">{value}</span>\n </div>\n </div>\n );\n}\n\nfunction CheckboxRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: RowProps<CheckboxField, TC>) {\n const value = (config[field.key] as boolean | undefined) ?? field.default;\n return (\n <div className=\"lk-control-row lk-control-row--inline\">\n <input\n id={`lk-ctrl-${field.key}`}\n className=\"lk-control-input lk-control-input--checkbox\"\n type=\"checkbox\"\n checked={value}\n onChange={(e) => setConfig(field.key as keyof TC, e.target.checked)}\n />\n <label className=\"lk-control-label\" htmlFor={`lk-ctrl-${field.key}`}>\n {field.label}\n </label>\n </div>\n );\n}\n\nfunction SelectRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: RowProps<SelectField, TC>) {\n const value = (config[field.key] as string | undefined) ?? field.default;\n return (\n <div className=\"lk-control-row\">\n <label className=\"lk-control-label\" htmlFor={`lk-ctrl-${field.key}`}>\n {field.label}\n </label>\n <select\n id={`lk-ctrl-${field.key}`}\n className=\"lk-control-input\"\n value={value}\n onChange={(e) => setConfig(field.key as keyof TC, e.target.value)}\n >\n {field.options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </div>\n );\n}\n\nfunction NumberRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: RowProps<NumberField, TC>) {\n const value = (config[field.key] as number | undefined) ?? field.default;\n return (\n <div className=\"lk-control-row\">\n <label className=\"lk-control-label\" htmlFor={`lk-ctrl-${field.key}`}>\n {field.label}\n </label>\n <input\n id={`lk-ctrl-${field.key}`}\n className=\"lk-control-input\"\n type=\"number\"\n value={value}\n min={field.min}\n max={field.max}\n step={field.step}\n onChange={(e) => setConfig(field.key as keyof TC, Number(e.target.value))}\n onBlur={(e) => {\n const n = Number(e.target.value);\n if (!Number.isFinite(n)) return;\n let clamped = n;\n if (field.min !== undefined && clamped < field.min) clamped = field.min;\n if (field.max !== undefined && clamped > field.max) clamped = field.max;\n if (clamped !== n) setConfig(field.key as keyof TC, clamped);\n }}\n />\n </div>\n );\n}\n\nfunction TextRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: RowProps<TextField, TC>) {\n const debounceMs = field.debounceMs ?? 150;\n const initial = (config[field.key] as string | undefined) ?? field.default;\n const [local, setLocal] = useState(initial);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const lastExternal = useRef(initial);\n\n useEffect(() => {\n const ext = (config[field.key] as string | undefined) ?? field.default;\n if (ext !== lastExternal.current) {\n lastExternal.current = ext;\n setLocal(ext);\n }\n }, [config, field.key, field.default]);\n\n return (\n <div className=\"lk-control-row\">\n <label className=\"lk-control-label\" htmlFor={`lk-ctrl-${field.key}`}>\n {field.label}\n </label>\n <input\n id={`lk-ctrl-${field.key}`}\n className=\"lk-control-input\"\n type=\"text\"\n value={local}\n placeholder={field.placeholder}\n maxLength={field.maxLength}\n onChange={(e) => {\n const next = e.target.value;\n setLocal(next);\n if (timer.current) clearTimeout(timer.current);\n if (debounceMs === 0) {\n lastExternal.current = next;\n setConfig(field.key as keyof TC, next);\n } else {\n timer.current = setTimeout(() => {\n lastExternal.current = next;\n setConfig(field.key as keyof TC, next);\n }, debounceMs);\n }\n }}\n />\n </div>\n );\n}\n\nfunction ColorRow<TC extends Record<string, unknown>>({\n field,\n config,\n setConfig,\n}: RowProps<ColorField, TC>) {\n const value = (config[field.key] as string | undefined) ?? field.default;\n return (\n <div className=\"lk-control-row lk-control-row--inline\">\n <label className=\"lk-control-label\" htmlFor={`lk-ctrl-${field.key}`}>\n {field.label}\n </label>\n <input\n id={`lk-ctrl-${field.key}`}\n className=\"lk-control-input lk-control-input--color\"\n type=\"color\"\n value={value}\n onChange={(e) => setConfig(field.key as keyof TC, e.target.value)}\n />\n </div>\n );\n}\n"]}