@weasel-js/labkit 1.4.4 → 1.5.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/README.md +13 -2
- package/dist/_dts/{CanvasStackContext-kTEZvEgE.d.ts → CanvasStackContext-CqkxkFJW.d.ts} +1 -1
- package/dist/_dts/ToggleBar.d-DbHoWKIA.d.ts +116 -0
- package/dist/_dts/{frac-z7ker2Vx.d.ts → frac-8R6UmlvN.d.ts} +253 -31
- package/dist/_dts/index-B8uu2ba7.d.ts +237 -0
- package/dist/_dts/{index-Blv9uQzu.d.ts → index-retnAut7.d.ts} +18 -10
- package/dist/_dts/{types-B9_zrHmb.d.ts → types-Ca2LCnPq.d.ts} +65 -2
- package/dist/_dts/usePanZoom-nS798rOF.d.ts +160 -0
- package/dist/_dts/useTrialState-jcx_TdU6.d.ts +212 -0
- package/dist/canvas/index.d.ts +17 -128
- package/dist/canvas/index.js +4 -2
- package/dist/chrome/index.d.ts +4 -92
- package/dist/chrome/index.js +9 -5
- package/dist/chunk-67SJLMC7.js +704 -0
- package/dist/chunk-67SJLMC7.js.map +1 -0
- package/dist/chunk-6ZDGOZQV.js +310 -0
- package/dist/chunk-6ZDGOZQV.js.map +1 -0
- package/dist/{chunk-MOM3GOVY.js → chunk-EKIICY6X.js} +111 -25
- package/dist/chunk-EKIICY6X.js.map +1 -0
- package/dist/{chunk-CTRKTLYZ.js → chunk-H6ZAOWNE.js} +11 -11
- package/dist/chunk-H6ZAOWNE.js.map +1 -0
- package/dist/{chunk-SMHP6XZ4.js → chunk-KJALCDWE.js} +9 -7
- package/dist/chunk-KJALCDWE.js.map +1 -0
- package/dist/{chunk-BKFVHKJH.js → chunk-MUKOW3TC.js} +3 -3
- package/dist/{chunk-BKFVHKJH.js.map → chunk-MUKOW3TC.js.map} +1 -1
- package/dist/chunk-NDRYLVOW.js +15 -0
- package/dist/chunk-NDRYLVOW.js.map +1 -0
- package/dist/{chunk-64ZCN3DA.js → chunk-NS64DXMN.js} +226 -159
- package/dist/chunk-NS64DXMN.js.map +1 -0
- package/dist/{chunk-I6JCVE24.js → chunk-PGETSEDK.js} +135 -13
- package/dist/chunk-PGETSEDK.js.map +1 -0
- package/dist/{chunk-FQMJUVHJ.js → chunk-QLSV2N3G.js} +32 -37
- package/dist/chunk-QLSV2N3G.js.map +1 -0
- package/dist/chunk-RWNDDKOH.js +682 -0
- package/dist/chunk-RWNDDKOH.js.map +1 -0
- package/dist/chunk-S2SHZRD7.js +7681 -0
- package/dist/chunk-S2SHZRD7.js.map +1 -0
- package/dist/{chunk-U3IYHIAE.js → chunk-TQBBYTAF.js} +19 -82
- package/dist/chunk-TQBBYTAF.js.map +1 -0
- package/dist/chunk-UU3NJO6N.js +72 -0
- package/dist/chunk-UU3NJO6N.js.map +1 -0
- package/dist/chunk-WIG6XHJ7.js +35 -0
- package/dist/chunk-WIG6XHJ7.js.map +1 -0
- package/dist/{chunk-TJ7QY3OC.js → chunk-ZPH5WTWF.js} +21 -15
- package/dist/chunk-ZPH5WTWF.js.map +1 -0
- package/dist/config/index.d.ts +232 -0
- package/dist/config/index.js +6 -0
- package/dist/config/index.js.map +1 -0
- package/dist/controls/index.d.ts +2 -3
- package/dist/controls/index.js +3 -2
- package/dist/dragdrop/index.d.ts +2 -4
- package/dist/index.d.ts +110 -381
- package/dist/index.js +691 -817
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +3 -5
- package/dist/layers/index.js +2 -2
- package/dist/loupe/index.d.ts +6 -8
- package/dist/loupe/index.js +2 -2
- package/dist/passthrough/weasel-ui.d.ts +164 -122
- package/dist/passthrough/weasel-ui.js +1 -1
- package/dist/primitives/index.d.ts +53 -6
- package/dist/primitives/index.js +6 -4
- package/dist/state/index.d.ts +12 -7
- package/dist/state/index.js +16 -13
- package/dist/state/index.js.map +1 -1
- package/dist/styles.css +84 -50
- package/dist/surface/index.d.ts +44 -7
- package/dist/surface/index.js +2 -1
- package/dist/ui/layers/index.js +1 -1
- package/dist/undo/index.d.ts +2 -4
- package/package.json +15 -8
- package/src/annotations/AnnotationOverlay.tsx +53 -5
- package/src/annotations/Annotations.overlay.test.tsx +85 -6
- package/src/annotations/drawOne.test.ts +1 -1
- package/src/annotations/drawOne.ts +2 -2
- package/src/annotations/paint.test.ts +2 -2
- package/src/annotations/paint.ts +1 -1
- package/src/annotations/preload.ts +6 -0
- package/src/annotations/store.test.ts +18 -0
- package/src/annotations/store.ts +5 -4
- package/src/annotations/svgNodes.test.ts +2 -2
- package/src/annotations/toolMap.ts +3 -3
- package/src/annotations/types.ts +6 -3
- package/src/canvas/CameraWheelContext.ts +12 -0
- package/src/canvas/CanvasStack.test.tsx +25 -0
- package/src/canvas/CanvasStack.tsx +31 -4
- package/src/canvas/Stage.less +18 -0
- package/src/canvas/Stage.test.tsx +186 -0
- package/src/canvas/Stage.tsx +134 -0
- package/src/canvas/camera.test.ts +17 -2
- package/src/canvas/camera.ts +9 -9
- package/src/canvas/index.ts +4 -0
- package/src/canvas/usePanZoom.ts +10 -5
- package/src/chrome/ChromeRegions.stories.tsx +0 -3
- package/src/chrome/LabChrome.tsx +114 -0
- package/src/chrome/builtins.tsx +20 -31
- package/src/chrome/index.ts +15 -0
- package/src/chrome/labRegions.test.tsx +208 -0
- package/src/chrome/labTypes.ts +50 -0
- package/src/chrome/merge.ts +7 -9
- package/src/chrome/regions/PaletteRegion.test.tsx +6 -4
- package/src/chrome/regions/PaletteRegion.tsx +21 -9
- package/src/chrome/regions/SidebarRegion.tsx +25 -13
- package/src/chrome/regions/StatusRegion.tsx +15 -8
- package/src/chrome/regions/ToolbarRegion.tsx +30 -16
- package/src/chrome/types.ts +47 -20
- package/src/config/builder.test.ts +4 -0
- package/src/config/builder.ts +16 -10
- package/src/config/declarationEmit.fixture.ts +11 -0
- package/src/config/declarationEmit.test.ts +51 -0
- package/src/config/entry.test.ts +46 -0
- package/src/config/index.ts +13 -1
- package/src/config/nodeClasses.test.ts +18 -0
- package/src/config/types.ts +3 -1
- package/src/controls/ControlPanel.test.tsx +16 -0
- package/src/controls/ControlPanel.tsx +5 -2
- package/src/fake-indexeddb-auto.d.ts +5 -0
- package/src/index.test.ts +7 -0
- package/src/index.ts +38 -5
- package/src/instrument/serializers.test.ts +22 -0
- package/src/instrument/serializers.ts +11 -0
- package/src/instrument/types.ts +25 -1
- package/src/lab/Lab.less +4 -13
- package/src/lab/Lab.persist.test.tsx +147 -0
- package/src/lab/Lab.stories.tsx +0 -2
- package/src/lab/Lab.surface.test.tsx +66 -3
- package/src/lab/Lab.test.tsx +91 -17
- package/src/lab/Lab.tsx +337 -124
- package/src/lab/LabFit.stories.less +18 -0
- package/src/lab/LabFit.stories.tsx +219 -0
- package/src/lab/LabFullChrome.stories.tsx +0 -3
- package/src/lab/LabHeader.test.tsx +40 -9
- package/src/lab/LabHeader.tsx +7 -13
- package/src/lab/LabPalette.tsx +13 -26
- package/src/lab/LabShell.less +49 -12
- package/src/lab/LabSwitcher.less +8 -1
- package/src/lab/Workspace.surface.test.tsx +2 -1
- package/src/lab/fitCheck.test.ts +57 -0
- package/src/lab/fitCheck.ts +125 -0
- package/src/loupe/AGENTS.md +1 -1
- package/src/loupe/Loupe.less +1 -1
- package/src/loupe/canvasLens.ts +1 -1
- package/src/loupe/types.ts +1 -1
- package/src/loupe/useLoupe.test.tsx +1 -1
- package/src/loupe/useLoupe.ts +1 -1
- package/src/passthrough/weasel-ui.ts +9 -0
- package/src/primitives/FloatingPanel.stories.tsx +13 -1
- package/src/primitives/FloatingPanel.test.tsx +47 -16
- package/src/primitives/FloatingPanel.tsx +11 -25
- package/src/primitives/Split.less +14 -0
- package/src/primitives/Split.test.tsx +108 -0
- package/src/primitives/Split.tsx +159 -0
- package/src/primitives/ZoomControl.tsx +5 -2
- package/src/primitives/index.ts +2 -0
- package/src/state/Persistence.stories.tsx +75 -0
- package/src/state/Persistence.tsx +33 -0
- package/src/state/SingletonExperiment.test.tsx +47 -53
- package/src/state/SingletonExperiment.tsx +24 -13
- package/src/state/adapterContract.ts +116 -0
- package/src/state/adapters.test.ts +113 -61
- package/src/state/adapters.ts +328 -69
- package/src/state/document.test.ts +71 -96
- package/src/state/document.ts +66 -52
- package/src/state/helpers.test.ts +1 -1
- package/src/state/index.ts +8 -0
- package/src/state/labRecords.test.ts +105 -0
- package/src/state/labRecords.ts +160 -0
- package/src/state/openLabStore.test.ts +658 -0
- package/src/state/openLabStore.ts +403 -0
- package/src/state/records.test.ts +234 -0
- package/src/state/records.ts +219 -0
- package/src/state/store.test.ts +104 -635
- package/src/state/store.ts +116 -157
- package/src/state/toolSlot.test.ts +1 -1
- package/src/state/types.ts +39 -15
- package/src/state/useOpenOnce.ts +68 -0
- package/src/state/usePersistedState.test.tsx +125 -0
- package/src/state/usePersistedState.ts +66 -0
- package/src/state/useTrialState.test.tsx +2 -3
- package/src/state/view.test.ts +49 -15
- package/src/state/view.ts +18 -1
- package/src/styles.less +2 -0
- package/src/surface/AGENTS.md +39 -7
- package/src/surface/SurfaceContext.ts +26 -3
- package/src/surface/index.ts +2 -0
- package/src/surface/useSurfaceTile.test.tsx +2 -1
- package/src/surface/useSurfaceTile.ts +6 -5
- package/src/surface/useTiledSurface.test.tsx +175 -0
- package/src/surface/useTiledSurface.ts +54 -5
- package/src/theme/Interstellar.stories.tsx +7 -7
- package/src/theme/interstellar.test.ts +1 -1
- package/src/theme/interstellar.tokens.json +2 -2
- package/src/tools/labTool.ts +11 -0
- package/src/trial/Trial.annotations.persist.test.tsx +100 -58
- package/src/trial/Trial.annotations.test.tsx +47 -4
- package/src/trial/Trial.job.test.tsx +3 -3
- package/src/trial/Trial.less +1 -26
- package/src/trial/Trial.stories.tsx +1 -2
- package/src/trial/Trial.test.tsx +28 -4
- package/src/trial/Trial.trialId.test.tsx +23 -1
- package/src/trial/Trial.tsx +118 -38
- package/src/trial/TrialBody.tsx +17 -141
- package/src/trial/TrialChrome.tsx +7 -4
- package/src/trial/trialOps.ts +5 -3
- package/dist/_dts/types-BP2OCcpg.d.ts +0 -155
- package/dist/_dts/types-x92Kfeme.d.ts +0 -62
- package/dist/_dts/useTrialState-D6Vb3T-g.d.ts +0 -99
- package/dist/chunk-2P6PP5N4.js +0 -553
- package/dist/chunk-2P6PP5N4.js.map +0 -1
- package/dist/chunk-64ZCN3DA.js.map +0 -1
- package/dist/chunk-CTRKTLYZ.js.map +0 -1
- package/dist/chunk-FQMJUVHJ.js.map +0 -1
- package/dist/chunk-I6JCVE24.js.map +0 -1
- package/dist/chunk-MOM3GOVY.js.map +0 -1
- package/dist/chunk-SMHP6XZ4.js.map +0 -1
- package/dist/chunk-TJ7QY3OC.js.map +0 -1
- package/dist/chunk-U3IYHIAE.js.map +0 -1
- package/dist/chunk-W3ECWC2K.js +0 -7335
- package/dist/chunk-W3ECWC2K.js.map +0 -1
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { usePersistedState } from './chunk-RWNDDKOH.js';
|
|
2
|
+
import { ce, le } from './chunk-S2SHZRD7.js';
|
|
2
3
|
import { openPointerSession } from '@weasel-js/core';
|
|
3
4
|
import { useRef, useMemo, useState, useEffect } from 'react';
|
|
4
|
-
import { floatingStrategy, DEFAULT_INSET, DEFAULT_ANCHOR, FLOATING_DRAG_PREFIX } from 'windease';
|
|
5
|
+
import { asNodeId, floatingStrategy, DEFAULT_INSET, DEFAULT_ANCHOR, Store, createNode, stripStrategy, FLOATING_DRAG_PREFIX } from 'windease';
|
|
5
6
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
7
|
+
import { Provider, StrategyRegistryProvider, Container } from 'windease/react';
|
|
6
8
|
|
|
7
9
|
var ITEM_ID = "panel";
|
|
8
10
|
function borderBoxOf(entry) {
|
|
@@ -15,21 +17,12 @@ function isInteractive(target) {
|
|
|
15
17
|
if (!(target instanceof Element)) return false;
|
|
16
18
|
return target.closest("input, button, a, select, textarea, [data-no-drag]") !== null;
|
|
17
19
|
}
|
|
18
|
-
function readStored(key) {
|
|
19
|
-
if (!key) return null;
|
|
20
|
-
try {
|
|
21
|
-
const raw = localStorage.getItem(key);
|
|
22
|
-
return raw ? JSON.parse(raw) : null;
|
|
23
|
-
} catch {
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
20
|
function FloatingPanel({
|
|
28
21
|
children,
|
|
29
22
|
anchor = DEFAULT_ANCHOR,
|
|
30
23
|
snapCorners,
|
|
31
24
|
inset = DEFAULT_INSET,
|
|
32
|
-
|
|
25
|
+
persist,
|
|
33
26
|
className
|
|
34
27
|
}) {
|
|
35
28
|
const ref = useRef(null);
|
|
@@ -37,16 +30,11 @@ function FloatingPanel({
|
|
|
37
30
|
const options = useMemo(() => ({ defaultAnchor: anchor, inset }), [anchor, inset]);
|
|
38
31
|
const [container, setContainer] = useState({ w: 0, h: 0 });
|
|
39
32
|
const [size, setSize] = useState({ w: 0, h: 0 });
|
|
40
|
-
const [place, setPlace] =
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
localStorage.setItem(storageKey, JSON.stringify(place));
|
|
47
|
-
} catch {
|
|
48
|
-
}
|
|
49
|
-
}, [place, storageKey]);
|
|
33
|
+
const [place, setPlace] = usePersistedState(persist, () => ({
|
|
34
|
+
x: 0,
|
|
35
|
+
y: 0,
|
|
36
|
+
anchor
|
|
37
|
+
}));
|
|
50
38
|
const item = useMemo(
|
|
51
39
|
() => ({
|
|
52
40
|
id: ITEM_ID,
|
|
@@ -228,7 +216,105 @@ function Sidebar({ children, title, collapsed = false, onToggle }) {
|
|
|
228
216
|
/* @__PURE__ */ jsx("div", { className: "lk-sidebar-body", children })
|
|
229
217
|
] });
|
|
230
218
|
}
|
|
219
|
+
var ZONE_ID = asNodeId("lk-split");
|
|
220
|
+
var SIDEBAR_ID = asNodeId("lk-split-sidebar");
|
|
221
|
+
var CONTENT_ID = asNodeId("lk-split-content");
|
|
222
|
+
var STRATEGIES = { strip: stripStrategy };
|
|
223
|
+
function Split({
|
|
224
|
+
sidebar,
|
|
225
|
+
children,
|
|
226
|
+
label = "Sidebar",
|
|
227
|
+
className,
|
|
228
|
+
sidebarClassName,
|
|
229
|
+
contentClassName,
|
|
230
|
+
width,
|
|
231
|
+
defaultWidth = 320,
|
|
232
|
+
minWidth = 140,
|
|
233
|
+
maxWidth = 720,
|
|
234
|
+
contentMinWidth = 160,
|
|
235
|
+
onWidthChange,
|
|
236
|
+
viewport
|
|
237
|
+
}) {
|
|
238
|
+
const storeRef = useRef(null);
|
|
239
|
+
if (storeRef.current === null) {
|
|
240
|
+
const store2 = new Store();
|
|
241
|
+
store2.registerNode(
|
|
242
|
+
createNode({
|
|
243
|
+
kind: "zone",
|
|
244
|
+
id: ZONE_ID,
|
|
245
|
+
container: { strategyId: "strip", config: { axis: "x", resizable: true } }
|
|
246
|
+
})
|
|
247
|
+
);
|
|
248
|
+
store2.registerNode(
|
|
249
|
+
createNode({
|
|
250
|
+
kind: "sidebar",
|
|
251
|
+
id: SIDEBAR_ID,
|
|
252
|
+
parentId: ZONE_ID,
|
|
253
|
+
meta: { title: label },
|
|
254
|
+
placement: { size: { w: width ?? defaultWidth } },
|
|
255
|
+
hints: { minSize: { w: minWidth, h: 0 }, maxSize: { w: maxWidth, h: 0 } }
|
|
256
|
+
})
|
|
257
|
+
);
|
|
258
|
+
store2.registerNode(
|
|
259
|
+
createNode({
|
|
260
|
+
kind: "content",
|
|
261
|
+
id: CONTENT_ID,
|
|
262
|
+
parentId: ZONE_ID,
|
|
263
|
+
meta: { title: "Content" },
|
|
264
|
+
hints: { minSize: { w: contentMinWidth, h: 0 } }
|
|
265
|
+
})
|
|
266
|
+
);
|
|
267
|
+
store2.showNode(SIDEBAR_ID);
|
|
268
|
+
store2.showNode(CONTENT_ID);
|
|
269
|
+
storeRef.current = store2;
|
|
270
|
+
}
|
|
271
|
+
const store = storeRef.current;
|
|
272
|
+
const widthRef = useRef(width ?? defaultWidth);
|
|
273
|
+
useEffect(() => {
|
|
274
|
+
store.setHints(SIDEBAR_ID, {
|
|
275
|
+
minSize: { w: minWidth, h: 0 },
|
|
276
|
+
maxSize: { w: maxWidth, h: 0 }
|
|
277
|
+
});
|
|
278
|
+
store.setHints(CONTENT_ID, { minSize: { w: contentMinWidth, h: 0 } });
|
|
279
|
+
}, [store, minWidth, maxWidth, contentMinWidth]);
|
|
280
|
+
useEffect(() => {
|
|
281
|
+
store.setMeta(SIDEBAR_ID, { title: label });
|
|
282
|
+
}, [store, label]);
|
|
283
|
+
useEffect(() => {
|
|
284
|
+
if (width === void 0 || width === widthRef.current) return;
|
|
285
|
+
widthRef.current = width;
|
|
286
|
+
store.patchPlacement(SIDEBAR_ID, { size: { w: width } });
|
|
287
|
+
}, [store, width]);
|
|
288
|
+
const onWidthChangeRef = useRef(onWidthChange);
|
|
289
|
+
onWidthChangeRef.current = onWidthChange;
|
|
290
|
+
useEffect(
|
|
291
|
+
() => store.events.on("node.placementChanged", () => {
|
|
292
|
+
const next = store.getPlacement(SIDEBAR_ID)?.size?.w;
|
|
293
|
+
if (typeof next !== "number" || next === widthRef.current) return;
|
|
294
|
+
widthRef.current = next;
|
|
295
|
+
onWidthChangeRef.current?.(next);
|
|
296
|
+
}),
|
|
297
|
+
[store]
|
|
298
|
+
);
|
|
299
|
+
const chrome = useMemo(
|
|
300
|
+
() => ({
|
|
301
|
+
sidebar: () => /* @__PURE__ */ jsx("div", { className: sidebarClassName, children: sidebar }),
|
|
302
|
+
content: () => /* @__PURE__ */ jsx("div", { className: contentClassName, children })
|
|
303
|
+
}),
|
|
304
|
+
[sidebar, children, sidebarClassName, contentClassName]
|
|
305
|
+
);
|
|
306
|
+
return /* @__PURE__ */ jsx(Provider, { store, children: /* @__PURE__ */ jsx(StrategyRegistryProvider, { strategies: STRATEGIES, children: /* @__PURE__ */ jsx(
|
|
307
|
+
Container,
|
|
308
|
+
{
|
|
309
|
+
parentId: ZONE_ID,
|
|
310
|
+
chrome,
|
|
311
|
+
className: className ? `lk-split windease-zone ${className}` : "lk-split windease-zone",
|
|
312
|
+
affordances: true,
|
|
313
|
+
viewport
|
|
314
|
+
}
|
|
315
|
+
) }) });
|
|
316
|
+
}
|
|
231
317
|
|
|
232
|
-
export { FloatingPanel, JobProgress, Legend, Sidebar };
|
|
233
|
-
//# sourceMappingURL=chunk-
|
|
234
|
-
//# sourceMappingURL=chunk-
|
|
318
|
+
export { FloatingPanel, JobProgress, Legend, Sidebar, Split };
|
|
319
|
+
//# sourceMappingURL=chunk-EKIICY6X.js.map
|
|
320
|
+
//# sourceMappingURL=chunk-EKIICY6X.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/primitives/FloatingPanel.tsx","../src/primitives/JobProgress.tsx","../src/primitives/Legend.tsx","../src/primitives/Sidebar.tsx","../src/primitives/Split.tsx"],"names":["jsx","jsxs","useRef","store","useEffect","useMemo"],"mappings":"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","file":"chunk-EKIICY6X.js","sourcesContent":["import { openPointerSession, type PointerSession } from '@weasel-js/core';\nimport type React from 'react';\nimport { type CSSProperties, type ReactNode, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n type Corner,\n DEFAULT_ANCHOR,\n DEFAULT_INSET,\n FLOATING_DRAG_PREFIX,\n type FloatingPlacement,\n floatingStrategy,\n} from 'windease';\nimport { usePersistedState } from '../state/usePersistedState';\n\n/** The strategy holds a map keyed by item id; a panel is always a lone item. */\nconst ITEM_ID = 'panel';\n\n/** Props for `<FloatingPanel>`. */\nexport interface FloatingPanelProps {\n children: ReactNode;\n /** Corner it rests in before it has ever been dragged. Default `'bottom-left'`. */\n anchor?: Corner;\n /** Corners that may capture it. Default: all four. */\n snapCorners?: readonly Corner[];\n /** Pixels in from a corner when snapped. Default 12. */\n inset?: number;\n /** Name to remember its position under, through `usePersistedState` — so it\n * is remembered only inside a lab or a `<Persistence>`. Omit to forget on\n * reload. */\n persist?: string;\n className?: string;\n}\n\n/**\n * The panel's footprint is its border box — `contentRect` excludes padding and\n * border, which would place a panel that then overflows its corner by exactly\n * that much.\n */\nfunction borderBoxOf(entry: ResizeObserverEntry): { w: number; h: number } {\n const box = entry.borderBoxSize?.[0];\n if (box) return { w: box.inlineSize, h: box.blockSize };\n return { w: entry.contentRect.width, h: entry.contentRect.height };\n}\n\n/** How far the pointer must travel before a press becomes a drag. */\nconst DRAG_THRESHOLD = 3;\n\n/** A pointerdown on one of these is the child's, not a drag. */\nfunction isInteractive(target: EventTarget | null): boolean {\n if (!(target instanceof Element)) return false;\n return target.closest('input, button, a, select, textarea, [data-no-drag]') !== null;\n}\n\n/**\n * A draggable box that floats over its offset parent and snaps to its corners.\n *\n * Drives windease's `floatingStrategy` as a pure function rather than mounting a\n * windease container: a lab overlay has one item and no zone tree, so the node\n * model would be all cost. Pointer handling is therefore this component's, and\n * windease's own affordance rendering never participates.\n */\nexport function FloatingPanel({\n children,\n anchor = DEFAULT_ANCHOR,\n snapCorners,\n inset = DEFAULT_INSET,\n persist,\n className,\n}: FloatingPanelProps) {\n const ref = useRef<HTMLDivElement>(null);\n const strategy = useMemo(() => floatingStrategy(), []);\n const options = useMemo(() => ({ defaultAnchor: anchor, inset }), [anchor, inset]);\n\n const [container, setContainer] = useState({ w: 0, h: 0 });\n const [size, setSize] = useState({ w: 0, h: 0 });\n const [place, setPlace] = usePersistedState<FloatingPlacement>(persist, () => ({\n x: 0,\n y: 0,\n anchor,\n }));\n\n const item = useMemo(\n () => ({\n id: ITEM_ID,\n meta: { floating: true, ...(snapCorners ? { snapCorners: [...snapCorners] } : {}) },\n natural: size,\n }),\n [size, snapCorners],\n );\n\n useEffect(() => {\n const el = ref.current;\n const parent = el?.offsetParent ?? el?.parentElement;\n if (!el || !parent) return;\n // Sizes come off the entry, not the DOM: jsdom reports 0 for every\n // clientWidth/offsetWidth, and an unmeasured item is withheld from layout.\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n if (entry.target === parent) {\n setContainer({ w: entry.contentRect.width, h: entry.contentRect.height });\n } else {\n setSize(borderBoxOf(entry));\n }\n }\n });\n observer.observe(parent);\n observer.observe(el);\n return () => observer.disconnect();\n }, []);\n\n const rect = strategy\n .layout({\n items: [item],\n container,\n state: { at: { [ITEM_ID]: place }, inner: undefined },\n options,\n })\n .placements.get(ITEM_ID);\n\n const dragging = useRef<{ x: number; y: number } | null>(null);\n const session = useRef<PointerSession | null>(null);\n const [isDragging, setDragging] = useState(false);\n // A drag outlives the render that started it, so the reducer's inputs are\n // read here and not from that render's closure — a container resized\n // mid-drag would otherwise place the panel against its old box.\n const layout = useRef({ container, options, item });\n layout.current = { container, options, item };\n\n useEffect(() => () => session.current?.cancel(), []);\n\n const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {\n if (isInteractive(e.target)) return;\n // The canvas stack underneath owns pan/zoom on the same pointer events.\n e.stopPropagation();\n const origin = e.currentTarget;\n const pointerId = e.pointerId;\n const start = { x: e.clientX, y: e.clientY };\n\n const end = () => {\n session.current = null;\n if (!dragging.current) return;\n dragging.current = null;\n setDragging(false);\n try {\n origin.releasePointerCapture?.(pointerId);\n } catch {\n // Capture is already gone whenever the session ended by losing it.\n }\n };\n\n // Armed, not capturing. Capture retargets mouseup to this element, and the\n // browser then synthesizes no `click` on the child under the cursor — so\n // capturing on press makes every non-native control in the panel dead to a\n // real mouse, silently. `isInteractive` only exempts the names it lists; a\n // `role=\"button\"` span, a react-aria control or a canvas is not among them.\n session.current = openPointerSession(\n origin,\n e,\n {\n onMove: (ev) => {\n if (!dragging.current) {\n if (Math.hypot(ev.clientX - start.x, ev.clientY - start.y) < DRAG_THRESHOLD) return;\n dragging.current = { ...start };\n setDragging(true);\n origin.setPointerCapture?.(pointerId);\n }\n const from = dragging.current;\n const dx = ev.clientX - from.x;\n const dy = ev.clientY - from.y;\n if (dx === 0 && dy === 0) return;\n dragging.current = { x: ev.clientX, y: ev.clientY };\n const { container: box, options: opts, item: only } = layout.current;\n setPlace(\n (prev) =>\n strategy.reduce?.(\n { at: { [ITEM_ID]: prev }, inner: undefined },\n {\n affordanceId: `${FLOATING_DRAG_PREFIX}${ITEM_ID}`,\n kind: 'drag',\n payload: { dx, dy },\n },\n { container: box, options: opts, items: [only] },\n ).at[ITEM_ID] ?? prev,\n );\n },\n onEnd: end,\n onCancel: end,\n },\n { capture: false },\n );\n };\n\n return (\n <div\n className={className ? `lk-floating-panel ${className}` : 'lk-floating-panel'}\n data-dragging={isDragging ? 'true' : undefined}\n data-placed={rect ? 'true' : undefined}\n onPointerDown={onPointerDown}\n ref={ref}\n style={\n {\n position: 'absolute',\n left: `${rect?.x ?? 0}px`,\n top: `${rect?.y ?? 0}px`,\n } as CSSProperties\n }\n >\n {children}\n </div>\n );\n}\n","import { ErrorIcon, WarningIcon } from '@weasel-js/ui';\nimport type { JobHandle } from '../job/types';\n\n/** Props for `<JobProgress>`. */\nexport interface JobProgressProps {\n job: JobHandle;\n className?: string;\n}\n\n/** A running job's progress, failures and outcome. Determinate once the job\n * reports a total; a job that cannot count up front never sends one, so the\n * bar stays indeterminate rather than inventing a denominator. */\nexport function JobProgress({ job, className }: JobProgressProps) {\n const { status, done, total, failures, error } = job;\n if (status === 'idle') return null;\n\n const determinate = total !== null && total > 0;\n const fraction = determinate ? Math.min(1, done / total) : 0;\n const cls = ['lk-job', `lk-job--${status}`, className].filter(Boolean).join(' ');\n\n return (\n <div className={cls}>\n <div\n className={`lk-job__track${determinate ? '' : ' lk-job__track--indeterminate'}`}\n role=\"progressbar\"\n aria-label=\"Job progress\"\n aria-valuenow={determinate ? done : undefined}\n aria-valuemin={determinate ? 0 : undefined}\n aria-valuemax={determinate ? total : undefined}\n >\n {/* Scaling a full-width bar keeps the fill off the layout path, so\n progress updates never reflow the status bar. */}\n <div\n className=\"lk-job__fill\"\n style={{ transform: `scaleX(${determinate ? fraction : 1})` }}\n />\n </div>\n\n <span className=\"lk-job__count\">\n {done}\n {determinate ? ` / ${total}` : ''}\n </span>\n\n {failures.length > 0 && (\n <span className=\"lk-job__failures\" title={failures.map((f) => f.error).join('\\n')}>\n <WarningIcon size={14} />\n {failures.length} failed\n </span>\n )}\n\n {error && (\n <span className=\"lk-job__error\" title={error}>\n <ErrorIcon size={14} />\n {error}\n </span>\n )}\n\n {status === 'running' && (\n <button type=\"button\" className=\"lk-job__cancel\" onClick={job.cancel}>\n Cancel\n </button>\n )}\n </div>\n );\n}\n","import type { CSSProperties } from 'react';\n\n/** How a legend entry's swatch is drawn, matching how the thing looks on canvas. */\nexport type LegendMark = 'line' | 'dash' | 'dot' | 'band';\n\n/** One row of a legend: a swatch and what it means. */\nexport interface LegendEntry {\n key: string;\n label: string;\n color: string;\n /** Defaults to `'line'`. */\n mark?: LegendMark;\n}\n\n/** Props for `<Legend>`. */\nexport interface LegendProps {\n entries: readonly LegendEntry[];\n className?: string;\n}\n\n/** A color key. Presentational only — no handlers, no state, no hover behavior. */\nexport function Legend({ entries, className }: LegendProps) {\n return (\n <ul className={className ? `lk-legend ${className}` : 'lk-legend'}>\n {entries.map((entry) => {\n const mark = entry.mark ?? 'line';\n return (\n <li className=\"lk-legend__row\" key={entry.key}>\n <span\n aria-hidden=\"true\"\n className={`lk-legend__swatch lk-legend__swatch--${mark}`}\n style={{ '--lk-legend-ink': entry.color } as CSSProperties}\n />\n <span className=\"lk-legend__label\">{entry.label}</span>\n </li>\n );\n })}\n </ul>\n );\n}\n","import type { ReactNode } from 'react';\n\n/** Props for `<Sidebar>`. */\nexport interface SidebarProps {\n children: ReactNode;\n title?: string;\n collapsed?: boolean;\n onToggle?: () => void;\n}\n\n/** A collapsible side panel with an optional title. Collapse state is the\n * caller's to hold. */\nexport function Sidebar({ children, title, collapsed = false, onToggle }: SidebarProps) {\n const className = `lk-sidebar${collapsed ? ' lk-sidebar--collapsed' : ''}`;\n return (\n <aside className={className}>\n {(title || onToggle) && (\n <div className=\"lk-sidebar-header\">\n {title && <span className=\"lk-sidebar-title\">{title}</span>}\n {onToggle && (\n <button\n type=\"button\"\n className=\"lk-sidebar-toggle\"\n onClick={onToggle}\n aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}\n >\n {collapsed ? '›' : '‹'}\n </button>\n )}\n </div>\n )}\n <div className=\"lk-sidebar-body\">{children}</div>\n </aside>\n );\n}\n","import { type ReactNode, useEffect, useMemo, useRef } from 'react';\nimport { asNodeId, createNode, Store, stripStrategy } from 'windease';\nimport { type ChromeMap, Container, Provider, StrategyRegistryProvider } from 'windease/react';\n\nconst ZONE_ID = asNodeId('lk-split');\nconst SIDEBAR_ID = asNodeId('lk-split-sidebar');\nconst CONTENT_ID = asNodeId('lk-split-content');\nconst STRATEGIES = { strip: stripStrategy as never };\n\n/** Props for `<Split>`. */\nexport interface SplitProps {\n /** The sidebar pane's contents. */\n sidebar: ReactNode;\n /** The content pane's contents. */\n children: ReactNode;\n /** Names the sidebar pane, and through it the seam. Default `'Sidebar'`. */\n label?: string;\n /** Extra classes on the strip, which always has `lk-split`. */\n className?: string;\n /** The class on the sidebar pane. */\n sidebarClassName?: string;\n /** The class on the content pane. */\n contentClassName?: string;\n /** The sidebar's current extent in pixels. Omit to leave it unpersisted: the\n * seam still works, its result just does not outlive the mount. */\n width?: number;\n /** Where the sidebar starts before anyone drags it. */\n defaultWidth?: number;\n minWidth?: number;\n maxWidth?: number;\n /** How little the content pane may be squeezed to. Reported as the seam's\n * reachable maximum, so the drag stops there rather than clamping silently. */\n contentMinWidth?: number;\n /** Fires through the drag, not on release: the pane is already there. */\n onWidthChange?: (width: number) => void;\n /**\n * Fixed extent. Omit in an app — the strip measures its own box. Supply it\n * where nothing measures, notably jsdom, or both panes lay out at zero.\n */\n viewport?: { w: number; h: number };\n}\n\n/**\n * A sidebar and a content pane as a two-pane strip with a draggable seam.\n *\n * The seam is `stripStrategy`'s own resize affordance, so it arrives as a\n * `role=\"separator\"` carrying the reachable range, operable by pointer and by\n * arrows / Home / End, and clamped against the content pane's floor rather\n * than against the sidebar's alone.\n */\nexport function Split({\n sidebar,\n children,\n label = 'Sidebar',\n className,\n sidebarClassName,\n contentClassName,\n width,\n defaultWidth = 320,\n minWidth = 140,\n maxWidth = 720,\n contentMinWidth = 160,\n onWidthChange,\n viewport,\n}: SplitProps) {\n // The cross-axis half of every size hint below is inert: a horizontal strip\n // stretches its panes to the container's height and never reads `h`.\n const storeRef = useRef<Store | null>(null);\n if (storeRef.current === null) {\n const store = new Store();\n store.registerNode(\n createNode({\n kind: 'zone',\n id: ZONE_ID,\n container: { strategyId: 'strip', config: { axis: 'x', resizable: true } },\n }),\n );\n store.registerNode(\n createNode({\n kind: 'sidebar',\n id: SIDEBAR_ID,\n parentId: ZONE_ID,\n meta: { title: label },\n placement: { size: { w: width ?? defaultWidth } },\n hints: { minSize: { w: minWidth, h: 0 }, maxSize: { w: maxWidth, h: 0 } },\n }),\n );\n store.registerNode(\n createNode({\n kind: 'content',\n id: CONTENT_ID,\n parentId: ZONE_ID,\n meta: { title: 'Content' },\n hints: { minSize: { w: contentMinWidth, h: 0 } },\n }),\n );\n store.showNode(SIDEBAR_ID);\n store.showNode(CONTENT_ID);\n storeRef.current = store;\n }\n const store = storeRef.current;\n\n // What the store last held, so the placement event can tell a drag from the\n // echo of our own write and a controlled `width` does not fight the seam.\n const widthRef = useRef(width ?? defaultWidth);\n\n useEffect(() => {\n store.setHints(SIDEBAR_ID, {\n minSize: { w: minWidth, h: 0 },\n maxSize: { w: maxWidth, h: 0 },\n });\n store.setHints(CONTENT_ID, { minSize: { w: contentMinWidth, h: 0 } });\n }, [store, minWidth, maxWidth, contentMinWidth]);\n\n useEffect(() => {\n store.setMeta(SIDEBAR_ID, { title: label });\n }, [store, label]);\n\n useEffect(() => {\n if (width === undefined || width === widthRef.current) return;\n widthRef.current = width;\n store.patchPlacement(SIDEBAR_ID, { size: { w: width } });\n }, [store, width]);\n\n const onWidthChangeRef = useRef(onWidthChange);\n onWidthChangeRef.current = onWidthChange;\n useEffect(\n () =>\n store.events.on('node.placementChanged', () => {\n const next = (store.getPlacement(SIDEBAR_ID)?.size as { w?: number } | undefined)?.w;\n if (typeof next !== 'number' || next === widthRef.current) return;\n widthRef.current = next;\n onWidthChangeRef.current?.(next);\n }),\n [store],\n );\n\n const chrome = useMemo<ChromeMap>(\n () => ({\n sidebar: () => <div className={sidebarClassName}>{sidebar}</div>,\n content: () => <div className={contentClassName}>{children}</div>,\n }),\n [sidebar, children, sidebarClassName, contentClassName],\n );\n\n return (\n <Provider store={store}>\n <StrategyRegistryProvider strategies={STRATEGIES}>\n <Container\n parentId={ZONE_ID}\n chrome={chrome}\n className={className ? `lk-split windease-zone ${className}` : 'lk-split windease-zone'}\n affordances\n viewport={viewport}\n />\n </StrategyRegistryProvider>\n </Provider>\n );\n}\n"]}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { DEFAULT_FRAME } from './chunk-2AYGEN57.js';
|
|
2
|
+
import { normalizeZoom } from '@weasel-js/core';
|
|
2
3
|
|
|
3
|
-
// src/canvas/camera.ts
|
|
4
|
-
function usableZoom(zoom) {
|
|
5
|
-
return Number.isFinite(zoom) && zoom > 0 ? zoom : 1;
|
|
6
|
-
}
|
|
7
4
|
function zoomAt(view, factor, at, { frame = DEFAULT_FRAME, min, max } = {}) {
|
|
8
5
|
const anchorX = at.x - frame.originPx.x;
|
|
9
6
|
const anchorY = at.y - frame.originPx.y;
|
|
10
|
-
const current =
|
|
11
|
-
const zoom =
|
|
7
|
+
const current = normalizeZoom(view.zoom);
|
|
8
|
+
const zoom = normalizeZoom(
|
|
9
|
+
Math.min(max ?? Number.POSITIVE_INFINITY, Math.max(min ?? 0, current * factor))
|
|
10
|
+
);
|
|
12
11
|
const ratio = zoom / current;
|
|
13
12
|
return {
|
|
14
13
|
zoom,
|
|
@@ -19,15 +18,16 @@ function zoomAt(view, factor, at, { frame = DEFAULT_FRAME, min, max } = {}) {
|
|
|
19
18
|
};
|
|
20
19
|
}
|
|
21
20
|
function centerOn(world, zoom, size, frame = DEFAULT_FRAME) {
|
|
21
|
+
const z = normalizeZoom(zoom);
|
|
22
22
|
return {
|
|
23
|
-
zoom,
|
|
23
|
+
zoom: z,
|
|
24
24
|
pan: {
|
|
25
|
-
x: size.width / 2 - frame.originPx.x - world.x *
|
|
26
|
-
y: size.height / 2 - frame.originPx.y - world.y *
|
|
25
|
+
x: size.width / 2 - frame.originPx.x - world.x * z,
|
|
26
|
+
y: size.height / 2 - frame.originPx.y - world.y * z * frame.yDir
|
|
27
27
|
}
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export { centerOn, zoomAt };
|
|
32
|
-
//# sourceMappingURL=chunk-
|
|
33
|
-
//# sourceMappingURL=chunk-
|
|
32
|
+
//# sourceMappingURL=chunk-H6ZAOWNE.js.map
|
|
33
|
+
//# sourceMappingURL=chunk-H6ZAOWNE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/canvas/camera.ts"],"names":[],"mappings":";;;AAqBO,SAAS,MAAA,CACd,IAAA,EACA,MAAA,EACA,EAAA,EACA,EAAE,KAAA,GAAQ,aAAA,EAAe,GAAA,EAAK,GAAA,EAAI,GAAmB,EAAC,EACvC;AAGf,EAAA,MAAM,OAAA,GAAU,EAAA,CAAG,CAAA,GAAI,KAAA,CAAM,QAAA,CAAS,CAAA;AACtC,EAAA,MAAM,OAAA,GAAU,EAAA,CAAG,CAAA,GAAI,KAAA,CAAM,QAAA,CAAS,CAAA;AACtC,EAAA,MAAM,OAAA,GAAU,aAAA,CAAc,IAAA,CAAK,IAAI,CAAA;AACvC,EAAA,MAAM,IAAA,GAAO,aAAA;AAAA,IACX,IAAA,CAAK,GAAA,CAAI,GAAA,IAAO,MAAA,CAAO,iBAAA,EAAmB,IAAA,CAAK,GAAA,CAAI,GAAA,IAAO,CAAA,EAAG,OAAA,GAAU,MAAM,CAAC;AAAA,GAChF;AACA,EAAA,MAAM,QAAQ,IAAA,GAAO,OAAA;AACrB,EAAA,OAAO;AAAA,IACL,IAAA;AAAA,IACA,GAAA,EAAK;AAAA,MACH,CAAA,EAAG,OAAA,GAAA,CAAW,OAAA,GAAU,IAAA,CAAK,IAAI,CAAA,IAAK,KAAA;AAAA,MACtC,CAAA,EAAG,OAAA,GAAA,CAAW,OAAA,GAAU,IAAA,CAAK,IAAI,CAAA,IAAK;AAAA;AACxC,GACF;AACF;AAIO,SAAS,QAAA,CACd,KAAA,EACA,IAAA,EACA,IAAA,EACA,QAAoB,aAAA,EACL;AACf,EAAA,MAAM,CAAA,GAAI,cAAc,IAAI,CAAA;AAC5B,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,CAAA;AAAA,IACN,GAAA,EAAK;AAAA,MACH,CAAA,EAAG,KAAK,KAAA,GAAQ,CAAA,GAAI,MAAM,QAAA,CAAS,CAAA,GAAI,MAAM,CAAA,GAAI,CAAA;AAAA,MACjD,CAAA,EAAG,IAAA,CAAK,MAAA,GAAS,CAAA,GAAI,KAAA,CAAM,SAAS,CAAA,GAAI,KAAA,CAAM,CAAA,GAAI,CAAA,GAAI,KAAA,CAAM;AAAA;AAC9D,GACF;AACF","file":"chunk-H6ZAOWNE.js","sourcesContent":["import { normalizeZoom } from '@weasel-js/core';\nimport type { Point, ViewTransform } from '../instrument/types';\nimport { DEFAULT_FRAME, type ViewportSize, type WorldFrame } from './worldSpec';\n\n/** Bounds and coordinate system a {@link zoomAt} works in. */\nexport interface ZoomAtOptions {\n /** The instrument's coordinate system, resolved against the viewport.\n * Omitted, `at` is measured from the element's top-left. */\n frame?: WorldFrame;\n min?: number;\n max?: number;\n}\n\n/**\n * Zoom a view by `factor` about a fixed point, so whatever world coordinate\n * `at` was over stays under it.\n *\n * `at` is in the element's own pixels — a cursor position, or the centre of a\n * lens. The clamp is applied before the pan is solved, so a zoom that hits a\n * bound still anchors on the zoom it actually reached.\n */\nexport function zoomAt(\n view: ViewTransform,\n factor: number,\n at: Point,\n { frame = DEFAULT_FRAME, min, max }: ZoomAtOptions = {},\n): ViewTransform {\n // `pan` is measured from the frame's origin, so anchoring at the raw point\n // drifts by `(1 - ratio) * originPx` per step on any frame that moves it.\n const anchorX = at.x - frame.originPx.x;\n const anchorY = at.y - frame.originPx.y;\n const current = normalizeZoom(view.zoom);\n const zoom = normalizeZoom(\n Math.min(max ?? Number.POSITIVE_INFINITY, Math.max(min ?? 0, current * factor)),\n );\n const ratio = zoom / current;\n return {\n zoom,\n pan: {\n x: anchorX - (anchorX - view.pan.x) * ratio,\n y: anchorY - (anchorY - view.pan.y) * ratio,\n },\n };\n}\n\n/** The view that puts a world point at the centre of a `size` viewport, at\n * `zoom`. What a lens showing a region around one point renders through. */\nexport function centerOn(\n world: Point,\n zoom: number,\n size: ViewportSize,\n frame: WorldFrame = DEFAULT_FRAME,\n): ViewTransform {\n const z = normalizeZoom(zoom);\n return {\n zoom: z,\n pan: {\n x: size.width / 2 - frame.originPx.x - world.x * z,\n y: size.height / 2 - frame.originPx.y - world.y * z * frame.yDir,\n },\n };\n}\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { y2, f2 } from './chunk-W3ECWC2K.js';
|
|
2
1
|
import { CanvasStackContext } from './chunk-CQLQPQ4P.js';
|
|
2
|
+
import { y2, g2 } from './chunk-S2SHZRD7.js';
|
|
3
3
|
import { useRef, useCallback, useEffect, useState, useContext } from 'react';
|
|
4
4
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
5
|
-
import { useVisibleRaf } from '@weasel-js/core';
|
|
5
|
+
import { useVisibleRaf, normalizeZoom } from '@weasel-js/core';
|
|
6
6
|
|
|
7
7
|
function nextIndex(current, key, count, orientation = "horizontal") {
|
|
8
8
|
if (count === 0) return null;
|
|
@@ -209,10 +209,12 @@ function ZoomControl({
|
|
|
209
209
|
zoom,
|
|
210
210
|
onZoomChange,
|
|
211
211
|
onZoomCommit,
|
|
212
|
-
min = 0.1,
|
|
213
|
-
max = 8,
|
|
212
|
+
min: minProp = 0.1,
|
|
213
|
+
max: maxProp = 8,
|
|
214
214
|
className
|
|
215
215
|
}) {
|
|
216
|
+
const min = normalizeZoom(minProp);
|
|
217
|
+
const max = normalizeZoom(maxProp);
|
|
216
218
|
const commit = onZoomCommit ?? onZoomChange;
|
|
217
219
|
const emit = (track, done) => {
|
|
218
220
|
const next = clamp(fromTrack(track), min, max);
|
|
@@ -235,7 +237,7 @@ function ZoomControl({
|
|
|
235
237
|
}
|
|
236
238
|
),
|
|
237
239
|
/* @__PURE__ */ jsx(
|
|
238
|
-
|
|
240
|
+
g2,
|
|
239
241
|
{
|
|
240
242
|
className: "lk-zoom__field",
|
|
241
243
|
width: "fit",
|
|
@@ -264,5 +266,5 @@ function Section({ children, end }) {
|
|
|
264
266
|
StatusBar.Section = Section;
|
|
265
267
|
|
|
266
268
|
export { FpsMeter, ScaleIndicator, StatusBar, Toolbar, ZoomControl, useRovingTabIndex };
|
|
267
|
-
//# sourceMappingURL=chunk-
|
|
268
|
-
//# sourceMappingURL=chunk-
|
|
269
|
+
//# sourceMappingURL=chunk-KJALCDWE.js.map
|
|
270
|
+
//# sourceMappingURL=chunk-KJALCDWE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/primitives/useRovingTabIndex.ts","../src/primitives/Toolbar.tsx","../src/primitives/fpsAverage.ts","../src/primitives/FpsMeter.tsx","../src/primitives/ScaleIndicator.tsx","../src/primitives/ZoomControl.tsx","../src/primitives/StatusBar.tsx"],"names":["useRef","useEffect","jsx","jsxs","y","g"],"mappings":"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","file":"chunk-KJALCDWE.js","sourcesContent":["import { type KeyboardEvent, useCallback, useEffect, useRef } from 'react';\n\n/** Which axis a toolbar's arrow keys walk. Matches `aria-orientation`. */\nexport type RovingOrientation = 'horizontal' | 'vertical';\n\n/** Where a key takes focus within `count` items, or null if it does not move it.\n *\n * Only the arrows on the toolbar's own axis move focus — APG leaves the\n * cross-axis arrows to the page, so a vertical strip does not swallow\n * ArrowLeft. `Home` / `End` work on both. */\nexport function nextIndex(\n current: number,\n key: string,\n count: number,\n orientation: RovingOrientation = 'horizontal',\n): number | null {\n if (count === 0) return null;\n const forward = orientation === 'vertical' ? 'ArrowDown' : 'ArrowRight';\n const back = orientation === 'vertical' ? 'ArrowUp' : 'ArrowLeft';\n if (key === forward) return (current + 1) % count;\n if (key === back) return (current - 1 + count) % count;\n if (key === 'Home') return 0;\n if (key === 'End') return count - 1;\n return null;\n}\n\n/**\n * The APG roving-tabindex contract: exactly one item in the tab order, arrows\n * moving focus within. `tabIndex` is written onto the DOM nodes rather than\n * threaded through props, because the items are arbitrary children — the\n * container never sees them as a list it could index.\n */\nexport function useRovingTabIndex<T extends HTMLElement>(\n orientation: RovingOrientation = 'horizontal',\n) {\n const ref = useRef<T | null>(null);\n const stop = useRef(0);\n\n const items = useCallback(\n (): HTMLElement[] =>\n ref.current\n ? Array.from(\n ref.current.querySelectorAll<HTMLElement>('button:not([disabled]), [role=\"button\"]'),\n )\n : [],\n [],\n );\n\n const setTabStop = useCallback(\n (index: number) => {\n const found = items();\n const at = Math.min(Math.max(index, 0), Math.max(found.length - 1, 0));\n stop.current = at;\n for (const [i, el] of found.entries()) el.tabIndex = i === at ? 0 : -1;\n },\n [items],\n );\n\n // Re-establish the tab stop whenever the item set changes — a trial's toolbar\n // contributions are dynamic, and `disabled` moves a button in and out of the\n // set without touching the child list. The focused item keeps the stop.\n useEffect(() => {\n const resync = () => {\n const focused = items().indexOf(document.activeElement as HTMLElement);\n setTabStop(focused === -1 ? stop.current : focused);\n };\n resync();\n if (!ref.current) return;\n const mo = new MutationObserver(resync);\n mo.observe(ref.current, { childList: true, subtree: true, attributeFilter: ['disabled'] });\n return () => mo.disconnect();\n }, [items, setTabStop]);\n\n const onKeyDown = useCallback(\n (e: KeyboardEvent<T>) => {\n const found = items();\n const current = found.indexOf(document.activeElement as HTMLElement);\n const next = nextIndex(current === -1 ? 0 : current, e.key, found.length, orientation);\n if (next === null) return;\n e.preventDefault();\n found[next]?.focus();\n setTabStop(next);\n },\n [items, setTabStop, orientation],\n );\n\n return { ref, onKeyDown };\n}\n","import type { MouseEventHandler, ReactNode } from 'react';\nimport { useRovingTabIndex } from './useRovingTabIndex';\n\n/** Props for `<Toolbar>`. */\nexport interface ToolbarProps {\n children: ReactNode;\n /** Names the toolbar for assistive tech. Required by the APG pattern when a\n * view holds more than one. */\n 'aria-label'?: string;\n}\n\n/** A horizontal bar of controls. Fill it with `<Toolbar.Title>`,\n * `<Toolbar.Group>`, `<Toolbar.Button>` and `<Toolbar.Spacer>`. */\nexport function Toolbar({ children, 'aria-label': ariaLabel }: ToolbarProps) {\n const { ref, onKeyDown } = useRovingTabIndex<HTMLDivElement>();\n return (\n <div\n ref={ref}\n className=\"lk-toolbar\"\n role=\"toolbar\"\n aria-label={ariaLabel}\n onKeyDown={onKeyDown}\n >\n {children}\n </div>\n );\n}\n\ninterface TitleProps {\n children: ReactNode;\n}\nfunction Title({ children }: TitleProps) {\n return <span className=\"lk-toolbar-title\">{children}</span>;\n}\n\n/** Props for `<Toolbar.Group>`. */\nexport interface ToolbarGroupProps {\n children: ReactNode;\n /** Pushes this group, and everything after it, to the far end. */\n end?: boolean;\n 'aria-label'?: string;\n}\n\n/** Related controls, kept tight and ruled off from their neighbors. Group\n * rather than separate with repeated `<Toolbar.Spacer>`: consecutive spacers\n * share the free space, so every gap collapses to the same slack and the bar\n * reads as one undifferentiated run. */\nfunction Group({ children, end, 'aria-label': ariaLabel }: ToolbarGroupProps) {\n return (\n // biome-ignore lint/a11y/useSemanticElements: a group inside role=\"toolbar\" is the APG pattern; the rule is local and cannot see the parent. <fieldset> means form controls, needs a <legend> to be named, and its UA min-width breaks flex children.\n <div\n className={`lk-toolbar-group${end ? ' lk-toolbar-group--end' : ''}`}\n role=\"group\"\n aria-label={ariaLabel}\n >\n {children}\n </div>\n );\n}\n\n/** Props for `<Toolbar.Button>`. */\nexport interface ToolbarButtonProps {\n children: ReactNode;\n onClick: MouseEventHandler<HTMLButtonElement>;\n disabled?: boolean;\n title?: string;\n /** Required when `children` is an icon with no text of its own. */\n 'aria-label'?: string;\n /** `danger` reddens on hover — for actions that discard work. */\n variant?: 'default' | 'danger';\n /** Square, for a button whose whole content is one glyph. */\n iconOnly?: boolean;\n /** Makes this a toggle: `aria-pressed`, and held down while true. */\n pressed?: boolean;\n}\n\nfunction Button({\n children,\n onClick,\n disabled,\n title,\n 'aria-label': ariaLabel,\n variant = 'default',\n iconOnly,\n pressed,\n}: ToolbarButtonProps) {\n const cls = [\n 'lk-toolbar-button',\n variant === 'danger' && 'lk-toolbar-button--danger',\n iconOnly && 'lk-toolbar-button--icon',\n pressed && 'lk-toolbar-button--pressed',\n ]\n .filter(Boolean)\n .join(' ');\n return (\n <button\n type=\"button\"\n className={cls}\n onClick={onClick}\n disabled={disabled}\n title={title}\n aria-label={ariaLabel ?? title}\n aria-pressed={pressed}\n >\n {children}\n </button>\n );\n}\n\nfunction Spacer() {\n return <span className=\"lk-toolbar-spacer\" aria-hidden=\"true\" />;\n}\n\n/** A label within a toolbar. */\nToolbar.Title = Title;\n/** A ruled-off run of related controls. */\nToolbar.Group = Group;\n/** A button within a toolbar. */\nToolbar.Button = Button;\n/** Flexible space that pushes what follows to the far end of the toolbar. */\nToolbar.Spacer = Spacer;\n","export function rollingAverage(samples: readonly number[]): number {\n if (samples.length === 0) return 0;\n const sum = samples.reduce((acc, n) => acc + n, 0);\n return Math.round(sum / samples.length);\n}\n","import { useVisibleRaf } from '@weasel-js/core';\nimport { useEffect, useRef, useState } from 'react';\nimport { rollingAverage } from './fpsAverage';\n\nconst SAMPLE_WINDOW = 30;\n\n/** A live frame-rate readout, averaged over the last 30 frames. */\nexport function FpsMeter() {\n const [fps, setFps] = useState(0);\n const samplesRef = useRef<number[]>([]);\n const lastTimeRef = useRef<number | null>(null);\n const rootRef = useRef<HTMLDivElement | null>(null);\n\n const loop = useVisibleRaf(\n (time) => {\n const last = lastTimeRef.current;\n if (last !== null) {\n const delta = time - last;\n if (delta > 0) {\n const samples = samplesRef.current;\n samples.push(1000 / delta);\n if (samples.length > SAMPLE_WINDOW) samples.shift();\n setFps(rollingAverage(samples));\n }\n }\n lastTimeRef.current = time;\n loop.request();\n },\n {\n target: rootRef,\n // The gap spent suspended is not a frame that took an hour to draw.\n onResume: () => {\n lastTimeRef.current = null;\n },\n },\n );\n\n useEffect(() => {\n loop.request();\n return () => loop.cancel();\n }, [loop]);\n\n return (\n <div className=\"lk-fps-meter\" ref={rootRef}>\n <span className=\"lk-fps-meter-value\">FPS {fps}</span>\n </div>\n );\n}\n","import { useContext } from 'react';\nimport { CanvasStackContext } from '../canvas/CanvasStackContext';\n\n/** Props for `<ScaleIndicator>`. */\nexport interface ScaleIndicatorProps {\n /** Current view zoom factor. If omitted, reads from CanvasStackContext. Defaults to 1. */\n zoom?: number;\n /** World pixels per unit at zoom=1. Default: 1. */\n pixelsPerUnit?: number;\n /** Unit label. Default: 'px'. */\n unit?: string;\n /** Target bar width in display pixels. Default: 100. */\n targetWidth?: number;\n}\n\n// Rounds n to a \"nice\" number — 1, 2, 5, 10, 20, 50, ...\nfunction niceNumber(n: number): number {\n if (n <= 0) return 1;\n const exp = Math.floor(Math.log10(n));\n const fraction = n / 10 ** exp;\n let nice: number;\n if (fraction < 1.5) nice = 1;\n else if (fraction < 3.5) nice = 2;\n else if (fraction < 7.5) nice = 5;\n else nice = 10;\n return nice * 10 ** exp;\n}\n\n/** A scale bar: a labeled rule showing how far a round number of world units\n * is at the current zoom. Reads the zoom from the surrounding canvas stack\n * unless one is passed. */\nexport function ScaleIndicator({\n zoom,\n pixelsPerUnit = 1,\n unit = 'px',\n targetWidth = 100,\n}: ScaleIndicatorProps) {\n const ctxValue = useContext(CanvasStackContext);\n const effectiveZoom = zoom ?? ctxValue?.view.zoom ?? 1;\n const effectivePxPerUnit = pixelsPerUnit * effectiveZoom;\n const targetUnits = targetWidth / effectivePxPerUnit;\n const niceUnits = niceNumber(targetUnits);\n const barWidth = niceUnits * effectivePxPerUnit;\n return (\n <div className=\"lk-scale-indicator\" role=\"img\" aria-label={`Scale: ${niceUnits} ${unit}`}>\n <div className=\"lk-scale-indicator-bar\" style={{ width: `${barWidth}px` }} />\n <span className=\"lk-scale-indicator-label\">\n {niceUnits} {unit}\n </span>\n </div>\n );\n}\n","import { normalizeZoom } from '@weasel-js/core';\nimport { NumberField, Slider } from '../passthrough/weasel-ui';\n\n/** Props for `<ZoomControl>`. */\nexport interface ZoomControlProps {\n /** Current zoom as a scale factor: 1 is actual size. */\n zoom: number;\n /** Fired continuously while the slider drags. */\n onZoomChange: (zoom: number) => void;\n /** Fired when a drag settles or the field is committed. Defaults to\n * `onZoomChange`. */\n onZoomCommit?: (zoom: number) => void;\n /** Smallest selectable zoom. Defaults to 0.1 (10%). */\n min?: number;\n /** Largest selectable zoom. Defaults to 8 (800%). */\n max?: number;\n className?: string;\n}\n\n// Zoom is perceptually geometric — halving is as big a step as doubling — so\n// the slider runs on log2 and 100% lands mid-track for a symmetric range.\nconst toTrack = (zoom: number): number => Math.log2(zoom);\nconst fromTrack = (v: number): number => 2 ** v;\n\nconst clamp = (v: number, lo: number, hi: number): number => Math.max(lo, Math.min(hi, v));\n\n/** Zoom as a log-scaled slider with an editable percentage beside it. Pair it\n * with zoom-in / zoom-out buttons in a `<Toolbar.Group>`; this component owns\n * only the continuous part. */\nexport function ZoomControl({\n zoom,\n onZoomChange,\n onZoomCommit,\n min: minProp = 0.1,\n max: maxProp = 8,\n className,\n}: ZoomControlProps) {\n const min = normalizeZoom(minProp);\n const max = normalizeZoom(maxProp);\n const commit = onZoomCommit ?? onZoomChange;\n const emit = (track: number, done: boolean): void => {\n const next = clamp(fromTrack(track), min, max);\n (done ? commit : onZoomChange)(next);\n };\n\n return (\n <div className={`lk-zoom${className ? ` ${className}` : ''}`}>\n <Slider\n className=\"lk-zoom__slider\"\n ariaLabel=\"Zoom\"\n density=\"slim\"\n min={toTrack(min)}\n max={toTrack(max)}\n // Detents at the octaves plus actual size, so 25/50/100/200/400% are\n // reachable by drag rather than only by typing.\n stops={[-2, -1, 0, 1, 2]}\n thumbs={[{ value: clamp(toTrack(zoom), toTrack(min), toTrack(max)) }]}\n onInput={(next) => emit(next[0].value, false)}\n onChange={(next) => emit(next[0].value, true)}\n readoutPlacement=\"none\"\n />\n {/* `style: 'percent'` formats a fraction, so the field carries the scale\n factor itself and renders 0.5 as \"50%\". */}\n <NumberField\n className=\"lk-zoom__field\"\n width=\"fit\"\n aria-label=\"Zoom\"\n value={zoom}\n minValue={min}\n maxValue={max}\n step={0.01}\n hideSteppers\n ghost\n formatOptions={{ style: 'percent', maximumFractionDigits: 0 }}\n onChange={(next) => {\n if (!Number.isFinite(next)) return;\n commit(clamp(next, min, max));\n }}\n />\n </div>\n );\n}\n","import type { ReactNode } from 'react';\n\n/** Props for `<StatusBar>`. */\nexport interface StatusBarProps {\n children: ReactNode;\n}\n\n/** A footer strip for readouts. Fill it with `<StatusBar.Section>`. */\nexport function StatusBar({ children }: StatusBarProps) {\n return <div className=\"lk-status-bar\">{children}</div>;\n}\n\n/** Props for `<StatusBar.Section>`. */\nexport interface StatusBarSectionProps {\n children: ReactNode;\n /** Pushes this section, and everything after it, to the far end. */\n end?: boolean;\n}\nfunction Section({ children, end }: StatusBarSectionProps) {\n return (\n <span className={`lk-status-bar-section${end ? ' lk-status-bar-section--end' : ''}`}>\n {children}\n </span>\n );\n}\n\n/** One readout within a status bar. */\nStatusBar.Section = Section;\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r5, n4 } from './chunk-
|
|
1
|
+
import { r5, n4 } from './chunk-S2SHZRD7.js';
|
|
2
2
|
import { openPointerSession } from '@weasel-js/core';
|
|
3
3
|
import { useState, useRef, useId, useEffect, Fragment as Fragment$1 } from 'react';
|
|
4
4
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
@@ -166,5 +166,5 @@ function LayerList({
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
export { LayerList };
|
|
169
|
-
//# sourceMappingURL=chunk-
|
|
170
|
-
//# sourceMappingURL=chunk-
|
|
169
|
+
//# sourceMappingURL=chunk-MUKOW3TC.js.map
|
|
170
|
+
//# sourceMappingURL=chunk-MUKOW3TC.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/layers/LayerList.tsx"],"names":["n","Fragment","r"],"mappings":"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","file":"chunk-BKFVHKJH.js","sourcesContent":["import { openPointerSession, type PointerSession } from '@weasel-js/core';\nimport { Disclosure } from '@weasel-js/ui';\nimport {\n Fragment,\n type ReactNode,\n type PointerEvent as ReactPointerEvent,\n useEffect,\n useId,\n useRef,\n useState,\n} from 'react';\nimport type { LayerDescriptor } from '../instrument/types';\nimport { DragHandleGlyph } from '../passthrough/weasel-ui';\n\n/** A layer that may itself contain layers. A plain `LayerDescriptor` is one\n * of these with no children, so a flat list needs no change. */\nexport interface LayerTreeNode extends LayerDescriptor {\n children?: LayerTreeNode[];\n /** Render this node's children hidden on first paint. Ignored once\n * `collapsedIds` is supplied. */\n defaultCollapsed?: boolean;\n}\n\n/** Props for `<LayerList>`. */\nexport interface LayerListProps {\n layers: LayerTreeNode[];\n visibility: Record<string, boolean>;\n /** The whole tree, with the dragged node moved. Reordering is scoped to a\n * node's siblings: a drag never changes which parent a layer sits under. */\n onReorder: (newOrder: LayerTreeNode[]) => void;\n onToggle: (id: string, visible: boolean) => void;\n /** Ids whose children are hidden. Supplying it makes collapse controlled;\n * without it the list keeps its own, seeded from `defaultCollapsed`. */\n collapsedIds?: readonly string[];\n onCollapsedChange?: (ids: string[]) => void;\n className?: string;\n}\n\ninterface DragState {\n /** Id of the dragged row's parent, or null at the top level. */\n parentId: string | null;\n fromIndex: number;\n toIndex: number;\n startY: number;\n /** Row height plus row gap, measured when the drag starts. */\n pitch: number;\n}\n\n/** Which row the drag currently marks. */\ninterface DragMark {\n parentId: string | null;\n index: number;\n}\n\nfunction findNode(nodes: LayerTreeNode[], id: string): LayerTreeNode | null {\n for (const node of nodes) {\n if (node.id === id) return node;\n const hit = node.children ? findNode(node.children, id) : null;\n if (hit) return hit;\n }\n return null;\n}\n\nfunction siblingsOf(layers: LayerTreeNode[], parentId: string | null): LayerTreeNode[] {\n if (parentId === null) return layers;\n return findNode(layers, parentId)?.children ?? [];\n}\n\nfunction withChildren(\n nodes: LayerTreeNode[],\n parentId: string | null,\n next: LayerTreeNode[],\n): LayerTreeNode[] {\n if (parentId === null) return next;\n return nodes.map((node) => {\n if (node.id === parentId) return { ...node, children: next };\n if (node.children) return { ...node, children: withChildren(node.children, parentId, next) };\n return node;\n });\n}\n\nfunction collectCollapsed(nodes: LayerTreeNode[], into: Set<string>): Set<string> {\n for (const node of nodes) {\n if (node.defaultCollapsed) into.add(node.id);\n if (node.children) collectCollapsed(node.children, into);\n }\n return into;\n}\n\nfunction anyNested(nodes: LayerTreeNode[]): boolean {\n return nodes.some((n) => (n.children?.length ?? 0) > 0);\n}\n\n/** A reorderable list of layers with per-layer visibility toggles. Layers\n * marked `alwaysOn` are pinned and cannot be reordered or hidden. Layers with\n * `children` render as an expandable subtree. */\nexport function LayerList({\n layers,\n visibility,\n onReorder,\n onToggle,\n collapsedIds,\n onCollapsedChange,\n className,\n}: LayerListProps) {\n const [dragMark, setDragMark] = useState<DragMark | null>(null);\n const [ownCollapsed, setOwnCollapsed] = useState<Set<string>>(() =>\n collectCollapsed(layers, new Set()),\n );\n const dragRef = useRef<DragState | null>(null);\n const sessionRef = useRef<PointerSession | null>(null);\n const uid = useId();\n // The session's callbacks outlive the render that opened them, so the commit\n // reads its inputs here rather than from that render's closure.\n const commitRef = useRef({ layers, onReorder });\n commitRef.current = { layers, onReorder };\n\n useEffect(() => () => sessionRef.current?.cancel(), []);\n\n const collapsed = collapsedIds ? new Set(collapsedIds) : ownCollapsed;\n\n const toggleCollapsed = (id: string) => {\n const next = new Set(collapsed);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n if (!collapsedIds) setOwnCollapsed(next);\n onCollapsedChange?.([...next]);\n };\n\n if (layers.length === 0) {\n return (\n <div className={className ? `lk-layer-list ${className}` : 'lk-layer-list'}>\n <div className=\"lk-layer-list__empty\">No layers</div>\n </div>\n );\n }\n\n const clearDrag = () => {\n dragRef.current = null;\n sessionRef.current = null;\n setDragMark(null);\n };\n\n const startDrag = (e: ReactPointerEvent<HTMLElement>, parentId: string | null, index: number) => {\n // Measured at grab time rather than hardcoded: a restyle that changes row\n // height or gap would otherwise silently skew every drag distance.\n const row = e.currentTarget.closest('.lk-layer-list__row');\n const list = row?.parentElement;\n const gap = list ? Number.parseFloat(getComputedStyle(list).rowGap) || 0 : 0;\n const pitch = row ? row.getBoundingClientRect().height + gap : 0;\n const drag: DragState = {\n parentId,\n fromIndex: index,\n toIndex: index,\n startY: e.clientY,\n pitch: pitch > 0 ? pitch : 1,\n };\n dragRef.current = drag;\n setDragMark({ parentId, index });\n sessionRef.current = openPointerSession(e.currentTarget, e, {\n onMove: (ev) => {\n const delta = Math.round((ev.clientY - drag.startY) / drag.pitch);\n const siblings = siblingsOf(commitRef.current.layers, parentId);\n const last = siblings.filter((l) => !l.alwaysOn).length - 1;\n drag.toIndex = Math.min(last, Math.max(0, drag.fromIndex + delta));\n setDragMark({ parentId, index: drag.toIndex });\n },\n onEnd: () => {\n const { layers: tree, onReorder: commit } = commitRef.current;\n const { fromIndex, toIndex } = drag;\n clearDrag();\n if (toIndex === fromIndex) return;\n const siblings = siblingsOf(tree, parentId);\n const rows = siblings.filter((l) => !l.alwaysOn);\n const locked = siblings.filter((l) => l.alwaysOn);\n const next = [...rows];\n const [moved] = next.splice(fromIndex, 1);\n if (moved) next.splice(toIndex, 0, moved);\n commit(withChildren(tree, parentId, [...next, ...locked]));\n },\n // An interrupted drag never named a destination, so it reorders nothing.\n onCancel: clearDrag,\n });\n };\n\n // A twisty column on every row keeps labels aligned across depths, but a\n // flat list would only be paying 20px for it.\n const nested = anyNested(layers);\n\n function renderRow(\n layer: LayerTreeNode,\n parentId: string | null,\n index: number,\n path: string,\n pinned: boolean,\n ): ReactNode {\n const kids = layer.children ?? [];\n const open = kids.length > 0 && !collapsed.has(layer.id);\n const bodyId = `${uid}${path}`;\n const dragging = !pinned && dragMark?.parentId === parentId && dragMark.index === index;\n const cls = ['lk-layer-list__row'];\n if (dragging) cls.push('lk-layer-list__row--dragging');\n if (pinned) cls.push('lk-layer-list__row--pinned');\n return (\n <Fragment key={layer.id}>\n <div className={cls.join(' ')}>\n {nested &&\n (kids.length > 0 ? (\n <Disclosure\n open={open}\n onToggle={() => toggleCollapsed(layer.id)}\n label={`${layer.label} sublayers`}\n controls={bodyId}\n />\n ) : (\n <span className=\"lk-layer-list__twisty-gap\" />\n ))}\n {pinned ? (\n <span className=\"lk-layer-list__lock\" role=\"img\" aria-label=\"Always on\">\n 🔒\n </span>\n ) : (\n <>\n <button\n type=\"button\"\n className=\"lk-layer-list__handle\"\n aria-label={`Reorder ${layer.label}`}\n onPointerDown={(e) => startDrag(e, parentId, index)}\n >\n <DragHandleGlyph size={13} />\n </button>\n <input\n className=\"lk-layer-list__check\"\n type=\"checkbox\"\n checked={visibility[layer.id] !== false}\n onChange={(e) => onToggle(layer.id, e.target.checked)}\n aria-label={`Toggle ${layer.label}`}\n />\n </>\n )}\n <span className=\"lk-layer-list__label\">{layer.label}</span>\n </div>\n {open && (\n <div id={bodyId} className=\"lk-layer-list__children\">\n {renderLevel(kids, layer.id, path)}\n </div>\n )}\n </Fragment>\n );\n }\n\n function renderLevel(nodes: LayerTreeNode[], parentId: string | null, path: string): ReactNode {\n const reorderable = nodes.filter((l) => !l.alwaysOn);\n const pinned = nodes.filter((l) => l.alwaysOn);\n return (\n <>\n {reorderable.map((layer, i) => renderRow(layer, parentId, i, `${path}-${i}`, false))}\n {pinned.map((layer, i) => renderRow(layer, parentId, i, `${path}-p${i}`, true))}\n </>\n );\n }\n\n return (\n <div className={className ? `lk-layer-list ${className}` : 'lk-layer-list'}>\n {renderLevel(layers, null, '')}\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/layers/LayerList.tsx"],"names":["n","Fragment","r"],"mappings":"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","file":"chunk-MUKOW3TC.js","sourcesContent":["import { openPointerSession, type PointerSession } from '@weasel-js/core';\nimport { Disclosure } from '@weasel-js/ui';\nimport {\n Fragment,\n type ReactNode,\n type PointerEvent as ReactPointerEvent,\n useEffect,\n useId,\n useRef,\n useState,\n} from 'react';\nimport type { LayerDescriptor } from '../instrument/types';\nimport { DragHandleGlyph } from '../passthrough/weasel-ui';\n\n/** A layer that may itself contain layers. A plain `LayerDescriptor` is one\n * of these with no children, so a flat list needs no change. */\nexport interface LayerTreeNode extends LayerDescriptor {\n children?: LayerTreeNode[];\n /** Render this node's children hidden on first paint. Ignored once\n * `collapsedIds` is supplied. */\n defaultCollapsed?: boolean;\n}\n\n/** Props for `<LayerList>`. */\nexport interface LayerListProps {\n layers: LayerTreeNode[];\n visibility: Record<string, boolean>;\n /** The whole tree, with the dragged node moved. Reordering is scoped to a\n * node's siblings: a drag never changes which parent a layer sits under. */\n onReorder: (newOrder: LayerTreeNode[]) => void;\n onToggle: (id: string, visible: boolean) => void;\n /** Ids whose children are hidden. Supplying it makes collapse controlled;\n * without it the list keeps its own, seeded from `defaultCollapsed`. */\n collapsedIds?: readonly string[];\n onCollapsedChange?: (ids: string[]) => void;\n className?: string;\n}\n\ninterface DragState {\n /** Id of the dragged row's parent, or null at the top level. */\n parentId: string | null;\n fromIndex: number;\n toIndex: number;\n startY: number;\n /** Row height plus row gap, measured when the drag starts. */\n pitch: number;\n}\n\n/** Which row the drag currently marks. */\ninterface DragMark {\n parentId: string | null;\n index: number;\n}\n\nfunction findNode(nodes: LayerTreeNode[], id: string): LayerTreeNode | null {\n for (const node of nodes) {\n if (node.id === id) return node;\n const hit = node.children ? findNode(node.children, id) : null;\n if (hit) return hit;\n }\n return null;\n}\n\nfunction siblingsOf(layers: LayerTreeNode[], parentId: string | null): LayerTreeNode[] {\n if (parentId === null) return layers;\n return findNode(layers, parentId)?.children ?? [];\n}\n\nfunction withChildren(\n nodes: LayerTreeNode[],\n parentId: string | null,\n next: LayerTreeNode[],\n): LayerTreeNode[] {\n if (parentId === null) return next;\n return nodes.map((node) => {\n if (node.id === parentId) return { ...node, children: next };\n if (node.children) return { ...node, children: withChildren(node.children, parentId, next) };\n return node;\n });\n}\n\nfunction collectCollapsed(nodes: LayerTreeNode[], into: Set<string>): Set<string> {\n for (const node of nodes) {\n if (node.defaultCollapsed) into.add(node.id);\n if (node.children) collectCollapsed(node.children, into);\n }\n return into;\n}\n\nfunction anyNested(nodes: LayerTreeNode[]): boolean {\n return nodes.some((n) => (n.children?.length ?? 0) > 0);\n}\n\n/** A reorderable list of layers with per-layer visibility toggles. Layers\n * marked `alwaysOn` are pinned and cannot be reordered or hidden. Layers with\n * `children` render as an expandable subtree. */\nexport function LayerList({\n layers,\n visibility,\n onReorder,\n onToggle,\n collapsedIds,\n onCollapsedChange,\n className,\n}: LayerListProps) {\n const [dragMark, setDragMark] = useState<DragMark | null>(null);\n const [ownCollapsed, setOwnCollapsed] = useState<Set<string>>(() =>\n collectCollapsed(layers, new Set()),\n );\n const dragRef = useRef<DragState | null>(null);\n const sessionRef = useRef<PointerSession | null>(null);\n const uid = useId();\n // The session's callbacks outlive the render that opened them, so the commit\n // reads its inputs here rather than from that render's closure.\n const commitRef = useRef({ layers, onReorder });\n commitRef.current = { layers, onReorder };\n\n useEffect(() => () => sessionRef.current?.cancel(), []);\n\n const collapsed = collapsedIds ? new Set(collapsedIds) : ownCollapsed;\n\n const toggleCollapsed = (id: string) => {\n const next = new Set(collapsed);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n if (!collapsedIds) setOwnCollapsed(next);\n onCollapsedChange?.([...next]);\n };\n\n if (layers.length === 0) {\n return (\n <div className={className ? `lk-layer-list ${className}` : 'lk-layer-list'}>\n <div className=\"lk-layer-list__empty\">No layers</div>\n </div>\n );\n }\n\n const clearDrag = () => {\n dragRef.current = null;\n sessionRef.current = null;\n setDragMark(null);\n };\n\n const startDrag = (e: ReactPointerEvent<HTMLElement>, parentId: string | null, index: number) => {\n // Measured at grab time rather than hardcoded: a restyle that changes row\n // height or gap would otherwise silently skew every drag distance.\n const row = e.currentTarget.closest('.lk-layer-list__row');\n const list = row?.parentElement;\n const gap = list ? Number.parseFloat(getComputedStyle(list).rowGap) || 0 : 0;\n const pitch = row ? row.getBoundingClientRect().height + gap : 0;\n const drag: DragState = {\n parentId,\n fromIndex: index,\n toIndex: index,\n startY: e.clientY,\n pitch: pitch > 0 ? pitch : 1,\n };\n dragRef.current = drag;\n setDragMark({ parentId, index });\n sessionRef.current = openPointerSession(e.currentTarget, e, {\n onMove: (ev) => {\n const delta = Math.round((ev.clientY - drag.startY) / drag.pitch);\n const siblings = siblingsOf(commitRef.current.layers, parentId);\n const last = siblings.filter((l) => !l.alwaysOn).length - 1;\n drag.toIndex = Math.min(last, Math.max(0, drag.fromIndex + delta));\n setDragMark({ parentId, index: drag.toIndex });\n },\n onEnd: () => {\n const { layers: tree, onReorder: commit } = commitRef.current;\n const { fromIndex, toIndex } = drag;\n clearDrag();\n if (toIndex === fromIndex) return;\n const siblings = siblingsOf(tree, parentId);\n const rows = siblings.filter((l) => !l.alwaysOn);\n const locked = siblings.filter((l) => l.alwaysOn);\n const next = [...rows];\n const [moved] = next.splice(fromIndex, 1);\n if (moved) next.splice(toIndex, 0, moved);\n commit(withChildren(tree, parentId, [...next, ...locked]));\n },\n // An interrupted drag never named a destination, so it reorders nothing.\n onCancel: clearDrag,\n });\n };\n\n // A twisty column on every row keeps labels aligned across depths, but a\n // flat list would only be paying 20px for it.\n const nested = anyNested(layers);\n\n function renderRow(\n layer: LayerTreeNode,\n parentId: string | null,\n index: number,\n path: string,\n pinned: boolean,\n ): ReactNode {\n const kids = layer.children ?? [];\n const open = kids.length > 0 && !collapsed.has(layer.id);\n const bodyId = `${uid}${path}`;\n const dragging = !pinned && dragMark?.parentId === parentId && dragMark.index === index;\n const cls = ['lk-layer-list__row'];\n if (dragging) cls.push('lk-layer-list__row--dragging');\n if (pinned) cls.push('lk-layer-list__row--pinned');\n return (\n <Fragment key={layer.id}>\n <div className={cls.join(' ')}>\n {nested &&\n (kids.length > 0 ? (\n <Disclosure\n open={open}\n onToggle={() => toggleCollapsed(layer.id)}\n label={`${layer.label} sublayers`}\n controls={bodyId}\n />\n ) : (\n <span className=\"lk-layer-list__twisty-gap\" />\n ))}\n {pinned ? (\n <span className=\"lk-layer-list__lock\" role=\"img\" aria-label=\"Always on\">\n 🔒\n </span>\n ) : (\n <>\n <button\n type=\"button\"\n className=\"lk-layer-list__handle\"\n aria-label={`Reorder ${layer.label}`}\n onPointerDown={(e) => startDrag(e, parentId, index)}\n >\n <DragHandleGlyph size={13} />\n </button>\n <input\n className=\"lk-layer-list__check\"\n type=\"checkbox\"\n checked={visibility[layer.id] !== false}\n onChange={(e) => onToggle(layer.id, e.target.checked)}\n aria-label={`Toggle ${layer.label}`}\n />\n </>\n )}\n <span className=\"lk-layer-list__label\">{layer.label}</span>\n </div>\n {open && (\n <div id={bodyId} className=\"lk-layer-list__children\">\n {renderLevel(kids, layer.id, path)}\n </div>\n )}\n </Fragment>\n );\n }\n\n function renderLevel(nodes: LayerTreeNode[], parentId: string | null, path: string): ReactNode {\n const reorderable = nodes.filter((l) => !l.alwaysOn);\n const pinned = nodes.filter((l) => l.alwaysOn);\n return (\n <>\n {reorderable.map((layer, i) => renderRow(layer, parentId, i, `${path}-${i}`, false))}\n {pinned.map((layer, i) => renderRow(layer, parentId, i, `${path}-p${i}`, true))}\n </>\n );\n }\n\n return (\n <div className={className ? `lk-layer-list ${className}` : 'lk-layer-list'}>\n {renderLevel(layers, null, '')}\n </div>\n );\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
// src/lab/LabContext.ts
|
|
4
|
+
var LabContext = createContext(null);
|
|
5
|
+
function useLabContext() {
|
|
6
|
+
const ctx = useContext(LabContext);
|
|
7
|
+
if (ctx === null) {
|
|
8
|
+
throw new Error("useLabContext must be used inside <Lab>");
|
|
9
|
+
}
|
|
10
|
+
return ctx;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { LabContext, useLabContext };
|
|
14
|
+
//# sourceMappingURL=chunk-NDRYLVOW.js.map
|
|
15
|
+
//# sourceMappingURL=chunk-NDRYLVOW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lab/LabContext.ts"],"names":[],"mappings":";;;AAkCO,IAAM,UAAA,GAAa,cAAsC,IAAI;AAG7D,SAAS,aAAA,GAAiC;AAC/C,EAAA,MAAM,GAAA,GAAM,WAAW,UAAU,CAAA;AACjC,EAAA,IAAI,QAAQ,IAAA,EAAM;AAChB,IAAA,MAAM,IAAI,MAAM,yCAAyC,CAAA;AAAA,EAC3D;AACA,EAAA,OAAO,GAAA;AACT","file":"chunk-NDRYLVOW.js","sourcesContent":["import { createContext, useContext } from 'react';\nimport type { ConfigRule, ControlRenderer } from '../config/types';\nimport type { InstrumentList } from '../instrument/types';\nimport type { LabMode, SavedSnapshot, TrialRecord } from '../state/types';\nimport type { AddTrialOptions } from '../trial/trialOps';\n\n/** Lab-wide state and commands: the available instruments, the open\n * trials and the operations over them, saved snapshots, and the color\n * mode. */\nexport interface LabContextValue {\n instruments: InstrumentList;\n trials: TrialRecord[];\n /** Open a trial running `instrumentName`. `options.config` is written over\n * that instrument's defaults, which is where a trial opened on a subject\n * says which subject. */\n addTrial: (instrumentName: string, options?: AddTrialOptions) => void;\n cloneTrial: (id: string) => void;\n closeTrial: (id: string) => void;\n resetTrial: (id: string) => void;\n reorderTrials: (ids: readonly string[]) => void;\n savedSnapshots: SavedSnapshot[];\n saveSnapshot: (trialId: string, name?: string) => void;\n loadSnapshot: (trialId: string, snapshotId: string) => void;\n deleteSnapshot: (snapshotId: string) => void;\n mode: LabMode;\n setMode: (m: LabMode) => void;\n /** Lab-wide rules run over every instrument's config leaves, before\n * labkit's own inference. */\n configRules?: readonly ConfigRule[];\n /** Lab-wide control overrides, keyed by config path or by leaf kind. */\n controls?: Record<string, ControlRenderer>;\n}\n\n/** Context carrying the surrounding lab. Prefer `useLabContext`. */\nexport const LabContext = createContext<LabContextValue | null>(null);\n\n/** The surrounding lab. Throws outside a `<Lab>`. */\nexport function useLabContext(): LabContextValue {\n const ctx = useContext(LabContext);\n if (ctx === null) {\n throw new Error('useLabContext must be used inside <Lab>');\n }\n return ctx;\n}\n"]}
|