@weasel-js/labkit 1.4.4 → 1.5.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/README.md +54 -6
- package/dist/_dts/{CanvasStackContext-kTEZvEgE.d.ts → CanvasStackContext-CJNMS6mz.d.ts} +1 -1
- package/dist/_dts/{PrefsForm.d-DDHFANkc.d.ts → PrefsForm.d-DPy9I8It.d.ts} +4 -0
- package/dist/_dts/Select.d-Gwr8fLpK.d.ts +453 -0
- package/dist/_dts/{frac-z7ker2Vx.d.ts → frac-B4okC2lX.d.ts} +291 -31
- package/dist/_dts/{index-Blv9uQzu.d.ts → index-BNdz-tNd.d.ts} +105 -17
- package/dist/_dts/index-CSJoj4LC.d.ts +248 -0
- package/dist/_dts/{types-B9_zrHmb.d.ts → types-DzoNY1pO.d.ts} +91 -4
- package/dist/_dts/usePanZoom-Pzf9f2jh.d.ts +160 -0
- package/dist/_dts/useTrialState-uLlmaccn.d.ts +212 -0
- package/dist/canvas/index.d.ts +18 -129
- package/dist/canvas/index.js +4 -2
- package/dist/chrome/index.d.ts +5 -93
- package/dist/chrome/index.js +10 -6
- package/dist/chunk-7PFH3CNM.js +150 -0
- package/dist/chunk-7PFH3CNM.js.map +1 -0
- package/dist/{chunk-I6JCVE24.js → chunk-CEIUBPPW.js} +143 -17
- package/dist/chunk-CEIUBPPW.js.map +1 -0
- package/dist/chunk-EH7UEQV7.js +590 -0
- package/dist/chunk-EH7UEQV7.js.map +1 -0
- package/dist/{chunk-MOM3GOVY.js → chunk-EHGVROVV.js} +125 -27
- package/dist/chunk-EHGVROVV.js.map +1 -0
- package/dist/{chunk-TJ7QY3OC.js → chunk-ERFMC6WU.js} +160 -78
- package/dist/chunk-ERFMC6WU.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-64ZCN3DA.js → chunk-IHUVQ3TX.js} +250 -160
- package/dist/chunk-IHUVQ3TX.js.map +1 -0
- package/dist/{chunk-FQMJUVHJ.js → chunk-IJJUK4FU.js} +53 -46
- package/dist/chunk-IJJUK4FU.js.map +1 -0
- package/dist/chunk-IXTQI7JX.js +737 -0
- package/dist/chunk-IXTQI7JX.js.map +1 -0
- package/dist/{chunk-BKFVHKJH.js → chunk-MDVTK5U3.js} +4 -4
- package/dist/{chunk-BKFVHKJH.js.map → chunk-MDVTK5U3.js.map} +1 -1
- package/dist/chunk-PHK4W562.js +72 -0
- package/dist/chunk-PHK4W562.js.map +1 -0
- package/dist/chunk-PJ7WEQBJ.js +18 -0
- package/dist/chunk-PJ7WEQBJ.js.map +1 -0
- package/dist/chunk-RWNDDKOH.js +682 -0
- package/dist/chunk-RWNDDKOH.js.map +1 -0
- package/dist/chunk-T3EC2DXD.js +12628 -0
- package/dist/chunk-T3EC2DXD.js.map +1 -0
- package/dist/{chunk-SMHP6XZ4.js → chunk-UN4U4P63.js} +10 -8
- package/dist/chunk-UN4U4P63.js.map +1 -0
- package/dist/chunk-WIG6XHJ7.js +35 -0
- package/dist/chunk-WIG6XHJ7.js.map +1 -0
- package/dist/chunk-Z76KEZAX.js +15 -0
- package/dist/chunk-Z76KEZAX.js.map +1 -0
- package/dist/chunk-ZDHY47EV.js +363 -0
- package/dist/chunk-ZDHY47EV.js.map +1 -0
- package/dist/config/index.d.ts +276 -0
- package/dist/config/index.js +8 -0
- package/dist/config/index.js.map +1 -0
- package/dist/controls/index.d.ts +3 -4
- package/dist/controls/index.js +4 -3
- package/dist/dragdrop/index.d.ts +3 -5
- package/dist/index.d.ts +266 -413
- package/dist/index.js +1775 -2251
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +4 -6
- package/dist/layers/index.js +2 -2
- package/dist/loupe/index.d.ts +69 -20
- package/dist/loupe/index.js +2 -2
- package/dist/passthrough/weasel-ui.d.ts +175 -309
- package/dist/passthrough/weasel-ui.js +1 -1
- package/dist/primitives/index.d.ts +55 -6
- package/dist/primitives/index.js +6 -4
- package/dist/state/index.d.ts +13 -8
- package/dist/state/index.js +18 -13
- package/dist/state/index.js.map +1 -1
- package/dist/styles.css +223 -76
- 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 +3 -5
- package/package.json +15 -8
- package/src/annotations/AnnotationOverlay.tsx +56 -5
- package/src/annotations/Annotations.less +4 -0
- package/src/annotations/Annotations.overlay.test.tsx +105 -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/svgNodes.ts +1 -0
- package/src/annotations/toolMap.test.ts +9 -5
- package/src/annotations/toolMap.ts +6 -3
- package/src/annotations/types.ts +6 -3
- package/src/canvas/CameraWheelContext.ts +12 -0
- package/src/canvas/CanvasStack.stories.tsx +1 -1
- 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.test.ts +28 -0
- package/src/canvas/usePanZoom.ts +21 -9
- package/src/chrome/ChromeRegions.stories.tsx +1 -4
- package/src/chrome/LabChrome.tsx +130 -0
- package/src/chrome/builtins.test.ts +1 -0
- package/src/chrome/builtins.tsx +21 -31
- package/src/chrome/index.ts +16 -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 +16 -9
- package/src/chrome/regions/SidebarRegion.stories.tsx +44 -0
- package/src/chrome/regions/SidebarRegion.tsx +25 -13
- package/src/chrome/regions/StatusRegion.tsx +15 -8
- package/src/chrome/regions/ToolbarRegion.tsx +34 -16
- package/src/chrome/types.ts +50 -20
- package/src/config/auto.test.ts +12 -0
- package/src/config/auto.ts +15 -0
- package/src/config/autoConfig.test.ts +107 -0
- package/src/config/autoConfig.ts +153 -0
- package/src/config/builder.test.ts +42 -0
- package/src/config/builder.ts +68 -17
- 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 +16 -1
- package/src/config/nodeClasses.test.ts +18 -0
- package/src/config/path.ts +7 -2
- package/src/config/resolve.test.ts +25 -0
- package/src/config/resolve.ts +17 -1
- package/src/config/types.ts +34 -2
- package/src/config/useResolvedConfig.ts +26 -0
- package/src/controls/AutoControls.stories.tsx +88 -0
- package/src/controls/ControlPanel.stories.tsx +42 -1
- package/src/controls/ControlPanel.test.tsx +530 -4
- package/src/controls/ControlPanel.tsx +562 -82
- package/src/fake-indexeddb-auto.d.ts +5 -0
- package/src/index.test.ts +7 -0
- package/src/index.ts +66 -5
- package/src/instrument/serializers.test.ts +22 -0
- package/src/instrument/serializers.ts +40 -1
- package/src/instrument/types.ts +25 -1
- package/src/lab/Lab.chrome.test.tsx +16 -0
- package/src/lab/Lab.less +4 -13
- package/src/lab/Lab.persist.test.tsx +147 -0
- package/src/lab/Lab.stories.tsx +1 -3
- package/src/lab/Lab.surface.test.tsx +71 -3
- package/src/lab/Lab.test.tsx +168 -16
- package/src/lab/Lab.tsx +434 -129
- package/src/lab/LabContext.ts +8 -0
- package/src/lab/LabFit.stories.less +18 -0
- package/src/lab/LabFit.stories.tsx +219 -0
- package/src/lab/LabFullChrome.stories.tsx +1 -4
- package/src/lab/LabHeader.test.tsx +40 -9
- package/src/lab/LabHeader.tsx +7 -14
- package/src/lab/LabPalette.tsx +13 -26
- package/src/lab/LabRoot.test.tsx +56 -0
- package/src/lab/LabRoot.tsx +47 -0
- package/src/lab/LabShell.less +73 -15
- package/src/lab/LabShell.stories.tsx +50 -1
- package/src/lab/LabShell.tsx +18 -27
- package/src/lab/LabSwitcher.less +18 -11
- package/src/lab/LabSwitcher.test.tsx +14 -9
- package/src/lab/LabSwitcher.tsx +8 -4
- package/src/lab/Workspace.less +1 -1
- package/src/lab/Workspace.stories.tsx +1 -1
- package/src/lab/Workspace.surface.test.tsx +1 -0
- package/src/lab/fitCheck.test.ts +57 -0
- package/src/lab/fitCheck.ts +125 -0
- package/src/lab/index.ts +2 -0
- package/src/layers/LayerList.stories.tsx +1 -1
- package/src/loupe/AGENTS.md +17 -5
- package/src/loupe/Loupe.less +1 -1
- package/src/loupe/LoupeGestures.tsx +67 -0
- package/src/loupe/TrialLoupe.tsx +43 -28
- package/src/loupe/canvasLens.ts +1 -1
- package/src/loupe/index.ts +4 -0
- package/src/loupe/loupeActions.ts +98 -0
- package/src/loupe/types.ts +1 -1
- package/src/loupe/useLoupe.test.tsx +60 -3
- package/src/loupe/useLoupe.ts +29 -49
- package/src/passthrough/weasel-ui.ts +9 -0
- package/src/primitives/FloatingPanel.stories.tsx +14 -2
- package/src/primitives/FloatingPanel.test.tsx +47 -16
- package/src/primitives/FloatingPanel.tsx +11 -25
- package/src/primitives/FpsMeter.stories.tsx +1 -1
- package/src/primitives/JobProgress.stories.tsx +1 -1
- package/src/primitives/Legend.stories.tsx +1 -1
- package/src/primitives/ScaleIndicator.stories.tsx +1 -1
- package/src/primitives/Sidebar.less +7 -0
- package/src/primitives/Sidebar.stories.tsx +1 -1
- package/src/primitives/Split.less +14 -0
- package/src/primitives/Split.test.tsx +133 -0
- package/src/primitives/Split.tsx +175 -0
- package/src/primitives/StatusBar.stories.tsx +1 -1
- package/src/primitives/Toolbar.stories.tsx +1 -1
- package/src/primitives/ZoomControl.tsx +5 -2
- package/src/primitives/index.ts +2 -0
- package/src/specimen/Specimen.less +95 -0
- package/src/specimen/Specimen.stories.tsx +13 -0
- package/src/specimen/Specimen.test.tsx +13 -0
- package/src/specimen/Specimen.tsx +888 -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 +169 -635
- package/src/state/store.ts +146 -163
- package/src/state/toolSlot.test.ts +1 -1
- package/src/state/types.ts +65 -17
- 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 +87 -3
- package/src/state/useTrialState.ts +9 -1
- package/src/state/view.test.ts +49 -15
- package/src/state/view.ts +18 -1
- package/src/styles.less +3 -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 +1 -0
- package/src/surface/useSurfaceTile.ts +6 -5
- package/src/surface/useTiledSurface.test.tsx +231 -1
- package/src/surface/useTiledSurface.ts +76 -15
- package/src/theme/Interstellar.stories.tsx +9 -9
- package/src/theme/interstellar.test.ts +17 -6
- package/src/theme/interstellar.theme.json +180 -0
- package/src/theme/interstellar.ts +8 -5
- package/src/tools/labTool.ts +11 -0
- package/src/trial/Trial.annotations.persist.test.tsx +100 -58
- package/src/trial/Trial.annotations.test.tsx +59 -7
- package/src/trial/Trial.canvas.test.tsx +32 -0
- package/src/trial/Trial.config.test.tsx +75 -2
- package/src/trial/Trial.job.test.tsx +3 -3
- package/src/trial/Trial.less +5 -26
- package/src/trial/Trial.stories.tsx +5 -3
- package/src/trial/Trial.test.tsx +31 -4
- package/src/trial/Trial.trialId.test.tsx +23 -1
- package/src/trial/Trial.tsx +139 -52
- package/src/trial/TrialBody.tsx +17 -141
- package/src/trial/TrialChrome.tsx +24 -7
- package/src/trial/trialOps.test.ts +101 -1
- package/src/trial/trialOps.ts +67 -7
- 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-E44SU6XS.js +0 -50
- package/dist/chunk-E44SU6XS.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 +0 -343
- package/dist/chunk-U3IYHIAE.js.map +0 -1
- package/dist/chunk-W3ECWC2K.js +0 -7335
- package/dist/chunk-W3ECWC2K.js.map +0 -1
- package/src/theme/interstellar.tokens.json +0 -61
package/src/loupe/AGENTS.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
on, painted by whichever painter suits the instrument's content.
|
|
5
5
|
|
|
6
6
|
The magnifier itself is not here. `@weasel-js/loupe` holds the model — aim,
|
|
7
|
-
factor, mode,
|
|
7
|
+
factor, mode, color, picking — over a `LoupeSurface` it asks five questions.
|
|
8
8
|
This directory binds that model to a labkit trial and draws it.
|
|
9
9
|
|
|
10
10
|
## Files
|
|
@@ -12,7 +12,9 @@ This directory binds that model to a labkit trial and draws it.
|
|
|
12
12
|
| File | Role |
|
|
13
13
|
|---|---|
|
|
14
14
|
| `types.ts` | `LoupeCapability`, and `resolveLoupe` filling in every default |
|
|
15
|
-
| `useLoupe.ts` | The model over a host element,
|
|
15
|
+
| `useLoupe.ts` | The model over a host element, and pointer aiming |
|
|
16
|
+
| `loupeActions.ts` | `loupe.peek` and `loupe.magnify`, as `Action` descriptors |
|
|
17
|
+
| `LoupeGestures.tsx` | Registers those and mounts the dispatcher on the host |
|
|
16
18
|
| `TrialLoupe.tsx` | Picks the painter and mounts the lens |
|
|
17
19
|
| `LoupeBubble.tsx` | The circular clip, positioned on the aim |
|
|
18
20
|
| `CanvasLoupe.tsx` | Painter for a `<CanvasStack>` |
|
|
@@ -44,6 +46,16 @@ StrictMode mounts / unmounts / mounts every effect — so disposing in the
|
|
|
44
46
|
cleanup leaves a magnifier that draws but silently ignores every aim. It owns
|
|
45
47
|
no resources; unmounting only reports the lens gone.
|
|
46
48
|
|
|
47
|
-
**The wheel
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
**The wheel is taken from pan/zoom by declining it, not by capturing it.**
|
|
50
|
+
`loupe.magnify`'s `enabled` returns a disabled reason while the lens is down, so
|
|
51
|
+
the dispatcher leaves the event unhandled and `usePanZoom` — a React handler on
|
|
52
|
+
the same element — sees it as usual. While the lens is up the action fires, the
|
|
53
|
+
dispatcher stops propagation, and React's root listener never runs.
|
|
54
|
+
|
|
55
|
+
**Aiming is a plain listener because a hover is not a gesture.**
|
|
56
|
+
`GESTURE_DESCRIPTORS` names no continuous-motion gesture, so `pointermove` /
|
|
57
|
+
`pointerleave` stay hand-attached in `useLoupe`. Everything else routes.
|
|
58
|
+
|
|
59
|
+
**`<LoupeGestures>` mounts outside the visibility gate.** Hold-to-peek is what
|
|
60
|
+
raises a lens that is down; gate its registration on `loupe.visible` and the
|
|
61
|
+
peek key stops working entirely.
|
package/src/loupe/Loupe.less
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// Nothing under the lens stops working while it is up: the pan, the wheel and
|
|
8
8
|
// any overlay of the instrument's own keep receiving the pointer.
|
|
9
9
|
pointer-events: none;
|
|
10
|
-
border: var(--wzl-border-w) solid var(--wzl-border-
|
|
10
|
+
border: var(--wzl-border-w) solid var(--wzl-border-strong);
|
|
11
11
|
box-shadow: 0 2px 8px var(--wzl-shadow);
|
|
12
12
|
// A painter's ground shows through wherever it draws nothing, and a vector
|
|
13
13
|
// lens leaves the gaps between marks transparent. This is the trial's own
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mounts the gesture dispatcher on a loupe's host and registers the loupe's
|
|
3
|
+
* actions on it, so the peek key and the wheel route the way every other
|
|
4
|
+
* weasel binding does.
|
|
5
|
+
*
|
|
6
|
+
* Three of the dispatcher's four element channels are off. Only `wheel` is
|
|
7
|
+
* wanted here; `contextMenu` suppresses the native menu unconditionally and
|
|
8
|
+
* `ingest` makes the host a file-drop target, neither of which a lab asked
|
|
9
|
+
* for by turning a magnifier on.
|
|
10
|
+
*/
|
|
11
|
+
import {
|
|
12
|
+
type ActionsRegistry,
|
|
13
|
+
type Tool,
|
|
14
|
+
useActionsRegistry,
|
|
15
|
+
useGestureDispatcher,
|
|
16
|
+
} from '@weasel-js/core';
|
|
17
|
+
import { type RefObject, useEffect, useMemo } from 'react';
|
|
18
|
+
import { createLoupeActions, type LoupeInputApi } from './loupeActions';
|
|
19
|
+
|
|
20
|
+
/** Props for `<LoupeGestures>`. */
|
|
21
|
+
export interface LoupeGesturesProps {
|
|
22
|
+
/** The element the dispatcher listens on — the same one the lens tracks. */
|
|
23
|
+
hostRef: RefObject<HTMLElement | null>;
|
|
24
|
+
input: LoupeInputApi;
|
|
25
|
+
/** `KeyboardEvent.key` for hold-to-peek, or `null` for no peek binding. */
|
|
26
|
+
peekKey: string | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const NO_TOOLS: ReadonlyMap<string, Tool> = new Map();
|
|
30
|
+
|
|
31
|
+
const CHANNELS = { pointer: false, contextMenu: false, ingest: false } as const;
|
|
32
|
+
|
|
33
|
+
function LoupeDispatch({
|
|
34
|
+
hostRef,
|
|
35
|
+
input,
|
|
36
|
+
peekKey,
|
|
37
|
+
registry,
|
|
38
|
+
}: LoupeGesturesProps & { registry: ActionsRegistry }) {
|
|
39
|
+
const actions = useMemo(() => createLoupeActions(input, peekKey), [input, peekKey]);
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
const offs = actions.map((a) => registry.register(a));
|
|
43
|
+
return () => {
|
|
44
|
+
for (const off of offs) off();
|
|
45
|
+
};
|
|
46
|
+
}, [registry, actions]);
|
|
47
|
+
|
|
48
|
+
useGestureDispatcher({
|
|
49
|
+
canvasRef: hostRef,
|
|
50
|
+
actions: registry,
|
|
51
|
+
toolsById: NO_TOOLS,
|
|
52
|
+
channels: CHANNELS,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The loupe's input, routed. Render it inside a `<WeaselProvider isolate>` —
|
|
60
|
+
* with no registry in scope it renders nothing rather than registering
|
|
61
|
+
* actions that could never fire.
|
|
62
|
+
*/
|
|
63
|
+
export function LoupeGestures(props: LoupeGesturesProps) {
|
|
64
|
+
const registry = useActionsRegistry();
|
|
65
|
+
if (!registry) return null;
|
|
66
|
+
return <LoupeDispatch {...props} registry={registry} />;
|
|
67
|
+
}
|
package/src/loupe/TrialLoupe.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { WeaselProvider } from '@weasel-js/core';
|
|
1
2
|
import { type RefObject, useContext, useMemo } from 'react';
|
|
2
3
|
import { CanvasStackContext } from '../canvas/CanvasStackContext';
|
|
3
4
|
import type { WorldSpec } from '../canvas/worldSpec';
|
|
@@ -6,6 +7,7 @@ import { CanvasLoupe } from './CanvasLoupe';
|
|
|
6
7
|
import { sampleStack } from './canvasLens';
|
|
7
8
|
import { DomLoupe } from './DomLoupe';
|
|
8
9
|
import { LoupeBubble } from './LoupeBubble';
|
|
10
|
+
import { LoupeGestures } from './LoupeGestures';
|
|
9
11
|
import type { ResolvedLoupe } from './types';
|
|
10
12
|
import { useHostSize } from './useHostSize';
|
|
11
13
|
import { useLoupe } from './useLoupe';
|
|
@@ -59,35 +61,48 @@ export function TrialLoupe({
|
|
|
59
61
|
const measured = useHostSize(host);
|
|
60
62
|
const size = surface?.size ?? measured;
|
|
61
63
|
|
|
62
|
-
|
|
64
|
+
// The gestures mount outside the visibility gate: hold-to-peek is what
|
|
65
|
+
// raises a lens that is down, so its binding has to be live while it is.
|
|
66
|
+
// One isolated scope per loupe, for the reason `<AnnotationOverlay>` gives
|
|
67
|
+
// — an actions registry holds one dispatcher.
|
|
68
|
+
const gestures = (
|
|
69
|
+
<WeaselProvider isolate>
|
|
70
|
+
<LoupeGestures hostRef={host} input={loupe.input} peekKey={capability.peekKey ?? null} />
|
|
71
|
+
</WeaselProvider>
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (!loupe.visible) return gestures;
|
|
63
75
|
|
|
64
76
|
return (
|
|
65
|
-
|
|
66
|
-
{
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
77
|
+
<>
|
|
78
|
+
{gestures}
|
|
79
|
+
<LoupeBubble aim={loupe.aim} diameter={capability.diameter}>
|
|
80
|
+
{capability.render ? (
|
|
81
|
+
<DomLoupe
|
|
82
|
+
aim={loupe.aim}
|
|
83
|
+
factor={loupe.factor}
|
|
84
|
+
mode={loupe.mode}
|
|
85
|
+
diameter={capability.diameter}
|
|
86
|
+
size={size}
|
|
87
|
+
view={view}
|
|
88
|
+
frame={stack?.frame}
|
|
89
|
+
state={state}
|
|
90
|
+
config={config}
|
|
91
|
+
render={capability.render}
|
|
92
|
+
/>
|
|
93
|
+
) : surface && stack ? (
|
|
94
|
+
<CanvasLoupe
|
|
95
|
+
aim={loupe.aim}
|
|
96
|
+
factor={loupe.factor}
|
|
97
|
+
mode={loupe.mode}
|
|
98
|
+
diameter={capability.diameter}
|
|
99
|
+
surface={surface}
|
|
100
|
+
view={stack.view}
|
|
101
|
+
frame={stack.frame}
|
|
102
|
+
worldSpec={worldSpec}
|
|
103
|
+
/>
|
|
104
|
+
) : null}
|
|
105
|
+
</LoupeBubble>
|
|
106
|
+
</>
|
|
92
107
|
);
|
|
93
108
|
}
|
package/src/loupe/canvasLens.ts
CHANGED
|
@@ -61,7 +61,7 @@ export function lensSourceRect(
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
|
-
* The
|
|
64
|
+
* The color a stack presents at a point: the topmost visible layer with
|
|
65
65
|
* anything opaque there, or `null` when every layer is transparent — which is
|
|
66
66
|
* the honest answer for a point showing nothing but the workspace behind.
|
|
67
67
|
*/
|
package/src/loupe/index.ts
CHANGED
|
@@ -6,6 +6,10 @@ export type { DomLoupeProps } from './DomLoupe';
|
|
|
6
6
|
export { DomLoupe } from './DomLoupe';
|
|
7
7
|
export type { LoupeBubbleProps } from './LoupeBubble';
|
|
8
8
|
export { LoupeBubble } from './LoupeBubble';
|
|
9
|
+
export type { LoupeGesturesProps } from './LoupeGestures';
|
|
10
|
+
export { LoupeGestures } from './LoupeGestures';
|
|
11
|
+
export type { LoupeInputApi } from './loupeActions';
|
|
12
|
+
export { createLoupeActions, LOUPE_MAGNIFY_ID, LOUPE_PEEK_ID } from './loupeActions';
|
|
9
13
|
export type { TrialLoupeProps } from './TrialLoupe';
|
|
10
14
|
export { TrialLoupe } from './TrialLoupe';
|
|
11
15
|
export type {
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The loupe's two bindable interactions, as `Action` descriptors the gesture
|
|
3
|
+
* dispatcher routes: hold a key to peek, and walk the magnification with the
|
|
4
|
+
* wheel.
|
|
5
|
+
*
|
|
6
|
+
* Free of React so the routing can be tested without a DOM.
|
|
7
|
+
*/
|
|
8
|
+
import type { Action } from '@weasel-js/core';
|
|
9
|
+
|
|
10
|
+
/** Canonical id of the hold-to-peek action. */
|
|
11
|
+
export const LOUPE_PEEK_ID = 'loupe.peek';
|
|
12
|
+
/** Canonical id of the wheel-magnify action. */
|
|
13
|
+
export const LOUPE_MAGNIFY_ID = 'loupe.magnify';
|
|
14
|
+
|
|
15
|
+
/** How fast the wheel walks the magnification. Gentler than the pan-zoom
|
|
16
|
+
* wheel: the lens' whole range is one order of magnitude. */
|
|
17
|
+
export const WHEEL_RATE = 0.002;
|
|
18
|
+
|
|
19
|
+
/** What the actions do to a live loupe. Every member is read through the
|
|
20
|
+
* object on each invocation, so one stable instance serves the life of the
|
|
21
|
+
* registration. */
|
|
22
|
+
export interface LoupeInputApi {
|
|
23
|
+
/** Whether the lens is up. The wheel belongs to the loupe only while it is;
|
|
24
|
+
* otherwise `loupe.magnify` declines and the dispatcher leaves the event
|
|
25
|
+
* unhandled, which is what hands it back to the lab's own pan/zoom. */
|
|
26
|
+
shown: () => boolean;
|
|
27
|
+
/** Raise or drop the hold-to-peek flag. */
|
|
28
|
+
setPeeking: (on: boolean) => void;
|
|
29
|
+
/** Multiply the magnification by one wheel notch of `deltaY`. */
|
|
30
|
+
magnifyBy: (deltaY: number) => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Build the loupe's actions. `peekKey` is a `KeyboardEvent.key`; passing
|
|
35
|
+
* `null` omits `loupe.peek` entirely rather than registering a binding no key
|
|
36
|
+
* can match.
|
|
37
|
+
*/
|
|
38
|
+
export function createLoupeActions(
|
|
39
|
+
input: LoupeInputApi,
|
|
40
|
+
peekKey: string | null,
|
|
41
|
+
): readonly Action[] {
|
|
42
|
+
const magnify: Action = {
|
|
43
|
+
id: LOUPE_MAGNIFY_ID,
|
|
44
|
+
label: 'Magnify (loupe)',
|
|
45
|
+
group: 'loupe',
|
|
46
|
+
// Every modifier optional: the lens claims the wheel whenever it is up,
|
|
47
|
+
// which is what the hand-rolled capture-phase listener did. A bare spec
|
|
48
|
+
// would forbid modifiers and hand Cmd+wheel back to the lab mid-peek.
|
|
49
|
+
defaultBinding: [
|
|
50
|
+
{
|
|
51
|
+
spec: {
|
|
52
|
+
kind: 'wheel',
|
|
53
|
+
mods: { alt: 'optional', ctrl: 'optional', meta: 'optional', shift: 'optional' },
|
|
54
|
+
},
|
|
55
|
+
opts: {},
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
enabled: () => (input.shown() ? true : 'not-applicable'),
|
|
59
|
+
invoker: {
|
|
60
|
+
timing: 'immediate',
|
|
61
|
+
run(_deps, params) {
|
|
62
|
+
input.magnifyBy((params?.deltaY as number | undefined) ?? 0);
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
if (peekKey === null) return [magnify];
|
|
68
|
+
|
|
69
|
+
const peek: Action = {
|
|
70
|
+
id: LOUPE_PEEK_ID,
|
|
71
|
+
label: 'Peek (loupe)',
|
|
72
|
+
group: 'loupe',
|
|
73
|
+
// Hotkey scope: peeking must beat whatever the lab's active tool binds to
|
|
74
|
+
// the same key, the way every other held-key trigger does.
|
|
75
|
+
scope: 'hotkey',
|
|
76
|
+
// Every modifier optional because the peek key may BE one: the keydown
|
|
77
|
+
// that carries `Alt` — the default — also reports `altKey: true`, and the
|
|
78
|
+
// matcher forbids any modifier a spec does not name.
|
|
79
|
+
defaultBinding: [
|
|
80
|
+
{
|
|
81
|
+
spec: {
|
|
82
|
+
kind: 'key-held',
|
|
83
|
+
key: peekKey,
|
|
84
|
+
mods: { alt: 'optional', ctrl: 'optional', meta: 'optional', shift: 'optional' },
|
|
85
|
+
},
|
|
86
|
+
opts: {},
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
invoker: {
|
|
90
|
+
timing: 'ongoing',
|
|
91
|
+
start() {
|
|
92
|
+
input.setPeeking(true);
|
|
93
|
+
return { onEnd: () => input.setPeeking(false) };
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
return [peek, magnify];
|
|
98
|
+
}
|
package/src/loupe/types.ts
CHANGED
|
@@ -43,7 +43,7 @@ export interface LoupeCapability<TS = unknown, TC = unknown> {
|
|
|
43
43
|
/** Held for a momentary peek while the loupe is off. Default `'Alt'`; `null`
|
|
44
44
|
* turns hold-to-peek off. Matched against `KeyboardEvent.key`. */
|
|
45
45
|
peekKey?: string | null;
|
|
46
|
-
/** Called with the
|
|
46
|
+
/** Called with the color under the aim, wherever the surface can say. The
|
|
47
47
|
* canvas painter reads it back; a DOM loupe has no pixels to sample. */
|
|
48
48
|
onColorChange?: (hex: string) => void;
|
|
49
49
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { act, fireEvent, render, screen } from '@testing-library/react';
|
|
2
|
+
import { WeaselProvider } from '@weasel-js/core';
|
|
2
3
|
import { type RefObject, StrictMode, useRef } from 'react';
|
|
3
4
|
import { describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { LoupeGestures } from './LoupeGestures';
|
|
4
6
|
import { resolveLoupe } from './types';
|
|
5
7
|
import { type LoupeState, useLoupe } from './useLoupe';
|
|
6
8
|
|
|
@@ -15,18 +17,29 @@ interface HarnessProps {
|
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
/** Reports the loupe's state as text, which is every assertion jsdom can make
|
|
18
|
-
* about a magnifier — that the lens shows the right region is a screenshot.
|
|
20
|
+
* about a magnifier — that the lens shows the right region is a screenshot.
|
|
21
|
+
*
|
|
22
|
+
* Mounts `<LoupeGestures>` the way `<TrialLoupe>` does, so the key and wheel
|
|
23
|
+
* cases below exercise the real dispatcher route rather than a stand-in. */
|
|
19
24
|
function Harness({ enabled = true, seen, sample, ...rest }: HarnessProps) {
|
|
20
25
|
const hostRef = useRef<HTMLDivElement | null>(null);
|
|
26
|
+
const capability = resolveLoupe(rest);
|
|
21
27
|
const loupe = useLoupe({
|
|
22
28
|
hostRef: hostRef as RefObject<HTMLElement | null>,
|
|
23
29
|
enabled,
|
|
24
30
|
sample,
|
|
25
|
-
capability
|
|
31
|
+
capability,
|
|
26
32
|
});
|
|
27
33
|
seen?.(loupe);
|
|
28
34
|
return (
|
|
29
35
|
<div ref={hostRef} data-testid="host">
|
|
36
|
+
<WeaselProvider isolate>
|
|
37
|
+
<LoupeGestures
|
|
38
|
+
hostRef={hostRef as RefObject<HTMLElement | null>}
|
|
39
|
+
input={loupe.input}
|
|
40
|
+
peekKey={capability.peekKey ?? null}
|
|
41
|
+
/>
|
|
42
|
+
</WeaselProvider>
|
|
30
43
|
<span data-testid="visible">{String(loupe.visible)}</span>
|
|
31
44
|
<span data-testid="aim">{`${loupe.aim.x},${loupe.aim.y}`}</span>
|
|
32
45
|
<span data-testid="factor">{loupe.factor}</span>
|
|
@@ -94,6 +107,7 @@ describe('useLoupe', () => {
|
|
|
94
107
|
act(() => {
|
|
95
108
|
fireEvent.keyDown(window, { key: 'Alt' });
|
|
96
109
|
});
|
|
110
|
+
expect(read('visible')).toBe('true');
|
|
97
111
|
act(() => {
|
|
98
112
|
fireEvent.blur(window);
|
|
99
113
|
});
|
|
@@ -148,7 +162,50 @@ describe('useLoupe', () => {
|
|
|
148
162
|
expect(wheel.defaultPrevented).toBe(true);
|
|
149
163
|
});
|
|
150
164
|
|
|
151
|
-
it('
|
|
165
|
+
it('stops a claimed wheel reaching the pan-zoom handler above it', () => {
|
|
166
|
+
// The real handler is React's, delegated to the root container, so what
|
|
167
|
+
// decides whether pan-zoom runs is whether the event bubbles past the
|
|
168
|
+
// host — `defaultPrevented` says nothing about that. A listener on an
|
|
169
|
+
// ancestor is the proxy for React's root one.
|
|
170
|
+
const above = vi.fn();
|
|
171
|
+
const { container } = render(<Harness />);
|
|
172
|
+
container.addEventListener('wheel', above);
|
|
173
|
+
|
|
174
|
+
const host = screen.getByTestId('host');
|
|
175
|
+
move(40, 25);
|
|
176
|
+
act(() => {
|
|
177
|
+
host.dispatchEvent(
|
|
178
|
+
new WheelEvent('wheel', { deltaY: -100, bubbles: true, cancelable: true }),
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
expect(above).not.toHaveBeenCalled();
|
|
182
|
+
|
|
183
|
+
fireEvent.pointerLeave(host);
|
|
184
|
+
act(() => {
|
|
185
|
+
host.dispatchEvent(
|
|
186
|
+
new WheelEvent('wheel', { deltaY: -100, bubbles: true, cancelable: true }),
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
expect(above).toHaveBeenCalledTimes(1);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('leaves the native context menu and file drops to the lab', () => {
|
|
193
|
+
// The dispatcher suppresses `contextmenu` unconditionally and makes its
|
|
194
|
+
// element a drop target; the loupe mounts with both channels off, so a
|
|
195
|
+
// lab that turns a magnifier on keeps its right-click and its drops.
|
|
196
|
+
render(<Harness />);
|
|
197
|
+
const host = screen.getByTestId('host');
|
|
198
|
+
move(40, 25);
|
|
199
|
+
|
|
200
|
+
const menu = new MouseEvent('contextmenu', { bubbles: true, cancelable: true });
|
|
201
|
+
act(() => {
|
|
202
|
+
host.dispatchEvent(menu);
|
|
203
|
+
});
|
|
204
|
+
expect(menu.defaultPrevented).toBe(false);
|
|
205
|
+
expect(host.classList.contains('weasel-dropover')).toBe(false);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('reports the color under the aim', () => {
|
|
152
209
|
const onColorChange = vi.fn();
|
|
153
210
|
render(
|
|
154
211
|
<Harness sample={(p) => (p.x > 10 ? '#ff0000' : '#00ff00')} onColorChange={onColorChange} />,
|
package/src/loupe/useLoupe.ts
CHANGED
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
type LoupePoint,
|
|
6
6
|
} from '@weasel-js/loupe';
|
|
7
7
|
import { type RefObject, useCallback, useEffect, useReducer, useRef } from 'react';
|
|
8
|
+
import type { LoupeInputApi } from './loupeActions';
|
|
9
|
+
import { WHEEL_RATE } from './loupeActions';
|
|
8
10
|
import type { ResolvedLoupe } from './types';
|
|
9
11
|
|
|
10
12
|
/** Options for {@link useLoupe}. */
|
|
@@ -14,7 +16,7 @@ export interface UseLoupeOptions {
|
|
|
14
16
|
hostRef: RefObject<HTMLElement | null>;
|
|
15
17
|
/** Whether the loupe is turned on. Hold-to-peek shows it regardless. */
|
|
16
18
|
enabled: boolean;
|
|
17
|
-
/** Hex
|
|
19
|
+
/** Hex color at a host point. Omitted, the loupe reports no color — which
|
|
18
20
|
* is the honest answer for a surface with no pixels to read. */
|
|
19
21
|
sample?: (p: LoupePoint) => string | null;
|
|
20
22
|
}
|
|
@@ -32,21 +34,21 @@ export interface LoupeState {
|
|
|
32
34
|
setFactor: (factor: number) => void;
|
|
33
35
|
/** Sample what the lens shows at a point inside it. */
|
|
34
36
|
pick: (p?: LoupePoint) => string | null;
|
|
37
|
+
/** What `<LoupeGestures>` drives the lens through. Stable for the life of
|
|
38
|
+
* the hook, so registering the actions on it does not churn. */
|
|
39
|
+
input: LoupeInputApi;
|
|
35
40
|
}
|
|
36
41
|
|
|
37
|
-
/** How fast the wheel walks the magnification. Gentler than the pan-zoom
|
|
38
|
-
* wheel: the lens' whole range is one order of magnitude. */
|
|
39
|
-
const WHEEL_RATE = 0.002;
|
|
40
|
-
|
|
41
42
|
/**
|
|
42
|
-
* Binds `@weasel-js/loupe`'s model to a host element and
|
|
43
|
-
*
|
|
43
|
+
* Binds `@weasel-js/loupe`'s model to a host element and tracks the pointer
|
|
44
|
+
* across it.
|
|
44
45
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
46
|
+
* The peek key and the wheel are not here: they are `loupe.peek` and
|
|
47
|
+
* `loupe.magnify`, routed through the gesture dispatcher that
|
|
48
|
+
* `<LoupeGestures>` mounts on the same host. Aiming stays a plain listener
|
|
49
|
+
* because a hover is not a gesture the grammar names — `GESTURE_DESCRIPTORS`
|
|
50
|
+
* has no continuous-motion entry, and inventing one to carry the lens' aim
|
|
51
|
+
* would be an input taxonomy change, not a loupe change.
|
|
50
52
|
*/
|
|
51
53
|
export function useLoupe({ capability, hostRef, enabled, sample }: UseLoupeOptions): LoupeState {
|
|
52
54
|
const [, bump] = useReducer((n: number) => n + 1, 0);
|
|
@@ -128,53 +130,30 @@ export function useLoupe({ capability, hostRef, enabled, sample }: UseLoupeOptio
|
|
|
128
130
|
overRef.current = false;
|
|
129
131
|
bump();
|
|
130
132
|
};
|
|
131
|
-
const onWheel = (e: WheelEvent): void => {
|
|
132
|
-
if (!shown()) return;
|
|
133
|
-
// Ahead of the stack's own pan-zoom, which is a React handler on this same
|
|
134
|
-
// element: capture runs first, and stopping propagation keeps the event
|
|
135
|
-
// from reaching React's root listener at all.
|
|
136
|
-
e.preventDefault();
|
|
137
|
-
e.stopPropagation();
|
|
138
|
-
model.setFactor(model.factor * Math.exp(-e.deltaY * WHEEL_RATE));
|
|
139
|
-
};
|
|
140
133
|
|
|
141
134
|
host.addEventListener('pointermove', onPointerMove);
|
|
142
135
|
host.addEventListener('pointerleave', onPointerLeave);
|
|
143
|
-
host.addEventListener('wheel', onWheel, { capture: true, passive: false });
|
|
144
136
|
return () => {
|
|
145
137
|
host.removeEventListener('pointermove', onPointerMove);
|
|
146
138
|
host.removeEventListener('pointerleave', onPointerLeave);
|
|
147
|
-
host.removeEventListener('wheel', onWheel, { capture: true });
|
|
148
139
|
};
|
|
149
140
|
}, [hostRef, model, shown]);
|
|
150
141
|
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
};
|
|
165
|
-
// A key held while the window loses focus never sends its keyup, so the
|
|
166
|
-
// peek would stay on until the key was pressed and released again.
|
|
167
|
-
const onBlur = (): void => setPeeking(false);
|
|
168
|
-
|
|
169
|
-
window.addEventListener('keydown', onKeyDown);
|
|
170
|
-
window.addEventListener('keyup', onKeyUp);
|
|
171
|
-
window.addEventListener('blur', onBlur);
|
|
172
|
-
return () => {
|
|
173
|
-
window.removeEventListener('keydown', onKeyDown);
|
|
174
|
-
window.removeEventListener('keyup', onKeyUp);
|
|
175
|
-
window.removeEventListener('blur', onBlur);
|
|
142
|
+
const inputRef = useRef<LoupeInputApi | null>(null);
|
|
143
|
+
if (inputRef.current === null) {
|
|
144
|
+
inputRef.current = {
|
|
145
|
+
shown,
|
|
146
|
+
setPeeking: (on) => {
|
|
147
|
+
if (peekingRef.current === on) return;
|
|
148
|
+
peekingRef.current = on;
|
|
149
|
+
bump();
|
|
150
|
+
},
|
|
151
|
+
magnifyBy: (deltaY) => {
|
|
152
|
+
const m = modelRef.current;
|
|
153
|
+
if (m) m.setFactor(m.factor * Math.exp(-deltaY * WHEEL_RATE));
|
|
154
|
+
},
|
|
176
155
|
};
|
|
177
|
-
}
|
|
156
|
+
}
|
|
178
157
|
|
|
179
158
|
return {
|
|
180
159
|
visible: shown(),
|
|
@@ -185,5 +164,6 @@ export function useLoupe({ capability, hostRef, enabled, sample }: UseLoupeOptio
|
|
|
185
164
|
setMode: model.setMode,
|
|
186
165
|
setFactor: model.setFactor,
|
|
187
166
|
pick: model.pick,
|
|
167
|
+
input: inputRef.current,
|
|
188
168
|
};
|
|
189
169
|
}
|
|
@@ -15,6 +15,8 @@ export {
|
|
|
15
15
|
type ActionsBarVariant,
|
|
16
16
|
type AddPointMode,
|
|
17
17
|
type AnchorRenderProps,
|
|
18
|
+
type AsyncOption,
|
|
19
|
+
type AsyncOptionsResult,
|
|
18
20
|
type AxesSettings,
|
|
19
21
|
Badge,
|
|
20
22
|
type BadgeProps,
|
|
@@ -37,6 +39,8 @@ export {
|
|
|
37
39
|
type ChromaCurve,
|
|
38
40
|
type ChromaCurvePoint,
|
|
39
41
|
ComboBox,
|
|
42
|
+
type ComboBoxCommit,
|
|
43
|
+
type ComboBoxFilter,
|
|
40
44
|
ComboBoxItem,
|
|
41
45
|
type ComboBoxItemProps,
|
|
42
46
|
type ComboBoxOption,
|
|
@@ -100,6 +104,9 @@ export {
|
|
|
100
104
|
type LayerStackProps,
|
|
101
105
|
type LogicalMod,
|
|
102
106
|
type LogicalModSpec,
|
|
107
|
+
MenuButton,
|
|
108
|
+
type MenuButtonItem,
|
|
109
|
+
type MenuButtonProps,
|
|
103
110
|
MINUS_SIGN,
|
|
104
111
|
NumberField,
|
|
105
112
|
type NumberFieldProps,
|
|
@@ -186,8 +193,10 @@ export {
|
|
|
186
193
|
ToolPalette,
|
|
187
194
|
type ToolPaletteProps,
|
|
188
195
|
type TrackCtx,
|
|
196
|
+
type UseAsyncOptionsProps,
|
|
189
197
|
type UseReorderDragListOptions,
|
|
190
198
|
type UseRovingTabIndexOptions,
|
|
199
|
+
useAsyncOptions,
|
|
191
200
|
useReorderDragList,
|
|
192
201
|
useRovingTabIndex,
|
|
193
202
|
visiblePrefSubtree,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@
|
|
1
|
+
import type { Meta, StoryObj } from '@weasel-js/forge';
|
|
2
|
+
import { localStorageAdapter } from '../state/adapters';
|
|
3
|
+
import { Persistence } from '../state/Persistence';
|
|
2
4
|
import { FloatingPanel } from './FloatingPanel';
|
|
3
5
|
import { Legend } from './Legend';
|
|
4
6
|
|
|
@@ -43,4 +45,14 @@ export const HoldingALegend: Story = {
|
|
|
43
45
|
},
|
|
44
46
|
};
|
|
45
47
|
|
|
46
|
-
|
|
48
|
+
// Remembered only under a provider; this one keeps it across story reloads.
|
|
49
|
+
export const Remembered: Story = {
|
|
50
|
+
args: { persist: 'story-panel' },
|
|
51
|
+
decorators: [
|
|
52
|
+
(Story) => (
|
|
53
|
+
<Persistence storageKey="labkit-story" storage={localStorageAdapter}>
|
|
54
|
+
<Story />
|
|
55
|
+
</Persistence>
|
|
56
|
+
),
|
|
57
|
+
],
|
|
58
|
+
};
|