@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
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ Annotations are on the `pre` tag until the next stable release:
|
|
|
36
36
|
## A lab
|
|
37
37
|
|
|
38
38
|
```tsx
|
|
39
|
-
import { type ConfigOf, defineInstrument, f, Lab
|
|
39
|
+
import { type ConfigOf, defineInstrument, f, Lab } from '@weasel-js/labkit';
|
|
40
40
|
import '@weasel-js/labkit/styles.css';
|
|
41
41
|
|
|
42
42
|
const config = f.schema({
|
|
@@ -64,13 +64,24 @@ export function App() {
|
|
|
64
64
|
instruments={[Histogram]}
|
|
65
65
|
defaultInstrument="Histogram"
|
|
66
66
|
title="Histogram"
|
|
67
|
-
storage={localStorageAdapter}
|
|
68
67
|
storageKey="histogram-lab"
|
|
69
68
|
/>
|
|
70
69
|
);
|
|
71
70
|
}
|
|
72
71
|
```
|
|
73
72
|
|
|
73
|
+
`storageKey` makes the lab persist: its trials, snapshots, layout and mode
|
|
74
|
+
survive a reload and stay in step across tabs. It persists to IndexedDB — or
|
|
75
|
+
localStorage, with a warning, where IndexedDB will not open — and `storage`
|
|
76
|
+
names another substrate: `localStorageAdapter`, `sessionStorageAdapter`,
|
|
77
|
+
`urlHashAdapter`, or your own `StorageAdapter` over a server. The lab renders
|
|
78
|
+
`fallback` while it loads. Leave `storageKey` off and nothing persists.
|
|
79
|
+
|
|
80
|
+
A widget's own state persists the same way through `usePersistedState(name,
|
|
81
|
+
initial)`, which is `useState` whose value survives a reload — kept per trial
|
|
82
|
+
inside one, and plain `useState` wherever no `<Lab>` or `<Persistence>` is
|
|
83
|
+
above it.
|
|
84
|
+
|
|
74
85
|
`f.schema` states the config once — values, types and controls — and the trial
|
|
75
86
|
renders the settings panel from it. `render` returns the instrument's own DOM;
|
|
76
87
|
returning `null` alongside `canvas` means the canvas layers are the whole
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { RefObject } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { k as ViewTransform, W as WorldFrame } from './frac-8R6UmlvN.js';
|
|
4
4
|
|
|
5
5
|
/** One layer of a canvas stack: its id, whether it is currently shown, and how
|
|
6
6
|
* it paints itself. */
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, ButtonHTMLAttributes, ReactElement } from 'react';
|
|
3
|
+
|
|
4
|
+
/** Visual weight of a button. Defaults to `secondary`. */
|
|
5
|
+
type ButtonVariant = 'primary' | 'secondary' | 'ghost';
|
|
6
|
+
/** Button height and type scale. */
|
|
7
|
+
type ButtonSize = 'sm' | 'md';
|
|
8
|
+
type ButtonBase = {
|
|
9
|
+
variant?: ButtonVariant;
|
|
10
|
+
size?: ButtonSize;
|
|
11
|
+
/** A button that stays down: renders `aria-pressed` and holds the active
|
|
12
|
+
* treatment while it is on. For a control that reports a state rather than
|
|
13
|
+
* firing an action -- a panel toggle, a mode switch. Left off, the button
|
|
14
|
+
* announces no pressed state at all, which is what an action button wants. */
|
|
15
|
+
pressed?: boolean;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
loading?: boolean;
|
|
18
|
+
leadingIcon?: ReactNode;
|
|
19
|
+
trailingIcon?: ReactNode;
|
|
20
|
+
fullWidth?: boolean;
|
|
21
|
+
type?: 'button' | 'submit' | 'reset';
|
|
22
|
+
className?: string;
|
|
23
|
+
style?: ButtonHTMLAttributes<HTMLButtonElement>['style'];
|
|
24
|
+
children?: ReactNode;
|
|
25
|
+
onClick?: ButtonHTMLAttributes<HTMLButtonElement>['onClick'];
|
|
26
|
+
};
|
|
27
|
+
type ButtonRegular = ButtonBase & {
|
|
28
|
+
iconOnly?: false;
|
|
29
|
+
ariaLabel?: string;
|
|
30
|
+
};
|
|
31
|
+
type ButtonIconOnly = ButtonBase & {
|
|
32
|
+
iconOnly: true;
|
|
33
|
+
ariaLabel: string;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Props for {@link Button}. Setting `iconOnly` makes `ariaLabel` required,
|
|
37
|
+
* since an icon-only button has no text for a screen reader to announce.
|
|
38
|
+
*/
|
|
39
|
+
type ButtonProps = ButtonRegular | ButtonIconOnly;
|
|
40
|
+
/**
|
|
41
|
+
* Standard button. `loading` swaps the leading icon for a spinner, marks the
|
|
42
|
+
* button `aria-busy`, and leaves the label in place; it does not disable the
|
|
43
|
+
* button, so pass `disabled` too if the click should be blocked.
|
|
44
|
+
*
|
|
45
|
+
* `ref` forwards to the underlying `<button>`.
|
|
46
|
+
*/
|
|
47
|
+
declare const Button: react.ForwardRefExoticComponent<ButtonProps & react.RefAttributes<HTMLButtonElement>>;
|
|
48
|
+
|
|
49
|
+
/** One segment of a {@link ToggleBar}, identified by its `value`. */
|
|
50
|
+
type ToggleBarItem<V extends string | number = string> = {
|
|
51
|
+
value: V;
|
|
52
|
+
label?: ReactNode;
|
|
53
|
+
ariaLabel?: string;
|
|
54
|
+
disabled?: boolean;
|
|
55
|
+
};
|
|
56
|
+
/** Segment height and type scale for a {@link ToggleBar}. */
|
|
57
|
+
type ToggleBarSize = 'sm' | 'md';
|
|
58
|
+
/** Visual treatment of a {@link ToggleBar}. */
|
|
59
|
+
type ToggleBarVariant = 'default' | 'minimal' | 'flat';
|
|
60
|
+
type CommonProps = {
|
|
61
|
+
ariaLabel?: string;
|
|
62
|
+
className?: string;
|
|
63
|
+
height?: number;
|
|
64
|
+
/** Size variant. `sm` is ~60% of the default height with reduced padding
|
|
65
|
+
* and font size — sized for dense surfaces like lab control panels. */
|
|
66
|
+
size?: ToggleBarSize;
|
|
67
|
+
/** Visual variant. `minimal` strips the pill track and glass treatment;
|
|
68
|
+
* selection becomes a flat accent. For dense diagnostic surfaces. */
|
|
69
|
+
variant?: ToggleBarVariant;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Props for {@link ToggleBar}, discriminated on `mode`. Single mode is
|
|
73
|
+
* controlled by one value or `null`; multiple mode by an array, and only it
|
|
74
|
+
* accepts `mixedValues`.
|
|
75
|
+
*/
|
|
76
|
+
type ToggleBarProps<V extends string | number = string> = (CommonProps & {
|
|
77
|
+
mode?: 'single';
|
|
78
|
+
items: readonly ToggleBarItem<V>[];
|
|
79
|
+
value: V | null;
|
|
80
|
+
onChange: (next: V | null) => void;
|
|
81
|
+
allowDeselect?: boolean;
|
|
82
|
+
}) | (CommonProps & {
|
|
83
|
+
mode: 'multiple';
|
|
84
|
+
items: readonly ToggleBarItem<V>[];
|
|
85
|
+
value: readonly V[];
|
|
86
|
+
/**
|
|
87
|
+
* Values that are neither on nor off — the sources this bar
|
|
88
|
+
* aggregates disagree (a text range that is bold in part of it, a
|
|
89
|
+
* multi-selection whose nodes differ). Rendered `aria-pressed="mixed"`,
|
|
90
|
+
* the ARIA tri-state a toggle button actually has, rather than
|
|
91
|
+
* `SelectionPanel`'s reduced-opacity-plus-`title` workaround for
|
|
92
|
+
* `Switch`, which has no indeterminate state to render.
|
|
93
|
+
*
|
|
94
|
+
* `value` wins where the two lists overlap, so a caller that can't
|
|
95
|
+
* cheaply keep them disjoint doesn't get an ambiguous segment.
|
|
96
|
+
*
|
|
97
|
+
* Clicking a mixed segment turns it fully **on**, matching the
|
|
98
|
+
* everywhere-else convention (and `toggleFlagInRange`'s rule for a
|
|
99
|
+
* partially-styled text range) that a mixed toggle resolves toward
|
|
100
|
+
* the affirmative rather than clearing.
|
|
101
|
+
*/
|
|
102
|
+
mixedValues?: readonly V[];
|
|
103
|
+
onChange: (next: V[]) => void;
|
|
104
|
+
});
|
|
105
|
+
/**
|
|
106
|
+
* Segmented control for choosing among a fixed set of values — one of them
|
|
107
|
+
* (`mode: 'single'`, the default) or any number (`mode: 'multiple'`).
|
|
108
|
+
*
|
|
109
|
+
* Single mode ignores a click on the already-selected segment unless
|
|
110
|
+
* `allowDeselect` is set. Arrow keys move focus without changing the value;
|
|
111
|
+
* Space and Enter commit.
|
|
112
|
+
*/
|
|
113
|
+
declare function ToggleBar<V extends string | number = string>(props: ToggleBarProps<V>): ReactElement;
|
|
114
|
+
|
|
115
|
+
export { Button as B, ToggleBar as T };
|
|
116
|
+
export type { ButtonProps as a, ButtonSize as b, ButtonVariant as c, ToggleBarItem as d, ToggleBarProps as e, ToggleBarSize as f, ToggleBarVariant as g };
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { ComponentType, ReactNode, RefObject } from 'react';
|
|
2
|
-
import { R as ResolvedConfig, a as
|
|
3
|
-
import { C as ConfigField } from './types-x92Kfeme.js';
|
|
4
|
-
import { c as SavedSnapshot, i as TrialInfo } from './types-BP2OCcpg.js';
|
|
2
|
+
import { C as ConfigPath, V as ValueAtPath, R as ResolvedConfig, a as ConfigField, b as ConfigSchema } from './types-Ca2LCnPq.js';
|
|
5
3
|
import { J as JobHandle, a as JobCapability } from './types-DJ79Tg5J.js';
|
|
6
4
|
import { Scene } from '@weasel-js/core';
|
|
7
5
|
|
|
@@ -33,15 +31,188 @@ declare function resolveFrame(spec: WorldSpec | undefined, size: ViewportSize):
|
|
|
33
31
|
* `draw` works in them. Must stay the exact inverse of `screenToWorld`. */
|
|
34
32
|
declare function applyCamera(ctx: CanvasRenderingContext2D, view: ViewTransform, frame?: WorldFrame): void;
|
|
35
33
|
|
|
34
|
+
/** A sidebar section torn out of its trial, and where it went. */
|
|
35
|
+
interface UndockedPanel {
|
|
36
|
+
trialId: string;
|
|
37
|
+
sectionId: string;
|
|
38
|
+
as: 'tile' | 'floating';
|
|
39
|
+
}
|
|
40
|
+
/** Undocked panels, keyed by {@link panelKey}. */
|
|
41
|
+
type UndockedPanels = Record<string, UndockedPanel>;
|
|
42
|
+
declare function panelKey(trialId: string, sectionId: string): string;
|
|
43
|
+
declare function undockPanel(panels: UndockedPanels, trialId: string, sectionId: string, as?: UndockedPanel['as']): UndockedPanels;
|
|
44
|
+
/** Dock one section back, or — with no `sectionId` — every panel the trial
|
|
45
|
+
* owns, which is what closing a trial wants. */
|
|
46
|
+
declare function dockPanel(panels: UndockedPanels, trialId: string, sectionId?: string): UndockedPanels;
|
|
47
|
+
|
|
48
|
+
/** A trial's undo history, as snapshots of its state either side of the
|
|
49
|
+
* present. */
|
|
50
|
+
interface UndoStack {
|
|
51
|
+
past: unknown[];
|
|
52
|
+
future: unknown[];
|
|
53
|
+
}
|
|
54
|
+
/** The trial a per-trial call is coming from. `id` is the id `useTileId`
|
|
55
|
+
* scopes a surface tile under, so a consumer keying its own per-trial
|
|
56
|
+
* registry and labkit's tiles agree on the key. */
|
|
57
|
+
interface TrialInfo<TV = unknown> {
|
|
58
|
+
id: string;
|
|
59
|
+
/** The trial's view, in whatever shape the instrument keeps it. */
|
|
60
|
+
view: TV;
|
|
61
|
+
}
|
|
62
|
+
/** One trial as the store holds it: which instrument it runs, that
|
|
63
|
+
* instrument's config and state, the camera, and the undo history. */
|
|
64
|
+
interface TrialRecord<TS = unknown, TC = unknown, TV = unknown> {
|
|
65
|
+
id: string;
|
|
66
|
+
instrumentName: string;
|
|
67
|
+
config: TC;
|
|
68
|
+
state: TS;
|
|
69
|
+
/** Opaque to labkit: persisted, restored on Reset and handed to the instrument,
|
|
70
|
+
* but never read into. A 3D lab puts an orbit here and keeps all three. */
|
|
71
|
+
view: TV;
|
|
72
|
+
/** The config `addTrial` opened this trial on, overlaid on the instrument's
|
|
73
|
+
* defaults. Kept so Reset restores the trial's own subject rather than the
|
|
74
|
+
* bare defaults. */
|
|
75
|
+
configSeed?: Partial<TC>;
|
|
76
|
+
/** This trial's own tool slot. Undefined means it reads the lab's. */
|
|
77
|
+
activeToolId?: string | null;
|
|
78
|
+
/** The extent the trial's sidebar was last dragged to, in pixels. Undefined
|
|
79
|
+
* until someone moves the seam. */
|
|
80
|
+
sidebarWidth?: number;
|
|
81
|
+
/** What the title bar reads. Undefined — the state a trial opens in, and the
|
|
82
|
+
* one `setTitle(null)` returns it to — means the instrument's name. */
|
|
83
|
+
title?: string | null;
|
|
84
|
+
/** Which of the trial's collapsible sections are folded, keyed as
|
|
85
|
+
* `TrialChromeContext.collapsedSections` describes. A key that is absent
|
|
86
|
+
* takes the section's own default. */
|
|
87
|
+
collapsedSections?: Record<string, boolean>;
|
|
88
|
+
/** The marks on this trial's annotation targets, as `AnnotationsApi.toJSON`
|
|
89
|
+
* wrote them. Opaque here, and absent for a trial whose instrument declares
|
|
90
|
+
* no `annotations` — or declares its own `storage`. Not in `state`, which
|
|
91
|
+
* belongs to the instrument and is typed as such. */
|
|
92
|
+
annotations?: unknown;
|
|
93
|
+
undoStack: UndoStack;
|
|
94
|
+
}
|
|
95
|
+
/** A named, saved copy of a trial's config and state, restorable into any
|
|
96
|
+
* trial running the same instrument. */
|
|
97
|
+
interface SavedSnapshot {
|
|
98
|
+
id: string;
|
|
99
|
+
name: string;
|
|
100
|
+
trialId: string;
|
|
101
|
+
instrumentName: string;
|
|
102
|
+
config: unknown;
|
|
103
|
+
state: unknown;
|
|
104
|
+
savedAt: number;
|
|
105
|
+
}
|
|
106
|
+
/** `auto` follows the OS; the other two are an explicit choice. */
|
|
107
|
+
type LabMode = 'auto' | 'light' | 'dark';
|
|
108
|
+
/** Everything a lab persists: its trials, its saved snapshots, and the
|
|
109
|
+
* chosen color mode. */
|
|
110
|
+
interface LabStoreState {
|
|
111
|
+
trials: TrialRecord[];
|
|
112
|
+
savedSnapshots: SavedSnapshot[];
|
|
113
|
+
mode: LabMode;
|
|
114
|
+
/** The lab's tool slot — what a trial with no slot of its own resolves to. */
|
|
115
|
+
activeToolId: string | null;
|
|
116
|
+
/** Per-trial tile extents, keyed by trial id. Opaque here — the
|
|
117
|
+
* shape belongs to whatever lays the trials out. */
|
|
118
|
+
layout: Record<string, unknown>;
|
|
119
|
+
/** Sidebar sections torn out of their trial. A panel here is not rendered in
|
|
120
|
+
* its trial's sidebar; the workspace renders it instead. */
|
|
121
|
+
undockedPanels: UndockedPanels;
|
|
122
|
+
/** The instruments this store serializes, migrates and fills configs for.
|
|
123
|
+
* Null for a store built without them, whose trials look their instrument
|
|
124
|
+
* up on the lab instead. Not persisted. */
|
|
125
|
+
instruments: InstrumentList | null;
|
|
126
|
+
}
|
|
127
|
+
/** A change someone else made to one record: its new value, or `undefined`
|
|
128
|
+
* when it was deleted. */
|
|
129
|
+
type StorageChange = [key: string, value: unknown];
|
|
130
|
+
/** Where a lab persists itself: asynchronous keyed storage of
|
|
131
|
+
* structured-clone values, so IndexedDB, the URL, memory or a server can all
|
|
132
|
+
* back one. */
|
|
133
|
+
interface StorageAdapter {
|
|
134
|
+
/** `undefined` when the key is absent. */
|
|
135
|
+
get(key: string): Promise<unknown>;
|
|
136
|
+
list(prefix: string): Promise<[string, unknown][]>;
|
|
137
|
+
/** Rejects when the value did not land. */
|
|
138
|
+
set(key: string, value: unknown): Promise<void>;
|
|
139
|
+
delete(key: string): Promise<void>;
|
|
140
|
+
/** Reports writes under `prefix` made by anyone but this adapter — another
|
|
141
|
+
* tab, another instance, a server. Omit when the substrate cannot tell. */
|
|
142
|
+
subscribe?(prefix: string, on: (changes: StorageChange[]) => void): () => void;
|
|
143
|
+
}
|
|
144
|
+
/** What `useTrialState` hands an instrument: its state and config, with
|
|
145
|
+
* a setter for each. */
|
|
146
|
+
interface TrialStateHandle<TS, TC> {
|
|
147
|
+
state: TS;
|
|
148
|
+
setState: (next: TS | ((prev: TS) => TS)) => void;
|
|
149
|
+
config: TC;
|
|
150
|
+
/** Write one config value, by dotted path — `'grid.size'` for a leaf under
|
|
151
|
+
* an `f.group`, `'cellSize'` for one at the root. */
|
|
152
|
+
setConfig: <P extends ConfigPath<TC> & string>(path: P, value: ValueAtPath<TC, P>) => void;
|
|
153
|
+
}
|
|
154
|
+
/** Options for `createLabStore`, which knows nothing about storage. */
|
|
155
|
+
interface CreateLabStoreOptions {
|
|
156
|
+
/** A document already read and migrated — what `openLabStore` hands over. */
|
|
157
|
+
initial?: LabDocument;
|
|
158
|
+
initialMode?: LabMode;
|
|
159
|
+
/** Each instrument's default config, keyed by instrument name, used to fill
|
|
160
|
+
* the gaps in a stored one. A config saved before its schema grew a branch
|
|
161
|
+
* arrives holding that branch's defaults rather than `undefined`, and keeps
|
|
162
|
+
* whatever keys the schema has since stopped naming. */
|
|
163
|
+
configDefaults?: Record<string, () => unknown>;
|
|
164
|
+
/** Each instrument's `migrateConfig`, keyed by instrument name, run on a
|
|
165
|
+
* stored config before its defaults fill it. */
|
|
166
|
+
configMigrations?: Record<string, (stored: unknown) => unknown>;
|
|
167
|
+
/** How each instrument's state survives a reload. Hydration is the first
|
|
168
|
+
* thing `createLabStore` does, so these have to arrive with the store. */
|
|
169
|
+
serializers?: InstrumentSerializers;
|
|
170
|
+
/** The instruments to serialize, migrate and fill configs for. Given, it
|
|
171
|
+
* supplies all three and the three fields above are ignored. */
|
|
172
|
+
instruments?: InstrumentList;
|
|
173
|
+
}
|
|
174
|
+
/** What a store does per instrument on the way in and out. */
|
|
175
|
+
interface InstrumentHooks {
|
|
176
|
+
serializers: InstrumentSerializers;
|
|
177
|
+
configDefaults: Record<string, () => unknown>;
|
|
178
|
+
configMigrations: Record<string, (stored: unknown) => unknown>;
|
|
179
|
+
}
|
|
180
|
+
/** Per-instrument serialize/deserialize hooks, keyed by instrument name. An
|
|
181
|
+
* instrument whose state is already JSON-safe needs no entry. `deserialize`
|
|
182
|
+
* is handed the config the state was saved against — a trial's own for a
|
|
183
|
+
* reload, the snapshot's for a load — since a state rebuilt without it can
|
|
184
|
+
* disagree with the settings sitting next to it. */
|
|
185
|
+
type InstrumentSerializers = Record<string, {
|
|
186
|
+
serialize?: (state: unknown) => unknown;
|
|
187
|
+
deserialize?: (data: unknown, config: unknown) => unknown;
|
|
188
|
+
} | undefined>;
|
|
189
|
+
/** A trial as it is persisted: everything but the undo history, which is
|
|
190
|
+
* session-only. */
|
|
191
|
+
type SerializedTrial = Omit<TrialRecord, 'undoStack'>;
|
|
192
|
+
/** Everything a lab persists, under one key, at a known version. */
|
|
193
|
+
interface LabDocument {
|
|
194
|
+
version: number;
|
|
195
|
+
trials: SerializedTrial[];
|
|
196
|
+
saves: SavedSnapshot[];
|
|
197
|
+
layout: Record<string, unknown>;
|
|
198
|
+
/** Sidebar sections torn out of their trial, keyed by trial and section. */
|
|
199
|
+
undockedPanels: UndockedPanels;
|
|
200
|
+
mode: LabMode;
|
|
201
|
+
}
|
|
202
|
+
/** Migrates a document one version forward. Index `i` in the chain takes a
|
|
203
|
+
* version-`i` document to version `i + 1`. */
|
|
204
|
+
type Migration = (doc: Record<string, unknown>) => Record<string, unknown>;
|
|
205
|
+
|
|
36
206
|
/** A named position in a trial's chrome. Content is not a region — that is
|
|
37
|
-
* the instrument. */
|
|
207
|
+
* the instrument. The lab's own boxes are named in `LabRegion`. */
|
|
38
208
|
type TrialRegion = 'titlebar' | 'toolbar' | 'palette' | 'sidebar' | 'viewport' | 'status';
|
|
39
209
|
/** An icon component taking a pixel size, as `@weasel-js/ui` glyphs do. */
|
|
40
210
|
type IconComponent = ComponentType<{
|
|
41
211
|
size?: number;
|
|
42
212
|
}>;
|
|
43
|
-
/** A button in
|
|
44
|
-
|
|
213
|
+
/** A button in a toolbar. `TCtx` is the chrome context its `onActivate` is
|
|
214
|
+
* handed — a trial's, or the lab's for a lab-level bar. */
|
|
215
|
+
interface ToolbarItem<TCtx = TrialChromeContext> {
|
|
45
216
|
icon: IconComponent;
|
|
46
217
|
label: string;
|
|
47
218
|
/** Shown in the tooltip. Not bound here — the trial owns its keymap. */
|
|
@@ -54,10 +225,10 @@ interface ToolbarItem {
|
|
|
54
225
|
pressed?: boolean;
|
|
55
226
|
/** Render the label beside the glyph rather than only in the tooltip. */
|
|
56
227
|
showLabel?: boolean;
|
|
57
|
-
/** Handed the
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
onActivate: (ctx:
|
|
228
|
+
/** Handed the chrome context it was declared against, so a contribution can
|
|
229
|
+
* reach `ctx.saveSnapshot()` and the rest without the `render` escape. A
|
|
230
|
+
* zero-argument handler stays valid. */
|
|
231
|
+
onActivate: (ctx: TCtx) => void;
|
|
61
232
|
}
|
|
62
233
|
/** A selectable tool in the palette region. */
|
|
63
234
|
interface ToolItem {
|
|
@@ -79,12 +250,12 @@ interface SidebarSection {
|
|
|
79
250
|
body: ReactNode;
|
|
80
251
|
}
|
|
81
252
|
/** A control acting on the view of the trial, not on the trial. */
|
|
82
|
-
interface ViewportControl {
|
|
253
|
+
interface ViewportControl<TCtx = TrialChromeContext> {
|
|
83
254
|
icon: IconComponent;
|
|
84
255
|
label: string;
|
|
85
256
|
disabled?: boolean;
|
|
86
257
|
/** Handed the trial's chrome context, as `ToolbarItem.onActivate` is. */
|
|
87
|
-
onActivate: (ctx:
|
|
258
|
+
onActivate: (ctx: TCtx) => void;
|
|
88
259
|
}
|
|
89
260
|
/** A readout in the status bar. */
|
|
90
261
|
interface StatusReadout {
|
|
@@ -93,7 +264,7 @@ interface StatusReadout {
|
|
|
93
264
|
/** Tooltip. */
|
|
94
265
|
title?: string;
|
|
95
266
|
}
|
|
96
|
-
/** What every contribution shares. */
|
|
267
|
+
/** What every contribution shares, whichever chrome it is declared against. */
|
|
97
268
|
interface ContributionBase {
|
|
98
269
|
id: string;
|
|
99
270
|
/** Groups sort by first appearance; items sort within a group by
|
|
@@ -136,18 +307,46 @@ type TrialContribution = (ContributionBase & {
|
|
|
136
307
|
item?: never;
|
|
137
308
|
render: (ctx: TrialChromeContext) => ReactNode;
|
|
138
309
|
});
|
|
310
|
+
/**
|
|
311
|
+
* A tool slot a region can reflect and write. Both chrome contexts carry one:
|
|
312
|
+
* a trial's resolves to the lab's when its instrument declares no tools.
|
|
313
|
+
*/
|
|
314
|
+
interface ToolSlotContext {
|
|
315
|
+
activeToolId: string | null;
|
|
316
|
+
setActiveTool: (id: string) => void;
|
|
317
|
+
}
|
|
318
|
+
/** What a sidebar region reflects and writes: which sections are folded, and —
|
|
319
|
+
* where the chrome can tear a section out — where it goes. A trial supplies
|
|
320
|
+
* all four; a lab supplies the fold state only. */
|
|
321
|
+
interface SidebarSlotContext {
|
|
322
|
+
collapsedSections: Readonly<Record<string, boolean>>;
|
|
323
|
+
setSectionCollapsed: (key: string, collapsed: boolean) => void;
|
|
324
|
+
undockedPanels?: readonly string[];
|
|
325
|
+
undockPanel?: (sectionId: string, as?: 'tile' | 'floating') => void;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* A contribution as a region renderer sees it. A renderer checks the region
|
|
329
|
+
* name against its own and narrows `item` itself, which is what lets one
|
|
330
|
+
* renderer serve both chromes — the trial's and the lab's — without knowing
|
|
331
|
+
* which context it was handed.
|
|
332
|
+
*/
|
|
333
|
+
interface RegionContribution<TCtx> extends ContributionBase {
|
|
334
|
+
region: string;
|
|
335
|
+
item?: unknown;
|
|
336
|
+
render?: (ctx: TCtx) => ReactNode;
|
|
337
|
+
}
|
|
139
338
|
/**
|
|
140
339
|
* Everything a contribution can read about the trial it is being rendered
|
|
141
340
|
* into. Replaces the three separate slot contexts, which each carried a
|
|
142
341
|
* hand-picked subset.
|
|
143
342
|
*/
|
|
144
|
-
interface TrialChromeContext {
|
|
343
|
+
interface TrialChromeContext extends ToolSlotContext, SidebarSlotContext {
|
|
145
344
|
trialId: string;
|
|
146
345
|
instrumentName: string;
|
|
147
|
-
/** What the title bar reads, which is the instrument's
|
|
148
|
-
* calls `setTitle`. */
|
|
346
|
+
/** What the title bar reads, which is the instrument's title (or, lacking
|
|
347
|
+
* one, its name) until something calls `setTitle`. */
|
|
149
348
|
title: string;
|
|
150
|
-
/** Retitle this trial; `null` restores the instrument
|
|
349
|
+
/** Retitle this trial; `null` restores the instrument's title. Persisted with
|
|
151
350
|
* the trial, so a title survives a reload. */
|
|
152
351
|
setTitle: (title: string | null) => void;
|
|
153
352
|
isLastTrial: boolean;
|
|
@@ -189,10 +388,6 @@ interface TrialChromeContext {
|
|
|
189
388
|
clone: () => void;
|
|
190
389
|
reset: () => void;
|
|
191
390
|
close: () => void;
|
|
192
|
-
/** Resolved active tool: the trial's slot, or the lab's when the trial has
|
|
193
|
-
* none. Null when neither holds one. */
|
|
194
|
-
activeToolId: string | null;
|
|
195
|
-
setActiveTool: (id: string) => void;
|
|
196
391
|
}
|
|
197
392
|
|
|
198
393
|
/** A point on the magnified surface, in its own CSS pixels. */
|
|
@@ -245,7 +440,7 @@ interface LoupeCapability<TS = unknown, TC = unknown> {
|
|
|
245
440
|
/** Held for a momentary peek while the loupe is off. Default `'Alt'`; `null`
|
|
246
441
|
* turns hold-to-peek off. Matched against `KeyboardEvent.key`. */
|
|
247
442
|
peekKey?: string | null;
|
|
248
|
-
/** Called with the
|
|
443
|
+
/** Called with the color under the aim, wherever the surface can say. The
|
|
249
444
|
* canvas painter reads it back; a DOM loupe has no pixels to sample. */
|
|
250
445
|
onColorChange?: (hex: string) => void;
|
|
251
446
|
}
|
|
@@ -353,6 +548,18 @@ interface CanvasCapability<TS = unknown, TC = unknown> {
|
|
|
353
548
|
minZoom?: number;
|
|
354
549
|
maxZoom?: number;
|
|
355
550
|
}
|
|
551
|
+
/** Declares that an instrument's DOM is content of a fixed size, which the
|
|
552
|
+
* trial pans and zooms the way it does a canvas's layers. Not combined with
|
|
553
|
+
* `canvas`: there the DOM is an unzoomed overlay, and `canvas` wins. */
|
|
554
|
+
interface StageCapability {
|
|
555
|
+
/** The content's own size in CSS pixels, at zoom 1. */
|
|
556
|
+
size: ViewportSize;
|
|
557
|
+
/** Where the view starts. Omitted, the content opens centered and shrunk to
|
|
558
|
+
* fit (`fitStage`). A function is called once the stage knows its size. */
|
|
559
|
+
initialView?: ViewTransform | ((viewport: ViewportSize) => ViewTransform);
|
|
560
|
+
minZoom?: number;
|
|
561
|
+
maxZoom?: number;
|
|
562
|
+
}
|
|
356
563
|
/** Declares which of an instrument's layers the trial should offer
|
|
357
564
|
* show/hide controls for. */
|
|
358
565
|
interface LayerCapability {
|
|
@@ -391,7 +598,8 @@ type HitResult = {
|
|
|
391
598
|
layerId?: string;
|
|
392
599
|
pointId?: string;
|
|
393
600
|
};
|
|
394
|
-
/** A trial's camera.
|
|
601
|
+
/** A trial's camera. `zoom` is always positive and finite once labkit holds
|
|
602
|
+
* it — see `normalize2DView`. */
|
|
395
603
|
type ViewTransform = {
|
|
396
604
|
zoom: number;
|
|
397
605
|
pan: Point;
|
|
@@ -424,7 +632,10 @@ type DragFeedback = {
|
|
|
424
632
|
* is what makes the trial provide the corresponding chrome.
|
|
425
633
|
*/
|
|
426
634
|
interface Instrument<TS = unknown, TC = unknown, TItem = unknown> {
|
|
635
|
+
/** The id a trial record names its instrument by. */
|
|
427
636
|
name: string;
|
|
637
|
+
/** What a trial of this instrument and the add-trial menu read. Default: `name`. */
|
|
638
|
+
title?: string;
|
|
428
639
|
defaultConfig: () => TC;
|
|
429
640
|
initialState: (config: TC) => TS;
|
|
430
641
|
/** The instrument's config, declared once: values, types and controls.
|
|
@@ -438,9 +649,16 @@ interface Instrument<TS = unknown, TC = unknown, TItem = unknown> {
|
|
|
438
649
|
* layers rather than instead of them; return `null` for canvas only. */
|
|
439
650
|
render: (ctx: RenderContext<TS, TC>) => ReactNode;
|
|
440
651
|
onConfigChange?: (config: TC, prev: TC, state: TS) => TS;
|
|
652
|
+
/** Moves a stored config's values to where the current schema keeps them —
|
|
653
|
+
* `gridSize` to `grid.size` — before the defaults fill its gaps, so it
|
|
654
|
+
* returns only what it moved and leaves the rest to them. It runs on every
|
|
655
|
+
* read of a stored config, including one already moved, so it must hand
|
|
656
|
+
* back a current config unchanged. */
|
|
657
|
+
migrateConfig?: (stored: unknown) => unknown;
|
|
441
658
|
serialize?: (state: TS) => unknown;
|
|
442
659
|
deserialize?: (data: unknown, config: TC) => TS;
|
|
443
660
|
canvas?: CanvasCapability<TS, TC>;
|
|
661
|
+
stage?: StageCapability;
|
|
444
662
|
layers?: LayerCapability;
|
|
445
663
|
dragDrop?: DragDropCapability<TS, TC>;
|
|
446
664
|
undo?: UndoCapability;
|
|
@@ -479,9 +697,10 @@ interface AnnotationStoreOptions {
|
|
|
479
697
|
targets: () => readonly AnnotationTargetInfo[];
|
|
480
698
|
/** Serialized scenes from a previous `toJSON`, keyed by target. */
|
|
481
699
|
restore?: Readonly<Record<string, unknown>>;
|
|
482
|
-
/** The instrument's vocabulary, so an export draws a mark in the
|
|
483
|
-
* status gives it.
|
|
484
|
-
|
|
700
|
+
/** The instrument's vocabulary, so an export draws a mark in the color its
|
|
701
|
+
* status gives it. A thunk is read at each capture, for a vocabulary that
|
|
702
|
+
* changes under a store built once. */
|
|
703
|
+
meaning?: AnnotationMeaning | (() => AnnotationMeaning | undefined);
|
|
485
704
|
/** The trial's live config. A getter for the same reason `targets` is: the
|
|
486
705
|
* store is built once and the config changes under it. */
|
|
487
706
|
config?: () => unknown;
|
|
@@ -535,7 +754,7 @@ interface AnnotationStatus {
|
|
|
535
754
|
id: string;
|
|
536
755
|
label: string;
|
|
537
756
|
/** What a mark in this status is drawn in. Omitted, it takes the default
|
|
538
|
-
* mark
|
|
757
|
+
* mark color — a status is allowed to be a label and nothing more. */
|
|
539
758
|
color?: string;
|
|
540
759
|
}
|
|
541
760
|
/** The optional meaning tier: what a mark means, as opposed to where it is.
|
|
@@ -651,9 +870,10 @@ interface AnnotationTarget extends AnnotationTargetInfo {
|
|
|
651
870
|
/** Where an instrument keeps its own marks. Declaring this means labkit never
|
|
652
871
|
* writes its trial slot — for an instrument whose marks belong in a format it
|
|
653
872
|
* already owns. Both halves are called outside React; `save` is already
|
|
654
|
-
* debounced by the time it arrives.
|
|
873
|
+
* debounced by the time it arrives. A trial shows an empty body until `load`
|
|
874
|
+
* settles. */
|
|
655
875
|
interface AnnotationStorage {
|
|
656
|
-
load: () => SerializedAnnotations | null | undefined
|
|
876
|
+
load: () => Promise<SerializedAnnotations | null | undefined>;
|
|
657
877
|
save: (doc: SerializedAnnotations) => void;
|
|
658
878
|
}
|
|
659
879
|
/** Declares that an instrument accepts marks: which regions take them,
|
|
@@ -663,6 +883,8 @@ interface AnnotationsCapability<TS = unknown, TC = unknown> {
|
|
|
663
883
|
* is called once per trial, and its targets are that trial's own. */
|
|
664
884
|
targets: (state: TS, config: TC, trial: TrialInfo) => readonly AnnotationTarget[];
|
|
665
885
|
meaning?: AnnotationMeaning;
|
|
886
|
+
/** An instrument replacing this one under a live trial must pass the same
|
|
887
|
+
* object: the trial keeps the marks it loaded from the first. */
|
|
666
888
|
storage?: AnnotationStorage;
|
|
667
889
|
/** Fires after every finished export, labkit's own chrome included. A
|
|
668
890
|
* notification, not an interception: a host wanting its own flow calls
|
|
@@ -758,5 +980,5 @@ declare function fracContains(box: FracRect, pt: FracPoint, tol?: number): boole
|
|
|
758
980
|
* answers false for two rects that merely overlap. */
|
|
759
981
|
declare function fracEncloses(outer: FracRect, inner: FracRect): boolean;
|
|
760
982
|
|
|
761
|
-
export { LOUPE_DEFAULTS as
|
|
762
|
-
export type {
|
|
983
|
+
export { DEFAULT_FRAME as D, LOUPE_DEFAULTS as a7, resolveLoupe as aA, roundFrac as aB, worldToFrac as aC, annotationsFromJSON as au, createAnnotationScene as av, createAnnotationStore as aw, fracContains as ax, fracEncloses as ay, fracToWorld as az, applyCamera as b, dockPanel as j, panelKey as p, resolveFrame as r, undockPanel as u };
|
|
984
|
+
export type { CanvasCapability as $, AnnotationData as A, AnnotationTarget as B, CreateLabStoreOptions as C, AnnotationsApi as E, AnnotationsCapability as F, TrialInfo as G, TrialTool as H, InstrumentSerializers as I, AnnotationKind as J, Instrument as K, LabDocument as L, Migration as M, TrialContribution as N, Annotation as O, PaletteItem as P, AnnotationInit as Q, AnnotationPatch as R, SerializedTrial as S, TrialRecord as T, UndoStack as U, ViewportSize as V, WorldFrame as W, AnnotationQuery as X, AnnotationStatus as Y, AnnotationStoreOptions as Z, AnnotationTargetInfo as _, WorldSpec as a, CanvasLayer as a0, CaptureDeps as a1, ContributionBase as a2, FracPoint as a3, FracRect as a4, HitResult as a5, IconComponent as a6, LayerCapability as a8, LayerDescriptor as a9, LoupePoint as aD, LoupeMode as aE, LoupeCapability as aa, LoupeDeclaration as ab, LoupeRenderArgs as ac, RegionContribution as ad, RenderContext as ae, ResolvedLoupe as af, SerializedAnnotations as ag, SidebarSection as ah, SidebarSlotContext as ai, StageCapability as aj, StatusReadout as ak, SystemEvent as al, ToolCapability as am, ToolItem as an, ToolSlotContext as ao, ToolbarItem as ap, TrialChromeContext as aq, TrialRegion as ar, UndoCapability as as, ViewportControl as at, StorageAdapter as c, LabStoreState as d, SavedSnapshot as e, StorageChange as f, TrialStateHandle as g, UndockedPanel as h, UndockedPanels as i, ViewTransform as k, Point as l, DragFeedback as m, DragDropCapability as n, LabMode as o, InstrumentList as q, InstrumentHooks as s, WorldRect as t, AnnotationMeaning as v, MarkScene as w, CaptureSource as x, CaptureOptions as y, CaptureResult as z };
|