@weasel-js/labkit 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_dts/{DrawCommand-C_XboUpz.d.ts → DrawCommand-DBB45NfN.d.ts} +196 -52
- package/dist/_dts/PrefsForm-BYa6cWnO.d.ts +201 -0
- package/dist/_dts/types-1Sdxy_Pv.d.ts +90 -0
- package/dist/_dts/types-BttTIent.d.ts +314 -0
- package/dist/_dts/types-DCGEcMO_.d.ts +162 -0
- package/dist/_dts/{useTrialState-BMNIx3Cy.d.ts → useTrialState-CsGMjhu9.d.ts} +5 -87
- package/dist/canvas/index.d.ts +7 -3
- package/dist/canvas/index.js +3 -1
- package/dist/chrome/index.d.ts +86 -0
- package/dist/chrome/index.js +9 -0
- package/dist/chrome/index.js.map +1 -0
- package/dist/chunk-3XXPU73K.js +138 -0
- package/dist/chunk-3XXPU73K.js.map +1 -0
- package/dist/{chunk-VUU5UXHE.js → chunk-4TMMVIDM.js} +8 -5
- package/dist/chunk-4TMMVIDM.js.map +1 -0
- package/dist/chunk-6X5RBC6G.js +366 -0
- package/dist/chunk-6X5RBC6G.js.map +1 -0
- package/dist/{chunk-574LJAV4.js → chunk-BDJXIBRZ.js} +33 -35
- package/dist/chunk-BDJXIBRZ.js.map +1 -0
- package/dist/{chunk-SFL7NFKN.js → chunk-BMW4TDP5.js} +15 -7
- package/dist/chunk-BMW4TDP5.js.map +1 -0
- package/dist/chunk-JDROYQM3.js +266 -0
- package/dist/chunk-JDROYQM3.js.map +1 -0
- package/dist/{chunk-THBG7FQZ.js → chunk-LGKRIUSW.js} +15 -12
- package/dist/chunk-LGKRIUSW.js.map +1 -0
- package/dist/{chunk-BOHF3PQO.js → chunk-QCICIHEO.js} +8129 -5196
- package/dist/chunk-QCICIHEO.js.map +1 -0
- package/dist/chunk-QRYPXSGP.js +212 -0
- package/dist/chunk-QRYPXSGP.js.map +1 -0
- package/dist/chunk-RGT4EJIB.js +6961 -0
- package/dist/chunk-RGT4EJIB.js.map +1 -0
- package/dist/chunk-SBXNQW4G.js +3 -0
- package/dist/chunk-SBXNQW4G.js.map +1 -0
- package/dist/chunk-T66HU3HX.js +17 -0
- package/dist/chunk-T66HU3HX.js.map +1 -0
- package/dist/chunk-WTI26YTM.js +266 -0
- package/dist/chunk-WTI26YTM.js.map +1 -0
- package/dist/controls/index.d.ts +19 -5
- package/dist/controls/index.js +3 -1
- package/dist/dragdrop/index.d.ts +46 -3
- package/dist/dragdrop/index.js +3 -1
- package/dist/index.d.ts +735 -565
- package/dist/index.js +661 -1019
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +5 -3
- package/dist/layers/index.js +4 -1
- package/dist/passthrough/weasel-canvas.d.ts +69 -13
- package/dist/passthrough/weasel-canvas.js +1 -1
- package/dist/passthrough/weasel-ui.d.ts +3811 -3671
- package/dist/passthrough/weasel-ui.js +3 -2
- package/dist/primitives/index.d.ts +102 -8
- package/dist/primitives/index.js +5 -1
- package/dist/state/index.d.ts +3 -2
- package/dist/state/index.js +2 -2
- package/dist/styles.css +490 -803
- package/dist/surface/index.js +2 -1
- package/dist/ui/layers/index.d.ts +8 -2
- package/dist/ui/layers/index.js +2 -3
- package/dist/undo/index.d.ts +4 -2
- package/package.json +16 -7
- package/src/canvas/AGENTS.md +4 -0
- package/src/canvas/CanvasStack.tsx +6 -2
- package/src/canvas/useLayerScheduler.ts +22 -15
- package/src/canvas/usePanZoom.test.ts +80 -0
- package/src/canvas/usePanZoom.ts +16 -2
- package/src/chrome/ChromeRegions.stories.tsx +106 -0
- package/src/chrome/builtins.test.ts +153 -0
- package/src/chrome/builtins.tsx +205 -0
- package/src/chrome/index.ts +25 -0
- package/src/chrome/merge.test.ts +51 -0
- package/src/chrome/merge.ts +52 -0
- package/src/chrome/regions/PaletteRegion.less +14 -0
- package/src/chrome/regions/PaletteRegion.test.tsx +36 -0
- package/src/chrome/regions/PaletteRegion.tsx +41 -0
- package/src/chrome/regions/SidebarRegion.tsx +56 -0
- package/src/chrome/regions/StatusRegion.tsx +31 -0
- package/src/chrome/regions/TitleBarRegion.tsx +41 -0
- package/src/chrome/regions/ToolbarRegion.test.tsx +82 -0
- package/src/chrome/regions/ToolbarRegion.tsx +71 -0
- package/src/chrome/regions/ViewportRegion.less +42 -0
- package/src/chrome/regions/ViewportRegion.tsx +37 -0
- package/src/chrome/regions/regions.test.tsx +81 -0
- package/src/chrome/types.ts +127 -0
- package/src/config/builder.test.ts +93 -0
- package/src/config/builder.ts +220 -0
- package/src/config/fromConfigField.test.ts +93 -0
- package/src/config/fromConfigField.ts +74 -0
- package/src/config/index.ts +22 -0
- package/src/config/resolve.test.ts +119 -0
- package/src/config/resolve.ts +67 -0
- package/src/config/rules.ts +68 -0
- package/src/config/types.ts +108 -0
- package/src/config/useConfigSchema.ts +30 -0
- package/src/config/visible.ts +20 -0
- package/src/controls/ControlPanel.less +4 -58
- package/src/controls/ControlPanel.stories.tsx +125 -0
- package/src/controls/ControlPanel.test.tsx +245 -10
- package/src/controls/ControlPanel.tsx +236 -203
- package/src/dragdrop/DragDropRuntime.tsx +36 -18
- package/src/dragdrop/DragGhost.less +1 -1
- package/src/dragdrop/Palette.less +1 -1
- package/src/dragdrop/index.ts +11 -0
- package/src/index.ts +74 -5
- package/src/instrument/SineWave.smoke.test.tsx +8 -1
- package/src/instrument/defineInstrument.test.ts +38 -1
- package/src/instrument/defineInstrument.ts +31 -4
- package/src/instrument/index.ts +1 -0
- package/src/instrument/types.ts +27 -4
- package/src/instrument/validateConfigSchema.test.ts +13 -3
- package/src/instrument/validateConfigSchema.ts +7 -10
- package/src/lab/Lab.chrome.test.tsx +65 -0
- package/src/lab/Lab.tsx +58 -15
- package/src/lab/LabContext.ts +6 -0
- package/src/lab/LabFullChrome.stories.tsx +308 -0
- package/src/lab/LabHeader.test.tsx +42 -0
- package/src/lab/LabHeader.tsx +57 -0
- package/src/lab/LabPalette.test.tsx +35 -0
- package/src/lab/LabPalette.tsx +34 -0
- package/src/lab/LabShell.less +50 -3
- package/src/lab/Workspace.less +1 -30
- package/src/lab/Workspace.test.tsx +20 -10
- package/src/lab/Workspace.tsx +5 -8
- package/src/lab/index.ts +3 -1
- package/src/layers/AGENTS.md +1 -1
- package/src/layers/LayerList.less +31 -7
- package/src/layers/LayerList.tsx +17 -4
- package/src/passthrough/weasel-ui.ts +28 -0
- package/src/primitives/FloatingPanel.less +28 -0
- package/src/primitives/FloatingPanel.stories.tsx +46 -0
- package/src/primitives/FloatingPanel.test.tsx +240 -0
- package/src/primitives/FloatingPanel.tsx +190 -0
- package/src/primitives/FpsMeter.test.tsx +44 -0
- package/src/primitives/FpsMeter.tsx +20 -9
- package/src/primitives/JobProgress.less +69 -0
- package/src/primitives/JobProgress.stories.tsx +48 -0
- package/src/primitives/JobProgress.tsx +65 -0
- package/src/primitives/Legend.less +47 -0
- package/src/primitives/Legend.stories.tsx +32 -0
- package/src/primitives/Legend.test.tsx +57 -0
- package/src/primitives/Legend.tsx +40 -0
- package/src/primitives/Sidebar.less +32 -2
- package/src/primitives/StatusBar.less +7 -0
- package/src/primitives/StatusBar.tsx +10 -3
- package/src/primitives/Toolbar.less +50 -3
- package/src/primitives/Toolbar.test.tsx +67 -1
- package/src/primitives/Toolbar.tsx +72 -6
- package/src/primitives/ZoomControl.less +31 -0
- package/src/primitives/ZoomControl.tsx +76 -0
- package/src/primitives/floating.entry.test.tsx +14 -0
- package/src/primitives/index.ts +10 -2
- package/src/primitives/useRovingTabIndex.test.ts +27 -0
- package/src/primitives/useRovingTabIndex.ts +79 -0
- package/src/state/store.ts +11 -3
- package/src/state/toolSlot.test.ts +36 -0
- package/src/state/types.ts +4 -0
- package/src/styles.less +6 -4
- package/src/surface/useTiledSurface.ts +14 -10
- package/src/test-setup.ts +26 -0
- package/src/theme/Interstellar.stories.tsx +1 -1
- package/src/theme/base.less +19 -2
- package/src/theme/interstellar.tokens.json +6 -2
- package/src/tools/types.ts +24 -0
- package/src/trial/Trial.config.test.tsx +108 -0
- package/src/trial/Trial.job.test.tsx +13 -15
- package/src/trial/Trial.less +115 -10
- package/src/trial/Trial.stories.tsx +1 -0
- package/src/trial/Trial.test.tsx +67 -96
- package/src/trial/Trial.tsx +80 -39
- package/src/trial/TrialChrome.tsx +99 -80
- package/src/trial/TrialDragContext.ts +10 -0
- package/src/trial/TrialTitleBar.tsx +41 -0
- package/src/trial/index.ts +1 -15
- package/src/trial/trialChrome.borders.test.ts +38 -0
- package/src/ui/layers/index.ts +5 -1
- package/dist/_dts/index-JFAYj5Tv.d.ts +0 -144
- package/dist/_dts/types-x92Kfeme.d.ts +0 -62
- package/dist/chunk-574LJAV4.js.map +0 -1
- package/dist/chunk-5R2ATYPJ.js +0 -201
- package/dist/chunk-5R2ATYPJ.js.map +0 -1
- package/dist/chunk-73KA7WBO.js +0 -5992
- package/dist/chunk-73KA7WBO.js.map +0 -1
- package/dist/chunk-BOHF3PQO.js.map +0 -1
- package/dist/chunk-G5TJVQQT.js +0 -161
- package/dist/chunk-G5TJVQQT.js.map +0 -1
- package/dist/chunk-PMAU3SEE.js +0 -126
- package/dist/chunk-PMAU3SEE.js.map +0 -1
- package/dist/chunk-SFL7NFKN.js.map +0 -1
- package/dist/chunk-THBG7FQZ.js.map +0 -1
- package/dist/chunk-VUU5UXHE.js.map +0 -1
- package/src/instrument/capabilityDetector.test.ts +0 -64
- package/src/instrument/capabilityDetector.ts +0 -20
- package/src/trial/DefaultSidebar.tsx +0 -29
- package/src/trial/DefaultStatusBar.tsx +0 -19
- package/src/trial/DefaultToolbar.tsx +0 -92
- package/src/trial/slotTypes.ts +0 -54
- package/src/ui/format.test.ts +0 -33
- package/src/ui/format.ts +0 -30
- package/src/ui/layers/LayerStack.less +0 -143
- package/src/ui/layers/LayerStack.stories.tsx +0 -45
- package/src/ui/layers/LayerStack.test.tsx +0 -170
- package/src/ui/layers/LayerStack.tsx +0 -220
- package/src/ui/properties/CheckboxRow.stories.tsx +0 -27
- package/src/ui/properties/ColorRow.stories.tsx +0 -106
- package/src/ui/properties/CurveField.less +0 -48
- package/src/ui/properties/CurveField.stories.tsx +0 -20
- package/src/ui/properties/CurveField.test.tsx +0 -127
- package/src/ui/properties/CurveField.tsx +0 -184
- package/src/ui/properties/EffectCard.tsx +0 -324
- package/src/ui/properties/Gallery.stories.tsx +0 -178
- package/src/ui/properties/NumberRow.stories.tsx +0 -65
- package/src/ui/properties/PropertyGroup.less +0 -50
- package/src/ui/properties/PropertyGroup.stories.tsx +0 -34
- package/src/ui/properties/PropertyGroup.test.tsx +0 -33
- package/src/ui/properties/PropertyGroup.tsx +0 -45
- package/src/ui/properties/PropertyList.stories.tsx +0 -83
- package/src/ui/properties/PropertyPanel.less +0 -592
- package/src/ui/properties/PropertyPanel.stories.tsx +0 -190
- package/src/ui/properties/PropertyPanel.test.tsx +0 -193
- package/src/ui/properties/PropertyPanel.tsx +0 -469
- package/src/ui/properties/PropertyRow.stories.tsx +0 -53
- package/src/ui/properties/SelectRow.stories.tsx +0 -58
- package/src/ui/properties/SliderRow.stories.tsx +0 -74
- package/src/ui/properties/SpeechBalloonPanels.stories.tsx +0 -396
- package/src/ui/properties/TextRow.stories.tsx +0 -62
- package/src/ui/properties/ToggleRow.stories.tsx +0 -57
- package/src/ui/properties/index.ts +0 -39
- package/src/ui/properties/storyLayouts.tsx +0 -36
package/dist/index.js
CHANGED
|
@@ -1,29 +1,34 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { useDragDrop, DragOverlay, Palette } from './chunk-3XXPU73K.js';
|
|
2
|
+
import { useSurfaceOptional } from './chunk-LGKRIUSW.js';
|
|
3
|
+
export { SurfaceContext, composeRects, rectsEqual, toDeviceRect, useSurface, useSurfaceOptional, useSurfaceTile, useTiledSurface } from './chunk-LGKRIUSW.js';
|
|
3
4
|
import { useJob } from './chunk-LN6JDUGB.js';
|
|
4
5
|
export { useJob } from './chunk-LN6JDUGB.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import './chunk-
|
|
8
|
-
|
|
9
|
-
export { FpsMeter, ScaleIndicator,
|
|
10
|
-
import { LabStoreContext, createLabStore, noneAdapter } from './chunk-
|
|
11
|
-
export { CURRENT_DOCUMENT_VERSION, LabStoreContext, LabStoreProvider, TrialIdContext, TrialIdProvider, createMemoryAdapter, labDocumentKey, localStorageAdapter, noneAdapter, quarantineKey, sessionStorageAdapter, urlHashAdapter, useLabStore, useTrialId, useTrialState } from './chunk-
|
|
12
|
-
import {
|
|
13
|
-
export { ControlPanel } from './chunk-
|
|
14
|
-
import { CanvasStack
|
|
15
|
-
export { CanvasStack
|
|
6
|
+
import { suppressContributions, mergeContributions, builtinContributions, TitleBarRegion, ToolbarRegion, PaletteRegion, SidebarRegion, ViewportRegion, StatusRegion } from './chunk-6X5RBC6G.js';
|
|
7
|
+
export { PaletteRegion, SidebarRegion, StatusRegion, TitleBarRegion, ToolbarRegion, ViewportRegion, builtinContributions, mergeContributions, suppressContributions } from './chunk-6X5RBC6G.js';
|
|
8
|
+
import { JobProgress } from './chunk-QRYPXSGP.js';
|
|
9
|
+
export { FloatingPanel, JobProgress, Legend, Sidebar } from './chunk-QRYPXSGP.js';
|
|
10
|
+
export { FpsMeter, ScaleIndicator, StatusBar, Toolbar, ZoomControl } from './chunk-JDROYQM3.js';
|
|
11
|
+
import { LabStoreContext, createLabStore, noneAdapter } from './chunk-4TMMVIDM.js';
|
|
12
|
+
export { CURRENT_DOCUMENT_VERSION, LabStoreContext, LabStoreProvider, TrialIdContext, TrialIdProvider, createMemoryAdapter, labDocumentKey, localStorageAdapter, noneAdapter, quarantineKey, sessionStorageAdapter, urlHashAdapter, useLabStore, useTrialId, useTrialState } from './chunk-4TMMVIDM.js';
|
|
13
|
+
import { fromConfigFields } from './chunk-WTI26YTM.js';
|
|
14
|
+
export { ControlPanel, fromConfigFields, isLeafVisible } from './chunk-WTI26YTM.js';
|
|
15
|
+
import { CanvasStack } from './chunk-BDJXIBRZ.js';
|
|
16
|
+
export { CanvasStack } from './chunk-BDJXIBRZ.js';
|
|
17
|
+
export { screenToWorld, worldToScreen } from './chunk-T66HU3HX.js';
|
|
16
18
|
export { CanvasStackContext } from './chunk-CPUJ3QXL.js';
|
|
17
|
-
import { LayerList } from './chunk-
|
|
18
|
-
export { LayerList } from './chunk-
|
|
19
|
-
import
|
|
19
|
+
import { LayerList } from './chunk-BMW4TDP5.js';
|
|
20
|
+
export { LayerList } from './chunk-BMW4TDP5.js';
|
|
21
|
+
import './chunk-SBXNQW4G.js';
|
|
22
|
+
import { Rt, p, r } from './chunk-RGT4EJIB.js';
|
|
23
|
+
export { k as CheckboxRow, O as ColorRow, m2 as CurveField, _2 as EffectCard, y as EffectCardList, c3 as LayerStack, j as NumberRow, b2 as PropertyGroup, S2 as PropertyList, x2 as PropertyPanel, w as PropertyRow, C as PropertySpan, M as SelectRow, E as SliderRow, g as Subpanel, A as TextRow, N as ToggleRow } from './chunk-RGT4EJIB.js';
|
|
24
|
+
import './chunk-QCICIHEO.js';
|
|
25
|
+
import { createEventBus, pushSnapshot, redo, undo } from './chunk-RL2LOLNI.js';
|
|
20
26
|
export { clearUndo, createEventBus, emptyStack, pushSnapshot, redo, undo } from './chunk-RL2LOLNI.js';
|
|
21
27
|
import { createContext, useRef, useCallback, useContext, useMemo, useState, Children, useLayoutEffect, useEffect } from 'react';
|
|
22
|
-
import {
|
|
28
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
23
29
|
import { useStore } from 'zustand/react';
|
|
24
|
-
import {
|
|
30
|
+
import { Provider, StrategyRegistryProvider, DragProvider, Container, useDragHandle } from 'windease/react';
|
|
25
31
|
import { asNodeId, Store, createNode, gridStrategy } from 'windease';
|
|
26
|
-
import { DragHandle, Provider, StrategyRegistryProvider, DragProvider, Container } from 'windease/react';
|
|
27
32
|
|
|
28
33
|
var PITCH_LIMIT = Math.PI / 2 - 0.01;
|
|
29
34
|
var YAW_PER_PX = 8e-3;
|
|
@@ -106,23 +111,260 @@ function useOrbit({
|
|
|
106
111
|
return { onWheel, onPointerDown, onPointerMove, onPointerUp, onDoubleClick, isDragging };
|
|
107
112
|
}
|
|
108
113
|
|
|
109
|
-
// src/
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
// src/config/builder.ts
|
|
115
|
+
var BaseNode = class {
|
|
116
|
+
constructor(defaultValue, annotations = {}, options = {}) {
|
|
117
|
+
this.annotations = annotations;
|
|
118
|
+
this.options = options;
|
|
119
|
+
this.default = defaultValue;
|
|
120
|
+
}
|
|
121
|
+
annotations;
|
|
122
|
+
options;
|
|
123
|
+
// `default` is a reserved word in a parameter position, so the field is
|
|
124
|
+
// declared rather than taken as a parameter property.
|
|
125
|
+
default;
|
|
126
|
+
/** Clone into the same subclass. Subclasses with extra constructor
|
|
127
|
+
* arguments override this. */
|
|
128
|
+
with(annotations, options) {
|
|
129
|
+
const Ctor = this.constructor;
|
|
130
|
+
return new Ctor(this.default, annotations, options);
|
|
131
|
+
}
|
|
132
|
+
ann(patch) {
|
|
133
|
+
return this.with({ ...this.annotations, ...patch }, this.options);
|
|
134
|
+
}
|
|
135
|
+
opt(patch) {
|
|
136
|
+
return this.with(this.annotations, { ...this.options, ...patch });
|
|
137
|
+
}
|
|
138
|
+
/** Human-readable label. Defaults to the key, title-cased. */
|
|
139
|
+
label(name) {
|
|
140
|
+
return this.ann({ name });
|
|
141
|
+
}
|
|
142
|
+
/** Longer help text, surfaced as a tooltip on the row label. */
|
|
143
|
+
describe(description) {
|
|
144
|
+
return this.ann({ description });
|
|
145
|
+
}
|
|
146
|
+
/** Omit from the panel unless it is asked to show hidden leaves. */
|
|
147
|
+
hidden() {
|
|
148
|
+
return this.ann({ hidden: true });
|
|
149
|
+
}
|
|
150
|
+
/** Pair with a sibling sharing this id, side-by-side on one row. */
|
|
151
|
+
pair(pair) {
|
|
152
|
+
return this.ann({ pair });
|
|
153
|
+
}
|
|
154
|
+
/** Render under a named section heading. */
|
|
155
|
+
section(section) {
|
|
156
|
+
return this.opt({ section });
|
|
157
|
+
}
|
|
158
|
+
/** Show this row only while the predicate holds. Presentational — the value
|
|
159
|
+
* stays in config and the instrument still reads it. */
|
|
160
|
+
showIf(predicate) {
|
|
161
|
+
return this.opt({ showIf: predicate });
|
|
162
|
+
}
|
|
163
|
+
/** Draw this one row yourself, keeping the kind, default and validation. */
|
|
164
|
+
render(renderer) {
|
|
165
|
+
return this.opt({ render: renderer });
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
var NumberNode = class extends BaseNode {
|
|
169
|
+
kind = "number";
|
|
170
|
+
/** Bound the value. A number with both bounds renders as a slider. */
|
|
171
|
+
range(min, max) {
|
|
172
|
+
return this.ann({ min, max });
|
|
173
|
+
}
|
|
174
|
+
step(step) {
|
|
175
|
+
return this.ann({ step });
|
|
176
|
+
}
|
|
177
|
+
/** Force a slider even without both bounds. */
|
|
178
|
+
slider() {
|
|
179
|
+
return this.ann({ control: "slider" });
|
|
180
|
+
}
|
|
181
|
+
/** Force a typed input even when both bounds are set. */
|
|
182
|
+
input() {
|
|
183
|
+
return this.ann({ control: "input" });
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
var BooleanNode = class extends BaseNode {
|
|
187
|
+
kind = "boolean";
|
|
188
|
+
/** Ask for a switch. `ControlPanel` still draws a checkbox; weasel-ui's
|
|
189
|
+
* `PrefsForm` honors the distinction. */
|
|
190
|
+
toggle() {
|
|
191
|
+
return this.ann({ control: "switch" });
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
var StringNode = class extends BaseNode {
|
|
195
|
+
kind = "string";
|
|
196
|
+
placeholder(placeholder) {
|
|
197
|
+
return this.ann({ placeholder });
|
|
198
|
+
}
|
|
199
|
+
maxLength(maxLength) {
|
|
200
|
+
return this.ann({ maxLength });
|
|
201
|
+
}
|
|
202
|
+
/** Milliseconds to debounce live writes. 0 commits every keystroke. */
|
|
203
|
+
debounce(debounceMs) {
|
|
204
|
+
return this.ann({ debounceMs });
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
var ColorNode = class extends BaseNode {
|
|
208
|
+
kind = "color";
|
|
209
|
+
};
|
|
210
|
+
var EnumNode = class extends BaseNode {
|
|
211
|
+
kind = "enum";
|
|
212
|
+
/** Render as a segmented control rather than a select. */
|
|
213
|
+
radio() {
|
|
214
|
+
return this.ann({ control: "radio" });
|
|
215
|
+
}
|
|
216
|
+
/** Relabel the choices. Named `labels` rather than `options` because
|
|
217
|
+
* `options` is the node's own extras bag. */
|
|
218
|
+
labels(options) {
|
|
219
|
+
return this.ann({ options });
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
var ValueNode = class extends BaseNode {
|
|
223
|
+
kind = null;
|
|
224
|
+
};
|
|
225
|
+
var CustomNode = class _CustomNode extends BaseNode {
|
|
226
|
+
constructor(kind, defaultValue, annotations = {}, options = {}) {
|
|
227
|
+
super(defaultValue, annotations, options);
|
|
228
|
+
this.kind = kind;
|
|
229
|
+
}
|
|
230
|
+
kind;
|
|
231
|
+
with(annotations, options) {
|
|
232
|
+
return new _CustomNode(this.kind, this.default, annotations, options);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
var expand = (options) => options.map((o) => typeof o === "string" ? { value: o, label: o } : o);
|
|
236
|
+
var f = {
|
|
237
|
+
number: (def) => new NumberNode(def),
|
|
238
|
+
boolean: (def) => new BooleanNode(def),
|
|
239
|
+
string: (def) => new StringNode(def),
|
|
240
|
+
color: (def) => new ColorNode(def),
|
|
241
|
+
/** A fixed set of choices. The default's literal type flows into the config
|
|
242
|
+
* type, so `f.enum('fast', ['fast', 'accurate'])` gives
|
|
243
|
+
* `'fast' | 'accurate'` with no `as const`. */
|
|
244
|
+
enum: (def, options) => new EnumNode(def, { options: expand(options) }),
|
|
245
|
+
/** A leaf with no declared kind: the rule chain decides, which is how a lab
|
|
246
|
+
* states a convention like "every `*Color` key is a color picker". */
|
|
247
|
+
value: (def) => new ValueNode(def),
|
|
248
|
+
/** A leaf of a kind a lab supplies the control for, through `controls`. */
|
|
249
|
+
custom: (kind, def, validate) => new CustomNode(kind, def, {}, validate ? { validate } : {}),
|
|
250
|
+
/** Collect leaves into an instrument's config. */
|
|
251
|
+
schema(nodes) {
|
|
252
|
+
return {
|
|
253
|
+
nodes,
|
|
254
|
+
defaults: () => {
|
|
255
|
+
const out = {};
|
|
256
|
+
for (const [key, node] of Object.entries(nodes)) out[key] = node.default;
|
|
257
|
+
return out;
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// src/config/rules.ts
|
|
264
|
+
function titleCase(key) {
|
|
265
|
+
const words = key.replace(/[_-]+/g, " ").split(/(?<=[a-z0-9])(?=[A-Z])/).join(" ").trim().toLowerCase();
|
|
266
|
+
return words.length === 0 ? words : words[0].toUpperCase() + words.slice(1);
|
|
267
|
+
}
|
|
268
|
+
var kindFromValue = (ctx) => {
|
|
269
|
+
const t = typeof ctx.default;
|
|
270
|
+
if (t === "boolean" || t === "number" || t === "string") return { kind: t };
|
|
271
|
+
return null;
|
|
272
|
+
};
|
|
273
|
+
var labelFromKey = (ctx) => ({ name: titleCase(ctx.key) });
|
|
274
|
+
var sliderWhenBounded = (ctx) => {
|
|
275
|
+
if (ctx.leaf.kind !== "number") return null;
|
|
276
|
+
const bounded = ctx.leaf.min !== void 0 && ctx.leaf.max !== void 0;
|
|
277
|
+
return bounded ? { control: "slider" } : null;
|
|
278
|
+
};
|
|
279
|
+
var descriptionDefault = () => ({ description: "" });
|
|
280
|
+
var builtinRules = [
|
|
281
|
+
kindFromValue,
|
|
282
|
+
labelFromKey,
|
|
283
|
+
sliderWhenBounded,
|
|
284
|
+
descriptionDefault
|
|
285
|
+
];
|
|
286
|
+
function applyRules(seed, ctx, rules) {
|
|
287
|
+
const acc = { ...seed };
|
|
288
|
+
for (const rule of rules) {
|
|
289
|
+
const patch = rule({ ...ctx, leaf: acc });
|
|
290
|
+
if (patch === null) continue;
|
|
291
|
+
for (const [k2, v] of Object.entries(patch)) {
|
|
292
|
+
if (v !== void 0 && acc[k2] === void 0) {
|
|
293
|
+
acc[k2] = v;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return acc;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/config/resolve.ts
|
|
301
|
+
function defined(annotations) {
|
|
302
|
+
const out = {};
|
|
303
|
+
for (const [k2, v] of Object.entries(annotations)) if (v !== void 0) out[k2] = v;
|
|
304
|
+
return out;
|
|
305
|
+
}
|
|
306
|
+
function resolveConfigSchema(schema, rules = []) {
|
|
307
|
+
const children = {};
|
|
308
|
+
const sectionOrder = [];
|
|
309
|
+
const sectionPaths = /* @__PURE__ */ new Map();
|
|
310
|
+
const showIf = /* @__PURE__ */ new Map();
|
|
311
|
+
const renderers = {};
|
|
312
|
+
const chain2 = [...rules, ...builtinRules];
|
|
313
|
+
for (const [key, node] of Object.entries(schema.nodes)) {
|
|
314
|
+
const seed = defined({
|
|
315
|
+
...node.annotations,
|
|
316
|
+
...node.kind === null ? {} : { kind: node.kind }
|
|
317
|
+
});
|
|
318
|
+
const patch = applyRules(seed, { key, path: key, default: node.default }, chain2);
|
|
319
|
+
children[key] = { ...patch, default: node.default };
|
|
320
|
+
const { section, showIf: predicate, render } = node.options;
|
|
321
|
+
if (section !== void 0) {
|
|
322
|
+
if (!sectionPaths.has(section)) {
|
|
323
|
+
sectionOrder.push(section);
|
|
324
|
+
sectionPaths.set(section, []);
|
|
325
|
+
}
|
|
326
|
+
sectionPaths.get(section)?.push(key);
|
|
327
|
+
}
|
|
328
|
+
if (predicate) showIf.set(key, predicate);
|
|
329
|
+
if (render) renderers[key] = render;
|
|
330
|
+
}
|
|
331
|
+
const group = { name: "", children };
|
|
332
|
+
const sections = sectionOrder.map((label) => ({
|
|
333
|
+
label,
|
|
334
|
+
paths: sectionPaths.get(label) ?? []
|
|
335
|
+
}));
|
|
336
|
+
return { group, sections, showIf, renderers };
|
|
337
|
+
}
|
|
338
|
+
var LabContext = createContext(null);
|
|
339
|
+
function useLabContext() {
|
|
340
|
+
const ctx = useContext(LabContext);
|
|
341
|
+
if (ctx === null) {
|
|
342
|
+
throw new Error("useLabContext must be used inside <Lab>");
|
|
343
|
+
}
|
|
344
|
+
return ctx;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// src/config/useConfigSchema.ts
|
|
348
|
+
var NO_RULES = [];
|
|
349
|
+
function useConfigSchema(instrument) {
|
|
350
|
+
const lab = useContext(LabContext);
|
|
351
|
+
const rules = lab?.configRules ?? NO_RULES;
|
|
352
|
+
return useMemo(
|
|
353
|
+
() => instrument.config ? resolveConfigSchema(instrument.config, rules) : fromConfigFields(instrument.configSchema?.() ?? []),
|
|
354
|
+
[instrument, rules]
|
|
355
|
+
);
|
|
117
356
|
}
|
|
118
357
|
|
|
119
358
|
// src/instrument/defineInstrument.ts
|
|
120
359
|
function defineInstrument(spec) {
|
|
360
|
+
if (spec.config && !("defaultConfig" in spec && spec.defaultConfig)) {
|
|
361
|
+
const schema = spec.config;
|
|
362
|
+
return { ...spec, defaultConfig: () => schema.defaults() };
|
|
363
|
+
}
|
|
121
364
|
return spec;
|
|
122
365
|
}
|
|
123
366
|
|
|
124
367
|
// src/instrument/validateConfigSchema.ts
|
|
125
|
-
var KNOWN_TYPES = ["slider", "checkbox", "select", "number", "text", "color"];
|
|
126
368
|
function validateConfigSchema(fields) {
|
|
127
369
|
const errors = [];
|
|
128
370
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
@@ -137,10 +379,6 @@ function validateConfigSchema(fields) {
|
|
|
137
379
|
if (field.label === "") {
|
|
138
380
|
errors.push(`Field "${field.key}" has empty label`);
|
|
139
381
|
}
|
|
140
|
-
if (!KNOWN_TYPES.includes(field.type)) {
|
|
141
|
-
errors.push(`Unknown field type: "${field.type}" on key "${field.key}"`);
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
382
|
if (field.type === "slider") {
|
|
145
383
|
if (!Number.isFinite(field.default)) {
|
|
146
384
|
errors.push(`Field "${field.key}": default must be a finite number`);
|
|
@@ -175,7 +413,7 @@ function validateConfigSchema(fields) {
|
|
|
175
413
|
return { valid: errors.length === 0, errors };
|
|
176
414
|
}
|
|
177
415
|
|
|
178
|
-
// ../theme/dist/chunk-
|
|
416
|
+
// ../theme/dist/chunk-O32P3G22.js
|
|
179
417
|
var THEME_SOURCES = {
|
|
180
418
|
"weasel": {
|
|
181
419
|
"primitives": {
|
|
@@ -361,6 +599,15 @@ var THEME_SOURCES = {
|
|
|
361
599
|
"type": "dimension",
|
|
362
600
|
"value": "5px"
|
|
363
601
|
},
|
|
602
|
+
"radius-lg": {
|
|
603
|
+
"type": "dimension",
|
|
604
|
+
"value": "14px"
|
|
605
|
+
},
|
|
606
|
+
"radius-pill": {
|
|
607
|
+
"type": "dimension",
|
|
608
|
+
"value": "999px",
|
|
609
|
+
"description": "Fully rounded ends \u2014 segmented controls, progress tracks."
|
|
610
|
+
},
|
|
364
611
|
"border-w": {
|
|
365
612
|
"type": "dimension",
|
|
366
613
|
"value": "1px"
|
|
@@ -378,29 +625,47 @@ var THEME_SOURCES = {
|
|
|
378
625
|
"tb-height": {
|
|
379
626
|
"type": "dimension",
|
|
380
627
|
"value": "28px",
|
|
381
|
-
"description": "Height of a horizontal toolbar strip (ActionsBar, OptionsBar,
|
|
382
|
-
},
|
|
383
|
-
"radius-lg": {
|
|
384
|
-
"type": "dimension",
|
|
385
|
-
"value": "14px"
|
|
628
|
+
"description": "Height of a horizontal toolbar strip (ActionsBar, OptionsBar, ToolOptionsBar). Taller than control-h so the controls inside a strip are not flush with its edges."
|
|
386
629
|
},
|
|
387
630
|
"control-h": {
|
|
388
631
|
"type": "dimension",
|
|
389
|
-
"value": "
|
|
390
|
-
"description": "Height of an interactive control \u2014 button, input, select. Distinct from tb-height, which sizes the strip a row of them sits in."
|
|
632
|
+
"value": "24px",
|
|
633
|
+
"description": "Height of an interactive control \u2014 button, input, select, segmented control. Distinct from tb-height, which sizes the strip a row of them sits in."
|
|
391
634
|
},
|
|
392
635
|
"glass-blur": {
|
|
393
636
|
"type": "dimension",
|
|
394
637
|
"value": "3px",
|
|
395
638
|
"description": "Backdrop blur radius for frosted surfaces. Pairs with glass-tint."
|
|
396
639
|
},
|
|
397
|
-
"font-size": {
|
|
640
|
+
"font-size-2xs": {
|
|
641
|
+
"type": "dimension",
|
|
642
|
+
"value": "9px",
|
|
643
|
+
"description": "Shortcut keys and index badges. The floor for chrome text."
|
|
644
|
+
},
|
|
645
|
+
"font-size-xs": {
|
|
398
646
|
"type": "dimension",
|
|
399
|
-
"value": "
|
|
647
|
+
"value": "10px",
|
|
648
|
+
"description": "Tool-button labels under an icon."
|
|
400
649
|
},
|
|
401
650
|
"font-size-sm": {
|
|
402
651
|
"type": "dimension",
|
|
403
|
-
"value": "11px"
|
|
652
|
+
"value": "11px",
|
|
653
|
+
"description": "Chrome labels, status readouts, section headings."
|
|
654
|
+
},
|
|
655
|
+
"font-size": {
|
|
656
|
+
"type": "dimension",
|
|
657
|
+
"value": "13px",
|
|
658
|
+
"description": "Controls and body text."
|
|
659
|
+
},
|
|
660
|
+
"font-size-lg": {
|
|
661
|
+
"type": "dimension",
|
|
662
|
+
"value": "16px",
|
|
663
|
+
"description": "Panel and dialog titles."
|
|
664
|
+
},
|
|
665
|
+
"font-size-xl": {
|
|
666
|
+
"type": "dimension",
|
|
667
|
+
"value": "20px",
|
|
668
|
+
"description": "The largest chrome text \u2014 a lab or app title."
|
|
404
669
|
},
|
|
405
670
|
"space-xs": {
|
|
406
671
|
"type": "dimension",
|
|
@@ -418,6 +683,20 @@ var THEME_SOURCES = {
|
|
|
418
683
|
"type": "dimension",
|
|
419
684
|
"value": "16px"
|
|
420
685
|
},
|
|
686
|
+
"tracking-none": {
|
|
687
|
+
"type": "dimension",
|
|
688
|
+
"value": "0"
|
|
689
|
+
},
|
|
690
|
+
"tracking-wide": {
|
|
691
|
+
"type": "dimension",
|
|
692
|
+
"value": "0.06em",
|
|
693
|
+
"description": "Uppercase section headings."
|
|
694
|
+
},
|
|
695
|
+
"tracking-wider": {
|
|
696
|
+
"type": "dimension",
|
|
697
|
+
"value": "0.08em",
|
|
698
|
+
"description": "Uppercase at 11px and below."
|
|
699
|
+
},
|
|
421
700
|
"z-toolbar": {
|
|
422
701
|
"type": "number",
|
|
423
702
|
"value": 10
|
|
@@ -430,6 +709,21 @@ var THEME_SOURCES = {
|
|
|
430
709
|
"type": "number",
|
|
431
710
|
"value": 30
|
|
432
711
|
},
|
|
712
|
+
"leading-tight": {
|
|
713
|
+
"type": "number",
|
|
714
|
+
"value": 1,
|
|
715
|
+
"description": "Single-line controls; the box sets the height."
|
|
716
|
+
},
|
|
717
|
+
"leading-snug": {
|
|
718
|
+
"type": "number",
|
|
719
|
+
"value": 1.2,
|
|
720
|
+
"description": "Headings and two-line labels."
|
|
721
|
+
},
|
|
722
|
+
"leading": {
|
|
723
|
+
"type": "number",
|
|
724
|
+
"value": 1.4,
|
|
725
|
+
"description": "Body and anything that wraps."
|
|
726
|
+
},
|
|
433
727
|
"backdrop": {
|
|
434
728
|
"type": "gradient",
|
|
435
729
|
"value": "none",
|
|
@@ -582,6 +876,16 @@ var THEME_SOURCES = {
|
|
|
582
876
|
"type": "color",
|
|
583
877
|
"value": "{color.gray-900}",
|
|
584
878
|
"description": "Text drawn on a filled foreground-colored element (e.g. a slider thumb). Flips with the mode, so it is a semantic, not an alias to a primitive."
|
|
879
|
+
},
|
|
880
|
+
"shadow": {
|
|
881
|
+
"type": "color",
|
|
882
|
+
"value": "rgba(0, 0, 0, 0.6)",
|
|
883
|
+
"description": "Elevation. Always darker than any surface \u2014 never derived from fg, which is near-white on dark."
|
|
884
|
+
},
|
|
885
|
+
"border-raised": {
|
|
886
|
+
"type": "color",
|
|
887
|
+
"value": "{color.gray-400}",
|
|
888
|
+
"description": "The line around a raised surface. Clears 3:1 against surface and surface-raised in either mode, which the general-purpose border does not; over surface-sunken it does not, so a panel on a sunken field needs its own answer."
|
|
585
889
|
}
|
|
586
890
|
},
|
|
587
891
|
"light": {
|
|
@@ -626,6 +930,14 @@ var THEME_SOURCES = {
|
|
|
626
930
|
"type": "color",
|
|
627
931
|
"value": "{color.gray-50}",
|
|
628
932
|
"description": "Flips with the mode \u2014 see the dark layer."
|
|
933
|
+
},
|
|
934
|
+
"shadow": {
|
|
935
|
+
"type": "color",
|
|
936
|
+
"value": "rgba(0, 0, 0, 0.18)"
|
|
937
|
+
},
|
|
938
|
+
"border-raised": {
|
|
939
|
+
"type": "color",
|
|
940
|
+
"value": "{color.gray-400}"
|
|
629
941
|
}
|
|
630
942
|
}
|
|
631
943
|
}
|
|
@@ -639,26 +951,26 @@ var weaselTheme = {
|
|
|
639
951
|
modes: THEME_SOURCES.weasel.modes
|
|
640
952
|
};
|
|
641
953
|
function hexToRgba(hex, alpha) {
|
|
642
|
-
const
|
|
643
|
-
if (!
|
|
644
|
-
const body =
|
|
645
|
-
const full = body.length === 3 ? body.replace(/./g, (
|
|
646
|
-
const
|
|
647
|
-
const
|
|
648
|
-
const
|
|
649
|
-
return `rgba(${
|
|
954
|
+
const m2 = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());
|
|
955
|
+
if (!m2) throw new Error(`Expected a hex color, got "${hex}"`);
|
|
956
|
+
const body = m2[1];
|
|
957
|
+
const full = body.length === 3 ? body.replace(/./g, (c2) => c2 + c2) : body;
|
|
958
|
+
const r2 = Number.parseInt(full.slice(0, 2), 16);
|
|
959
|
+
const g2 = Number.parseInt(full.slice(2, 4), 16);
|
|
960
|
+
const b2 = Number.parseInt(full.slice(4, 6), 16);
|
|
961
|
+
return `rgba(${r2}, ${g2}, ${b2}, ${alpha})`;
|
|
650
962
|
}
|
|
651
963
|
var REF = /^\{([^}]+)\}$/;
|
|
652
964
|
function refTarget(value) {
|
|
653
965
|
if (typeof value !== "string") return null;
|
|
654
|
-
const
|
|
655
|
-
if (!
|
|
656
|
-
const path =
|
|
966
|
+
const m2 = REF.exec(value.trim());
|
|
967
|
+
if (!m2) return null;
|
|
968
|
+
const path = m2[1];
|
|
657
969
|
const dot = path.indexOf(".");
|
|
658
970
|
return dot === -1 ? path : path.slice(dot + 1);
|
|
659
971
|
}
|
|
660
972
|
function fontStack(value) {
|
|
661
|
-
return value.map((
|
|
973
|
+
return value.map((f2) => typeof f2 === "string" && /\s/.test(f2) ? `'${f2}'` : String(f2)).join(", ");
|
|
662
974
|
}
|
|
663
975
|
function serialize(type, value) {
|
|
664
976
|
if (Array.isArray(value)) {
|
|
@@ -792,9 +1104,9 @@ function flattenTokens(doc) {
|
|
|
792
1104
|
for (const [groupName, group] of Object.entries(doc)) {
|
|
793
1105
|
if (groupName.startsWith("$")) continue;
|
|
794
1106
|
if (typeof group !== "object" || group === null) continue;
|
|
795
|
-
const
|
|
796
|
-
const groupType =
|
|
797
|
-
for (const [leaf, node] of Object.entries(
|
|
1107
|
+
const g2 = group;
|
|
1108
|
+
const groupType = g2.$type;
|
|
1109
|
+
for (const [leaf, node] of Object.entries(g2)) {
|
|
798
1110
|
if (leaf.startsWith("$")) continue;
|
|
799
1111
|
if (!isToken(node)) continue;
|
|
800
1112
|
const type = node.$type ?? groupType;
|
|
@@ -865,7 +1177,9 @@ var interstellar_tokens_default = {
|
|
|
865
1177
|
"fg-subtle": { $value: "#5a5a78" },
|
|
866
1178
|
accent: { $value: "#b08adb" },
|
|
867
1179
|
"accent-hover": { $value: "#c6a3ee" },
|
|
868
|
-
"focus-ring": { $value: "rgba(176, 138, 219, 0.45)" }
|
|
1180
|
+
"focus-ring": { $value: "rgba(176, 138, 219, 0.45)" },
|
|
1181
|
+
shadow: { $value: "rgba(0, 0, 0, 0.7)" },
|
|
1182
|
+
"border-raised": { $value: "rgba(255, 255, 255, 0.36)" }
|
|
869
1183
|
},
|
|
870
1184
|
gradient: {
|
|
871
1185
|
$type: "gradient",
|
|
@@ -887,7 +1201,9 @@ var interstellar_tokens_default = {
|
|
|
887
1201
|
"fg-subtle": { $value: "#a89b85" },
|
|
888
1202
|
accent: { $value: "#a86f3c" },
|
|
889
1203
|
"accent-hover": { $value: "#c2854f" },
|
|
890
|
-
"focus-ring": { $value: "rgba(168, 111, 60, 0.35)" }
|
|
1204
|
+
"focus-ring": { $value: "rgba(168, 111, 60, 0.35)" },
|
|
1205
|
+
shadow: { $value: "rgba(80, 60, 40, 0.18)" },
|
|
1206
|
+
"border-raised": { $value: "#9e8b71" }
|
|
891
1207
|
}
|
|
892
1208
|
}
|
|
893
1209
|
}
|
|
@@ -895,130 +1211,6 @@ var interstellar_tokens_default = {
|
|
|
895
1211
|
|
|
896
1212
|
// src/theme/interstellar.ts
|
|
897
1213
|
var interstellarTheme = loadDTCG(interstellar_tokens_default);
|
|
898
|
-
function DragGhost({ item, screenPos }) {
|
|
899
|
-
const [mounted, setMounted] = useState(false);
|
|
900
|
-
useEffect(() => setMounted(true), []);
|
|
901
|
-
if (!mounted || typeof document === "undefined") return null;
|
|
902
|
-
const style = {
|
|
903
|
-
left: `${screenPos.x + 12}px`,
|
|
904
|
-
top: `${screenPos.y + 12}px`
|
|
905
|
-
};
|
|
906
|
-
return createPortal(
|
|
907
|
-
/* @__PURE__ */ jsx("div", { className: "lk-drag-ghost", style, children: /* @__PURE__ */ jsx("span", { className: "lk-drag-ghost__label", children: item.label }) }),
|
|
908
|
-
document.body
|
|
909
|
-
);
|
|
910
|
-
}
|
|
911
|
-
function useDragDrop({
|
|
912
|
-
capability,
|
|
913
|
-
canvasContainerRef,
|
|
914
|
-
view,
|
|
915
|
-
state,
|
|
916
|
-
config,
|
|
917
|
-
setState,
|
|
918
|
-
emit
|
|
919
|
-
}) {
|
|
920
|
-
const [drag, setDrag] = useState(null);
|
|
921
|
-
const dragRef = useRef(null);
|
|
922
|
-
dragRef.current = drag;
|
|
923
|
-
const rafThrottle = useRef(null);
|
|
924
|
-
const isOverCanvas = useCallback(
|
|
925
|
-
(screenPos) => {
|
|
926
|
-
const el = canvasContainerRef.current;
|
|
927
|
-
if (!el) return false;
|
|
928
|
-
const r = el.getBoundingClientRect();
|
|
929
|
-
return screenPos.x >= r.left && screenPos.x <= r.right && screenPos.y >= r.top && screenPos.y <= r.bottom;
|
|
930
|
-
},
|
|
931
|
-
[canvasContainerRef]
|
|
932
|
-
);
|
|
933
|
-
const screenToWorldFromContainer = useCallback(
|
|
934
|
-
(screenPos) => {
|
|
935
|
-
const el = canvasContainerRef.current;
|
|
936
|
-
if (!el) return null;
|
|
937
|
-
const r = el.getBoundingClientRect();
|
|
938
|
-
return screenToWorld({ x: screenPos.x - r.left, y: screenPos.y - r.top }, view);
|
|
939
|
-
},
|
|
940
|
-
[canvasContainerRef, view]
|
|
941
|
-
);
|
|
942
|
-
useEffect(() => {
|
|
943
|
-
if (!drag) return;
|
|
944
|
-
const handleMove = (e) => {
|
|
945
|
-
const screenPos = { x: e.clientX, y: e.clientY };
|
|
946
|
-
const current = dragRef.current;
|
|
947
|
-
if (!current) return;
|
|
948
|
-
if (rafThrottle.current !== null) {
|
|
949
|
-
dragRef.current = { ...current, screenPos };
|
|
950
|
-
setDrag(dragRef.current);
|
|
951
|
-
return;
|
|
952
|
-
}
|
|
953
|
-
rafThrottle.current = requestAnimationFrame(() => {
|
|
954
|
-
rafThrottle.current = null;
|
|
955
|
-
const active = dragRef.current;
|
|
956
|
-
if (!active) return;
|
|
957
|
-
let feedback = null;
|
|
958
|
-
if (capability.onDragOver && isOverCanvas(screenPos)) {
|
|
959
|
-
const world = screenToWorldFromContainer(screenPos);
|
|
960
|
-
if (world) feedback = capability.onDragOver(world, active.item, state, config);
|
|
961
|
-
}
|
|
962
|
-
setDrag({ ...active, screenPos, feedback });
|
|
963
|
-
});
|
|
964
|
-
};
|
|
965
|
-
const handleUp = (e) => {
|
|
966
|
-
const screenPos = { x: e.clientX, y: e.clientY };
|
|
967
|
-
const active = dragRef.current;
|
|
968
|
-
if (!active) return;
|
|
969
|
-
if (rafThrottle.current !== null) {
|
|
970
|
-
cancelAnimationFrame(rafThrottle.current);
|
|
971
|
-
rafThrottle.current = null;
|
|
972
|
-
}
|
|
973
|
-
if (isOverCanvas(screenPos)) {
|
|
974
|
-
const world = screenToWorldFromContainer(screenPos);
|
|
975
|
-
if (world) {
|
|
976
|
-
const nextState = capability.onDrop(world, active.item, state, config);
|
|
977
|
-
setState(nextState);
|
|
978
|
-
emit("canvas.itemAdded");
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
setDrag(null);
|
|
982
|
-
};
|
|
983
|
-
window.addEventListener("pointermove", handleMove);
|
|
984
|
-
window.addEventListener("pointerup", handleUp);
|
|
985
|
-
return () => {
|
|
986
|
-
window.removeEventListener("pointermove", handleMove);
|
|
987
|
-
window.removeEventListener("pointerup", handleUp);
|
|
988
|
-
};
|
|
989
|
-
}, [drag, capability, state, config, setState, emit, isOverCanvas, screenToWorldFromContainer]);
|
|
990
|
-
const startDrag = useCallback((item, originScreenPos) => {
|
|
991
|
-
setDrag({ item, screenPos: originScreenPos, feedback: null });
|
|
992
|
-
}, []);
|
|
993
|
-
return { drag, startDrag };
|
|
994
|
-
}
|
|
995
|
-
function DragOverlay({ drag }) {
|
|
996
|
-
if (!drag) return null;
|
|
997
|
-
return /* @__PURE__ */ jsx(DragGhost, { item: drag.item, screenPos: drag.screenPos });
|
|
998
|
-
}
|
|
999
|
-
function Palette({ items, onDragStart, className }) {
|
|
1000
|
-
return /* @__PURE__ */ jsx("div", { className: className ? `lk-palette ${className}` : "lk-palette", children: items.map((item) => /* @__PURE__ */ jsx(
|
|
1001
|
-
"button",
|
|
1002
|
-
{
|
|
1003
|
-
type: "button",
|
|
1004
|
-
className: "lk-palette__item",
|
|
1005
|
-
onPointerDown: (e) => {
|
|
1006
|
-
e.preventDefault();
|
|
1007
|
-
onDragStart(item, { x: e.clientX, y: e.clientY });
|
|
1008
|
-
},
|
|
1009
|
-
children: /* @__PURE__ */ jsx("span", { className: "lk-palette__label", children: item.label })
|
|
1010
|
-
},
|
|
1011
|
-
item.id
|
|
1012
|
-
)) });
|
|
1013
|
-
}
|
|
1014
|
-
var LabContext = createContext(null);
|
|
1015
|
-
function useLabContext() {
|
|
1016
|
-
const ctx = useContext(LabContext);
|
|
1017
|
-
if (ctx === null) {
|
|
1018
|
-
throw new Error("useLabContext must be used inside <Lab>");
|
|
1019
|
-
}
|
|
1020
|
-
return ctx;
|
|
1021
|
-
}
|
|
1022
1214
|
|
|
1023
1215
|
// src/state/view.ts
|
|
1024
1216
|
var DEFAULT_VIEW = { zoom: 1, pan: { x: 0, y: 0 } };
|
|
@@ -1030,112 +1222,40 @@ function as2DView(view) {
|
|
|
1030
1222
|
if (typeof v.pan.x !== "number" || typeof v.pan.y !== "number") return null;
|
|
1031
1223
|
return { zoom: v.zoom, pan: { x: v.pan.x, y: v.pan.y } };
|
|
1032
1224
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
ctx.instrumentName,
|
|
1037
|
-
" has no config fields."
|
|
1038
|
-
] }) });
|
|
1039
|
-
}
|
|
1040
|
-
return /* @__PURE__ */ jsx(Sidebar, { title: ctx.instrumentName, children: /* @__PURE__ */ jsx(
|
|
1041
|
-
ControlPanel,
|
|
1042
|
-
{
|
|
1043
|
-
fields: ctx.configFields,
|
|
1044
|
-
config: ctx.config,
|
|
1045
|
-
setConfig: (key, value) => ctx.setConfig(key, value)
|
|
1046
|
-
}
|
|
1047
|
-
) });
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
// src/ui/format.ts
|
|
1051
|
-
var MINUS = "\u2212";
|
|
1052
|
-
function formatNumber(n, fractionDigits) {
|
|
1053
|
-
if (!Number.isFinite(n)) return String(n);
|
|
1054
|
-
const s = fractionDigits == null ? String(n) : n.toFixed(fractionDigits);
|
|
1055
|
-
return s.startsWith("-") ? MINUS + s.slice(1) : s;
|
|
1225
|
+
var TrialDragContext = createContext(null);
|
|
1226
|
+
function useTrialDrag() {
|
|
1227
|
+
return useContext(TrialDragContext);
|
|
1056
1228
|
}
|
|
1057
|
-
function
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
if (zoom <= 2) return `${formatNumber(Math.round(zoom * 100))}%`;
|
|
1063
|
-
if (zoom >= 100) return `${Math.round(zoom).toLocaleString("en-US")}x`;
|
|
1064
|
-
return `${formatNumber(Math.round(zoom * 10) / 10)}x`;
|
|
1065
|
-
}
|
|
1066
|
-
function DefaultStatusBar({ ctx }) {
|
|
1067
|
-
return /* @__PURE__ */ jsxs(StatusBar, { children: [
|
|
1068
|
-
/* @__PURE__ */ jsx(StatusBar.Section, { children: ctx.instrumentName }),
|
|
1069
|
-
ctx.zoom === null ? null : /* @__PURE__ */ jsx(StatusBar.Section, { children: formatZoom(ctx.zoom) })
|
|
1229
|
+
function Draggable({ nodeId, title, children }) {
|
|
1230
|
+
const handlers = useDragHandle(nodeId);
|
|
1231
|
+
return /* @__PURE__ */ jsxs("div", { className: "lk-trial__titlebar lk-trial__titlebar--draggable", ...handlers, children: [
|
|
1232
|
+
/* @__PURE__ */ jsx("span", { className: "lk-trial__title", children: title }),
|
|
1233
|
+
children
|
|
1070
1234
|
] });
|
|
1071
1235
|
}
|
|
1072
|
-
function
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
onClick: ctx.redo,
|
|
1080
|
-
disabled: !ctx.canRedo,
|
|
1081
|
-
title: "Redo (Cmd/Ctrl+Shift+Z)",
|
|
1082
|
-
children: "Redo"
|
|
1083
|
-
}
|
|
1084
|
-
),
|
|
1085
|
-
/* @__PURE__ */ jsx(Toolbar.Spacer, {})
|
|
1086
|
-
] }),
|
|
1087
|
-
ctx.hasCanvas && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1088
|
-
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.zoomOut, title: "Zoom out", children: "\u2212" }),
|
|
1089
|
-
/* @__PURE__ */ jsx("span", { className: "lk-toolbar__zoom-label", children: formatZoom(ctx.zoom) }),
|
|
1090
|
-
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.zoomIn, title: "Zoom in", children: "+" }),
|
|
1091
|
-
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.resetZoom, title: "Reset zoom", children: "1:1" }),
|
|
1092
|
-
/* @__PURE__ */ jsx(Toolbar.Spacer, {})
|
|
1093
|
-
] }),
|
|
1094
|
-
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: () => ctx.saveSnapshot(), title: "Save snapshot (Cmd/Ctrl+S)", children: "Save" }),
|
|
1095
|
-
ctx.savedSnapshots.length > 0 && /* @__PURE__ */ jsxs(
|
|
1096
|
-
"select",
|
|
1097
|
-
{
|
|
1098
|
-
className: "lk-toolbar__load-select",
|
|
1099
|
-
"aria-label": "Load snapshot",
|
|
1100
|
-
defaultValue: "",
|
|
1101
|
-
onChange: (e) => {
|
|
1102
|
-
const id = e.target.value;
|
|
1103
|
-
if (id) {
|
|
1104
|
-
ctx.loadSnapshot(id);
|
|
1105
|
-
e.target.value = "";
|
|
1106
|
-
}
|
|
1107
|
-
},
|
|
1108
|
-
children: [
|
|
1109
|
-
/* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "Load\u2026" }),
|
|
1110
|
-
ctx.savedSnapshots.map((sn) => /* @__PURE__ */ jsx("option", { value: sn.id, children: sn.name }, sn.id))
|
|
1111
|
-
]
|
|
1112
|
-
}
|
|
1113
|
-
),
|
|
1114
|
-
/* @__PURE__ */ jsx(Toolbar.Spacer, {}),
|
|
1115
|
-
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.clone, title: "Clone trial", children: "Clone" }),
|
|
1116
|
-
/* @__PURE__ */ jsx(Toolbar.Button, { onClick: ctx.reset, title: "Reset trial", children: "Reset" }),
|
|
1117
|
-
/* @__PURE__ */ jsx(
|
|
1118
|
-
Toolbar.Button,
|
|
1119
|
-
{
|
|
1120
|
-
onClick: ctx.close,
|
|
1121
|
-
disabled: ctx.isLastTrial,
|
|
1122
|
-
title: ctx.isLastTrial ? "Cannot close the last trial" : "Close trial",
|
|
1123
|
-
children: "Close"
|
|
1124
|
-
}
|
|
1125
|
-
)
|
|
1236
|
+
function TrialTitleBar({ title, children }) {
|
|
1237
|
+
const drag = useTrialDrag();
|
|
1238
|
+
if (drag)
|
|
1239
|
+
return /* @__PURE__ */ jsx(Draggable, { nodeId: drag.nodeId, title, children });
|
|
1240
|
+
return /* @__PURE__ */ jsxs("div", { className: "lk-trial__titlebar", children: [
|
|
1241
|
+
/* @__PURE__ */ jsx("span", { className: "lk-trial__title", children: title }),
|
|
1242
|
+
children
|
|
1126
1243
|
] });
|
|
1127
1244
|
}
|
|
1245
|
+
var NO_OP = () => {
|
|
1246
|
+
};
|
|
1128
1247
|
function TrialChrome({
|
|
1129
1248
|
trialId,
|
|
1130
1249
|
record,
|
|
1131
1250
|
instrument,
|
|
1132
1251
|
isLastTrial,
|
|
1133
|
-
toolbar,
|
|
1134
|
-
sidebar,
|
|
1135
|
-
statusBar,
|
|
1136
1252
|
undoBindings,
|
|
1137
1253
|
job,
|
|
1138
|
-
|
|
1254
|
+
trialChrome,
|
|
1255
|
+
chrome,
|
|
1256
|
+
suppress,
|
|
1257
|
+
activeToolId = null,
|
|
1258
|
+
setActiveTool = NO_OP,
|
|
1139
1259
|
children
|
|
1140
1260
|
}) {
|
|
1141
1261
|
const lab = useLabContext();
|
|
@@ -1145,7 +1265,8 @@ function TrialChrome({
|
|
|
1145
1265
|
const updateTrialConfig = useStore(storeCtx.store, (s) => s.updateTrialConfig);
|
|
1146
1266
|
const updateTrialState = useStore(storeCtx.store, (s) => s.updateTrialState);
|
|
1147
1267
|
const view2d = as2DView(record.view);
|
|
1148
|
-
const
|
|
1268
|
+
const configSchema = useConfigSchema(instrument);
|
|
1269
|
+
const ctx = useMemo(() => {
|
|
1149
1270
|
const setZoom = (z) => {
|
|
1150
1271
|
if (!view2d) return;
|
|
1151
1272
|
updateTrialView(trialId, { ...view2d, zoom: z });
|
|
@@ -1153,33 +1274,15 @@ function TrialChrome({
|
|
|
1153
1274
|
return {
|
|
1154
1275
|
trialId,
|
|
1155
1276
|
instrumentName: record.instrumentName,
|
|
1156
|
-
|
|
1277
|
+
isLastTrial,
|
|
1278
|
+
zoom: view2d ? view2d.zoom : null,
|
|
1279
|
+
setZoom,
|
|
1157
1280
|
canUndo: undoBindings?.canUndo ?? false,
|
|
1158
1281
|
canRedo: undoBindings?.canRedo ?? false,
|
|
1159
|
-
undo: undoBindings?.undo ??
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
zoom: view2d?.zoom ?? 1,
|
|
1164
|
-
setZoom,
|
|
1165
|
-
zoomIn: () => setZoom((view2d?.zoom ?? 1) * 1.25),
|
|
1166
|
-
zoomOut: () => setZoom((view2d?.zoom ?? 1) * 0.8),
|
|
1167
|
-
resetZoom: () => setZoom(1),
|
|
1168
|
-
hasCanvas: instrument.canvas != null,
|
|
1169
|
-
savedSnapshots: lab.savedSnapshots.filter((s) => s.trialId === trialId),
|
|
1170
|
-
saveSnapshot: (name) => lab.saveSnapshot(trialId, name),
|
|
1171
|
-
loadSnapshot: (snapshotId) => lab.loadSnapshot(trialId, snapshotId),
|
|
1172
|
-
clone: () => lab.cloneTrial(trialId),
|
|
1173
|
-
reset: () => lab.resetTrial(trialId),
|
|
1174
|
-
close: () => lab.closeTrial(trialId),
|
|
1175
|
-
isLastTrial
|
|
1176
|
-
};
|
|
1177
|
-
}, [trialId, record, instrument, lab, isLastTrial, updateTrialView, undoBindings, view2d]);
|
|
1178
|
-
const sidebarCtx = useMemo(
|
|
1179
|
-
() => ({
|
|
1180
|
-
trialId,
|
|
1181
|
-
instrumentName: record.instrumentName,
|
|
1182
|
-
configFields: instrument.configSchema?.() ?? [],
|
|
1282
|
+
undo: undoBindings?.undo ?? NO_OP,
|
|
1283
|
+
redo: undoBindings?.redo ?? NO_OP,
|
|
1284
|
+
configSchema,
|
|
1285
|
+
configFields: instrument.config ? [] : instrument.configSchema?.() ?? [],
|
|
1183
1286
|
config: record.config,
|
|
1184
1287
|
setConfig: (key, value) => {
|
|
1185
1288
|
const prevConfig = record.config;
|
|
@@ -1194,25 +1297,54 @@ function TrialChrome({
|
|
|
1194
1297
|
const nextState = instrument.onConfigChange(nextConfig, prevConfig, record.state);
|
|
1195
1298
|
updateTrialState(trialId, nextState);
|
|
1196
1299
|
}
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1300
|
+
},
|
|
1301
|
+
savedSnapshots: lab.savedSnapshots.filter((s) => s.trialId === trialId),
|
|
1302
|
+
saveSnapshot: (name) => lab.saveSnapshot(trialId, name),
|
|
1303
|
+
loadSnapshot: (snapshotId) => lab.loadSnapshot(trialId, snapshotId),
|
|
1304
|
+
clone: () => lab.cloneTrial(trialId),
|
|
1305
|
+
reset: () => lab.resetTrial(trialId),
|
|
1306
|
+
close: () => lab.closeTrial(trialId),
|
|
1307
|
+
activeToolId,
|
|
1308
|
+
setActiveTool
|
|
1309
|
+
};
|
|
1310
|
+
}, [
|
|
1202
1311
|
trialId,
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1312
|
+
record,
|
|
1313
|
+
instrument,
|
|
1314
|
+
lab,
|
|
1315
|
+
isLastTrial,
|
|
1316
|
+
updateTrialView,
|
|
1317
|
+
updateTrialConfig,
|
|
1318
|
+
updateTrialState,
|
|
1319
|
+
undoBindings,
|
|
1320
|
+
view2d,
|
|
1321
|
+
activeToolId,
|
|
1322
|
+
setActiveTool,
|
|
1323
|
+
configSchema
|
|
1324
|
+
]);
|
|
1325
|
+
const contributions = useMemo(
|
|
1326
|
+
() => suppressContributions(
|
|
1327
|
+
mergeContributions(
|
|
1328
|
+
builtinContributions(instrument, ctx, lab.controls),
|
|
1329
|
+
[...instrument.chrome ?? []],
|
|
1330
|
+
[...trialChrome ?? []],
|
|
1331
|
+
[...chrome ?? []]
|
|
1332
|
+
),
|
|
1333
|
+
suppress ?? []
|
|
1334
|
+
),
|
|
1335
|
+
[instrument, ctx, trialChrome, chrome, suppress, lab.controls]
|
|
1336
|
+
);
|
|
1337
|
+
const inRegion = (region) => contributions.filter((c2) => c2.region === region);
|
|
1206
1338
|
const handleKeyDown = (e) => {
|
|
1207
1339
|
const mod = e.metaKey || e.ctrlKey;
|
|
1208
1340
|
if (!mod) return;
|
|
1209
1341
|
if (e.key === "z" || e.key === "Z") {
|
|
1210
1342
|
e.preventDefault();
|
|
1211
|
-
if (e.shiftKey)
|
|
1212
|
-
else
|
|
1343
|
+
if (e.shiftKey) ctx.redo();
|
|
1344
|
+
else ctx.undo();
|
|
1213
1345
|
} else if (e.key === "s" || e.key === "S") {
|
|
1214
1346
|
e.preventDefault();
|
|
1215
|
-
|
|
1347
|
+
ctx.saveSnapshot();
|
|
1216
1348
|
}
|
|
1217
1349
|
};
|
|
1218
1350
|
return /* @__PURE__ */ jsxs(
|
|
@@ -1220,40 +1352,32 @@ function TrialChrome({
|
|
|
1220
1352
|
{
|
|
1221
1353
|
className: "lk-trial",
|
|
1222
1354
|
"aria-label": `Trial ${record.instrumentName}`,
|
|
1355
|
+
tabIndex: -1,
|
|
1223
1356
|
onKeyDown: handleKeyDown,
|
|
1224
1357
|
children: [
|
|
1225
|
-
/* @__PURE__ */ jsx(
|
|
1358
|
+
/* @__PURE__ */ jsx(TrialTitleBar, { title: record.instrumentName, children: /* @__PURE__ */ jsx(TitleBarRegion, { contributions: inRegion("titlebar"), ctx }) }),
|
|
1359
|
+
/* @__PURE__ */ jsx("div", { className: "lk-trial__toolbar", children: /* @__PURE__ */ jsx(ToolbarRegion, { contributions: inRegion("toolbar"), ctx }) }),
|
|
1226
1360
|
/* @__PURE__ */ jsxs("div", { className: "lk-trial__body", children: [
|
|
1227
|
-
/* @__PURE__ */
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1361
|
+
/* @__PURE__ */ jsx(PaletteRegion, { contributions: inRegion("palette"), ctx }),
|
|
1362
|
+
/* @__PURE__ */ jsx("div", { className: "lk-trial__sidebar", children: /* @__PURE__ */ jsx(SidebarRegion, { contributions: inRegion("sidebar"), ctx }) }),
|
|
1363
|
+
/* @__PURE__ */ jsxs("div", { className: `lk-trial__content${instrument.canvas ? " lk-trial__content--flush" : ""}`, children: [
|
|
1364
|
+
children,
|
|
1365
|
+
/* @__PURE__ */ jsx(ViewportRegion, { contributions: inRegion("viewport"), ctx })
|
|
1366
|
+
] })
|
|
1232
1367
|
] }),
|
|
1233
1368
|
/* @__PURE__ */ jsxs("div", { className: "lk-trial__status", children: [
|
|
1234
|
-
job ? /* @__PURE__ */
|
|
1235
|
-
|
|
1236
|
-
job.done,
|
|
1237
|
-
job.total === null ? "" : ` / ${job.total}`
|
|
1238
|
-
] }),
|
|
1239
|
-
job.failures.length > 0 ? /* @__PURE__ */ jsxs("span", { className: "lk-trial__job-failures", children: [
|
|
1240
|
-
job.failures.length,
|
|
1241
|
-
" failed"
|
|
1242
|
-
] }) : null,
|
|
1243
|
-
job.error ? /* @__PURE__ */ jsx("span", { className: "lk-trial__job-error", children: job.error }) : null,
|
|
1244
|
-
job.status === "running" ? /* @__PURE__ */ jsx("button", { type: "button", className: "lk-trial__job-cancel", onClick: job.cancel, children: "Cancel" }) : null
|
|
1245
|
-
] }) : null,
|
|
1246
|
-
statusBar ? statusBar(statusCtx) : /* @__PURE__ */ jsx(DefaultStatusBar, { ctx: statusCtx })
|
|
1369
|
+
job ? /* @__PURE__ */ jsx(JobProgress, { job }) : null,
|
|
1370
|
+
/* @__PURE__ */ jsx(StatusRegion, { contributions: inRegion("status"), ctx })
|
|
1247
1371
|
] })
|
|
1248
1372
|
]
|
|
1249
1373
|
}
|
|
1250
1374
|
);
|
|
1251
1375
|
}
|
|
1252
|
-
function Trial({ id }) {
|
|
1376
|
+
function Trial({ id, chrome, suppress }) {
|
|
1253
1377
|
const lab = useLabContext();
|
|
1254
1378
|
const storeCtx = useContext(LabStoreContext);
|
|
1255
1379
|
if (!storeCtx) throw new Error("[labkit] <Trial> requires <LabStoreProvider>");
|
|
1256
|
-
const record = useStore(storeCtx.store, (s) => s.trials.find((
|
|
1380
|
+
const record = useStore(storeCtx.store, (s) => s.trials.find((w2) => w2.id === id));
|
|
1257
1381
|
if (!record) {
|
|
1258
1382
|
return /* @__PURE__ */ jsxs("div", { className: "lk-trial lk-trial--unknown", children: [
|
|
1259
1383
|
"Trial not found: ",
|
|
@@ -1273,16 +1397,21 @@ function Trial({ id }) {
|
|
|
1273
1397
|
record,
|
|
1274
1398
|
instrument,
|
|
1275
1399
|
store: storeCtx.store,
|
|
1276
|
-
isLast: lab.trials.length <= 1
|
|
1400
|
+
isLast: lab.trials.length <= 1,
|
|
1401
|
+
chrome,
|
|
1402
|
+
suppress
|
|
1277
1403
|
}
|
|
1278
1404
|
);
|
|
1279
1405
|
}
|
|
1280
|
-
function TrialRuntime({ record, instrument, store, isLast }) {
|
|
1406
|
+
function TrialRuntime({ record, instrument, store, isLast, chrome, suppress }) {
|
|
1281
1407
|
const canvasContainerRef = useRef(null);
|
|
1282
1408
|
const updateTrialState = useStore(store, (s) => s.updateTrialState);
|
|
1283
1409
|
const updateTrialConfig = useStore(store, (s) => s.updateTrialConfig);
|
|
1284
1410
|
const updateTrialView = useStore(store, (s) => s.updateTrialView);
|
|
1285
1411
|
const updateTrialUndoStack = useStore(store, (s) => s.updateTrialUndoStack);
|
|
1412
|
+
const labToolId = useStore(store, (s) => s.activeToolId);
|
|
1413
|
+
const setLabTool = useStore(store, (s) => s.setLabTool);
|
|
1414
|
+
const setTrialTool = useStore(store, (s) => s.setTrialTool);
|
|
1286
1415
|
const busRef = useRef(null);
|
|
1287
1416
|
if (busRef.current === null) busRef.current = createEventBus();
|
|
1288
1417
|
const bus = busRef.current;
|
|
@@ -1293,12 +1422,18 @@ function TrialRuntime({ record, instrument, store, isLast }) {
|
|
|
1293
1422
|
const maxDepth = undoCap?.maxDepth ?? 50;
|
|
1294
1423
|
const snapshotIfNeeded = (event) => {
|
|
1295
1424
|
if (!undoCap || !undoEvents.has(event)) return;
|
|
1296
|
-
const current = store.getState().trials.find((
|
|
1425
|
+
const current = store.getState().trials.find((w2) => w2.id === record.id);
|
|
1297
1426
|
if (!current) return;
|
|
1298
1427
|
const snap = structuredClone(current.state);
|
|
1299
1428
|
updateTrialUndoStack(record.id, (prev) => pushSnapshot(prev, snap, maxDepth));
|
|
1300
1429
|
};
|
|
1301
1430
|
const setView = (v) => updateTrialView(record.id, v);
|
|
1431
|
+
const declaresTools = instrument.tools != null;
|
|
1432
|
+
const resolvedToolId = declaresTools ? record.activeToolId ?? instrument.tools?.initial ?? instrument.tools?.tools[0]?.id ?? null : labToolId;
|
|
1433
|
+
const setActiveTool = (id) => {
|
|
1434
|
+
if (declaresTools) setTrialTool(record.id, id);
|
|
1435
|
+
else setLabTool(id);
|
|
1436
|
+
};
|
|
1302
1437
|
const view2d = as2DView(record.view);
|
|
1303
1438
|
const jobCap = instrument.job;
|
|
1304
1439
|
const job = useJob({
|
|
@@ -1332,7 +1467,8 @@ function TrialRuntime({ record, instrument, store, isLast }) {
|
|
|
1332
1467
|
setZoom: (z) => {
|
|
1333
1468
|
if (!view2d) return;
|
|
1334
1469
|
updateTrialView(record.id, { ...view2d, zoom: z });
|
|
1335
|
-
}
|
|
1470
|
+
},
|
|
1471
|
+
activeToolId: resolvedToolId
|
|
1336
1472
|
},
|
|
1337
1473
|
emit: (event) => {
|
|
1338
1474
|
snapshotIfNeeded(event);
|
|
@@ -1359,7 +1495,7 @@ function TrialRuntime({ record, instrument, store, isLast }) {
|
|
|
1359
1495
|
const canvasLayers = useMemo(() => {
|
|
1360
1496
|
if (!instrument.canvas) return [];
|
|
1361
1497
|
const baseLayers = instrument.canvas.layers;
|
|
1362
|
-
const ordered = layerOrder ? [...baseLayers].sort((a,
|
|
1498
|
+
const ordered = layerOrder ? [...baseLayers].sort((a, b2) => layerOrder.indexOf(a.id) - layerOrder.indexOf(b2.id)) : baseLayers;
|
|
1363
1499
|
return ordered.map((layer) => ({
|
|
1364
1500
|
id: layer.id,
|
|
1365
1501
|
visible: layerVisibility[layer.id] !== false,
|
|
@@ -1372,7 +1508,7 @@ function TrialRuntime({ record, instrument, store, isLast }) {
|
|
|
1372
1508
|
}, [instrument.canvas, record.state, record.config, layerVisibility, layerOrder]);
|
|
1373
1509
|
const layerDescriptors = useMemo(() => {
|
|
1374
1510
|
if (!instrument.layers) return [];
|
|
1375
|
-
return instrument.layers.ids.map((
|
|
1511
|
+
return instrument.layers.ids.map((l) => typeof l === "string" ? { id: l, label: l } : l);
|
|
1376
1512
|
}, [instrument.layers]);
|
|
1377
1513
|
const dragDropResult = useDragDrop({
|
|
1378
1514
|
capability: instrument.dragDrop ?? { palette: [], onDrop: (_p, _i, s) => s },
|
|
@@ -1406,12 +1542,12 @@ function TrialRuntime({ record, instrument, store, isLast }) {
|
|
|
1406
1542
|
render: (ctx) => {
|
|
1407
1543
|
const el = canvasContainerRef.current;
|
|
1408
1544
|
if (!el) return;
|
|
1409
|
-
const
|
|
1545
|
+
const r2 = el.getBoundingClientRect();
|
|
1410
1546
|
ctx.save();
|
|
1411
1547
|
ctx.strokeStyle = fb.ok ? "#3a7" : "#c44";
|
|
1412
1548
|
ctx.lineWidth = 2;
|
|
1413
1549
|
ctx.beginPath();
|
|
1414
|
-
ctx.arc(screen.x -
|
|
1550
|
+
ctx.arc(screen.x - r2.left, screen.y - r2.top, 16, 0, Math.PI * 2);
|
|
1415
1551
|
ctx.stroke();
|
|
1416
1552
|
ctx.restore();
|
|
1417
1553
|
}
|
|
@@ -1420,45 +1556,64 @@ function TrialRuntime({ record, instrument, store, isLast }) {
|
|
|
1420
1556
|
}, [canvasLayers, dragDropResult.drag]);
|
|
1421
1557
|
let body;
|
|
1422
1558
|
if (instrument.canvas) {
|
|
1423
|
-
body = /* @__PURE__ */ jsxs(
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
}
|
|
1438
|
-
),
|
|
1439
|
-
/* @__PURE__ */ jsx(DragOverlay, { drag: dragDropResult.drag })
|
|
1440
|
-
]
|
|
1441
|
-
}
|
|
1442
|
-
);
|
|
1559
|
+
body = /* @__PURE__ */ jsxs("div", { ref: canvasContainerRef, className: "lk-trial__canvas-host", children: [
|
|
1560
|
+
/* @__PURE__ */ jsx(
|
|
1561
|
+
CanvasStack,
|
|
1562
|
+
{
|
|
1563
|
+
layers: layersWithFeedback,
|
|
1564
|
+
view: view2d ?? DEFAULT_VIEW,
|
|
1565
|
+
onViewChange: setView,
|
|
1566
|
+
minZoom: instrument.canvas.minZoom,
|
|
1567
|
+
maxZoom: instrument.canvas.maxZoom,
|
|
1568
|
+
children: instrument.render(renderCtx)
|
|
1569
|
+
}
|
|
1570
|
+
),
|
|
1571
|
+
/* @__PURE__ */ jsx(DragOverlay, { drag: dragDropResult.drag })
|
|
1572
|
+
] });
|
|
1443
1573
|
} else {
|
|
1444
1574
|
body = instrument.render(renderCtx);
|
|
1445
1575
|
}
|
|
1446
|
-
const
|
|
1447
|
-
const
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1576
|
+
const startDrag = dragDropResult.startDrag;
|
|
1577
|
+
const paletteNode = useMemo(
|
|
1578
|
+
() => paletteItems.length > 0 ? /* @__PURE__ */ jsx(Palette, { items: paletteItems, onDragStart: startDrag }) : null,
|
|
1579
|
+
[paletteItems, startDrag]
|
|
1580
|
+
);
|
|
1581
|
+
const layerListNode = useMemo(
|
|
1582
|
+
() => instrument.layers && layerDescriptors.length > 0 ? /* @__PURE__ */ jsx(
|
|
1583
|
+
LayerList,
|
|
1584
|
+
{
|
|
1585
|
+
layers: layerDescriptors,
|
|
1586
|
+
visibility: layerVisibility,
|
|
1587
|
+
onReorder: (next) => {
|
|
1588
|
+
setLayerOrder(next.map((l) => l.id));
|
|
1589
|
+
bus.emit("layers.reorder");
|
|
1590
|
+
},
|
|
1591
|
+
onToggle: (lid, visible) => {
|
|
1592
|
+
setLayerVisibility((prev) => ({ ...prev, [lid]: visible }));
|
|
1593
|
+
bus.emit("layers.toggle");
|
|
1594
|
+
}
|
|
1459
1595
|
}
|
|
1596
|
+
) : null,
|
|
1597
|
+
[instrument.layers, layerDescriptors, layerVisibility, bus]
|
|
1598
|
+
);
|
|
1599
|
+
const extraChrome = useMemo(() => {
|
|
1600
|
+
const out = [];
|
|
1601
|
+
if (paletteNode) {
|
|
1602
|
+
out.push({
|
|
1603
|
+
id: "dragdrop-palette",
|
|
1604
|
+
region: "sidebar",
|
|
1605
|
+
item: { title: "Parts", body: paletteNode }
|
|
1606
|
+
});
|
|
1460
1607
|
}
|
|
1461
|
-
|
|
1608
|
+
if (layerListNode) {
|
|
1609
|
+
out.push({
|
|
1610
|
+
id: "layer-list",
|
|
1611
|
+
region: "sidebar",
|
|
1612
|
+
item: { title: "Layers", body: layerListNode }
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
return out;
|
|
1616
|
+
}, [paletteNode, layerListNode]);
|
|
1462
1617
|
return /* @__PURE__ */ jsx(
|
|
1463
1618
|
TrialChrome,
|
|
1464
1619
|
{
|
|
@@ -1468,10 +1623,11 @@ function TrialRuntime({ record, instrument, store, isLast }) {
|
|
|
1468
1623
|
instrument,
|
|
1469
1624
|
isLastTrial: isLast,
|
|
1470
1625
|
undoBindings,
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1626
|
+
trialChrome: extraChrome,
|
|
1627
|
+
chrome,
|
|
1628
|
+
suppress,
|
|
1629
|
+
activeToolId: resolvedToolId,
|
|
1630
|
+
setActiveTool,
|
|
1475
1631
|
children: body
|
|
1476
1632
|
}
|
|
1477
1633
|
);
|
|
@@ -1504,7 +1660,7 @@ function addTrial(trials, instruments, instrumentName) {
|
|
|
1504
1660
|
return [...trials, record];
|
|
1505
1661
|
}
|
|
1506
1662
|
function cloneTrial(trials, id) {
|
|
1507
|
-
const sourceIdx = trials.findIndex((
|
|
1663
|
+
const sourceIdx = trials.findIndex((w2) => w2.id === id);
|
|
1508
1664
|
const source = trials[sourceIdx];
|
|
1509
1665
|
if (!source) return trials;
|
|
1510
1666
|
const clone = {
|
|
@@ -1519,11 +1675,11 @@ function cloneTrial(trials, id) {
|
|
|
1519
1675
|
}
|
|
1520
1676
|
function closeTrial(trials, id) {
|
|
1521
1677
|
if (trials.length <= 1) return trials;
|
|
1522
|
-
const next = trials.filter((
|
|
1678
|
+
const next = trials.filter((w2) => w2.id !== id);
|
|
1523
1679
|
return next.length === trials.length ? trials : next;
|
|
1524
1680
|
}
|
|
1525
1681
|
function resetTrial(trials, id, instruments) {
|
|
1526
|
-
const idx = trials.findIndex((
|
|
1682
|
+
const idx = trials.findIndex((w2) => w2.id === id);
|
|
1527
1683
|
const current = trials[idx];
|
|
1528
1684
|
if (!current) return trials;
|
|
1529
1685
|
const instrument = findInstrument(instruments, current.instrumentName);
|
|
@@ -1538,12 +1694,80 @@ function resetTrial(trials, id, instruments) {
|
|
|
1538
1694
|
return [...trials.slice(0, idx), reset, ...trials.slice(idx + 1)];
|
|
1539
1695
|
}
|
|
1540
1696
|
function reorderTrials(trials, ids) {
|
|
1541
|
-
const byId = new Map(trials.map((
|
|
1542
|
-
const named = ids.map((id) => byId.get(id)).filter((
|
|
1543
|
-
const seen = new Set(named.map((
|
|
1544
|
-
const rest = trials.filter((
|
|
1697
|
+
const byId = new Map(trials.map((w2) => [w2.id, w2]));
|
|
1698
|
+
const named = ids.map((id) => byId.get(id)).filter((w2) => w2 !== void 0);
|
|
1699
|
+
const seen = new Set(named.map((w2) => w2.id));
|
|
1700
|
+
const rest = trials.filter((w2) => !seen.has(w2.id));
|
|
1545
1701
|
return [...named, ...rest];
|
|
1546
1702
|
}
|
|
1703
|
+
var MODES = [
|
|
1704
|
+
{ value: "auto", label: "Auto" },
|
|
1705
|
+
{ value: "light", label: "Light" },
|
|
1706
|
+
{ value: "dark", label: "Dark" }
|
|
1707
|
+
];
|
|
1708
|
+
function LabHeader() {
|
|
1709
|
+
const lab = useLabContext();
|
|
1710
|
+
const only = lab.instruments.length === 1 ? lab.instruments[0] : null;
|
|
1711
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1712
|
+
only ? /* @__PURE__ */ jsxs(
|
|
1713
|
+
"button",
|
|
1714
|
+
{
|
|
1715
|
+
type: "button",
|
|
1716
|
+
className: "lk-lab-header__add",
|
|
1717
|
+
onClick: () => lab.addTrial(only.name),
|
|
1718
|
+
children: [
|
|
1719
|
+
/* @__PURE__ */ jsx(Rt, { size: 16 }),
|
|
1720
|
+
/* @__PURE__ */ jsx("span", { children: "Add trial" })
|
|
1721
|
+
]
|
|
1722
|
+
}
|
|
1723
|
+
) : (
|
|
1724
|
+
// Held at null so the control stays an "add one" action rather than
|
|
1725
|
+
// reading as the current instrument.
|
|
1726
|
+
/* @__PURE__ */ jsx(
|
|
1727
|
+
p,
|
|
1728
|
+
{
|
|
1729
|
+
className: "lk-lab-header__add-select",
|
|
1730
|
+
"aria-label": "Add trial",
|
|
1731
|
+
placeholder: "Add trial\u2026",
|
|
1732
|
+
selectedKey: null,
|
|
1733
|
+
options: lab.instruments.map((i) => ({ value: i.name, label: i.name })),
|
|
1734
|
+
onSelectionChange: (name) => {
|
|
1735
|
+
if (name != null) lab.addTrial(String(name));
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
)
|
|
1739
|
+
),
|
|
1740
|
+
/* @__PURE__ */ jsx(
|
|
1741
|
+
r,
|
|
1742
|
+
{
|
|
1743
|
+
className: "lk-lab-header__mode",
|
|
1744
|
+
ariaLabel: "Color mode",
|
|
1745
|
+
items: MODES,
|
|
1746
|
+
value: lab.mode,
|
|
1747
|
+
onChange: (next) => {
|
|
1748
|
+
if (next) lab.setMode(next);
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
)
|
|
1752
|
+
] });
|
|
1753
|
+
}
|
|
1754
|
+
function LabPalette({ tools }) {
|
|
1755
|
+
const storeCtx = useContext(LabStoreContext);
|
|
1756
|
+
if (!storeCtx) throw new Error("[labkit] LabPalette requires <LabStoreProvider>");
|
|
1757
|
+
const activeToolId = useStore(storeCtx.store, (s) => s.activeToolId);
|
|
1758
|
+
const setLabTool = useStore(storeCtx.store, (s) => s.setLabTool);
|
|
1759
|
+
const contributions = tools.map((t) => ({
|
|
1760
|
+
id: t.id,
|
|
1761
|
+
region: "palette",
|
|
1762
|
+
group: t.group,
|
|
1763
|
+
item: { icon: t.icon, label: t.label, shortcut: t.shortcut }
|
|
1764
|
+
}));
|
|
1765
|
+
const ctx = {
|
|
1766
|
+
activeToolId,
|
|
1767
|
+
setActiveTool: setLabTool
|
|
1768
|
+
};
|
|
1769
|
+
return /* @__PURE__ */ jsx(PaletteRegion, { contributions, ctx });
|
|
1770
|
+
}
|
|
1547
1771
|
function query() {
|
|
1548
1772
|
if (typeof window === "undefined" || !window.matchMedia) return null;
|
|
1549
1773
|
return window.matchMedia("(prefers-color-scheme: light)");
|
|
@@ -1600,7 +1824,7 @@ function Workspace({
|
|
|
1600
1824
|
}) {
|
|
1601
1825
|
const items = Children.toArray(children);
|
|
1602
1826
|
const idKey = ids ? ids.join(",") : `#${items.length}`;
|
|
1603
|
-
const nodeIds = useMemo(() => items.map((
|
|
1827
|
+
const nodeIds = useMemo(() => items.map((_2, i) => asNodeId(ids?.[i] ?? `lk-ws-${i}`)), [idKey]);
|
|
1604
1828
|
const layoutRef = useRef(layout);
|
|
1605
1829
|
layoutRef.current = layout;
|
|
1606
1830
|
const onLayoutChangeRef = useRef(onLayoutChange);
|
|
@@ -1663,10 +1887,10 @@ function Workspace({
|
|
|
1663
1887
|
[KIND]: ({ node }) => {
|
|
1664
1888
|
const content = byId.get(node.id) ?? null;
|
|
1665
1889
|
if (!reorderable) return content;
|
|
1666
|
-
return
|
|
1667
|
-
|
|
1668
|
-
/* @__PURE__ */ jsx("div", { className: "lk-trial-
|
|
1669
|
-
|
|
1890
|
+
return (
|
|
1891
|
+
// No grip strip: the trial's own title bar is the drag surface.
|
|
1892
|
+
/* @__PURE__ */ jsx(TrialDragContext.Provider, { value: { nodeId: node.id }, children: /* @__PURE__ */ jsx("div", { className: "lk-trial-tile", children: content }) })
|
|
1893
|
+
);
|
|
1670
1894
|
}
|
|
1671
1895
|
};
|
|
1672
1896
|
}, [nodeIds, items, reorderable]);
|
|
@@ -1703,9 +1927,9 @@ var NEBULA_SLOTS = [
|
|
|
1703
1927
|
{ cx: "85%", cy: "18%", sx: "55%", sy: "80%", stop: "70%" }
|
|
1704
1928
|
];
|
|
1705
1929
|
function buildNebula(colors) {
|
|
1706
|
-
const blobs = colors.map((
|
|
1930
|
+
const blobs = colors.map((c2, i) => {
|
|
1707
1931
|
const p2 = NEBULA_SLOTS[i % NEBULA_SLOTS.length];
|
|
1708
|
-
return `radial-gradient(ellipse ${p2.sx} ${p2.sy} at ${p2.cx} ${p2.cy}, color-mix(in srgb, ${
|
|
1932
|
+
return `radial-gradient(ellipse ${p2.sx} ${p2.sy} at ${p2.cx} ${p2.cy}, color-mix(in srgb, ${c2} 22%, transparent), transparent ${p2.stop})`;
|
|
1709
1933
|
});
|
|
1710
1934
|
blobs.push("radial-gradient(ellipse at center, #0a0a18 0%, #02020a 100%)");
|
|
1711
1935
|
return blobs.join(", ");
|
|
@@ -1718,6 +1942,12 @@ function Lab({
|
|
|
1718
1942
|
mode,
|
|
1719
1943
|
nebula,
|
|
1720
1944
|
title,
|
|
1945
|
+
footer,
|
|
1946
|
+
chrome,
|
|
1947
|
+
suppress,
|
|
1948
|
+
tools,
|
|
1949
|
+
configRules,
|
|
1950
|
+
controls,
|
|
1721
1951
|
children
|
|
1722
1952
|
}) {
|
|
1723
1953
|
if (process.env.NODE_ENV !== "production" && instruments.length === 0) {
|
|
@@ -1746,8 +1976,8 @@ function Lab({
|
|
|
1746
1976
|
}, [mode, store]);
|
|
1747
1977
|
const contextValue = useMemo(() => {
|
|
1748
1978
|
const replaceTrials = (next) => {
|
|
1749
|
-
const currentById = new Map(store.getState().trials.map((
|
|
1750
|
-
const merged = next.map((
|
|
1979
|
+
const currentById = new Map(store.getState().trials.map((w2) => [w2.id, w2]));
|
|
1980
|
+
const merged = next.map((w2) => currentById.get(w2.id) ?? w2);
|
|
1751
1981
|
store.setState({ trials: merged });
|
|
1752
1982
|
store.getState().setMode(store.getState().mode);
|
|
1753
1983
|
};
|
|
@@ -1771,18 +2001,19 @@ function Lab({
|
|
|
1771
2001
|
},
|
|
1772
2002
|
resetTrial: (id) => {
|
|
1773
2003
|
const next = resetTrial(store.getState().trials, id, instruments);
|
|
1774
|
-
const record = next.find((
|
|
2004
|
+
const record = next.find((w2) => w2.id === id);
|
|
1775
2005
|
if (!record) return;
|
|
1776
2006
|
store.setState((s) => ({
|
|
1777
2007
|
trials: s.trials.map(
|
|
1778
|
-
(
|
|
2008
|
+
(w2) => w2.id === id ? { ...w2, config: record.config, state: record.state, view: record.view } : w2
|
|
1779
2009
|
)
|
|
1780
2010
|
}));
|
|
1781
2011
|
store.getState().updateTrialView(id, record.view);
|
|
1782
2012
|
},
|
|
1783
2013
|
savedSnapshots,
|
|
1784
2014
|
saveSnapshot: (trialId, name) => {
|
|
1785
|
-
store.getState().
|
|
2015
|
+
const taken = store.getState().savedSnapshots.filter((s) => s.trialId === trialId).length;
|
|
2016
|
+
store.getState().saveSnapshot(trialId, name ?? `Snapshot ${taken + 1}`);
|
|
1786
2017
|
},
|
|
1787
2018
|
loadSnapshot: (trialId, snapshotId) => {
|
|
1788
2019
|
store.getState().loadSnapshot(snapshotId, trialId);
|
|
@@ -1791,11 +2022,13 @@ function Lab({
|
|
|
1791
2022
|
store.getState().deleteSnapshot(snapshotId);
|
|
1792
2023
|
},
|
|
1793
2024
|
mode: modeValue,
|
|
1794
|
-
setMode: (
|
|
1795
|
-
store.getState().setMode(
|
|
1796
|
-
}
|
|
2025
|
+
setMode: (m2) => {
|
|
2026
|
+
store.getState().setMode(m2);
|
|
2027
|
+
},
|
|
2028
|
+
configRules,
|
|
2029
|
+
controls
|
|
1797
2030
|
};
|
|
1798
|
-
}, [instruments, trials, savedSnapshots, modeValue, store]);
|
|
2031
|
+
}, [instruments, trials, savedSnapshots, modeValue, store, configRules, controls]);
|
|
1799
2032
|
const backdropStyle = resolvedMode === "dark" && nebula && nebula.length > 0 ? { ["--wzl-backdrop"]: buildNebula(nebula) } : void 0;
|
|
1800
2033
|
return /* @__PURE__ */ jsx(LabStoreContext.Provider, { value: { store }, children: /* @__PURE__ */ jsx(LabContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
|
|
1801
2034
|
ThemeProvider,
|
|
@@ -1804,628 +2037,37 @@ function Lab({
|
|
|
1804
2037
|
mode: resolvedMode,
|
|
1805
2038
|
className: "lk-lab",
|
|
1806
2039
|
style: backdropStyle,
|
|
1807
|
-
children: /* @__PURE__ */ jsx(
|
|
1808
|
-
|
|
1809
|
-
{
|
|
1810
|
-
ids: trials.map((w) => w.id),
|
|
1811
|
-
resizable: true,
|
|
1812
|
-
reorderable: true,
|
|
1813
|
-
onReorder: (ids) => contextValue.reorderTrials(ids),
|
|
1814
|
-
layout,
|
|
1815
|
-
onLayoutChange: (next) => store.getState().setLayout(next),
|
|
1816
|
-
children: trials.map((w) => /* @__PURE__ */ jsx(Trial, { id: w.id }, w.id))
|
|
1817
|
-
}
|
|
1818
|
-
) })
|
|
1819
|
-
}
|
|
1820
|
-
) }) });
|
|
1821
|
-
}
|
|
1822
|
-
function CurveField({
|
|
1823
|
-
values,
|
|
1824
|
-
min,
|
|
1825
|
-
max,
|
|
1826
|
-
step,
|
|
1827
|
-
width,
|
|
1828
|
-
height = 110,
|
|
1829
|
-
defaults,
|
|
1830
|
-
onChange,
|
|
1831
|
-
marks
|
|
1832
|
-
}) {
|
|
1833
|
-
const points = useMemo(() => {
|
|
1834
|
-
const out = [];
|
|
1835
|
-
for (let i = 0; i + 1 < values.length; i += 2) out.push({ x: values[i], y: values[i + 1] });
|
|
1836
|
-
return out;
|
|
1837
|
-
}, [values]);
|
|
1838
|
-
const handleChange = useCallback(
|
|
1839
|
-
(next) => {
|
|
1840
|
-
const flat = new Array(next.length * 2);
|
|
1841
|
-
for (let i = 0; i < next.length; i++) {
|
|
1842
|
-
const ySnap = Math.round(next[i].y / step) * step;
|
|
1843
|
-
const y = Math.max(min, Math.min(max, ySnap));
|
|
1844
|
-
flat[i * 2] = next[i].x;
|
|
1845
|
-
flat[i * 2 + 1] = y;
|
|
1846
|
-
}
|
|
1847
|
-
p("curve-field", "handleChange", {
|
|
1848
|
-
nLen: next.length,
|
|
1849
|
-
flatLen: flat.length,
|
|
1850
|
-
sample: flat.slice(0, 4)
|
|
1851
|
-
});
|
|
1852
|
-
onChange(flat);
|
|
1853
|
-
},
|
|
1854
|
-
[onChange, min, max, step]
|
|
1855
|
-
);
|
|
1856
|
-
const handleFlip = useCallback(() => {
|
|
1857
|
-
const flipped = [];
|
|
1858
|
-
for (let i = 0; i + 1 < values.length; i += 2) {
|
|
1859
|
-
flipped.push({ x: 1 - values[i], y: values[i + 1] });
|
|
1860
|
-
}
|
|
1861
|
-
flipped.sort((a, b) => a.x - b.x);
|
|
1862
|
-
const out = [];
|
|
1863
|
-
for (const p2 of flipped) out.push(p2.x, p2.y);
|
|
1864
|
-
onChange(out);
|
|
1865
|
-
}, [values, onChange]);
|
|
1866
|
-
const handleFlipVertical = useCallback(() => {
|
|
1867
|
-
const mid = (min + max) / 2;
|
|
1868
|
-
const flipped = new Array(values.length);
|
|
1869
|
-
for (let i = 0; i + 1 < values.length; i += 2) {
|
|
1870
|
-
flipped[i] = values[i];
|
|
1871
|
-
flipped[i + 1] = 2 * mid - values[i + 1];
|
|
1872
|
-
}
|
|
1873
|
-
onChange(flipped);
|
|
1874
|
-
}, [values, min, max, onChange]);
|
|
1875
|
-
return /* @__PURE__ */ jsxs("div", { className: "lk-curve-field", children: [
|
|
1876
|
-
/* @__PURE__ */ jsxs("div", { className: "lk-curve-field__plot", children: [
|
|
1877
|
-
/* @__PURE__ */ jsx(
|
|
1878
|
-
F,
|
|
1879
|
-
{
|
|
1880
|
-
value: points,
|
|
1881
|
-
onInput: handleChange,
|
|
1882
|
-
domain: "1d",
|
|
1883
|
-
constrain: "function",
|
|
1884
|
-
xRange: [0, 1],
|
|
1885
|
-
yRange: [min, max],
|
|
1886
|
-
width,
|
|
1887
|
-
height,
|
|
1888
|
-
endpoints: "pinned-x",
|
|
1889
|
-
addPointMode: "click-curve",
|
|
1890
|
-
minPoints: 2,
|
|
1891
|
-
grid: {},
|
|
1892
|
-
history: false
|
|
1893
|
-
}
|
|
1894
|
-
),
|
|
1895
|
-
marks && marks.length > 0 && /* @__PURE__ */ jsx(
|
|
1896
|
-
"svg",
|
|
1897
|
-
{
|
|
1898
|
-
className: "lk-curve-field__marks",
|
|
1899
|
-
width,
|
|
1900
|
-
height,
|
|
1901
|
-
viewBox: `0 0 ${width} ${height}`,
|
|
1902
|
-
"aria-hidden": "true",
|
|
1903
|
-
children: marks.map((m) => {
|
|
1904
|
-
const color = m.color ?? "#ffcc00";
|
|
1905
|
-
if (m.kind === "band") {
|
|
1906
|
-
const x0 = Math.max(0, Math.min(1, m.x[0])) * width;
|
|
1907
|
-
const x1 = Math.max(0, Math.min(1, m.x[1])) * width;
|
|
1908
|
-
const left = Math.min(x0, x1);
|
|
1909
|
-
const w = Math.abs(x1 - x0);
|
|
1910
|
-
return /* @__PURE__ */ jsxs("g", { children: [
|
|
1911
|
-
/* @__PURE__ */ jsx("rect", { x: left, y: 0, width: w, height, fill: color, fillOpacity: 0.4 }),
|
|
1912
|
-
/* @__PURE__ */ jsx(
|
|
1913
|
-
"line",
|
|
1914
|
-
{
|
|
1915
|
-
x1: left,
|
|
1916
|
-
y1: 0,
|
|
1917
|
-
x2: left,
|
|
1918
|
-
y2: height,
|
|
1919
|
-
stroke: color,
|
|
1920
|
-
strokeOpacity: 0.85,
|
|
1921
|
-
strokeWidth: 1
|
|
1922
|
-
}
|
|
1923
|
-
),
|
|
1924
|
-
/* @__PURE__ */ jsx(
|
|
1925
|
-
"line",
|
|
1926
|
-
{
|
|
1927
|
-
x1: left + w,
|
|
1928
|
-
y1: 0,
|
|
1929
|
-
x2: left + w,
|
|
1930
|
-
y2: height,
|
|
1931
|
-
stroke: color,
|
|
1932
|
-
strokeOpacity: 0.85,
|
|
1933
|
-
strokeWidth: 1
|
|
1934
|
-
}
|
|
1935
|
-
)
|
|
1936
|
-
] }, `band-${m.x[0]}-${m.x[1]}-${color}`);
|
|
1937
|
-
}
|
|
1938
|
-
const x = Math.max(0, Math.min(1, m.x)) * width;
|
|
1939
|
-
return /* @__PURE__ */ jsx(
|
|
1940
|
-
"line",
|
|
1941
|
-
{
|
|
1942
|
-
x1: x,
|
|
1943
|
-
y1: 0,
|
|
1944
|
-
x2: x,
|
|
1945
|
-
y2: height,
|
|
1946
|
-
stroke: color,
|
|
1947
|
-
strokeWidth: 1
|
|
1948
|
-
},
|
|
1949
|
-
`mark-${m.x}-${color}`
|
|
1950
|
-
);
|
|
1951
|
-
})
|
|
1952
|
-
}
|
|
1953
|
-
)
|
|
1954
|
-
] }),
|
|
1955
|
-
/* @__PURE__ */ jsxs("div", { className: "lk-curve-field__actions", children: [
|
|
1956
|
-
/* @__PURE__ */ jsx("button", { type: "button", className: "lk-curve-field__action", onClick: handleFlip, children: "Flip horizontally" }),
|
|
1957
|
-
/* @__PURE__ */ jsx("button", { type: "button", className: "lk-curve-field__action", onClick: handleFlipVertical, children: "Flip vertically" }),
|
|
1958
|
-
defaults && /* @__PURE__ */ jsx(
|
|
1959
|
-
"button",
|
|
2040
|
+
children: /* @__PURE__ */ jsx(
|
|
2041
|
+
LabShell,
|
|
1960
2042
|
{
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
children:
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
}
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
return /* @__PURE__ */ jsxs("div", { className: cls, children: [
|
|
1973
|
-
/* @__PURE__ */ jsxs("h4", { className: "lk-subpanel__title", children: [
|
|
1974
|
-
/* @__PURE__ */ jsx("span", { children: title }),
|
|
1975
|
-
/* @__PURE__ */ jsx("hr", {})
|
|
1976
|
-
] }),
|
|
1977
|
-
children
|
|
1978
|
-
] });
|
|
1979
|
-
}
|
|
1980
|
-
function EffectCard({
|
|
1981
|
-
title,
|
|
1982
|
-
primary,
|
|
1983
|
-
accent,
|
|
1984
|
-
index,
|
|
1985
|
-
expanded = true,
|
|
1986
|
-
onToggleExpanded,
|
|
1987
|
-
onRemove,
|
|
1988
|
-
draggable,
|
|
1989
|
-
onPressHandle,
|
|
1990
|
-
onReleaseHandle,
|
|
1991
|
-
onDragStart,
|
|
1992
|
-
onDragEnd,
|
|
1993
|
-
onDragOver,
|
|
1994
|
-
onDragLeave,
|
|
1995
|
-
onDrop,
|
|
1996
|
-
dragging,
|
|
1997
|
-
children,
|
|
1998
|
-
className
|
|
1999
|
-
}) {
|
|
2000
|
-
const stateClass = `${expanded ? "is-expanded" : "is-collapsed"}${dragging ? " is-dragging" : ""}${accent ? " has-accent" : ""}`;
|
|
2001
|
-
const cls = `lk-effect-card ${stateClass}${className ? ` ${className}` : ""}`;
|
|
2002
|
-
const style = accent ? { "--lk-panel-accent": accent } : void 0;
|
|
2003
|
-
const headProps = onToggleExpanded ? {
|
|
2004
|
-
role: "button",
|
|
2005
|
-
tabIndex: 0,
|
|
2006
|
-
onClick: onToggleExpanded,
|
|
2007
|
-
onKeyDown: (e) => {
|
|
2008
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
2009
|
-
e.preventDefault();
|
|
2010
|
-
onToggleExpanded();
|
|
2011
|
-
}
|
|
2012
|
-
}
|
|
2013
|
-
} : {};
|
|
2014
|
-
return (
|
|
2015
|
-
// biome-ignore lint/a11y/noStaticElementInteractions: native HTML5 drag-and-drop container — drag events are not click/keyboard interactions and have no semantic element; the in-card handle and header carry the a11y affordances.
|
|
2016
|
-
/* @__PURE__ */ jsxs(
|
|
2017
|
-
"div",
|
|
2018
|
-
{
|
|
2019
|
-
className: cls,
|
|
2020
|
-
style,
|
|
2021
|
-
draggable,
|
|
2022
|
-
onDragStart,
|
|
2023
|
-
onDragEnd,
|
|
2024
|
-
onDragOver,
|
|
2025
|
-
onDragLeave,
|
|
2026
|
-
onDrop,
|
|
2027
|
-
children: [
|
|
2028
|
-
/* @__PURE__ */ jsxs("div", { className: "lk-effect-card__head", ...headProps, children: [
|
|
2029
|
-
index != null ? /* @__PURE__ */ jsx(
|
|
2030
|
-
"button",
|
|
2031
|
-
{
|
|
2032
|
-
type: "button",
|
|
2033
|
-
className: "lk-effect-card__index-badge lk-effect-card__handle",
|
|
2034
|
-
title: "Drag to reorder \xB7 click to toggle",
|
|
2035
|
-
"aria-label": `Item ${index + 1} \u2014 drag to reorder, click to toggle`,
|
|
2036
|
-
onMouseDown: onPressHandle,
|
|
2037
|
-
onMouseUp: onReleaseHandle,
|
|
2038
|
-
onClick: (e) => {
|
|
2039
|
-
e.stopPropagation();
|
|
2040
|
-
onToggleExpanded?.();
|
|
2041
|
-
},
|
|
2042
|
-
children: index + 1
|
|
2043
|
-
}
|
|
2044
|
-
) : /* @__PURE__ */ jsx(
|
|
2045
|
-
"span",
|
|
2046
|
-
{
|
|
2047
|
-
className: "lk-effect-card__handle",
|
|
2048
|
-
"aria-hidden": "true",
|
|
2049
|
-
title: "Drag to reorder",
|
|
2050
|
-
onMouseDown: onPressHandle,
|
|
2051
|
-
onMouseUp: onReleaseHandle,
|
|
2052
|
-
children: /* @__PURE__ */ jsx(DragHandleIcon, {})
|
|
2053
|
-
}
|
|
2054
|
-
),
|
|
2055
|
-
/* @__PURE__ */ jsx("span", { className: "lk-effect-card__title", children: title }),
|
|
2056
|
-
primary != null && /* @__PURE__ */ jsx("span", { className: "lk-effect-card__primary", children: primary }),
|
|
2057
|
-
onRemove && /* @__PURE__ */ jsx(
|
|
2058
|
-
"button",
|
|
2043
|
+
title: title ?? "Labkit",
|
|
2044
|
+
mode: modeValue,
|
|
2045
|
+
footer,
|
|
2046
|
+
header: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2047
|
+
/* @__PURE__ */ jsx(LabHeader, {}),
|
|
2048
|
+
children
|
|
2049
|
+
] }),
|
|
2050
|
+
children: /* @__PURE__ */ jsxs("div", { className: "lk-lab__body", children: [
|
|
2051
|
+
tools ? /* @__PURE__ */ jsx(LabPalette, { tools }) : null,
|
|
2052
|
+
/* @__PURE__ */ jsx(
|
|
2053
|
+
Workspace,
|
|
2059
2054
|
{
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
"aria-label": "Remove",
|
|
2068
|
-
children: "\u2715"
|
|
2055
|
+
ids: trials.map((w2) => w2.id),
|
|
2056
|
+
resizable: true,
|
|
2057
|
+
reorderable: true,
|
|
2058
|
+
onReorder: (ids) => contextValue.reorderTrials(ids),
|
|
2059
|
+
layout,
|
|
2060
|
+
onLayoutChange: (next) => store.getState().setLayout(next),
|
|
2061
|
+
children: trials.map((w2) => /* @__PURE__ */ jsx(Trial, { id: w2.id, chrome, suppress }, w2.id))
|
|
2069
2062
|
}
|
|
2070
2063
|
)
|
|
2071
|
-
] })
|
|
2072
|
-
expanded && /* @__PURE__ */ jsx("div", { className: "lk-effect-card__body", children })
|
|
2073
|
-
]
|
|
2074
|
-
}
|
|
2075
|
-
)
|
|
2076
|
-
);
|
|
2077
|
-
}
|
|
2078
|
-
function DragHandleIcon() {
|
|
2079
|
-
return /* @__PURE__ */ jsxs("svg", { width: "10", height: "14", viewBox: "0 0 10 14", "aria-hidden": "true", children: [
|
|
2080
|
-
/* @__PURE__ */ jsx("circle", { cx: "3", cy: "3", r: "1.3", fill: "currentColor" }),
|
|
2081
|
-
/* @__PURE__ */ jsx("circle", { cx: "7", cy: "3", r: "1.3", fill: "currentColor" }),
|
|
2082
|
-
/* @__PURE__ */ jsx("circle", { cx: "3", cy: "7", r: "1.3", fill: "currentColor" }),
|
|
2083
|
-
/* @__PURE__ */ jsx("circle", { cx: "7", cy: "7", r: "1.3", fill: "currentColor" }),
|
|
2084
|
-
/* @__PURE__ */ jsx("circle", { cx: "3", cy: "11", r: "1.3", fill: "currentColor" }),
|
|
2085
|
-
/* @__PURE__ */ jsx("circle", { cx: "7", cy: "11", r: "1.3", fill: "currentColor" })
|
|
2086
|
-
] });
|
|
2087
|
-
}
|
|
2088
|
-
function EffectCardList({
|
|
2089
|
-
items,
|
|
2090
|
-
renderItem,
|
|
2091
|
-
onReorder,
|
|
2092
|
-
empty,
|
|
2093
|
-
defaultExpandedIds
|
|
2094
|
-
}) {
|
|
2095
|
-
const [expanded, setExpanded] = useState(() => new Set(defaultExpandedIds ?? []));
|
|
2096
|
-
const [draggingId, setDraggingId] = useState(null);
|
|
2097
|
-
const [pressedId, setPressedId] = useState(null);
|
|
2098
|
-
const [dropHint, setDropHint] = useState(
|
|
2099
|
-
null
|
|
2100
|
-
);
|
|
2101
|
-
if (items.length === 0) {
|
|
2102
|
-
return /* @__PURE__ */ jsx(Fragment, { children: empty ?? /* @__PURE__ */ jsx("div", { className: "lk-effect-card-list__empty", children: "No items." }) });
|
|
2103
|
-
}
|
|
2104
|
-
return /* @__PURE__ */ jsx("div", { className: "lk-effect-card-list", children: items.map((item) => {
|
|
2105
|
-
const isExpanded = expanded.has(item.id);
|
|
2106
|
-
const isDragging = draggingId === item.id;
|
|
2107
|
-
const isPressed = pressedId === item.id;
|
|
2108
|
-
const showHintBefore = dropHint && dropHint.id === item.id && dropHint.position === "before";
|
|
2109
|
-
const showHintAfter = dropHint && dropHint.id === item.id && dropHint.position === "after";
|
|
2110
|
-
const toggleExpanded = () => setExpanded((prev) => {
|
|
2111
|
-
const next = new Set(prev);
|
|
2112
|
-
if (next.has(item.id)) next.delete(item.id);
|
|
2113
|
-
else next.add(item.id);
|
|
2114
|
-
return next;
|
|
2115
|
-
});
|
|
2116
|
-
return /* @__PURE__ */ jsxs("div", { className: "lk-effect-card-list__wrap", children: [
|
|
2117
|
-
showHintBefore && /* @__PURE__ */ jsx("div", { className: "lk-effect-card-list__drop-hint" }),
|
|
2118
|
-
renderItem(item, {
|
|
2119
|
-
isExpanded,
|
|
2120
|
-
isDragging,
|
|
2121
|
-
toggleExpanded,
|
|
2122
|
-
cardProps: {
|
|
2123
|
-
expanded: isExpanded,
|
|
2124
|
-
dragging: isDragging,
|
|
2125
|
-
draggable: isPressed,
|
|
2126
|
-
onToggleExpanded: toggleExpanded,
|
|
2127
|
-
onPressHandle: () => setPressedId(item.id),
|
|
2128
|
-
onReleaseHandle: () => setPressedId(null),
|
|
2129
|
-
onDragStart: (e) => {
|
|
2130
|
-
e.dataTransfer.effectAllowed = "move";
|
|
2131
|
-
e.dataTransfer.setData("text/plain", String(item.id));
|
|
2132
|
-
setDraggingId(item.id);
|
|
2133
|
-
},
|
|
2134
|
-
onDragEnd: () => {
|
|
2135
|
-
setDraggingId(null);
|
|
2136
|
-
setPressedId(null);
|
|
2137
|
-
setDropHint(null);
|
|
2138
|
-
},
|
|
2139
|
-
onDragOver: (e) => {
|
|
2140
|
-
if (draggingId === null || draggingId === item.id) return;
|
|
2141
|
-
e.preventDefault();
|
|
2142
|
-
e.dataTransfer.dropEffect = "move";
|
|
2143
|
-
const rect = e.currentTarget.getBoundingClientRect();
|
|
2144
|
-
const position = e.clientY < rect.top + rect.height / 2 ? "before" : "after";
|
|
2145
|
-
setDropHint({ id: item.id, position });
|
|
2146
|
-
},
|
|
2147
|
-
onDragLeave: () => {
|
|
2148
|
-
setDropHint((h) => h && h.id === item.id ? null : h);
|
|
2149
|
-
},
|
|
2150
|
-
onDrop: (e) => {
|
|
2151
|
-
e.preventDefault();
|
|
2152
|
-
if (draggingId === null || draggingId === item.id) {
|
|
2153
|
-
setDropHint(null);
|
|
2154
|
-
return;
|
|
2155
|
-
}
|
|
2156
|
-
const rect = e.currentTarget.getBoundingClientRect();
|
|
2157
|
-
const position = e.clientY < rect.top + rect.height / 2 ? "before" : "after";
|
|
2158
|
-
onReorder(draggingId, item.id, position);
|
|
2159
|
-
setDropHint(null);
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2162
|
-
}),
|
|
2163
|
-
showHintAfter && /* @__PURE__ */ jsx("div", { className: "lk-effect-card-list__drop-hint" })
|
|
2164
|
-
] }, String(item.id));
|
|
2165
|
-
}) });
|
|
2166
|
-
}
|
|
2167
|
-
function PropertyGroup({
|
|
2168
|
-
title,
|
|
2169
|
-
hidden,
|
|
2170
|
-
children,
|
|
2171
|
-
className,
|
|
2172
|
-
pack = "auto-color"
|
|
2173
|
-
}) {
|
|
2174
|
-
if (hidden) return null;
|
|
2175
|
-
const packClass = pack === "pairs" ? " lk-property-group--pairs" : "";
|
|
2176
|
-
const cls = `lk-property-group${packClass}${className ? ` ${className}` : ""}`;
|
|
2177
|
-
return /* @__PURE__ */ jsxs("div", { className: cls, children: [
|
|
2178
|
-
/* @__PURE__ */ jsxs("h3", { className: "lk-property-group__title", children: [
|
|
2179
|
-
/* @__PURE__ */ jsx("hr", {}),
|
|
2180
|
-
/* @__PURE__ */ jsx("span", { children: title }),
|
|
2181
|
-
/* @__PURE__ */ jsx("hr", {})
|
|
2182
|
-
] }),
|
|
2183
|
-
/* @__PURE__ */ jsx("div", { className: "lk-property-group__body", children })
|
|
2184
|
-
] });
|
|
2185
|
-
}
|
|
2186
|
-
function PropertyPanel({ title, children, className }) {
|
|
2187
|
-
const cls = className ? `lk-property-panel ${className}` : "lk-property-panel";
|
|
2188
|
-
return /* @__PURE__ */ jsxs("div", { className: cls, children: [
|
|
2189
|
-
title != null && /* @__PURE__ */ jsx("h2", { className: "lk-property-panel__title", children: title }),
|
|
2190
|
-
children
|
|
2191
|
-
] });
|
|
2192
|
-
}
|
|
2193
|
-
function PropertyList({ children, className, pack = "auto-color" }) {
|
|
2194
|
-
const packClass = pack === "pairs" ? " lk-property-list--pairs" : "";
|
|
2195
|
-
const cls = `lk-property-list${packClass}${className ? ` ${className}` : ""}`;
|
|
2196
|
-
return /* @__PURE__ */ jsx("div", { className: cls, children });
|
|
2197
|
-
}
|
|
2198
|
-
function PropertyRow({
|
|
2199
|
-
label,
|
|
2200
|
-
readout,
|
|
2201
|
-
variant = "default",
|
|
2202
|
-
layout = "block",
|
|
2203
|
-
children,
|
|
2204
|
-
htmlFor,
|
|
2205
|
-
className
|
|
2206
|
-
}) {
|
|
2207
|
-
const variantClass = variant === "default" ? "" : ` lk-property-row--${variant}`;
|
|
2208
|
-
const layoutClass = variant === "default" && layout === "inline" ? " lk-property-row--inline" : "";
|
|
2209
|
-
const cls = `lk-property-row${variantClass}${layoutClass}${className ? ` ${className}` : ""}`;
|
|
2210
|
-
return /* @__PURE__ */ jsxs("label", { className: cls, htmlFor, children: [
|
|
2211
|
-
/* @__PURE__ */ jsxs("span", { className: "lk-property-row__label", children: [
|
|
2212
|
-
label,
|
|
2213
|
-
readout != null && /* @__PURE__ */ jsx("em", { className: "lk-property-row__readout", children: readout })
|
|
2214
|
-
] }),
|
|
2215
|
-
children
|
|
2216
|
-
] });
|
|
2217
|
-
}
|
|
2218
|
-
function SliderRow({
|
|
2219
|
-
label,
|
|
2220
|
-
value,
|
|
2221
|
-
min,
|
|
2222
|
-
max,
|
|
2223
|
-
step = 1,
|
|
2224
|
-
onChange,
|
|
2225
|
-
format,
|
|
2226
|
-
unit,
|
|
2227
|
-
layout
|
|
2228
|
-
}) {
|
|
2229
|
-
const decimals = step >= 1 ? 0 : Math.min(6, Math.max(0, Math.ceil(-Math.log10(step))));
|
|
2230
|
-
const effectiveFormat = format ?? ((n) => formatNumber(n, decimals));
|
|
2231
|
-
return /* @__PURE__ */ jsx(
|
|
2232
|
-
PropertyRow,
|
|
2233
|
-
{
|
|
2234
|
-
label,
|
|
2235
|
-
readout: /* @__PURE__ */ jsx(
|
|
2236
|
-
EditableReadout,
|
|
2237
|
-
{
|
|
2238
|
-
value,
|
|
2239
|
-
min,
|
|
2240
|
-
max,
|
|
2241
|
-
format: effectiveFormat,
|
|
2242
|
-
unit,
|
|
2243
|
-
onCommit: onChange
|
|
2244
|
-
}
|
|
2245
|
-
),
|
|
2246
|
-
layout,
|
|
2247
|
-
children: /* @__PURE__ */ jsx(
|
|
2248
|
-
"input",
|
|
2249
|
-
{
|
|
2250
|
-
type: "range",
|
|
2251
|
-
tabIndex: -1,
|
|
2252
|
-
min,
|
|
2253
|
-
max,
|
|
2254
|
-
step,
|
|
2255
|
-
value,
|
|
2256
|
-
onChange: (e) => {
|
|
2257
|
-
const v = Number(e.target.value);
|
|
2258
|
-
p("property-panel", "slider", { label, value: v });
|
|
2259
|
-
onChange(v);
|
|
2260
|
-
}
|
|
2064
|
+
] })
|
|
2261
2065
|
}
|
|
2262
2066
|
)
|
|
2263
2067
|
}
|
|
2264
|
-
);
|
|
2265
|
-
}
|
|
2266
|
-
function EditableReadout({ value, min, max, format, unit, onCommit }) {
|
|
2267
|
-
const [draft, setDraft] = useState(null);
|
|
2268
|
-
const fmt = (n) => format ? format(n) : formatNumber(n);
|
|
2269
|
-
const displayValue = draft !== null ? draft : (() => {
|
|
2270
|
-
const formatted = fmt(value);
|
|
2271
|
-
return typeof formatted === "string" ? formatted : String(formatted);
|
|
2272
|
-
})();
|
|
2273
|
-
const suffix = unit == null ? null : typeof unit === "string" ? /* @__PURE__ */ jsx("span", { className: "lk-property-row__readout-unit", children: unit }) : unit;
|
|
2274
|
-
const commit = () => {
|
|
2275
|
-
if (draft !== null) {
|
|
2276
|
-
const n = parseSignedNumber(draft);
|
|
2277
|
-
if (Number.isFinite(n)) onCommit(Math.min(max, Math.max(min, n)));
|
|
2278
|
-
}
|
|
2279
|
-
setDraft(null);
|
|
2280
|
-
};
|
|
2281
|
-
return /* @__PURE__ */ jsxs("span", { className: "lk-property-row__readout-group", children: [
|
|
2282
|
-
/* @__PURE__ */ jsx(
|
|
2283
|
-
"input",
|
|
2284
|
-
{
|
|
2285
|
-
type: "text",
|
|
2286
|
-
inputMode: "decimal",
|
|
2287
|
-
className: "lk-property-row__readout-input",
|
|
2288
|
-
value: displayValue,
|
|
2289
|
-
onFocus: (e) => {
|
|
2290
|
-
const formatted = fmt(value);
|
|
2291
|
-
setDraft(typeof formatted === "string" ? formatted : String(formatted));
|
|
2292
|
-
e.currentTarget.select();
|
|
2293
|
-
},
|
|
2294
|
-
onChange: (e) => setDraft(e.target.value.replace(/-/g, "\u2212")),
|
|
2295
|
-
onBlur: commit,
|
|
2296
|
-
onClick: (e) => {
|
|
2297
|
-
e.preventDefault();
|
|
2298
|
-
e.stopPropagation();
|
|
2299
|
-
e.currentTarget.focus();
|
|
2300
|
-
},
|
|
2301
|
-
onMouseDown: (e) => e.stopPropagation(),
|
|
2302
|
-
onKeyDown: (e) => {
|
|
2303
|
-
if (e.key === "Enter") {
|
|
2304
|
-
commit();
|
|
2305
|
-
e.currentTarget.blur();
|
|
2306
|
-
} else if (e.key === "Escape") {
|
|
2307
|
-
setDraft(null);
|
|
2308
|
-
e.currentTarget.blur();
|
|
2309
|
-
}
|
|
2310
|
-
}
|
|
2311
|
-
}
|
|
2312
|
-
),
|
|
2313
|
-
suffix
|
|
2314
|
-
] });
|
|
2315
|
-
}
|
|
2316
|
-
function ColorRow({
|
|
2317
|
-
label,
|
|
2318
|
-
value,
|
|
2319
|
-
onChange,
|
|
2320
|
-
alpha,
|
|
2321
|
-
onAlphaChange,
|
|
2322
|
-
alphaDisabled
|
|
2323
|
-
}) {
|
|
2324
|
-
const showAlpha = alpha != null;
|
|
2325
|
-
const className = alphaDisabled ? "lk-property-row--alpha-disabled" : void 0;
|
|
2326
|
-
return /* @__PURE__ */ jsxs(PropertyRow, { label, variant: "color", className, children: [
|
|
2327
|
-
/* @__PURE__ */ jsx("input", { type: "color", value, onChange: (e) => onChange(e.target.value) }),
|
|
2328
|
-
showAlpha && /* @__PURE__ */ jsx(
|
|
2329
|
-
"input",
|
|
2330
|
-
{
|
|
2331
|
-
type: "range",
|
|
2332
|
-
className: "lk-property-row__alpha",
|
|
2333
|
-
min: 0,
|
|
2334
|
-
max: 1,
|
|
2335
|
-
step: 0.01,
|
|
2336
|
-
value: alpha,
|
|
2337
|
-
disabled: alphaDisabled,
|
|
2338
|
-
onChange: (e) => onAlphaChange?.(Number(e.target.value))
|
|
2339
|
-
}
|
|
2340
|
-
)
|
|
2341
|
-
] });
|
|
2342
|
-
}
|
|
2343
|
-
function CheckboxRow({ label, value, onChange }) {
|
|
2344
|
-
return /* @__PURE__ */ jsx(PropertyRow, { label, variant: "checkbox", children: /* @__PURE__ */ jsx("input", { type: "checkbox", checked: value, onChange: (e) => onChange(e.target.checked) }) });
|
|
2345
|
-
}
|
|
2346
|
-
function TextRow({ label, value, onChange, placeholder, maxLength, layout }) {
|
|
2347
|
-
return /* @__PURE__ */ jsx(PropertyRow, { label, layout, children: /* @__PURE__ */ jsx(
|
|
2348
|
-
"input",
|
|
2349
|
-
{
|
|
2350
|
-
type: "text",
|
|
2351
|
-
value,
|
|
2352
|
-
placeholder,
|
|
2353
|
-
maxLength,
|
|
2354
|
-
onChange: (e) => onChange(e.target.value)
|
|
2355
|
-
}
|
|
2356
|
-
) });
|
|
2357
|
-
}
|
|
2358
|
-
function NumberRow({
|
|
2359
|
-
label,
|
|
2360
|
-
value,
|
|
2361
|
-
onChange,
|
|
2362
|
-
min,
|
|
2363
|
-
max,
|
|
2364
|
-
step,
|
|
2365
|
-
placeholder,
|
|
2366
|
-
layout
|
|
2367
|
-
}) {
|
|
2368
|
-
return /* @__PURE__ */ jsx(PropertyRow, { label, layout, children: /* @__PURE__ */ jsx(
|
|
2369
|
-
"input",
|
|
2370
|
-
{
|
|
2371
|
-
type: "number",
|
|
2372
|
-
value,
|
|
2373
|
-
min,
|
|
2374
|
-
max,
|
|
2375
|
-
step,
|
|
2376
|
-
placeholder,
|
|
2377
|
-
onChange: (e) => {
|
|
2378
|
-
const raw = e.target.value;
|
|
2379
|
-
if (raw === "") return;
|
|
2380
|
-
const n = Number(raw);
|
|
2381
|
-
if (Number.isFinite(n)) onChange(n);
|
|
2382
|
-
}
|
|
2383
|
-
}
|
|
2384
|
-
) });
|
|
2385
|
-
}
|
|
2386
|
-
function SelectRow({
|
|
2387
|
-
label,
|
|
2388
|
-
value,
|
|
2389
|
-
options,
|
|
2390
|
-
onChange,
|
|
2391
|
-
layout
|
|
2392
|
-
}) {
|
|
2393
|
-
return /* @__PURE__ */ jsx(PropertyRow, { label, layout, children: /* @__PURE__ */ jsx(
|
|
2394
|
-
"select",
|
|
2395
|
-
{
|
|
2396
|
-
value,
|
|
2397
|
-
onChange: (e) => {
|
|
2398
|
-
const v = e.target.value;
|
|
2399
|
-
p("property-panel", "select", { label, value: v });
|
|
2400
|
-
onChange(v);
|
|
2401
|
-
},
|
|
2402
|
-
children: options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
|
|
2403
|
-
}
|
|
2404
|
-
) });
|
|
2405
|
-
}
|
|
2406
|
-
function ToggleRow({
|
|
2407
|
-
label,
|
|
2408
|
-
value,
|
|
2409
|
-
options,
|
|
2410
|
-
onChange,
|
|
2411
|
-
layout
|
|
2412
|
-
}) {
|
|
2413
|
-
return /* @__PURE__ */ jsx(PropertyRow, { label, layout, children: /* @__PURE__ */ jsx("div", { className: "lk-property-row__toggle", children: options.map((opt) => {
|
|
2414
|
-
const selected = opt.value === value;
|
|
2415
|
-
return /* @__PURE__ */ jsx(
|
|
2416
|
-
"button",
|
|
2417
|
-
{
|
|
2418
|
-
type: "button",
|
|
2419
|
-
"aria-pressed": selected,
|
|
2420
|
-
className: selected ? "lk-property-row__toggle-button lk-property-row__toggle-button--selected" : "lk-property-row__toggle-button",
|
|
2421
|
-
onClick: () => onChange(opt.value),
|
|
2422
|
-
children: opt.label
|
|
2423
|
-
},
|
|
2424
|
-
opt.value
|
|
2425
|
-
);
|
|
2426
|
-
}) }) });
|
|
2068
|
+
) }) });
|
|
2427
2069
|
}
|
|
2428
2070
|
|
|
2429
|
-
export {
|
|
2071
|
+
export { DEFAULT_VIEW, Lab, LabContext, LabPalette, LabShell, PITCH_LIMIT, Trial, TrialChrome, Workspace, addTrial, applyRules, as2DView, builtinRules, clampPitch, cloneTrial, closeTrial, defineInstrument, f, interstellarTheme, orbitAfterDrag, orbitAfterWheel, reorderTrials, resetTrial, resolveConfigSchema, titleCase, useConfigSchema, useLabContext, useOrbit, validateConfigSchema, wrapYaw };
|
|
2430
2072
|
//# sourceMappingURL=index.js.map
|
|
2431
2073
|
//# sourceMappingURL=index.js.map
|