@weasel-js/labkit 1.6.0 → 1.6.1
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/{CanvasStackContext-DQgcH1ny.d.ts → CanvasStackContext-Wo6OwIVM.d.ts} +1 -1
- package/dist/_dts/FloatingPanel-7j0IcXwz.d.ts +30 -0
- package/dist/_dts/{LayerList.d-2c9yup5Y.d.ts → LayerList.d-BoELAMsM.d.ts} +2 -2
- package/dist/_dts/{index-DINmCuLA.d.ts → PropertyField.d-D0ndfIIt.d.ts} +34 -83
- package/dist/_dts/{Select.d-BchGMC2Y.d.ts → Select.d-Cr8-zoup.d.ts} +4 -5
- package/dist/_dts/{usePanZoom-Bq73pwFe.d.ts → Stage-DWBBi8IU.d.ts} +80 -35
- package/dist/_dts/{index-B0--8PEM.d.ts → index-CKyX0ME_.d.ts} +12 -13
- package/dist/_dts/{frac-DTRq7Xpb.d.ts → types-B7yBTTF9.d.ts} +699 -649
- package/dist/_dts/{types-B7y8hX1D.d.ts → types-DTuViBvc.d.ts} +20 -1
- package/dist/_dts/{useTrialState-CP4jfuKl.d.ts → useTrialState-CMaV6UD4.d.ts} +2 -3
- package/dist/canvas/index.d.ts +39 -7
- package/dist/canvas/index.js +5 -3
- package/dist/chrome/index.d.ts +4 -5
- package/dist/chrome/index.js +8 -6
- package/dist/chunk-24W5VEJW.js +18 -0
- package/dist/chunk-24W5VEJW.js.map +1 -0
- package/dist/chunk-3RY6WL5T.js +38 -0
- package/dist/chunk-3RY6WL5T.js.map +1 -0
- package/dist/{chunk-H5VIV7MQ.js → chunk-4TT3PNPR.js} +60 -51
- package/dist/chunk-4TT3PNPR.js.map +1 -0
- package/dist/{chunk-ZORVNOKX.js → chunk-6FLXL3I7.js} +9 -145
- package/dist/chunk-6FLXL3I7.js.map +1 -0
- package/dist/{chunk-HFRE4QQV.js → chunk-ASOK76ZH.js} +53 -22
- package/dist/chunk-ASOK76ZH.js.map +1 -0
- package/dist/chunk-AWQG7FCC.js +19 -0
- package/dist/chunk-AWQG7FCC.js.map +1 -0
- package/dist/chunk-CGHF6RZV.js +300 -0
- package/dist/chunk-CGHF6RZV.js.map +1 -0
- package/dist/chunk-DWMSB4CA.js +228 -0
- package/dist/chunk-DWMSB4CA.js.map +1 -0
- package/dist/{chunk-USO6TERP.js → chunk-GHGO52SO.js} +11 -9
- package/dist/chunk-GHGO52SO.js.map +1 -0
- package/dist/{chunk-MOLIUNVH.js → chunk-HKV7LRHY.js} +4 -4
- package/dist/{chunk-MOLIUNVH.js.map → chunk-HKV7LRHY.js.map} +1 -1
- package/dist/{chunk-VQ47KWD5.js → chunk-IRTGUQ5O.js} +5 -4
- package/dist/chunk-IRTGUQ5O.js.map +1 -0
- package/dist/{chunk-H6ZAOWNE.js → chunk-JVBYV6TL.js} +3 -3
- package/dist/{chunk-H6ZAOWNE.js.map → chunk-JVBYV6TL.js.map} +1 -1
- package/dist/chunk-KHK5FGER.js +145 -0
- package/dist/chunk-KHK5FGER.js.map +1 -0
- package/dist/chunk-NEXR37YJ.js +144 -0
- package/dist/chunk-NEXR37YJ.js.map +1 -0
- package/dist/{chunk-BVL7Y574.js → chunk-O6FFFOHS.js} +14 -6
- package/dist/chunk-O6FFFOHS.js.map +1 -0
- package/dist/chunk-QGMOA2YE.js +731 -0
- package/dist/chunk-QGMOA2YE.js.map +1 -0
- package/dist/{chunk-ERFMC6WU.js → chunk-S4NCI32V.js} +19 -10
- package/dist/chunk-S4NCI32V.js.map +1 -0
- package/dist/{chunk-3FI7Y4R4.js → chunk-SLAQPUI4.js} +4 -4
- package/dist/{chunk-3FI7Y4R4.js.map → chunk-SLAQPUI4.js.map} +1 -1
- package/dist/{chunk-AWH3SRPW.js → chunk-TJ77A4A3.js} +3 -31
- package/dist/chunk-TJ77A4A3.js.map +1 -0
- package/dist/{chunk-WUJXJC5O.js → chunk-TYN6MLXX.js} +691 -1395
- package/dist/chunk-TYN6MLXX.js.map +1 -0
- package/dist/{chunk-RS3HRQWU.js → chunk-WPLAANH4.js} +4 -3
- package/dist/chunk-WPLAANH4.js.map +1 -0
- package/dist/config/index.d.ts +36 -35
- package/dist/config/index.js +4 -5
- package/dist/controls/index.d.ts +91 -7
- package/dist/controls/index.js +5 -4
- package/dist/dragdrop/index.d.ts +8 -6
- package/dist/dragdrop/index.js +3 -2
- package/dist/index.d.ts +50 -77
- package/dist/index.js +138 -254
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +3 -4
- package/dist/layers/index.js +2 -1
- package/dist/loupe/index.d.ts +19 -16
- package/dist/loupe/index.js +5 -3
- package/dist/overview/index.d.ts +56 -0
- package/dist/overview/index.js +288 -0
- package/dist/overview/index.js.map +1 -0
- package/dist/passthrough/weasel-ui.d.ts +38 -38
- package/dist/passthrough/weasel-ui.js +2 -1
- package/dist/primitives/index.d.ts +19 -43
- package/dist/primitives/index.js +5 -3
- package/dist/state/index.d.ts +7 -7
- package/dist/state/index.js +4 -5
- package/dist/state/index.js.map +1 -1
- package/dist/styles.css +63 -1
- package/dist/undo/index.d.ts +3 -3
- package/package.json +13 -9
- package/src/annotations/index.ts +1 -0
- package/src/annotations/store.test.ts +26 -0
- package/src/annotations/store.ts +25 -6
- package/src/annotations/toolMap.test.ts +37 -2
- package/src/annotations/toolMap.ts +13 -0
- package/src/annotations/types.ts +27 -0
- package/src/canvas/AGENTS.md +8 -10
- package/src/canvas/CameraInput.test.tsx +136 -0
- package/src/canvas/CameraInput.tsx +311 -0
- package/src/canvas/CanvasStack.tsx +42 -58
- package/src/canvas/LinkedCursor.less +16 -0
- package/src/canvas/LinkedCursor.tsx +54 -0
- package/src/canvas/Stage.tsx +42 -37
- package/src/canvas/cameraView.test.ts +60 -0
- package/src/canvas/cameraView.ts +61 -0
- package/src/canvas/index.ts +11 -2
- package/src/chrome/regions/SidebarRegion.test.tsx +9 -2
- package/src/config/builder.ts +3 -3
- package/src/config/entry.test.ts +3 -1
- package/src/config/fromConfigField.ts +1 -0
- package/src/config/index.ts +4 -1
- package/src/config/resolve.ts +11 -1
- package/src/config/sectionTree.test.ts +12 -6
- package/src/config/sectionTree.ts +15 -5
- package/src/config/types.ts +21 -0
- package/src/controls/ControlPanel.test.tsx +12 -2
- package/src/controls/ControlPanel.tsx +28 -9
- package/src/controls/inDialog.test.tsx +18 -0
- package/src/controls/inDialog.tsx +2 -10
- package/src/index.test.ts +1 -1
- package/src/index.ts +8 -4
- package/src/instrument/SineWave.smoke.test.tsx +2 -0
- package/src/instrument/types.ts +10 -1
- package/src/lab/Lab.nebula.test.tsx +11 -2
- package/src/lab/Lab.test.tsx +3 -1
- package/src/lab/Lab.tsx +4 -4
- package/src/loupe/AGENTS.md +7 -6
- package/src/loupe/LoupeGestures.tsx +14 -9
- package/src/loupe/TrialLoupe.tsx +5 -5
- package/src/loupe/index.ts +1 -0
- package/src/loupe/loupeActions.ts +6 -2
- package/src/overview/AGENTS.md +38 -0
- package/src/overview/OverviewMarks.test.tsx +100 -0
- package/src/overview/OverviewMarks.tsx +93 -0
- package/src/overview/TrialOverview.less +53 -0
- package/src/overview/TrialOverview.stories.tsx +50 -0
- package/src/overview/TrialOverview.test.tsx +116 -0
- package/src/overview/TrialOverview.tsx +374 -0
- package/src/overview/index.ts +2 -0
- package/src/passthrough/weasel-ui.ts +7 -0
- package/src/specimen/Specimen.tsx +19 -2
- package/src/state/labRecords.test.ts +4 -1
- package/src/state/store.test.ts +4 -4
- package/src/state/useTrialState.test.tsx +1 -1
- package/src/styles.less +2 -0
- package/src/theme/Interstellar.stories.tsx +1 -5
- package/src/tools/labTool.ts +6 -2
- package/src/trial/Trial.annotations.test.tsx +16 -0
- package/src/trial/Trial.loupe.test.tsx +73 -1
- package/src/trial/Trial.stories.tsx +2 -0
- package/src/trial/Trial.tsx +17 -1
- package/src/trial/trialOps.test.ts +4 -4
- package/dist/_dts/inDialog-D5ZKGjiD.d.ts +0 -23
- package/dist/_dts/number.d-OeQYQoUO.d.ts +0 -28
- package/dist/chunk-2AYGEN57.js +0 -32
- package/dist/chunk-2AYGEN57.js.map +0 -1
- package/dist/chunk-AWH3SRPW.js.map +0 -1
- package/dist/chunk-BVL7Y574.js.map +0 -1
- package/dist/chunk-ERFMC6WU.js.map +0 -1
- package/dist/chunk-H5VIV7MQ.js.map +0 -1
- package/dist/chunk-HFRE4QQV.js.map +0 -1
- package/dist/chunk-RS3HRQWU.js.map +0 -1
- package/dist/chunk-TT5C5S6R.js +0 -368
- package/dist/chunk-TT5C5S6R.js.map +0 -1
- package/dist/chunk-USO6TERP.js.map +0 -1
- package/dist/chunk-VQ47KWD5.js.map +0 -1
- package/dist/chunk-WUJXJC5O.js.map +0 -1
- package/dist/chunk-ZORVNOKX.js.map +0 -1
- package/src/canvas/usePanZoom.test.ts +0 -244
- package/src/canvas/usePanZoom.ts +0 -147
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { DEFAULT_FRAME } from './chunk-24W5VEJW.js';
|
|
2
|
+
import { createContext, useContext, useRef, useMemo, useEffect } from 'react';
|
|
3
|
+
import { normalizeZoom, usePointerContext, DepRegistryProvider, ActionsProvider, ActiveToolContextProvider, PointerContextProvider, useActionsRegistry, useDepSource, makeViewportZoomAction, viewportDragPanAction, useGestureDispatcher } from '@weasel-js/core';
|
|
4
|
+
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
var CameraWheelContext = createContext(null);
|
|
7
|
+
var DEFAULT_VIEW = { zoom: 1, pan: { x: 0, y: 0 } };
|
|
8
|
+
function as2DView(view) {
|
|
9
|
+
if (typeof view !== "object" || view === null) return null;
|
|
10
|
+
const v = view;
|
|
11
|
+
if (typeof v.zoom !== "number") return null;
|
|
12
|
+
if (typeof v.pan !== "object" || v.pan === null) return null;
|
|
13
|
+
if (typeof v.pan.x !== "number" || typeof v.pan.y !== "number") return null;
|
|
14
|
+
return normalize2DView({ zoom: v.zoom, pan: { x: v.pan.x, y: v.pan.y } });
|
|
15
|
+
}
|
|
16
|
+
function normalize2DView(view) {
|
|
17
|
+
const zoom = normalizeZoom(view.zoom);
|
|
18
|
+
const x = Number.isFinite(view.pan.x) ? view.pan.x : 0;
|
|
19
|
+
const y = Number.isFinite(view.pan.y) ? view.pan.y : 0;
|
|
20
|
+
if (zoom === view.zoom && x === view.pan.x && y === view.pan.y) return view;
|
|
21
|
+
return { zoom, pan: { x, y } };
|
|
22
|
+
}
|
|
23
|
+
function withZoom(view, zoom) {
|
|
24
|
+
return normalize2DView({ zoom, pan: view.pan });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/canvas/cameraView.ts
|
|
28
|
+
function toCameraView(view, frame = DEFAULT_FRAME) {
|
|
29
|
+
const v = normalize2DView(view);
|
|
30
|
+
return {
|
|
31
|
+
x: -(frame.originPx.x + v.pan.x) / v.zoom,
|
|
32
|
+
y: -(frame.originPx.y + v.pan.y) / v.zoom,
|
|
33
|
+
scale: { x: v.zoom, y: v.zoom }
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function fromCameraView(view, frame = DEFAULT_FRAME) {
|
|
37
|
+
const zoom = view.scale.x;
|
|
38
|
+
return {
|
|
39
|
+
zoom,
|
|
40
|
+
pan: { x: -view.x * zoom - frame.originPx.x, y: -view.y * zoom - frame.originPx.y }
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function frameLocalToWorld(p, frame = DEFAULT_FRAME) {
|
|
44
|
+
return { x: p.x, y: p.y * frame.yDir };
|
|
45
|
+
}
|
|
46
|
+
function clampZoomAbout(prev, next, min, max) {
|
|
47
|
+
const s0 = prev.scale.x;
|
|
48
|
+
const s1 = next.scale.x;
|
|
49
|
+
const clamped = Math.min(max, Math.max(min, s1));
|
|
50
|
+
if (clamped === s1) return next;
|
|
51
|
+
if (s0 === s1) return { ...next, scale: { x: clamped, y: clamped } };
|
|
52
|
+
const inv = 1 / s0 - 1 / s1;
|
|
53
|
+
const px = (next.x - prev.x) / inv;
|
|
54
|
+
const py = (next.y - prev.y) / inv;
|
|
55
|
+
return {
|
|
56
|
+
x: prev.x + px / s0 - px / clamped,
|
|
57
|
+
y: prev.y + py / s0 - py / clamped,
|
|
58
|
+
scale: { x: clamped, y: clamped }
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
var STAGE_VIEW_ID = "stage";
|
|
62
|
+
var CameraContext = createContext(null);
|
|
63
|
+
var CameraScopeContext = createContext(false);
|
|
64
|
+
function CameraScope({ children }) {
|
|
65
|
+
const inScope = useContext(CameraScopeContext);
|
|
66
|
+
const pointer = usePointerContext();
|
|
67
|
+
if (inScope) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
68
|
+
const scoped = /* @__PURE__ */ jsx(CameraScopeContext.Provider, { value: true, children });
|
|
69
|
+
return /* @__PURE__ */ jsx(DepRegistryProvider, { children: /* @__PURE__ */ jsx(ActionsProvider, { children: /* @__PURE__ */ jsx(ActiveToolContextProvider, { children: pointer ? scoped : /* @__PURE__ */ jsx(PointerContextProvider, { children: scoped }) }) }) });
|
|
70
|
+
}
|
|
71
|
+
function isPositiveFinite(n) {
|
|
72
|
+
return Number.isFinite(n) && n > 0;
|
|
73
|
+
}
|
|
74
|
+
function useCameraView({
|
|
75
|
+
view,
|
|
76
|
+
onViewChange,
|
|
77
|
+
frame,
|
|
78
|
+
hostRef,
|
|
79
|
+
minZoom = 0.1,
|
|
80
|
+
maxZoom = 32
|
|
81
|
+
}) {
|
|
82
|
+
const live = useRef({ view: normalize2DView(view), onViewChange, frame });
|
|
83
|
+
live.current = { view: normalize2DView(view), onViewChange, frame };
|
|
84
|
+
const initialZoom = useRef(isPositiveFinite(view.zoom) ? view.zoom : null);
|
|
85
|
+
const bounds = useRef({ min: minZoom, max: maxZoom });
|
|
86
|
+
const opening = initialZoom.current;
|
|
87
|
+
bounds.current = {
|
|
88
|
+
min: opening == null ? minZoom : Math.min(minZoom, opening),
|
|
89
|
+
max: opening == null ? maxZoom : Math.max(maxZoom, opening)
|
|
90
|
+
};
|
|
91
|
+
return useMemo(() => {
|
|
92
|
+
const get = () => toCameraView(live.current.view, live.current.frame);
|
|
93
|
+
return {
|
|
94
|
+
get,
|
|
95
|
+
set: (next) => {
|
|
96
|
+
const { min, max } = bounds.current;
|
|
97
|
+
const clamped = clampZoomAbout(get(), next, min, max);
|
|
98
|
+
const vt = fromCameraView(clamped, live.current.frame);
|
|
99
|
+
live.current.view = vt;
|
|
100
|
+
live.current.onViewChange(vt);
|
|
101
|
+
},
|
|
102
|
+
hostSize: () => {
|
|
103
|
+
const r = hostRef.current?.getBoundingClientRect();
|
|
104
|
+
return r ? { width: r.width, height: r.height } : null;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}, [hostRef]);
|
|
108
|
+
}
|
|
109
|
+
var TAP_ID = "camera.tap";
|
|
110
|
+
var NO_TOOLS = /* @__PURE__ */ new Map();
|
|
111
|
+
var CHANNELS = { contextMenu: false, ingest: false };
|
|
112
|
+
function CameraInput(props) {
|
|
113
|
+
const registry = useActionsRegistry();
|
|
114
|
+
if (!registry) return null;
|
|
115
|
+
return /* @__PURE__ */ jsx(CameraDispatch, { ...props, registry });
|
|
116
|
+
}
|
|
117
|
+
function CameraDispatch({
|
|
118
|
+
hostRef,
|
|
119
|
+
camera,
|
|
120
|
+
frame,
|
|
121
|
+
onTap,
|
|
122
|
+
registry
|
|
123
|
+
}) {
|
|
124
|
+
const frameRef = useRef(frame);
|
|
125
|
+
frameRef.current = frame;
|
|
126
|
+
const onTapRef = useRef(onTap);
|
|
127
|
+
onTapRef.current = onTap;
|
|
128
|
+
useDepSource("view", () => camera);
|
|
129
|
+
useDepSource("rootView", () => camera);
|
|
130
|
+
const actions = useMemo(() => {
|
|
131
|
+
const zoom = makeViewportZoomAction({
|
|
132
|
+
wheel: "plain",
|
|
133
|
+
min: 1e-9,
|
|
134
|
+
max: Number.POSITIVE_INFINITY
|
|
135
|
+
});
|
|
136
|
+
const wheelOnly = zoom.defaultBinding.filter(
|
|
137
|
+
(b) => b.spec.kind === "wheel" || b.spec.kind === "pinch"
|
|
138
|
+
);
|
|
139
|
+
const tap = {
|
|
140
|
+
id: TAP_ID,
|
|
141
|
+
label: "Tap the canvas",
|
|
142
|
+
defaultBinding: { kind: "click" },
|
|
143
|
+
invoker: {
|
|
144
|
+
timing: "immediate",
|
|
145
|
+
run: (_deps, params) => {
|
|
146
|
+
const p = params;
|
|
147
|
+
if (p?.worldX === void 0 || p.worldY === void 0) return;
|
|
148
|
+
onTapRef.current?.(frameLocalToWorld({ x: p.worldX, y: p.worldY }, frameRef.current));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
return [
|
|
153
|
+
viewportDragPanAction,
|
|
154
|
+
{ ...zoom, defaultBinding: wheelOnly },
|
|
155
|
+
tap
|
|
156
|
+
];
|
|
157
|
+
}, []);
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
const offs = actions.map((a) => registry.register(a));
|
|
160
|
+
return () => {
|
|
161
|
+
for (const off of offs) off();
|
|
162
|
+
};
|
|
163
|
+
}, [registry, actions]);
|
|
164
|
+
const clientToWorld = useMemo(
|
|
165
|
+
() => (cx, cy) => {
|
|
166
|
+
const r = hostRef.current?.getBoundingClientRect();
|
|
167
|
+
const v = camera.get();
|
|
168
|
+
return {
|
|
169
|
+
x: (cx - (r?.left ?? 0)) / v.scale.x + v.x,
|
|
170
|
+
y: (cy - (r?.top ?? 0)) / v.scale.y + v.y
|
|
171
|
+
};
|
|
172
|
+
},
|
|
173
|
+
[hostRef, camera]
|
|
174
|
+
);
|
|
175
|
+
useGestureDispatcher({
|
|
176
|
+
canvasRef: hostRef,
|
|
177
|
+
actions: registry,
|
|
178
|
+
toolsById: NO_TOOLS,
|
|
179
|
+
clientToWorld,
|
|
180
|
+
channels: CHANNELS
|
|
181
|
+
});
|
|
182
|
+
usePublishPointer(hostRef, clientToWorld, frame, STAGE_VIEW_ID);
|
|
183
|
+
const wheelSlot = useContext(CameraWheelContext);
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
if (!wheelSlot) return;
|
|
186
|
+
const forward = (e) => {
|
|
187
|
+
hostRef.current?.dispatchEvent(new WheelEvent("wheel", e));
|
|
188
|
+
};
|
|
189
|
+
wheelSlot.current = forward;
|
|
190
|
+
return () => {
|
|
191
|
+
if (wheelSlot.current === forward) wheelSlot.current = null;
|
|
192
|
+
};
|
|
193
|
+
}, [wheelSlot, hostRef]);
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
function usePublishPointer(hostRef, clientToLocal, frame, viewId) {
|
|
197
|
+
const store = usePointerContext();
|
|
198
|
+
const frameRef = useRef(frame);
|
|
199
|
+
frameRef.current = frame;
|
|
200
|
+
const toLocal = useRef(clientToLocal);
|
|
201
|
+
toLocal.current = clientToLocal;
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
const el = hostRef.current;
|
|
204
|
+
if (!el || !store) return;
|
|
205
|
+
const onMove = (e) => {
|
|
206
|
+
const w = frameLocalToWorld(toLocal.current(e.clientX, e.clientY), frameRef.current);
|
|
207
|
+
store.set({ worldX: w.x, worldY: w.y, viewId });
|
|
208
|
+
};
|
|
209
|
+
const onLeave = (e) => {
|
|
210
|
+
if (e.buttons !== 0) return;
|
|
211
|
+
if (store.get()?.viewId === viewId) store.set(null);
|
|
212
|
+
};
|
|
213
|
+
el.addEventListener("pointermove", onMove);
|
|
214
|
+
el.addEventListener("pointerleave", onLeave);
|
|
215
|
+
return () => {
|
|
216
|
+
el.removeEventListener("pointermove", onMove);
|
|
217
|
+
el.removeEventListener("pointerleave", onLeave);
|
|
218
|
+
};
|
|
219
|
+
}, [hostRef, store, viewId]);
|
|
220
|
+
}
|
|
221
|
+
function pointerElsewhere(p, viewId, frame) {
|
|
222
|
+
if (!p || p.viewId === viewId) return null;
|
|
223
|
+
return frameLocalToWorld({ x: p.worldX, y: p.worldY }, frame);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export { CameraContext, CameraInput, CameraScope, CameraScopeContext, CameraWheelContext, DEFAULT_VIEW, STAGE_VIEW_ID, as2DView, clampZoomAbout, frameLocalToWorld, fromCameraView, normalize2DView, pointerElsewhere, toCameraView, useCameraView, usePublishPointer, withZoom };
|
|
227
|
+
//# sourceMappingURL=chunk-DWMSB4CA.js.map
|
|
228
|
+
//# sourceMappingURL=chunk-DWMSB4CA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/canvas/CameraWheelContext.ts","../src/state/view.ts","../src/canvas/cameraView.ts","../src/canvas/CameraInput.tsx"],"names":["createContext"],"mappings":"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","file":"chunk-DWMSB4CA.js","sourcesContent":["import { createContext } from 'react';\n\n/** Where a trial's camera takes a wheel event from outside its own element. */\nexport type CameraWheelSlot = { current: ((e: WheelEvent) => void) | null };\n\n/**\n * The trial's camera, for input that lands outside it. An annotation target's\n * input box is portalled into the surface container, so a wheel over a mark\n * never bubbles through the stage or canvas stack it sits on; the box hands\n * the event here instead.\n */\nexport const CameraWheelContext = createContext<CameraWheelSlot | null>(null);\n","import { normalizeZoom } from '@weasel-js/core';\nimport type { ViewTransform } from '../instrument/types';\n\n/** The 2D view labkit has always shipped, and what a trial gets when it names no\n * other. Re-exported under its own name so a consumer can say which it means. */\nexport type ViewTransform2D = ViewTransform;\n\nexport const DEFAULT_VIEW: ViewTransform2D = { zoom: 1, pan: { x: 0, y: 0 } };\n\n/** A trial's view is opaque to labkit, so anything that needs the 2D shape — the\n * zoom chrome, `CanvasStack` — asks for it and handles not getting it. */\nexport function as2DView(view: unknown): ViewTransform2D | null {\n if (typeof view !== 'object' || view === null) return null;\n const v = view as Partial<ViewTransform2D>;\n if (typeof v.zoom !== 'number') return null;\n if (typeof v.pan !== 'object' || v.pan === null) return null;\n if (typeof v.pan.x !== 'number' || typeof v.pan.y !== 'number') return null;\n return normalize2DView({ zoom: v.zoom, pan: { x: v.pan.x, y: v.pan.y } });\n}\n\n/** `view` inside the invariant every view holds: a zoom that is not a finite\n * number of at least `ZOOM_FLOOR` becomes the floor, and a non-finite pan\n * becomes 0. Returns `view` itself when it already holds. */\nexport function normalize2DView(view: ViewTransform2D): ViewTransform2D {\n const zoom = normalizeZoom(view.zoom);\n const x = Number.isFinite(view.pan.x) ? view.pan.x : 0;\n const y = Number.isFinite(view.pan.y) ? view.pan.y : 0;\n if (zoom === view.zoom && x === view.pan.x && y === view.pan.y) return view;\n return { zoom, pan: { x, y } };\n}\n\n/** `view` at a new zoom, through the same rule. */\nexport function withZoom(view: ViewTransform2D, zoom: number): ViewTransform2D {\n return normalize2DView({ zoom, pan: view.pan });\n}\n","/**\n * labkit's camera as a weasel `View`, so core's viewport actions drive it.\n *\n * labkit places a world point on screen at\n * `originPx + pan + zoom * (x, yDir * y)`. A weasel `View` places one at\n * `(p - (x, y)) * scale`. Over *frame-local* coordinates — the world with `y`\n * multiplied by `yDir` — the two are the same affine map, which is all a pan\n * or a zoom about a screen point needs.\n */\nimport type { View } from '@weasel-js/core';\nimport type { Point, ViewTransform } from '../instrument/types';\nimport { normalize2DView } from '../state/view';\nimport { DEFAULT_FRAME, type WorldFrame } from './worldSpec';\n\n/** The weasel `View` equal to `view` under `frame`. */\nexport function toCameraView(view: ViewTransform, frame: WorldFrame = DEFAULT_FRAME): View {\n const v = normalize2DView(view);\n return {\n x: -(frame.originPx.x + v.pan.x) / v.zoom,\n y: -(frame.originPx.y + v.pan.y) / v.zoom,\n scale: { x: v.zoom, y: v.zoom },\n };\n}\n\n/** The labkit camera equal to weasel `view` under `frame`. */\nexport function fromCameraView(view: View, frame: WorldFrame = DEFAULT_FRAME): ViewTransform {\n const zoom = view.scale.x;\n return {\n zoom,\n pan: { x: -view.x * zoom - frame.originPx.x, y: -view.y * zoom - frame.originPx.y },\n };\n}\n\n/** A frame-local point in the instrument's world, and back — the map is its\n * own inverse. */\nexport function frameLocalToWorld(p: Point, frame: WorldFrame = DEFAULT_FRAME): Point {\n return { x: p.x, y: p.y * frame.yDir };\n}\n\n/**\n * `next` with its zoom held to [`min`, `max`], about the screen point the step\n * from `prev` was anchored on, so a zoom that hits a bound stays anchored where\n * the pointer is. A pan (equal scales) passes through.\n */\nexport function clampZoomAbout(prev: View, next: View, min: number, max: number): View {\n const s0 = prev.scale.x;\n const s1 = next.scale.x;\n const clamped = Math.min(max, Math.max(min, s1));\n if (clamped === s1) return next;\n if (s0 === s1) return { ...next, scale: { x: clamped, y: clamped } };\n // The screen point both views agree on: p / s0 + x0 = p / s1 + x1.\n const inv = 1 / s0 - 1 / s1;\n const px = (next.x - prev.x) / inv;\n const py = (next.y - prev.y) / inv;\n // The same point under the clamped scale.\n return {\n x: prev.x + px / s0 - px / clamped,\n y: prev.y + py / s0 - py / clamped,\n scale: { x: clamped, y: clamped },\n };\n}\n","/**\n * A labkit camera driven by weasel's dispatcher: drag pans, the wheel zooms\n * about the pointer, a tap reports the world point, and the pointer is\n * published into the store in scope. What `usePanZoom` used to hand-roll.\n */\nimport {\n type Action,\n ActionsProvider,\n type ActionsRegistry,\n ActiveToolContextProvider,\n DepRegistryProvider,\n makeViewportZoomAction,\n PointerContextProvider,\n type PointerWorldPos,\n type Tool,\n useActionsRegistry,\n useDepSource,\n useGestureDispatcher,\n usePointerContext,\n type View,\n type ViewApi,\n viewportDragPanAction,\n} from '@weasel-js/core';\nimport {\n createContext,\n type ReactNode,\n type RefObject,\n useContext,\n useEffect,\n useMemo,\n useRef,\n} from 'react';\nimport type { Point, ViewTransform } from '../instrument/types';\nimport { normalize2DView } from '../state/view';\nimport { CameraWheelContext } from './CameraWheelContext';\nimport { clampZoomAbout, frameLocalToWorld, fromCameraView, toCameraView } from './cameraView';\nimport type { ViewportSize, WorldFrame } from './worldSpec';\n\n/** The view id the stage publishes its pointer under. */\nexport const STAGE_VIEW_ID = 'stage';\n\n/**\n * A camera, as the things that sit beside it read it: an overview, a loupe, a\n * linked cursor. Points are *frame-local* — the world with `y` multiplied by\n * `frame.yDir` — which is the space `view` works in.\n */\nexport interface CameraContextValue {\n /** The camera as a weasel `ViewApi`, clamped to its zoom range. */\n view: ViewApi;\n frame: WorldFrame;\n /** The element the camera's dispatcher listens on. */\n element: () => HTMLElement | null;\n /** The content's own rect, frame-local, when it has one — a `<Stage>`'s. */\n content?: { x: number; y: number; width: number; height: number };\n}\n\n/** The camera around the caller, or `null` outside one. */\nexport const CameraContext = createContext<CameraContextValue | null>(null);\n\n/** Set by a host that already provides one input scope for everything inside\n * it — a trial — so the camera, the loupe and an overview share it. */\nexport const CameraScopeContext = createContext(false);\n\n/**\n * The actions and deps a camera routes through: the ones a trial provides, or\n * an isolated set of its own. Isolated, because an actions registry holds one\n * dispatcher, and a camera must not take input from a canvas beside it. The\n * pointer store is the exception — one already in scope is kept, since sharing\n * it is the point.\n */\nexport function CameraScope({ children }: { children: ReactNode }) {\n const inScope = useContext(CameraScopeContext);\n const pointer = usePointerContext();\n if (inScope) return <>{children}</>;\n const scoped = <CameraScopeContext.Provider value={true}>{children}</CameraScopeContext.Provider>;\n return (\n <DepRegistryProvider>\n <ActionsProvider>\n <ActiveToolContextProvider>\n {pointer ? scoped : <PointerContextProvider>{scoped}</PointerContextProvider>}\n </ActiveToolContextProvider>\n </ActionsProvider>\n </DepRegistryProvider>\n );\n}\n\n/** Options for {@link useCameraView}. */\nexport interface CameraViewOptions {\n view: ViewTransform;\n onViewChange: (v: ViewTransform) => void;\n frame: WorldFrame;\n hostRef: RefObject<HTMLElement | null>;\n minZoom?: number;\n maxZoom?: number;\n}\n\nfunction isPositiveFinite(n: number): boolean {\n return Number.isFinite(n) && n > 0;\n}\n\n/**\n * A labkit camera as a stable `ViewApi`. Its zoom range is widened to keep the\n * opening zoom reachable — captured once, so a zoom-out cannot shrink the\n * range and strand the opening view behind it.\n */\nexport function useCameraView({\n view,\n onViewChange,\n frame,\n hostRef,\n minZoom = 0.1,\n maxZoom = 32,\n}: CameraViewOptions): ViewApi {\n const live = useRef({ view: normalize2DView(view), onViewChange, frame });\n live.current = { view: normalize2DView(view), onViewChange, frame };\n const initialZoom = useRef(isPositiveFinite(view.zoom) ? view.zoom : null);\n const bounds = useRef({ min: minZoom, max: maxZoom });\n const opening = initialZoom.current;\n bounds.current = {\n min: opening == null ? minZoom : Math.min(minZoom, opening),\n max: opening == null ? maxZoom : Math.max(maxZoom, opening),\n };\n\n return useMemo<ViewApi>(() => {\n const get = (): View => toCameraView(live.current.view, live.current.frame);\n return {\n get,\n set: (next: View) => {\n const { min, max } = bounds.current;\n const clamped = clampZoomAbout(get(), next, min, max);\n const vt = fromCameraView(clamped, live.current.frame);\n // Written ahead of the render, so a second event inside one commit\n // steps from this one.\n live.current.view = vt;\n live.current.onViewChange(vt);\n },\n hostSize: () => {\n const r = hostRef.current?.getBoundingClientRect();\n return r ? { width: r.width, height: r.height } : null;\n },\n };\n }, [hostRef]);\n}\n\nconst TAP_ID = 'camera.tap';\nconst NO_TOOLS: ReadonlyMap<string, Tool> = new Map();\nconst CHANNELS = { contextMenu: false, ingest: false } as const;\n\n/** Props for `<CameraInput>`. */\nexport interface CameraInputProps {\n hostRef: RefObject<HTMLElement | null>;\n camera: ViewApi;\n frame: WorldFrame;\n /** A primary-button press released without crossing the drag threshold, at\n * the world point it landed on. */\n onTap?: (world: Point) => void;\n}\n\n/**\n * Mounts the dispatcher on `hostRef` and routes the camera's gestures through\n * it. Render it inside a `<CameraScope>`; with no actions registry in scope it\n * renders nothing.\n */\nexport function CameraInput(props: CameraInputProps) {\n const registry = useActionsRegistry();\n if (!registry) return null;\n return <CameraDispatch {...props} registry={registry} />;\n}\n\nfunction CameraDispatch({\n hostRef,\n camera,\n frame,\n onTap,\n registry,\n}: CameraInputProps & { registry: ActionsRegistry }) {\n const frameRef = useRef(frame);\n frameRef.current = frame;\n const onTapRef = useRef(onTap);\n onTapRef.current = onTap;\n\n useDepSource('view', () => camera);\n useDepSource('rootView', () => camera);\n\n const actions = useMemo<Action[]>(() => {\n // Unclamped here: `camera.set` clamps, to a range widened around the\n // opening zoom, and anchors the clamp where the wheel was.\n const zoom = makeViewportZoomAction({\n wheel: 'plain',\n min: 1e-9,\n max: Number.POSITIVE_INFINITY,\n });\n // The wheel and the trackpad's two pinch forms only: the zoom keys listen\n // on the window, and every trial on a page would take them at once.\n const wheelOnly = (zoom.defaultBinding as { spec: { kind: string } }[]).filter(\n (b) => b.spec.kind === 'wheel' || b.spec.kind === 'pinch',\n );\n const tap: Action = {\n id: TAP_ID,\n label: 'Tap the canvas',\n defaultBinding: { kind: 'click' },\n invoker: {\n timing: 'immediate',\n run: (_deps, params) => {\n const p = params as { worldX?: number; worldY?: number } | undefined;\n if (p?.worldX === undefined || p.worldY === undefined) return;\n onTapRef.current?.(frameLocalToWorld({ x: p.worldX, y: p.worldY }, frameRef.current));\n },\n },\n };\n return [\n viewportDragPanAction,\n { ...zoom, defaultBinding: wheelOnly as Action['defaultBinding'] },\n tap,\n ];\n }, []);\n\n useEffect(() => {\n const offs = actions.map((a) => registry.register(a));\n return () => {\n for (const off of offs) off();\n };\n }, [registry, actions]);\n\n const clientToWorld = useMemo(\n () => (cx: number, cy: number) => {\n const r = hostRef.current?.getBoundingClientRect();\n const v = camera.get();\n return {\n x: (cx - (r?.left ?? 0)) / v.scale.x + v.x,\n y: (cy - (r?.top ?? 0)) / v.scale.y + v.y,\n };\n },\n [hostRef, camera],\n );\n\n useGestureDispatcher({\n canvasRef: hostRef,\n actions: registry,\n toolsById: NO_TOOLS,\n clientToWorld,\n channels: CHANNELS,\n });\n\n usePublishPointer(hostRef, clientToWorld, frame, STAGE_VIEW_ID);\n\n // Input that lands outside the camera's element — an annotation target's\n // box, portalled into the surface — reaches the dispatcher through here.\n const wheelSlot = useContext(CameraWheelContext);\n useEffect(() => {\n if (!wheelSlot) return;\n const forward = (e: WheelEvent): void => {\n hostRef.current?.dispatchEvent(new WheelEvent('wheel', e));\n };\n wheelSlot.current = forward;\n return () => {\n if (wheelSlot.current === forward) wheelSlot.current = null;\n };\n }, [wheelSlot, hostRef]);\n\n return null;\n}\n\n/**\n * Publish the pointer over `hostRef` into the store in scope, in the\n * instrument's world, as `viewId`; clear it when the pointer leaves with no\n * button held.\n */\nexport function usePublishPointer(\n hostRef: RefObject<HTMLElement | null>,\n clientToLocal: (cx: number, cy: number) => Point,\n frame: WorldFrame,\n viewId: string,\n): void {\n const store = usePointerContext();\n const frameRef = useRef(frame);\n frameRef.current = frame;\n const toLocal = useRef(clientToLocal);\n toLocal.current = clientToLocal;\n useEffect(() => {\n const el = hostRef.current;\n if (!el || !store) return;\n const onMove = (e: PointerEvent): void => {\n const w = frameLocalToWorld(toLocal.current(e.clientX, e.clientY), frameRef.current);\n store.set({ worldX: w.x, worldY: w.y, viewId });\n };\n const onLeave = (e: PointerEvent): void => {\n if (e.buttons !== 0) return;\n if (store.get()?.viewId === viewId) store.set(null);\n };\n el.addEventListener('pointermove', onMove);\n el.addEventListener('pointerleave', onLeave);\n return () => {\n el.removeEventListener('pointermove', onMove);\n el.removeEventListener('pointerleave', onLeave);\n };\n }, [hostRef, store, viewId]);\n}\n\n/** The pointer as a frame-local point, or `null` when there is none or it is\n * over `viewId` itself. */\nexport function pointerElsewhere(\n p: PointerWorldPos,\n viewId: string,\n frame: WorldFrame,\n): Point | null {\n if (!p || p.viewId === viewId) return null;\n return frameLocalToWorld({ x: p.worldX, y: p.worldY }, frame);\n}\n\nexport type { ViewportSize };\n"]}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { LabContext } from './chunk-Z76KEZAX.js';
|
|
2
|
-
import { resolveConfigSchema } from './chunk-
|
|
3
|
-
import { isLeafVisible, fromConfigFields } from './chunk-
|
|
4
|
-
import { valueAtPath } from './chunk-
|
|
5
|
-
import { s4 } from './chunk-WUJXJC5O.js';
|
|
2
|
+
import { resolveConfigSchema } from './chunk-O6FFFOHS.js';
|
|
3
|
+
import { isLeafVisible, fromConfigFields } from './chunk-IRTGUQ5O.js';
|
|
4
|
+
import { valueAtPath } from './chunk-TJ77A4A3.js';
|
|
6
5
|
import { useContext, useMemo } from 'react';
|
|
7
6
|
|
|
8
7
|
// src/config/sectionTree.ts
|
|
8
|
+
function isLeaf(node) {
|
|
9
|
+
return "kind" in node;
|
|
10
|
+
}
|
|
9
11
|
function slugOf(label, taken) {
|
|
10
12
|
const base = label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "section";
|
|
11
13
|
if (!taken.has(base)) return base;
|
|
@@ -16,7 +18,7 @@ function prune(resolved, group, at, config, showHidden) {
|
|
|
16
18
|
for (const [key, child] of Object.entries(group.children)) {
|
|
17
19
|
const path = at === "" ? key : `${at}.${key}`;
|
|
18
20
|
if (!isLeafVisible(resolved, path, config, showHidden)) continue;
|
|
19
|
-
if (
|
|
21
|
+
if (isLeaf(child)) {
|
|
20
22
|
children[key] = child;
|
|
21
23
|
continue;
|
|
22
24
|
}
|
|
@@ -38,7 +40,7 @@ function sectionTree(resolved, config, showHidden = false) {
|
|
|
38
40
|
claimed.add(path);
|
|
39
41
|
const child = resolved.group.children[path];
|
|
40
42
|
if (!child || !isLeafVisible(resolved, path, config, showHidden)) continue;
|
|
41
|
-
if (
|
|
43
|
+
if (isLeaf(child)) {
|
|
42
44
|
kept[path] = child;
|
|
43
45
|
} else {
|
|
44
46
|
const subtree = prune(resolved, child, path, config, showHidden);
|
|
@@ -55,7 +57,7 @@ function sectionTree(resolved, config, showHidden = false) {
|
|
|
55
57
|
}
|
|
56
58
|
for (const [key, child] of Object.entries(resolved.group.children)) {
|
|
57
59
|
if (claimed.has(key) || !isLeafVisible(resolved, key, config, showHidden)) continue;
|
|
58
|
-
if (
|
|
60
|
+
if (isLeaf(child)) {
|
|
59
61
|
children[key] = child;
|
|
60
62
|
} else {
|
|
61
63
|
const subtree = prune(resolved, child, key, config, showHidden);
|
|
@@ -86,5 +88,5 @@ function useConfigSchema(instrument) {
|
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
export { sectionTree, useConfigSchema };
|
|
89
|
-
//# sourceMappingURL=chunk-
|
|
90
|
-
//# sourceMappingURL=chunk-
|
|
91
|
+
//# sourceMappingURL=chunk-GHGO52SO.js.map
|
|
92
|
+
//# sourceMappingURL=chunk-GHGO52SO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config/sectionTree.ts","../src/config/useConfigSchema.ts"],"names":[],"mappings":"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","file":"chunk-GHGO52SO.js","sourcesContent":["import type { PrefGroup, PrefLeaf } from '@weasel-js/ui';\nimport { valueAtPath } from './path';\nimport type { ResolvedConfig } from './types';\nimport { isLeafVisible } from './visible';\n\n/**\n * A resolved schema rearranged so its sections are groups.\n *\n * `PrefsForm`'s rail navigates the schema tree — top-level groups are its\n * items, their group children indent under them. A resolved schema keeps its\n * sections beside the tree instead, so a flat schema's rail comes out as one\n * unnamed item however many headings the panel draws. This puts the sections\n * where the rail looks for them.\n */\nexport interface SectionTree {\n /** The rail's schema. Top-level children are the schema's root sections,\n * followed by any root node no section claimed. */\n group: PrefGroup;\n /** `config` renested to mirror `group` — the value tree `PrefsForm` takes. */\n values: Record<string, unknown>;\n /** The config path a path within `group` stands for. */\n pathAt: (railPath: string) => string;\n}\n\n/** Structural rather than `isPrefLeaf`, so the config entry stays free of a\n * runtime import from ui. */\nfunction isLeaf(node: PrefLeaf | PrefGroup): node is PrefLeaf {\n return 'kind' in node;\n}\n\n/** A section label as an object key: no dots, since a dotted path is how every\n * caller addresses a node, and a label is free to hold one. */\nfunction slugOf(label: string, taken: ReadonlySet<string>): string {\n const base =\n label\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-|-$/g, '') || 'section';\n if (!taken.has(base)) return base;\n for (let n = 2; ; n += 1) if (!taken.has(`${base}-${n}`)) return `${base}-${n}`;\n}\n\n/** A subtree with everything `showIf` or `hidden` rules out cut away, or null\n * when that is all of it. A group whose children all went is not a rail item\n * the reader can open onto nothing. */\nfunction prune(\n resolved: ResolvedConfig,\n group: PrefGroup,\n at: string,\n config: Record<string, unknown>,\n showHidden: boolean,\n): PrefGroup | null {\n const children: PrefGroup['children'] = {};\n for (const [key, child] of Object.entries(group.children)) {\n const path = at === '' ? key : `${at}.${key}`;\n if (!isLeafVisible(resolved, path, config, showHidden)) continue;\n if (isLeaf(child)) {\n children[key] = child;\n continue;\n }\n const kept = prune(resolved, child, path, config, showHidden);\n if (kept) children[key] = kept;\n }\n return Object.keys(children).length === 0 ? null : { ...group, children };\n}\n\n/**\n * Turn a resolved schema's root sections into the top level of a `PrefGroup`,\n * for `PrefsForm`'s rail layout.\n *\n * A section naming a group brings that group in whole, so it lands as an\n * indented rail item; a section naming a leaf drops it loose into the\n * section's own pane. Sections nested under a group are left alone — they\n * bucket rows inside a pane, which is the panel's job and not the rail's.\n */\nexport function sectionTree(\n resolved: ResolvedConfig,\n config: Record<string, unknown>,\n showHidden = false,\n): SectionTree {\n const children: PrefGroup['children'] = {};\n const values: Record<string, unknown> = {};\n const slugs = new Set<string>();\n const claimed = new Set<string>();\n\n for (const section of resolved.sections) {\n if (section.at !== '') continue;\n const kept: PrefGroup['children'] = {};\n const held: Record<string, unknown> = {};\n for (const path of section.paths) {\n claimed.add(path);\n const child = resolved.group.children[path];\n if (!child || !isLeafVisible(resolved, path, config, showHidden)) continue;\n if (isLeaf(child)) {\n kept[path] = child;\n } else {\n const subtree = prune(resolved, child, path, config, showHidden);\n if (!subtree) continue;\n kept[path] = subtree;\n }\n held[path] = valueAtPath(config, path);\n }\n if (Object.keys(kept).length === 0) continue;\n const slug = slugOf(section.label, slugs);\n slugs.add(slug);\n children[slug] = { name: section.label, children: kept };\n values[slug] = held;\n }\n\n // Whatever no section claimed stays at the top level, where `prefRailItems`\n // gathers loose leaves into one item named for the root.\n for (const [key, child] of Object.entries(resolved.group.children)) {\n if (claimed.has(key) || !isLeafVisible(resolved, key, config, showHidden)) continue;\n if (isLeaf(child)) {\n children[key] = child;\n } else {\n const subtree = prune(resolved, child, key, config, showHidden);\n if (!subtree) continue;\n children[key] = subtree;\n }\n values[key] = valueAtPath(config, key);\n }\n\n return {\n group: { name: resolved.group.name, children },\n values,\n pathAt: (railPath) => {\n const cut = railPath.indexOf('.');\n const head = cut === -1 ? railPath : railPath.slice(0, cut);\n if (!slugs.has(head)) return railPath;\n return cut === -1 ? '' : railPath.slice(cut + 1);\n },\n };\n}\n","import { useContext, useMemo } from 'react';\nimport type { Instrument } from '../instrument/types';\nimport { LabContext } from '../lab/LabContext';\nimport { fromConfigFields } from './fromConfigField';\nimport { resolveConfigSchema } from './resolve';\nimport type { ResolvedConfig } from './types';\n\nconst NO_RULES: readonly [] = [];\n\n/**\n * An instrument's controls, resolved against the surrounding lab's rules.\n *\n * Resolution cannot happen in `defineInstrument` — rules are lab-scoped and\n * that runs at module load — so it happens here, at render. Both instrument\n * paths land in the same shape, so callers never branch on which was used.\n */\nexport function useConfigSchema(\n // biome-ignore lint/suspicious/noExplicitAny: instruments are stored contravariantly; see InstrumentList\n instrument: Instrument<any, any, any>, // eslint-disable-line @typescript-eslint/no-explicit-any\n): ResolvedConfig {\n const lab = useContext(LabContext);\n const rules = lab?.configRules ?? NO_RULES;\n return useMemo(\n () =>\n instrument.config\n ? resolveConfigSchema(instrument.config, rules)\n : fromConfigFields(instrument.configSchema?.() ?? []),\n [instrument, rules],\n );\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { newId, emptyUndoStack, deserializeTrials, openRecords, labPrefix, documentOfRecords, recordsOfDocument, trialRecord, trialValuesPrefix, saveRecord, LAYOUT_RECORD, UNDOCK_RECORD, META_RECORD, parseRecordName, compareTrialOrder, serializeTrials, labStorageKey } from './chunk-RWNDDKOH.js';
|
|
2
|
-
import { resolveConfigSchema, useResolvedConfig } from './chunk-
|
|
3
|
-
import { applyConfigWrite, fillConfigDefaults } from './chunk-
|
|
2
|
+
import { resolveConfigSchema, useResolvedConfig } from './chunk-O6FFFOHS.js';
|
|
3
|
+
import { applyConfigWrite, fillConfigDefaults } from './chunk-TJ77A4A3.js';
|
|
4
4
|
import { LabStoreContext, TrialIdContext } from './chunk-2YILQ7Y5.js';
|
|
5
5
|
import { createStore } from 'zustand/vanilla';
|
|
6
6
|
import { useContext } from 'react';
|
|
@@ -733,5 +733,5 @@ function useTrialState() {
|
|
|
733
733
|
}
|
|
734
734
|
|
|
735
735
|
export { CURRENT_DOCUMENT_VERSION, createLabStore, dockPanel, labDocumentKey, openLabStore, panelKey, quarantineKey, undockPanel, unruledConfigSchema, useTrialState };
|
|
736
|
-
//# sourceMappingURL=chunk-
|
|
737
|
-
//# sourceMappingURL=chunk-
|
|
736
|
+
//# sourceMappingURL=chunk-HKV7LRHY.js.map
|
|
737
|
+
//# sourceMappingURL=chunk-HKV7LRHY.js.map
|