@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.
- package/dist/_dts/{DrawCommand-CitWPxMY.d.ts → DrawCommand-BkZztJsW.d.ts} +59 -1
- package/dist/_dts/{index-C6Yze7sQ.d.ts → index-CFlDPeZh.d.ts} +30 -1
- package/dist/_dts/{types-Si4Fw-1F.d.ts → types-x92Kfeme.d.ts} +14 -0
- package/dist/_dts/{useExperimentState-CJn2hHzd.d.ts → useExperimentState-D7EQnnwJ.d.ts} +47 -0
- package/dist/canvas/index.d.ts +13 -2
- package/dist/canvas/index.js +2 -2
- package/dist/{chunk-54IQ2DX7.js → chunk-574LJAV4.js} +3 -3
- package/dist/chunk-574LJAV4.js.map +1 -0
- package/dist/{chunk-VLAHRJOC.js → chunk-5R2ATYPJ.js} +2 -2
- package/dist/{chunk-VLAHRJOC.js.map → chunk-5R2ATYPJ.js.map} +1 -1
- package/dist/{chunk-R4TACNW7.js → chunk-BAPZPDDA.js} +631 -591
- package/dist/chunk-BAPZPDDA.js.map +1 -0
- package/dist/{chunk-PWC7AQZM.js → chunk-CPUJ3QXL.js} +2 -2
- package/dist/chunk-CPUJ3QXL.js.map +1 -0
- package/dist/{chunk-2ZRE7WGQ.js → chunk-DEWXYFEU.js} +6 -6
- package/dist/chunk-DEWXYFEU.js.map +1 -0
- package/dist/{chunk-3P56ZCCJ.js → chunk-DWV7SFKR.js} +601 -269
- package/dist/chunk-DWV7SFKR.js.map +1 -0
- package/dist/{chunk-53XSBIUK.js → chunk-KSTEW2AF.js} +18 -2
- package/dist/chunk-KSTEW2AF.js.map +1 -0
- package/dist/{chunk-HXZHVU4G.js → chunk-PMAU3SEE.js} +3 -3
- package/dist/chunk-PMAU3SEE.js.map +1 -0
- package/dist/{chunk-7BKDG73Z.js → chunk-RL2LOLNI.js} +2 -2
- package/dist/chunk-RL2LOLNI.js.map +1 -0
- package/dist/{chunk-2QNYYL3V.js → chunk-SFL7NFKN.js} +2 -2
- package/dist/chunk-SFL7NFKN.js.map +1 -0
- package/dist/controls/index.d.ts +4 -2
- package/dist/controls/index.js +1 -1
- package/dist/dragdrop/index.d.ts +2 -2
- package/dist/index.d.ts +180 -15
- package/dist/index.js +146 -37
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +6 -3
- package/dist/layers/index.js +1 -1
- package/dist/passthrough/weasel-canvas.d.ts +5 -1
- package/dist/passthrough/weasel-canvas.js +1 -1
- package/dist/passthrough/weasel-ui.d.ts +444 -7
- package/dist/passthrough/weasel-ui.js +2 -2
- package/dist/primitives/index.d.ts +13 -0
- package/dist/primitives/index.js +2 -2
- package/dist/state/index.d.ts +16 -3
- package/dist/state/index.js +2 -2
- package/dist/state/index.js.map +1 -1
- package/dist/styles.css +163 -6
- package/dist/ui/layers/index.d.ts +5 -0
- package/dist/ui/layers/index.js +3 -3
- package/dist/undo/index.d.ts +17 -2
- package/dist/undo/index.js +1 -1
- package/package.json +2 -1
- package/src/canvas/CanvasStack.tsx +4 -0
- package/src/canvas/CanvasStackContext.ts +3 -0
- package/src/canvas/canvasCoords.ts +2 -0
- package/src/canvas/useLayerScheduler.ts +2 -0
- package/src/controls/ControlPanel.tsx +2 -0
- package/src/controls/types.ts +14 -0
- package/src/instrument/capabilityDetector.ts +3 -0
- package/src/instrument/defineInstrument.ts +2 -0
- package/src/instrument/types.ts +29 -0
- package/src/instrument/validateConfigSchema.ts +5 -0
- package/src/lab/Lab.tsx +17 -2
- package/src/lab/LabContext.ts +6 -0
- package/src/lab/LabShell.tsx +4 -0
- package/src/lab/WorkspaceGrid.less +37 -5
- package/src/lab/WorkspaceGrid.stories.tsx +16 -0
- package/src/lab/WorkspaceGrid.test.tsx +84 -10
- package/src/lab/WorkspaceGrid.tsx +188 -12
- package/src/lab/index.ts +0 -2
- package/src/layers/LayerList.tsx +3 -0
- package/src/primitives/FpsMeter.tsx +1 -0
- package/src/primitives/ScaleIndicator.tsx +4 -0
- package/src/primitives/Sidebar.tsx +3 -0
- package/src/primitives/StatusBar.tsx +3 -0
- package/src/primitives/Toolbar.tsx +6 -0
- package/src/state/SingletonExperiment.tsx +1 -0
- package/src/state/adapters.ts +8 -0
- package/src/state/context.tsx +9 -0
- package/src/state/helpers.ts +13 -1
- package/src/state/store.test.ts +2 -1
- package/src/state/store.ts +25 -0
- package/src/state/types.ts +20 -0
- package/src/state/useExperimentState.ts +3 -0
- package/src/test-setup.ts +19 -0
- package/src/ui/layers/LayerStack.tsx +5 -0
- package/src/ui/properties/CurveField.tsx +4 -1
- package/src/ui/properties/EffectCard.tsx +12 -0
- package/src/ui/properties/PropertyGroup.tsx +1 -0
- package/src/ui/properties/PropertyPanel.tsx +28 -0
- package/src/undo/eventBus.ts +4 -0
- package/src/undo/undoStack.ts +11 -0
- package/src/workspace/DefaultSidebar.tsx +3 -0
- package/src/workspace/DefaultStatusBar.tsx +3 -0
- package/src/workspace/DefaultToolbar.tsx +3 -0
- package/src/workspace/Workspace.stories.tsx +1 -0
- package/src/workspace/Workspace.test.tsx +1 -0
- package/src/workspace/Workspace.tsx +3 -0
- package/src/workspace/WorkspaceChrome.tsx +4 -0
- package/src/workspace/index.ts +1 -0
- package/src/workspace/slotTypes.ts +9 -0
- package/src/workspace/workspaceOps.test.ts +34 -1
- package/src/workspace/workspaceOps.ts +24 -0
- package/dist/chunk-2QNYYL3V.js.map +0 -1
- package/dist/chunk-2ZRE7WGQ.js.map +0 -1
- package/dist/chunk-3P56ZCCJ.js.map +0 -1
- package/dist/chunk-53XSBIUK.js.map +0 -1
- package/dist/chunk-54IQ2DX7.js.map +0 -1
- package/dist/chunk-7BKDG73Z.js.map +0 -1
- package/dist/chunk-HXZHVU4G.js.map +0 -1
- package/dist/chunk-PWC7AQZM.js.map +0 -1
- package/dist/chunk-R4TACNW7.js.map +0 -1
- package/src/lab/gridDims.test.ts +0 -35
- 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)
|
|
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-
|
|
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 };
|
package/dist/canvas/index.d.ts
CHANGED
|
@@ -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-
|
|
5
|
-
import '../_dts/types-
|
|
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 };
|
package/dist/canvas/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { CanvasStack, screenToWorld, worldToScreen } from '../chunk-
|
|
2
|
-
export { CanvasStackContext } from '../chunk-
|
|
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-
|
|
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-
|
|
222
|
-
//# sourceMappingURL=chunk-
|
|
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"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/controls/ControlPanel.tsx"],"names":[],"mappings":";;;;AAkBO,SAAS,YAAA,CAAiD;AAAA,EAC/D,MAAA;AAAA,EACA,MAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF,CAAA,EAA0B;AACxB,EAAA,uBACE,GAAA,CAAC,SAAI,SAAA,EAAW,SAAA,GAAY,oBAAoB,SAAS,CAAA,CAAA,GAAK,oBAC3D,QAAA,EAAA,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,qBACX,GAAA,CAAC,cAA2B,KAAA,EAAc,MAAA,EAAgB,aAAzC,KAAA,CAAM,GAAyD,CACjF,CAAA,EACH,CAAA;AAEJ;AAQA,SAAS,UAAA,CAA+C;AAAA,EACtD,KAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,EAAwB;AACtB,EAAA,QAAQ,MAAM,IAAA;AAAM,IAClB,KAAK,QAAA;AACH,MAAA,uBAAO,GAAA,CAAC,SAAA,EAAA,EAAU,KAAA,EAAc,MAAA,EAAgB,SAAA,EAAsB,CAAA;AAAA,IACxE,KAAK,UAAA;AACH,MAAA,uBAAO,GAAA,CAAC,WAAA,EAAA,EAAY,KAAA,EAAc,MAAA,EAAgB,SAAA,EAAsB,CAAA;AAAA,IAC1E,KAAK,QAAA;AACH,MAAA,uBAAO,GAAA,CAAC,SAAA,EAAA,EAAU,KAAA,EAAc,MAAA,EAAgB,SAAA,EAAsB,CAAA;AAAA,IACxE,KAAK,QAAA;AACH,MAAA,uBAAO,GAAA,CAAC,SAAA,EAAA,EAAU,KAAA,EAAc,MAAA,EAAgB,SAAA,EAAsB,CAAA;AAAA,IACxE,KAAK,MAAA;AACH,MAAA,uBAAO,GAAA,CAAC,OAAA,EAAA,EAAQ,KAAA,EAAc,MAAA,EAAgB,SAAA,EAAsB,CAAA;AAAA,IACtE,KAAK,OAAA;AACH,MAAA,uBAAO,GAAA,CAAC,QAAA,EAAA,EAAS,KAAA,EAAc,MAAA,EAAgB,SAAA,EAAsB,CAAA;AAAA,IACvE;AACE,MAAA,OAAO,IAAA;AAAA;AAEb;AAQA,SAAS,SAAA,CAA8C;AAAA,EACrD,KAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,EAA8B;AAC5B,EAAA,MAAM,KAAA,GAAS,MAAA,CAAO,KAAA,CAAM,GAAG,KAA4B,KAAA,CAAM,OAAA;AACjE,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,gBAAA,EACb,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,OAAA,EAAA,EAAM,WAAU,kBAAA,EAAmB,OAAA,EAAS,WAAW,KAAA,CAAM,GAAG,CAAA,CAAA,EAC9D,QAAA,EAAA,KAAA,CAAM,KAAA,EACT,CAAA;AAAA,oBACA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,uBAAA,EACb,QAAA,EAAA;AAAA,sBAAA,GAAA;AAAA,QAAC,OAAA;AAAA,QAAA;AAAA,UACC,EAAA,EAAI,CAAA,QAAA,EAAW,KAAA,CAAM,GAAG,CAAA,CAAA;AAAA,UACxB,SAAA,EAAU,0CAAA;AAAA,UACV,IAAA,EAAK,OAAA;AAAA,UACL,KAAK,KAAA,CAAM,GAAA;AAAA,UACX,KAAK,KAAA,CAAM,GAAA;AAAA,UACX,IAAA,EAAM,MAAM,IAAA,IAAQ,CAAA;AAAA,UACpB,KAAA;AAAA,UACA,QAAA,EAAU,CAAC,CAAA,KAAM,SAAA,CAAU,KAAA,CAAM,KAAiB,MAAA,CAAO,CAAA,CAAE,MAAA,CAAO,KAAK,CAAC;AAAA;AAAA,OAC1E;AAAA,sBACA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,yBAAA,EAA2B,QAAA,EAAA,KAAA,EAAM;AAAA,KAAA,EACnD;AAAA,GAAA,EACF,CAAA;AAEJ;AAEA,SAAS,WAAA,CAAgD;AAAA,EACvD,KAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,EAAgC;AAC9B,EAAA,MAAM,KAAA,GAAS,MAAA,CAAO,KAAA,CAAM,GAAG,KAA6B,KAAA,CAAM,OAAA;AAClE,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,uCAAA,EACb,QAAA,EAAA;AAAA,oBAAA,GAAA;AAAA,MAAC,OAAA;AAAA,MAAA;AAAA,QACC,EAAA,EAAI,CAAA,QAAA,EAAW,KAAA,CAAM,GAAG,CAAA,CAAA;AAAA,QACxB,SAAA,EAAU,6CAAA;AAAA,QACV,IAAA,EAAK,UAAA;AAAA,QACL,OAAA,EAAS,KAAA;AAAA,QACT,QAAA,EAAU,CAAC,CAAA,KAAM,SAAA,CAAU,MAAM,GAAA,EAAiB,CAAA,CAAE,OAAO,OAAO;AAAA;AAAA,KACpE;AAAA,oBACA,GAAA,CAAC,OAAA,EAAA,EAAM,SAAA,EAAU,kBAAA,EAAmB,OAAA,EAAS,WAAW,KAAA,CAAM,GAAG,CAAA,CAAA,EAC9D,QAAA,EAAA,KAAA,CAAM,KAAA,EACT;AAAA,GAAA,EACF,CAAA;AAEJ;AAEA,SAAS,SAAA,CAA8C;AAAA,EACrD,KAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,EAA8B;AAC5B,EAAA,MAAM,KAAA,GAAS,MAAA,CAAO,KAAA,CAAM,GAAG,KAA4B,KAAA,CAAM,OAAA;AACjE,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,gBAAA,EACb,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,OAAA,EAAA,EAAM,WAAU,kBAAA,EAAmB,OAAA,EAAS,WAAW,KAAA,CAAM,GAAG,CAAA,CAAA,EAC9D,QAAA,EAAA,KAAA,CAAM,KAAA,EACT,CAAA;AAAA,oBACA,GAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,EAAA,EAAI,CAAA,QAAA,EAAW,KAAA,CAAM,GAAG,CAAA,CAAA;AAAA,QACxB,SAAA,EAAU,kBAAA;AAAA,QACV,KAAA;AAAA,QACA,QAAA,EAAU,CAAC,CAAA,KAAM,SAAA,CAAU,MAAM,GAAA,EAAiB,CAAA,CAAE,OAAO,KAAK,CAAA;AAAA,QAE/D,QAAA,EAAA,KAAA,CAAM,OAAA,CAAQ,GAAA,CAAI,CAAC,wBAClB,GAAA,CAAC,QAAA,EAAA,EAAuB,KAAA,EAAO,GAAA,CAAI,KAAA,EAChC,QAAA,EAAA,GAAA,CAAI,KAAA,EAAA,EADM,GAAA,CAAI,KAEjB,CACD;AAAA;AAAA;AACH,GAAA,EACF,CAAA;AAEJ;AAEA,SAAS,SAAA,CAA8C;AAAA,EACrD,KAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,EAA8B;AAC5B,EAAA,MAAM,KAAA,GAAS,MAAA,CAAO,KAAA,CAAM,GAAG,KAA4B,KAAA,CAAM,OAAA;AACjE,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,gBAAA,EACb,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,OAAA,EAAA,EAAM,WAAU,kBAAA,EAAmB,OAAA,EAAS,WAAW,KAAA,CAAM,GAAG,CAAA,CAAA,EAC9D,QAAA,EAAA,KAAA,CAAM,KAAA,EACT,CAAA;AAAA,oBACA,GAAA;AAAA,MAAC,OAAA;AAAA,MAAA;AAAA,QACC,EAAA,EAAI,CAAA,QAAA,EAAW,KAAA,CAAM,GAAG,CAAA,CAAA;AAAA,QACxB,SAAA,EAAU,kBAAA;AAAA,QACV,IAAA,EAAK,QAAA;AAAA,QACL,KAAA;AAAA,QACA,KAAK,KAAA,CAAM,GAAA;AAAA,QACX,KAAK,KAAA,CAAM,GAAA;AAAA,QACX,MAAM,KAAA,CAAM,IAAA;AAAA,QACZ,QAAA,EAAU,CAAC,CAAA,KAAM,SAAA,CAAU,KAAA,CAAM,KAAiB,MAAA,CAAO,CAAA,CAAE,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,QACxE,MAAA,EAAQ,CAAC,CAAA,KAAM;AACb,UAAA,MAAM,CAAA,GAAI,MAAA,CAAO,CAAA,CAAE,MAAA,CAAO,KAAK,CAAA;AAC/B,UAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,CAAC,CAAA,EAAG;AACzB,UAAA,IAAI,OAAA,GAAU,CAAA;AACd,UAAA,IAAI,MAAM,GAAA,KAAQ,MAAA,IAAa,UAAU,KAAA,CAAM,GAAA,YAAe,KAAA,CAAM,GAAA;AACpE,UAAA,IAAI,MAAM,GAAA,KAAQ,MAAA,IAAa,UAAU,KAAA,CAAM,GAAA,YAAe,KAAA,CAAM,GAAA;AACpE,UAAA,IAAI,OAAA,KAAY,CAAA,EAAG,SAAA,CAAU,KAAA,CAAM,KAAiB,OAAO,CAAA;AAAA,QAC7D;AAAA;AAAA;AACF,GAAA,EACF,CAAA;AAEJ;AAEA,SAAS,OAAA,CAA4C;AAAA,EACnD,KAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,EAA4B;AAC1B,EAAA,MAAM,UAAA,GAAa,MAAM,UAAA,IAAc,GAAA;AACvC,EAAA,MAAM,OAAA,GAAW,MAAA,CAAO,KAAA,CAAM,GAAG,KAA4B,KAAA,CAAM,OAAA;AACnE,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAS,OAAO,CAAA;AAC1C,EAAA,MAAM,KAAA,GAAQ,OAA6C,IAAI,CAAA;AAC/D,EAAA,MAAM,YAAA,GAAe,OAAO,OAAO,CAAA;AAEnC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,GAAA,GAAO,MAAA,CAAO,KAAA,CAAM,GAAG,KAA4B,KAAA,CAAM,OAAA;AAC/D,IAAA,IAAI,GAAA,KAAQ,aAAa,OAAA,EAAS;AAChC,MAAA,YAAA,CAAa,OAAA,GAAU,GAAA;AACvB,MAAA,QAAA,CAAS,GAAG,CAAA;AAAA,IACd;AAAA,EACF,GAAG,CAAC,MAAA,EAAQ,MAAM,GAAA,EAAK,KAAA,CAAM,OAAO,CAAC,CAAA;AAErC,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,gBAAA,EACb,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,OAAA,EAAA,EAAM,WAAU,kBAAA,EAAmB,OAAA,EAAS,WAAW,KAAA,CAAM,GAAG,CAAA,CAAA,EAC9D,QAAA,EAAA,KAAA,CAAM,KAAA,EACT,CAAA;AAAA,oBACA,GAAA;AAAA,MAAC,OAAA;AAAA,MAAA;AAAA,QACC,EAAA,EAAI,CAAA,QAAA,EAAW,KAAA,CAAM,GAAG,CAAA,CAAA;AAAA,QACxB,SAAA,EAAU,kBAAA;AAAA,QACV,IAAA,EAAK,MAAA;AAAA,QACL,KAAA,EAAO,KAAA;AAAA,QACP,aAAa,KAAA,CAAM,WAAA;AAAA,QACnB,WAAW,KAAA,CAAM,SAAA;AAAA,QACjB,QAAA,EAAU,CAAC,CAAA,KAAM;AACf,UAAA,MAAM,IAAA,GAAO,EAAE,MAAA,CAAO,KAAA;AACtB,UAAA,QAAA,CAAS,IAAI,CAAA;AACb,UAAA,IAAI,KAAA,CAAM,OAAA,EAAS,YAAA,CAAa,KAAA,CAAM,OAAO,CAAA;AAC7C,UAAA,IAAI,eAAe,CAAA,EAAG;AACpB,YAAA,YAAA,CAAa,OAAA,GAAU,IAAA;AACvB,YAAA,SAAA,CAAU,KAAA,CAAM,KAAiB,IAAI,CAAA;AAAA,UACvC,CAAA,MAAO;AACL,YAAA,KAAA,CAAM,OAAA,GAAU,WAAW,MAAM;AAC/B,cAAA,YAAA,CAAa,OAAA,GAAU,IAAA;AACvB,cAAA,SAAA,CAAU,KAAA,CAAM,KAAiB,IAAI,CAAA;AAAA,YACvC,GAAG,UAAU,CAAA;AAAA,UACf;AAAA,QACF;AAAA;AAAA;AACF,GAAA,EACF,CAAA;AAEJ;AAEA,SAAS,QAAA,CAA6C;AAAA,EACpD,KAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,EAA6B;AAC3B,EAAA,MAAM,KAAA,GAAS,MAAA,CAAO,KAAA,CAAM,GAAG,KAA4B,KAAA,CAAM,OAAA;AACjE,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,uCAAA,EACb,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,OAAA,EAAA,EAAM,WAAU,kBAAA,EAAmB,OAAA,EAAS,WAAW,KAAA,CAAM,GAAG,CAAA,CAAA,EAC9D,QAAA,EAAA,KAAA,CAAM,KAAA,EACT,CAAA;AAAA,oBACA,GAAA;AAAA,MAAC,OAAA;AAAA,MAAA;AAAA,QACC,EAAA,EAAI,CAAA,QAAA,EAAW,KAAA,CAAM,GAAG,CAAA,CAAA;AAAA,QACxB,SAAA,EAAU,0CAAA;AAAA,QACV,IAAA,EAAK,OAAA;AAAA,QACL,KAAA;AAAA,QACA,QAAA,EAAU,CAAC,CAAA,KAAM,SAAA,CAAU,MAAM,GAAA,EAAiB,CAAA,CAAE,OAAO,KAAK;AAAA;AAAA;AAClE,GAAA,EACF,CAAA;AAEJ","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"]}
|