@weasel-js/labkit 1.5.0 → 1.5.2
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 +41 -4
- package/dist/_dts/{CanvasStackContext-CqkxkFJW.d.ts → CanvasStackContext-CMMo180l.d.ts} +1 -1
- package/dist/_dts/{PrefsForm.d-DDHFANkc.d.ts → PrefsForm.d-DPy9I8It.d.ts} +4 -0
- package/dist/_dts/Select.d-ZTiwzYyT.d.ts +479 -0
- package/dist/_dts/{frac-8R6UmlvN.d.ts → frac-B4hFJMiN.d.ts} +54 -6
- package/dist/_dts/{index-retnAut7.d.ts → index-8LiKe3A-.d.ts} +77 -10
- package/dist/_dts/{index-B8uu2ba7.d.ts → index-DlKolEIZ.d.ts} +20 -8
- package/dist/_dts/{types-Ca2LCnPq.d.ts → types-Db-3NNk1.d.ts} +28 -4
- package/dist/_dts/{usePanZoom-nS798rOF.d.ts → usePanZoom-SSTZe410.d.ts} +4 -47
- package/dist/_dts/{useTrialState-jcx_TdU6.d.ts → useTrialState-pnAE-UBD.d.ts} +1 -1
- package/dist/canvas/index.d.ts +5 -5
- package/dist/canvas/index.js +1 -1
- package/dist/chrome/index.d.ts +4 -4
- package/dist/chrome/index.js +7 -7
- package/dist/chunk-6BUQ77ZV.js +18 -0
- package/dist/chunk-6BUQ77ZV.js.map +1 -0
- package/dist/{chunk-KJALCDWE.js → chunk-753Y6HE6.js} +9 -9
- package/dist/chunk-753Y6HE6.js.map +1 -0
- package/dist/{chunk-6ZDGOZQV.js → chunk-A2ACAGW6.js} +72 -20
- package/dist/chunk-A2ACAGW6.js.map +1 -0
- package/dist/{chunk-NS64DXMN.js → chunk-CKUKF72R.js} +44 -21
- package/dist/chunk-CKUKF72R.js.map +1 -0
- package/dist/{chunk-MUKOW3TC.js → chunk-D35PFTS7.js} +4 -4
- package/dist/{chunk-MUKOW3TC.js.map → chunk-D35PFTS7.js.map} +1 -1
- package/dist/{chunk-UU3NJO6N.js → chunk-EMXAIRUJ.js} +3 -3
- package/dist/{chunk-UU3NJO6N.js.map → chunk-EMXAIRUJ.js.map} +1 -1
- package/dist/{chunk-ZPH5WTWF.js → chunk-ERFMC6WU.js} +142 -66
- package/dist/chunk-ERFMC6WU.js.map +1 -0
- package/dist/chunk-FKLT3ITC.js +590 -0
- package/dist/chunk-FKLT3ITC.js.map +1 -0
- package/dist/{chunk-QLSV2N3G.js → chunk-IJJUK4FU.js} +25 -13
- package/dist/chunk-IJJUK4FU.js.map +1 -0
- package/dist/{chunk-EKIICY6X.js → chunk-KJCUERQF.js} +37 -25
- package/dist/chunk-KJCUERQF.js.map +1 -0
- package/dist/chunk-LASCBWW7.js +13456 -0
- package/dist/chunk-LASCBWW7.js.map +1 -0
- package/dist/{chunk-67SJLMC7.js → chunk-LRI3MBGR.js} +45 -12
- package/dist/chunk-LRI3MBGR.js.map +1 -0
- package/dist/chunk-REOBGYMC.js +150 -0
- package/dist/chunk-REOBGYMC.js.map +1 -0
- package/dist/{chunk-PGETSEDK.js → chunk-TT5C5S6R.js} +11 -90
- package/dist/chunk-TT5C5S6R.js.map +1 -0
- package/dist/{chunk-NDRYLVOW.js → chunk-Z76KEZAX.js} +2 -2
- package/dist/chunk-Z76KEZAX.js.map +1 -0
- package/dist/config/index.d.ts +62 -19
- package/dist/config/index.js +6 -4
- package/dist/controls/index.d.ts +3 -3
- package/dist/controls/index.js +4 -4
- package/dist/dragdrop/index.d.ts +3 -3
- package/dist/index.d.ts +230 -62
- package/dist/index.js +1367 -1698
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +4 -4
- package/dist/layers/index.js +2 -2
- package/dist/loupe/index.d.ts +67 -16
- package/dist/loupe/index.js +1 -1
- package/dist/passthrough/weasel-ui.d.ts +57 -230
- package/dist/passthrough/weasel-ui.js +1 -1
- package/dist/primitives/index.d.ts +3 -1
- package/dist/primitives/index.js +3 -3
- package/dist/state/index.d.ts +5 -5
- package/dist/state/index.js +5 -3
- package/dist/state/index.js.map +1 -1
- package/dist/styles.css +266 -57
- package/dist/surface/index.js +1 -1
- package/dist/ui/layers/index.js +1 -1
- package/dist/undo/index.d.ts +3 -3
- package/package.json +9 -9
- package/src/annotations/AnnotationOverlay.tsx +3 -0
- package/src/annotations/Annotations.less +4 -0
- package/src/annotations/Annotations.overlay.test.tsx +20 -0
- package/src/annotations/svgNodes.ts +1 -0
- package/src/annotations/toolMap.test.ts +9 -5
- package/src/annotations/toolMap.ts +3 -0
- package/src/canvas/CanvasStack.stories.tsx +1 -1
- package/src/canvas/index.ts +0 -9
- package/src/canvas/usePanZoom.test.ts +28 -0
- package/src/canvas/usePanZoom.ts +11 -4
- package/src/chrome/ChromeRegions.stories.tsx +1 -1
- package/src/chrome/LabChrome.tsx +21 -5
- package/src/chrome/builtins.test.ts +1 -0
- package/src/chrome/builtins.tsx +1 -0
- package/src/chrome/index.ts +1 -0
- package/src/chrome/labTypes.ts +3 -3
- package/src/chrome/regions/PaletteRegion.test.tsx +18 -0
- package/src/chrome/regions/PaletteRegion.tsx +6 -10
- package/src/chrome/regions/SidebarRegion.stories.tsx +44 -0
- package/src/chrome/regions/TitleBarRegion.tsx +1 -1
- package/src/chrome/regions/ToolbarRegion.tsx +11 -7
- package/src/chrome/types.ts +11 -2
- 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 +38 -0
- package/src/config/builder.ts +55 -11
- package/src/config/index.ts +3 -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 +32 -2
- package/src/config/useResolvedConfig.ts +26 -0
- package/src/controls/AutoControls.stories.tsx +88 -0
- package/src/controls/ControlPanel.less +23 -0
- package/src/controls/ControlPanel.stories.tsx +83 -1
- package/src/controls/ControlPanel.test.tsx +457 -4
- package/src/controls/ControlPanel.tsx +534 -73
- package/src/index.test.ts +0 -2
- package/src/index.ts +33 -11
- package/src/instrument/serializers.ts +29 -1
- package/src/lab/Lab.chrome.test.tsx +16 -0
- package/src/lab/Lab.stories.tsx +1 -1
- package/src/lab/Lab.surface.test.tsx +7 -2
- package/src/lab/Lab.test.tsx +79 -1
- package/src/lab/Lab.tsx +124 -16
- package/src/lab/LabContext.ts +8 -0
- package/src/lab/LabFit.stories.tsx +4 -4
- package/src/lab/LabFullChrome.stories.tsx +1 -1
- package/src/lab/LabHeader.tsx +0 -1
- package/src/lab/LabRoot.test.tsx +56 -0
- package/src/lab/LabRoot.tsx +47 -0
- package/src/lab/LabShell.less +24 -3
- package/src/lab/LabShell.stories.tsx +50 -1
- package/src/lab/LabShell.tsx +18 -27
- package/src/lab/LabSwitcher.less +10 -10
- 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 -1
- package/src/lab/index.ts +2 -0
- package/src/layers/LayerList.less +1 -1
- package/src/layers/LayerList.stories.tsx +2 -2
- package/src/loupe/AGENTS.md +16 -4
- package/src/loupe/LoupeGestures.tsx +67 -0
- package/src/loupe/TrialLoupe.tsx +43 -28
- package/src/loupe/index.ts +4 -0
- package/src/loupe/loupeActions.ts +98 -0
- package/src/loupe/useLoupe.test.tsx +59 -2
- package/src/loupe/useLoupe.ts +28 -48
- package/src/passthrough/weasel-ui.ts +3 -3
- package/src/primitives/FloatingPanel.stories.tsx +1 -1
- package/src/primitives/FpsMeter.less +6 -1
- package/src/primitives/FpsMeter.stories.tsx +1 -1
- package/src/primitives/FpsMeter.test.tsx +12 -4
- package/src/primitives/FpsMeter.tsx +6 -1
- package/src/primitives/JobProgress.stories.tsx +2 -2
- 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.test.tsx +25 -0
- package/src/primitives/Split.tsx +36 -20
- package/src/primitives/StatusBar.less +6 -0
- package/src/primitives/StatusBar.stories.tsx +1 -1
- package/src/primitives/Toolbar.stories.tsx +1 -1
- 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 +4 -4
- package/src/state/store.test.ts +65 -0
- package/src/state/store.ts +32 -8
- package/src/state/types.ts +31 -2
- package/src/state/useTrialState.test.tsx +85 -0
- package/src/state/useTrialState.ts +9 -1
- package/src/state/view.test.ts +4 -4
- package/src/styles.less +1 -0
- package/src/surface/index.ts +1 -1
- package/src/surface/useSurfaceTile.test.tsx +1 -1
- package/src/surface/useTiledSurface.test.tsx +65 -10
- package/src/surface/useTiledSurface.ts +23 -11
- package/src/theme/Interstellar.stories.tsx +2 -2
- package/src/theme/interstellar.test.ts +16 -5
- package/src/theme/interstellar.theme.json +180 -0
- package/src/theme/interstellar.ts +8 -5
- package/src/tools/labTool.ts +3 -3
- package/src/trial/Trial.annotations.test.tsx +13 -4
- package/src/trial/Trial.canvas.test.tsx +36 -0
- package/src/trial/Trial.config.test.tsx +75 -2
- package/src/trial/Trial.less +4 -0
- package/src/trial/Trial.stories.tsx +4 -1
- package/src/trial/Trial.test.tsx +3 -0
- package/src/trial/Trial.tsx +21 -14
- package/src/trial/TrialChrome.tsx +17 -3
- package/src/trial/trialOps.test.ts +101 -1
- package/src/trial/trialOps.ts +62 -4
- package/dist/_dts/ToggleBar.d-DbHoWKIA.d.ts +0 -116
- package/dist/chunk-67SJLMC7.js.map +0 -1
- package/dist/chunk-6ZDGOZQV.js.map +0 -1
- package/dist/chunk-E44SU6XS.js +0 -50
- package/dist/chunk-E44SU6XS.js.map +0 -1
- package/dist/chunk-EKIICY6X.js.map +0 -1
- package/dist/chunk-KJALCDWE.js.map +0 -1
- package/dist/chunk-NDRYLVOW.js.map +0 -1
- package/dist/chunk-NS64DXMN.js.map +0 -1
- package/dist/chunk-PGETSEDK.js.map +0 -1
- package/dist/chunk-QLSV2N3G.js.map +0 -1
- package/dist/chunk-S2SHZRD7.js +0 -7681
- package/dist/chunk-S2SHZRD7.js.map +0 -1
- package/dist/chunk-TQBBYTAF.js +0 -280
- package/dist/chunk-TQBBYTAF.js.map +0 -1
- package/dist/chunk-ZPH5WTWF.js.map +0 -1
- package/src/canvas/useOrbit.test.ts +0 -167
- package/src/canvas/useOrbit.ts +0 -156
- package/src/theme/interstellar.tokens.json +0 -61
package/src/index.test.ts
CHANGED
|
@@ -72,8 +72,6 @@ describe('surface, job and orbit entry points', () => {
|
|
|
72
72
|
expect(typeof kit.annotationsFromJSON).toBe('function');
|
|
73
73
|
expect(typeof kit.seenFrom).toBe('function');
|
|
74
74
|
expect(typeof kit.fracToWorld).toBe('function');
|
|
75
|
-
expect(typeof kit.useOrbit).toBe('function');
|
|
76
|
-
expect(typeof kit.orbitAfterDrag).toBe('function');
|
|
77
75
|
expect(typeof kit.usePanZoom).toBe('function');
|
|
78
76
|
expect(typeof kit.useJob).toBe('function');
|
|
79
77
|
expect(typeof kit.as2DView).toBe('function');
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
// The property-panel family, LayerStack, ToggleBar and Button live in
|
|
1
|
+
// The property-panel family, LayerStack, Input, Select, TokenPanel, ToggleBar and Button live in
|
|
2
2
|
// `@weasel-js/ui`; labkit re-exports them so chrome built on labkit needs no
|
|
3
3
|
// direct ui dependency. Named, not `export *` — a star re-export of an external
|
|
4
4
|
// package emits no binding in the bundle.
|
|
5
|
+
// The playback glyphs ride along for the same reason: chrome that labels a control with
|
|
6
|
+
// one should not need `@weasel-js/ui` in its manifest to draw a play button.
|
|
5
7
|
export type {
|
|
6
8
|
ButtonProps,
|
|
7
9
|
ButtonSize,
|
|
@@ -13,6 +15,9 @@ export type {
|
|
|
13
15
|
EffectCardListItem,
|
|
14
16
|
EffectCardListProps,
|
|
15
17
|
EffectCardProps,
|
|
18
|
+
IconProps,
|
|
19
|
+
InputProps,
|
|
20
|
+
JogProps,
|
|
16
21
|
LayerStackItem,
|
|
17
22
|
LayerStackProps,
|
|
18
23
|
NumberRowProps,
|
|
@@ -25,6 +30,7 @@ export type {
|
|
|
25
30
|
PropertyRowProps,
|
|
26
31
|
PropertyRowVariant,
|
|
27
32
|
PropertySpanProps,
|
|
33
|
+
SelectProps,
|
|
28
34
|
SelectRowProps,
|
|
29
35
|
SliderRowProps,
|
|
30
36
|
SubpanelProps,
|
|
@@ -34,27 +40,48 @@ export type {
|
|
|
34
40
|
ToggleBarSize,
|
|
35
41
|
ToggleBarVariant,
|
|
36
42
|
ToggleRowProps,
|
|
43
|
+
TokenCategory,
|
|
44
|
+
TokenEntry,
|
|
45
|
+
TokenPanelProps,
|
|
46
|
+
TokenScale,
|
|
47
|
+
TokenScaleRule,
|
|
37
48
|
} from '@weasel-js/ui';
|
|
38
49
|
export {
|
|
39
50
|
Button,
|
|
40
51
|
CheckboxRow,
|
|
52
|
+
CloseIcon,
|
|
53
|
+
CollapseIcon,
|
|
41
54
|
ColorRow,
|
|
42
55
|
CurveField,
|
|
43
56
|
EffectCard,
|
|
44
57
|
EffectCardList,
|
|
58
|
+
ExpandIcon,
|
|
59
|
+
Icon,
|
|
60
|
+
Input,
|
|
61
|
+
inferTokenType,
|
|
62
|
+
Jog,
|
|
45
63
|
LayerStack,
|
|
46
64
|
NumberRow,
|
|
65
|
+
PauseIcon,
|
|
66
|
+
PlayIcon,
|
|
47
67
|
PropertyGroup,
|
|
48
68
|
PropertyList,
|
|
49
69
|
PropertyPanel,
|
|
50
70
|
PropertyRow,
|
|
51
71
|
PropertySpan,
|
|
72
|
+
refitScale,
|
|
73
|
+
ResetIcon,
|
|
74
|
+
Select,
|
|
52
75
|
SelectRow,
|
|
53
76
|
SliderRow,
|
|
77
|
+
StepBackIcon,
|
|
78
|
+
StepForwardIcon,
|
|
79
|
+
StopIcon,
|
|
54
80
|
Subpanel,
|
|
55
81
|
TextRow,
|
|
56
82
|
ToggleBar,
|
|
57
83
|
ToggleRow,
|
|
84
|
+
TokenPanel,
|
|
58
85
|
} from '@weasel-js/ui';
|
|
59
86
|
export type {
|
|
60
87
|
Annotation,
|
|
@@ -124,13 +151,9 @@ export type {
|
|
|
124
151
|
CanvasStackContextValue,
|
|
125
152
|
CanvasStackProps,
|
|
126
153
|
CanvasStackSurface,
|
|
127
|
-
OrbitHandlers,
|
|
128
|
-
OrbitView,
|
|
129
154
|
PanZoomHandlers,
|
|
130
155
|
StageProps,
|
|
131
|
-
UseOrbitOptions,
|
|
132
156
|
UsePanZoomOptions,
|
|
133
|
-
Vec3,
|
|
134
157
|
ViewportSize,
|
|
135
158
|
WorldFrame,
|
|
136
159
|
WorldSpec,
|
|
@@ -141,22 +164,18 @@ export {
|
|
|
141
164
|
CanvasStack,
|
|
142
165
|
CanvasStackContext,
|
|
143
166
|
centerOn,
|
|
144
|
-
clampPitch,
|
|
145
167
|
DEFAULT_FRAME,
|
|
146
168
|
fitStage,
|
|
147
|
-
orbitAfterDrag,
|
|
148
|
-
orbitAfterWheel,
|
|
149
|
-
PITCH_LIMIT,
|
|
150
169
|
resolveFrame,
|
|
151
170
|
Stage,
|
|
152
171
|
screenToWorld,
|
|
153
|
-
useOrbit,
|
|
154
172
|
usePanZoom,
|
|
155
173
|
worldToScreen,
|
|
156
|
-
wrapYaw,
|
|
157
174
|
zoomAt,
|
|
158
175
|
} from './canvas';
|
|
159
176
|
export * from './chrome';
|
|
177
|
+
export { type Auto, auto, isAuto } from './config/auto';
|
|
178
|
+
export { autoPathsOf, resolveAutoConfig } from './config/autoConfig';
|
|
160
179
|
export {
|
|
161
180
|
BaseNode,
|
|
162
181
|
BooleanNode,
|
|
@@ -205,6 +224,7 @@ export type {
|
|
|
205
224
|
ValueAtPath,
|
|
206
225
|
} from './config/types';
|
|
207
226
|
export { useConfigSchema } from './config/useConfigSchema';
|
|
227
|
+
export { useResolvedConfig } from './config/useResolvedConfig';
|
|
208
228
|
export { isLeafVisible } from './config/visible';
|
|
209
229
|
export { ControlPanel } from './controls/ControlPanel';
|
|
210
230
|
export type {
|
|
@@ -246,6 +266,7 @@ export type { LayerListProps, LayerTreeNode } from './layers';
|
|
|
246
266
|
export { LayerList } from './layers';
|
|
247
267
|
export * from './loupe';
|
|
248
268
|
export * from './primitives';
|
|
269
|
+
export { SPECIMEN_SECTIONS, Specimen } from './specimen/Specimen';
|
|
249
270
|
export {
|
|
250
271
|
createIndexedDbAdapter,
|
|
251
272
|
createMemoryAdapter,
|
|
@@ -270,6 +291,7 @@ export { Persistence, type PersistenceProps } from './state/Persistence';
|
|
|
270
291
|
export type { RecordCache, RecordChange } from './state/records';
|
|
271
292
|
export type {
|
|
272
293
|
CreateLabStoreOptions,
|
|
294
|
+
LabDensity,
|
|
273
295
|
LabDocument,
|
|
274
296
|
LabMode,
|
|
275
297
|
LabStoreState,
|
|
@@ -1,5 +1,33 @@
|
|
|
1
|
+
import { resolveConfigSchema } from '../config/resolve';
|
|
2
|
+
import type { ResolvedConfig } from '../config/types';
|
|
1
3
|
import type { InstrumentSerializers } from '../state/types';
|
|
2
|
-
import type { InstrumentList } from './types';
|
|
4
|
+
import type { Instrument, InstrumentList } from './types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* One instrument's config schema, resolved without the lab's rules — which
|
|
8
|
+
* neither the store nor a trial composed outside a lab has access to. No rule
|
|
9
|
+
* can reach `autoResolve`, `unpinned` or `manual`, so for reading those the
|
|
10
|
+
* two resolutions agree; anything reading the rendered leaves wants
|
|
11
|
+
* `useConfigSchema` instead. `undefined` for a legacy `configSchema()`
|
|
12
|
+
* instrument, which declares no auto annotations at all.
|
|
13
|
+
*/
|
|
14
|
+
export function unruledConfigSchema(
|
|
15
|
+
// biome-ignore lint/suspicious/noExplicitAny: instruments are stored contravariantly; see InstrumentList
|
|
16
|
+
instrument: Instrument<any, any, any>, // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
17
|
+
): ResolvedConfig | undefined {
|
|
18
|
+
return instrument.config ? resolveConfigSchema(instrument.config, []) : undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Each instrument's unruled config schema, keyed by name — what the store
|
|
22
|
+
* reads a leaf's auto annotations off. */
|
|
23
|
+
export function configSchemasOf(instruments: InstrumentList): Record<string, ResolvedConfig> {
|
|
24
|
+
const out: Record<string, ResolvedConfig> = {};
|
|
25
|
+
for (const instrument of instruments) {
|
|
26
|
+
const schema = unruledConfigSchema(instrument);
|
|
27
|
+
if (schema) out[instrument.name] = schema;
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
3
31
|
|
|
4
32
|
/**
|
|
5
33
|
* Each instrument's default config, keyed by name — what `createLabStore`
|
|
@@ -62,4 +62,20 @@ describe('<Lab> chrome', () => {
|
|
|
62
62
|
render(<Lab title="T" instruments={[withChrome]} defaultInstrument="Bare" />);
|
|
63
63
|
expect(screen.getByText('ready')).toBeInTheDocument();
|
|
64
64
|
});
|
|
65
|
+
|
|
66
|
+
it('renders an aside contribution in its own pane, after the workspace', () => {
|
|
67
|
+
render(
|
|
68
|
+
<Lab
|
|
69
|
+
title="T"
|
|
70
|
+
instruments={[bare]}
|
|
71
|
+
defaultInstrument="Bare"
|
|
72
|
+
labChrome={[{ id: 'notes', region: 'aside', item: { title: 'Notes', body: <p>jot</p> } }]}
|
|
73
|
+
/>,
|
|
74
|
+
);
|
|
75
|
+
const body = screen.getByText('jot');
|
|
76
|
+
const trial = screen.getByRole('region', { name: /trial/i });
|
|
77
|
+
expect(body.closest('.lk-lab__aside')).not.toBeNull();
|
|
78
|
+
expect(trial.compareDocumentPosition(body) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
|
79
|
+
expect(screen.getByRole('separator', { name: /Lab aside/ })).toBeInTheDocument();
|
|
80
|
+
});
|
|
65
81
|
});
|
package/src/lab/Lab.stories.tsx
CHANGED
|
@@ -12,7 +12,12 @@ import type { ReactNode } from 'react';
|
|
|
12
12
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
13
13
|
import { defineInstrument } from '../instrument/defineInstrument';
|
|
14
14
|
import { SurfaceContext } from '../surface/SurfaceContext';
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
useSurfaceCanvas,
|
|
17
|
+
useSurfaceOptional,
|
|
18
|
+
useSurfaceTile,
|
|
19
|
+
useTileId,
|
|
20
|
+
} from '../surface/useSurfaceTile';
|
|
16
21
|
import type { SurfaceFrame, SurfaceHandle } from '../surface/useTiledSurface';
|
|
17
22
|
import { useTiledSurface } from '../surface/useTiledSurface';
|
|
18
23
|
import { Lab } from './Lab';
|
|
@@ -203,7 +208,7 @@ describe('<Lab> tile round trip', () => {
|
|
|
203
208
|
});
|
|
204
209
|
|
|
205
210
|
describe('<Lab> pages', () => {
|
|
206
|
-
it(
|
|
211
|
+
it("gives the title a way back to the project's other labs", () => {
|
|
207
212
|
const { getByRole } = render(
|
|
208
213
|
<Lab
|
|
209
214
|
instruments={[probeInstrument]}
|
package/src/lab/Lab.test.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { act, render, screen, waitFor } from '@testing-library/react';
|
|
1
|
+
import { act, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
|
|
2
2
|
import { type ReactNode, useEffect } from 'react';
|
|
3
3
|
import { describe, expect, it } from 'vitest';
|
|
4
4
|
import type { Instrument } from '../instrument/types';
|
|
@@ -238,3 +238,81 @@ describe('instruments that change while mounted', () => {
|
|
|
238
238
|
expect(mounts).toBe(1);
|
|
239
239
|
});
|
|
240
240
|
});
|
|
241
|
+
|
|
242
|
+
describe('focused trial', () => {
|
|
243
|
+
const regions = () => screen.getAllByRole('region', { name: /trial/i });
|
|
244
|
+
const framed: Instrument = {
|
|
245
|
+
...stub,
|
|
246
|
+
name: 'Framed',
|
|
247
|
+
render: () => <iframe title="story frame" />,
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
it('is the only trial until another opens, then the one that opened', () => {
|
|
251
|
+
mountLab();
|
|
252
|
+
expect(labRef?.focusedTrialId).toBe(labRef?.trials[0]?.id);
|
|
253
|
+
act(() => labRef?.addTrial('StubB'));
|
|
254
|
+
expect(labRef?.focusedTrialId).toBe(labRef?.trials[1]?.id);
|
|
255
|
+
act(() => labRef?.cloneTrial(labRef?.trials[0]?.id ?? ''));
|
|
256
|
+
expect(labRef?.focusedTrialId).toBe(labRef?.trials[1]?.id);
|
|
257
|
+
expect(labRef?.trials.map((t) => t.instrumentName)).toEqual(['Stub', 'Stub', 'StubB']);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('moves to a trial a pointer goes down in', () => {
|
|
261
|
+
mountLab();
|
|
262
|
+
act(() => labRef?.addTrial('StubB'));
|
|
263
|
+
fireEvent.pointerDown(within(regions()[0] as HTMLElement).getByTestId('stub-content'));
|
|
264
|
+
expect(labRef?.focusedTrialId).toBe(labRef?.trials[0]?.id);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('moves to a trial focus moves into', () => {
|
|
268
|
+
mountLab();
|
|
269
|
+
act(() => labRef?.addTrial('StubB'));
|
|
270
|
+
act(() => (regions()[0] as HTMLElement).focus());
|
|
271
|
+
expect(labRef?.focusedTrialId).toBe(labRef?.trials[0]?.id);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('moves to a trial whose frame takes focus, which fires nothing in the lab’s document', async () => {
|
|
275
|
+
mountLab({ instruments: [framed, stubB], defaultInstrument: 'Framed' });
|
|
276
|
+
act(() => labRef?.addTrial('StubB'));
|
|
277
|
+
act(() => screen.getByTitle('story frame').focus());
|
|
278
|
+
fireEvent.blur(window);
|
|
279
|
+
await waitFor(() => expect(labRef?.focusedTrialId).toBe(labRef?.trials[0]?.id));
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('falls back to the first trial when the focused one closes', () => {
|
|
283
|
+
mountLab();
|
|
284
|
+
act(() => labRef?.addTrial('StubB'));
|
|
285
|
+
act(() => labRef?.closeTrial(labRef?.trials[1]?.id ?? ''));
|
|
286
|
+
expect(labRef?.focusedTrialId).toBe(labRef?.trials[0]?.id);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('is marked on its trial only when there is more than one', () => {
|
|
290
|
+
mountLab();
|
|
291
|
+
expect(regions()[0]).not.toHaveAttribute('data-focused');
|
|
292
|
+
act(() => labRef?.addTrial('StubB'));
|
|
293
|
+
expect(regions()[1]).toHaveAttribute('data-focused', 'true');
|
|
294
|
+
expect(regions()[0]).not.toHaveAttribute('data-focused');
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
describe('swapTrial', () => {
|
|
299
|
+
it('runs another instrument in the trial’s place, and keeps focus on that place', () => {
|
|
300
|
+
mountLab();
|
|
301
|
+
act(() => labRef?.addTrial('Stub'));
|
|
302
|
+
const second = labRef?.trials[1]?.id;
|
|
303
|
+
act(() => labRef?.focusTrial(labRef?.trials[0]?.id ?? ''));
|
|
304
|
+
act(() => labRef?.swapTrial(labRef?.trials[0]?.id ?? '', 'StubB'));
|
|
305
|
+
expect(labRef?.trials.map((t) => t.instrumentName)).toEqual(['StubB', 'Stub']);
|
|
306
|
+
expect(labRef?.trials[1]?.id).toBe(second);
|
|
307
|
+
expect(labRef?.focusedTrialId).toBe(labRef?.trials[0]?.id);
|
|
308
|
+
expect(screen.getByTestId('stub-b-content')).toBeInTheDocument();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('leaves focus where it was when the swapped trial did not have it', () => {
|
|
312
|
+
mountLab();
|
|
313
|
+
act(() => labRef?.addTrial('Stub'));
|
|
314
|
+
const focused = labRef?.focusedTrialId;
|
|
315
|
+
act(() => labRef?.swapTrial(labRef?.trials[0]?.id ?? '', 'StubB'));
|
|
316
|
+
expect(labRef?.focusedTrialId).toBe(focused);
|
|
317
|
+
});
|
|
318
|
+
});
|
package/src/lab/Lab.tsx
CHANGED
|
@@ -13,6 +13,7 @@ import { useStore } from 'zustand/react';
|
|
|
13
13
|
import { AnnotationPreloadContext } from '../annotations/preload';
|
|
14
14
|
import { ANNOTATION_TOOLS } from '../annotations/toolMap';
|
|
15
15
|
import {
|
|
16
|
+
LabAsideRegion,
|
|
16
17
|
LabFooterRegion,
|
|
17
18
|
LabHeaderRegion,
|
|
18
19
|
LabSidebarRegion,
|
|
@@ -29,7 +30,7 @@ import { type OpenedLabStore, openLabStore } from '../state/openLabStore';
|
|
|
29
30
|
import { PersistenceContext } from '../state/Persistence';
|
|
30
31
|
import { createRecordCache } from '../state/records';
|
|
31
32
|
import { createLabStore, type LabStore } from '../state/store';
|
|
32
|
-
import type { LabMode, StorageAdapter, TrialRecord } from '../state/types';
|
|
33
|
+
import type { LabDensity, LabMode, StorageAdapter, TrialRecord } from '../state/types';
|
|
33
34
|
import { useOpenOnce, useWarnIgnoredChange } from '../state/useOpenOnce';
|
|
34
35
|
import { usePersistedState } from '../state/usePersistedState';
|
|
35
36
|
import { SurfaceCanvasContext, SurfaceContext } from '../surface/SurfaceContext';
|
|
@@ -44,6 +45,7 @@ import {
|
|
|
44
45
|
closeTrial as closeTrialOp,
|
|
45
46
|
reorderTrials as reorderTrialsOp,
|
|
46
47
|
resetTrial as resetTrialOp,
|
|
48
|
+
swapTrial as swapTrialOp,
|
|
47
49
|
} from '../trial/trialOps';
|
|
48
50
|
import { useLabFitWarning } from './fitCheck';
|
|
49
51
|
import { LabContext, type LabContextValue } from './LabContext';
|
|
@@ -62,6 +64,10 @@ interface LabBaseProps {
|
|
|
62
64
|
instruments: InstrumentList;
|
|
63
65
|
defaultInstrument: string;
|
|
64
66
|
mode?: LabMode;
|
|
67
|
+
/** How much room the lab's chrome takes. Default `'comfortable'`; a lab whose
|
|
68
|
+
* window is the whole app, rather than a panel beside one, reads better at
|
|
69
|
+
* `'roomy'`. The trials' own contents are unaffected. */
|
|
70
|
+
density?: LabDensity;
|
|
65
71
|
/** Rendered while a stored lab loads. Default: the lab's empty shell. */
|
|
66
72
|
fallback?: ReactNode;
|
|
67
73
|
/**
|
|
@@ -173,12 +179,17 @@ async function openStoredLab(
|
|
|
173
179
|
function LabFallback({
|
|
174
180
|
title,
|
|
175
181
|
mode,
|
|
182
|
+
density,
|
|
176
183
|
pages,
|
|
177
184
|
path,
|
|
178
|
-
}: Pick<LabBaseProps, 'title' | 'mode' | 'pages' | 'path'>) {
|
|
185
|
+
}: Pick<LabBaseProps, 'title' | 'mode' | 'density' | 'pages' | 'path'>) {
|
|
179
186
|
const resolvedMode = useResolvedMode(mode ?? 'auto');
|
|
180
187
|
return (
|
|
181
|
-
<ThemeProvider
|
|
188
|
+
<ThemeProvider
|
|
189
|
+
theme={interstellarTheme}
|
|
190
|
+
selection={{ mode: resolvedMode, density: density ?? 'comfortable' }}
|
|
191
|
+
className="lk-lab"
|
|
192
|
+
>
|
|
182
193
|
<LabShell
|
|
183
194
|
title={title ?? 'Labkit'}
|
|
184
195
|
mode={mode}
|
|
@@ -228,6 +239,32 @@ function LabPanes({
|
|
|
228
239
|
undefined,
|
|
229
240
|
{ scope: 'lab' },
|
|
230
241
|
);
|
|
242
|
+
const [asideWidth, setAsideWidth] = usePersistedState<number | undefined>(
|
|
243
|
+
'lk-lab-aside-width',
|
|
244
|
+
undefined,
|
|
245
|
+
{ scope: 'lab' },
|
|
246
|
+
);
|
|
247
|
+
const main = contributions.some((c) => c.region === 'aside') ? (
|
|
248
|
+
<Split
|
|
249
|
+
className="lk-lab__panes"
|
|
250
|
+
side="end"
|
|
251
|
+
sidebarClassName="lk-lab__aside"
|
|
252
|
+
contentClassName="lk-lab__main"
|
|
253
|
+
label="Lab aside"
|
|
254
|
+
defaultWidth={320}
|
|
255
|
+
width={asideWidth}
|
|
256
|
+
onWidthChange={(w) => {
|
|
257
|
+
setAsideWidth(w);
|
|
258
|
+
surface?.invalidateRects();
|
|
259
|
+
}}
|
|
260
|
+
sidebar={<LabAsideRegion contributions={contributions} />}
|
|
261
|
+
>
|
|
262
|
+
{children}
|
|
263
|
+
</Split>
|
|
264
|
+
) : (
|
|
265
|
+
children
|
|
266
|
+
);
|
|
267
|
+
if (!contributions.some((c) => c.region === 'sidebar')) return main;
|
|
231
268
|
return (
|
|
232
269
|
<Split
|
|
233
270
|
className="lk-lab__panes"
|
|
@@ -242,7 +279,7 @@ function LabPanes({
|
|
|
242
279
|
}}
|
|
243
280
|
sidebar={<LabSidebarRegion contributions={contributions} />}
|
|
244
281
|
>
|
|
245
|
-
{
|
|
282
|
+
{main}
|
|
246
283
|
</Split>
|
|
247
284
|
);
|
|
248
285
|
}
|
|
@@ -259,10 +296,15 @@ export function Lab(props: LabProps) {
|
|
|
259
296
|
storageKey === undefined ? openUnstoredLab(props) : openStoredLab(props, storageKey, storage),
|
|
260
297
|
);
|
|
261
298
|
if (!opened) {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
299
|
+
if (props.fallback !== undefined) return props.fallback;
|
|
300
|
+
return (
|
|
301
|
+
<LabFallback
|
|
302
|
+
title={props.title}
|
|
303
|
+
mode={props.mode}
|
|
304
|
+
density={props.density}
|
|
305
|
+
pages={props.pages}
|
|
306
|
+
path={props.path}
|
|
307
|
+
/>
|
|
266
308
|
);
|
|
267
309
|
}
|
|
268
310
|
return <LabRuntime {...props} opened={opened} />;
|
|
@@ -271,6 +313,7 @@ export function Lab(props: LabProps) {
|
|
|
271
313
|
function LabRuntime({
|
|
272
314
|
instruments,
|
|
273
315
|
mode,
|
|
316
|
+
density,
|
|
274
317
|
nebula,
|
|
275
318
|
title,
|
|
276
319
|
pages,
|
|
@@ -295,6 +338,10 @@ function LabRuntime({
|
|
|
295
338
|
}, [instruments, store]);
|
|
296
339
|
|
|
297
340
|
const trials = useStore(store, (s) => s.trials);
|
|
341
|
+
const [focusPick, setFocusPick] = useState<string | null>(null);
|
|
342
|
+
const focusedTrialId = trials.some((t) => t.id === focusPick)
|
|
343
|
+
? focusPick
|
|
344
|
+
: (trials[0]?.id ?? null);
|
|
298
345
|
const savedSnapshots = useStore(store, (s) => s.savedSnapshots);
|
|
299
346
|
const modeValue = useStore(store, (s) => s.mode);
|
|
300
347
|
const layout = useStore(store, (s) => s.layout);
|
|
@@ -359,6 +406,39 @@ function LabRuntime({
|
|
|
359
406
|
[outerSurface, attachOwnSurface],
|
|
360
407
|
);
|
|
361
408
|
useLabFitWarning(labBody);
|
|
409
|
+
|
|
410
|
+
useEffect(() => {
|
|
411
|
+
if (!labBody) return;
|
|
412
|
+
const pick = (node: unknown): void => {
|
|
413
|
+
const owned = new Set(store.getState().trials.map((t) => t.id));
|
|
414
|
+
const nearest = (from: Element | null | undefined) =>
|
|
415
|
+
from?.closest<HTMLElement>('.lk-trial[data-trial-id]') ?? null;
|
|
416
|
+
let trial =
|
|
417
|
+
typeof (node as Element | null)?.closest === 'function' ? nearest(node as Element) : null;
|
|
418
|
+
// A trial can hold a lab of its own, whose trials this lab does not own.
|
|
419
|
+
while (trial && !owned.has(trial.dataset.trialId ?? '')) trial = nearest(trial.parentElement);
|
|
420
|
+
if (trial?.dataset.trialId) setFocusPick(trial.dataset.trialId);
|
|
421
|
+
};
|
|
422
|
+
const onInput = (event: Event): void => pick(event.target);
|
|
423
|
+
// Focus moving into a frame fires nothing in this document: the window
|
|
424
|
+
// blurs, and the frame is the active element once it has.
|
|
425
|
+
let pending: ReturnType<typeof setTimeout> | undefined;
|
|
426
|
+
const doc = labBody.ownerDocument;
|
|
427
|
+
const win = doc.defaultView;
|
|
428
|
+
const onBlur = (): void => {
|
|
429
|
+
clearTimeout(pending);
|
|
430
|
+
pending = setTimeout(() => pick(doc.activeElement), 0);
|
|
431
|
+
};
|
|
432
|
+
labBody.addEventListener('pointerdown', onInput, true);
|
|
433
|
+
labBody.addEventListener('focusin', onInput, true);
|
|
434
|
+
win?.addEventListener('blur', onBlur);
|
|
435
|
+
return () => {
|
|
436
|
+
clearTimeout(pending);
|
|
437
|
+
labBody.removeEventListener('pointerdown', onInput, true);
|
|
438
|
+
labBody.removeEventListener('focusin', onInput, true);
|
|
439
|
+
win?.removeEventListener('blur', onBlur);
|
|
440
|
+
};
|
|
441
|
+
}, [labBody, store]);
|
|
362
442
|
const surface = outerSurface ?? ownSurface;
|
|
363
443
|
const surfaceCanvases = useMemo(
|
|
364
444
|
() =>
|
|
@@ -388,7 +468,7 @@ function LabRuntime({
|
|
|
388
468
|
[annotates, tools, labChrome],
|
|
389
469
|
);
|
|
390
470
|
const hasFooterChrome = labChromeAll.some((c) => c.region === 'footer');
|
|
391
|
-
const
|
|
471
|
+
const hasPaneChrome = labChromeAll.some((c) => c.region === 'sidebar' || c.region === 'aside');
|
|
392
472
|
|
|
393
473
|
useEffect(() => {
|
|
394
474
|
if (mode && mode !== store.getState().mode) {
|
|
@@ -402,17 +482,23 @@ function LabRuntime({
|
|
|
402
482
|
const merged = next.map((w) => currentById.get(w.id) ?? w);
|
|
403
483
|
store.setState({ trials: merged });
|
|
404
484
|
};
|
|
485
|
+
const replaceAndFocusAdded = (next: TrialRecord[]): void => {
|
|
486
|
+
const before = new Set(store.getState().trials.map((w) => w.id));
|
|
487
|
+
replaceTrials(next);
|
|
488
|
+
const added = next.find((w) => !before.has(w.id));
|
|
489
|
+
if (added) setFocusPick(added.id);
|
|
490
|
+
};
|
|
405
491
|
|
|
406
492
|
return {
|
|
407
493
|
instruments,
|
|
408
494
|
trials,
|
|
409
495
|
addTrial: (instrumentName, options) => {
|
|
410
|
-
|
|
411
|
-
|
|
496
|
+
replaceAndFocusAdded(
|
|
497
|
+
addTrialOp(store.getState().trials, instruments, instrumentName, options),
|
|
498
|
+
);
|
|
412
499
|
},
|
|
413
500
|
cloneTrial: (id) => {
|
|
414
|
-
|
|
415
|
-
replaceTrials(next);
|
|
501
|
+
replaceAndFocusAdded(cloneTrialOp(store.getState().trials, id));
|
|
416
502
|
},
|
|
417
503
|
closeTrial: (id) => {
|
|
418
504
|
const next = closeTrialOp(store.getState().trials, id);
|
|
@@ -421,6 +507,19 @@ function LabRuntime({
|
|
|
421
507
|
reorderTrials: (ids) => {
|
|
422
508
|
replaceTrials(reorderTrialsOp(store.getState().trials, ids));
|
|
423
509
|
},
|
|
510
|
+
swapTrial: (id, instrumentName, options) => {
|
|
511
|
+
const current = store.getState();
|
|
512
|
+
const next = swapTrialOp(current.trials, instruments, id, instrumentName, options);
|
|
513
|
+
if (next === current.trials) return;
|
|
514
|
+
const record = next[current.trials.findIndex((w) => w.id === id)] as TrialRecord;
|
|
515
|
+
// Written before the tile registers, which is when the grid reads it.
|
|
516
|
+
const { [id]: extent, ...layoutRest } = current.layout as TrialLayout;
|
|
517
|
+
if (extent) current.setLayout({ ...layoutRest, [record.id]: extent });
|
|
518
|
+
replaceTrials(next);
|
|
519
|
+
if (focusedTrialId === id) setFocusPick(record.id);
|
|
520
|
+
},
|
|
521
|
+
focusedTrialId,
|
|
522
|
+
focusTrial: (id) => setFocusPick(id),
|
|
424
523
|
resetTrial: (id) => {
|
|
425
524
|
const next = resetTrialOp(store.getState().trials, id, instruments);
|
|
426
525
|
const record = next.find((w) => w.id === id);
|
|
@@ -451,7 +550,16 @@ function LabRuntime({
|
|
|
451
550
|
configRules,
|
|
452
551
|
controls,
|
|
453
552
|
};
|
|
454
|
-
}, [
|
|
553
|
+
}, [
|
|
554
|
+
instruments,
|
|
555
|
+
trials,
|
|
556
|
+
focusedTrialId,
|
|
557
|
+
savedSnapshots,
|
|
558
|
+
modeValue,
|
|
559
|
+
store,
|
|
560
|
+
configRules,
|
|
561
|
+
controls,
|
|
562
|
+
]);
|
|
455
563
|
|
|
456
564
|
// Only override the backdrop in dark, where there is one to override.
|
|
457
565
|
// Setting a CSS custom property is the sanctioned use of inline style.
|
|
@@ -483,7 +591,7 @@ function LabRuntime({
|
|
|
483
591
|
<LabContext.Provider value={contextValue}>
|
|
484
592
|
<ThemeProvider
|
|
485
593
|
theme={interstellarTheme}
|
|
486
|
-
|
|
594
|
+
selection={{ mode: resolvedMode, density: density ?? 'comfortable' }}
|
|
487
595
|
className="lk-lab"
|
|
488
596
|
style={backdropStyle}
|
|
489
597
|
>
|
|
@@ -537,7 +645,7 @@ function LabRuntime({
|
|
|
537
645
|
/>
|
|
538
646
|
</>
|
|
539
647
|
)}
|
|
540
|
-
{
|
|
648
|
+
{hasPaneChrome ? (
|
|
541
649
|
<LabPanes contributions={labChromeAll}>
|
|
542
650
|
<LabPalette contributions={labChromeAll} />
|
|
543
651
|
{workspace}
|
package/src/lab/LabContext.ts
CHANGED
|
@@ -18,6 +18,14 @@ export interface LabContextValue {
|
|
|
18
18
|
closeTrial: (id: string) => void;
|
|
19
19
|
resetTrial: (id: string) => void;
|
|
20
20
|
reorderTrials: (ids: readonly string[]) => void;
|
|
21
|
+
/** Put a fresh trial running `instrumentName` in trial `id`'s place, at its
|
|
22
|
+
* size. The replacement has a new id, and takes the focus if `id` had it. */
|
|
23
|
+
swapTrial: (id: string, instrumentName: string, options?: AddTrialOptions) => void;
|
|
24
|
+
/** The trial last pointed at or focused, counting focus that moved into a
|
|
25
|
+
* frame inside it. A trial `addTrial` or `cloneTrial` opens takes it. Falls
|
|
26
|
+
* back to the first trial; null only when there are none. */
|
|
27
|
+
focusedTrialId: string | null;
|
|
28
|
+
focusTrial: (id: string) => void;
|
|
21
29
|
savedSnapshots: SavedSnapshot[];
|
|
22
30
|
saveSnapshot: (trialId: string, name?: string) => void;
|
|
23
31
|
loadSnapshot: (trialId: string, snapshotId: string) => void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@
|
|
1
|
+
import type { Meta, StoryObj } from '@weasel-js/forge';
|
|
2
|
+
import { expect, userEvent, waitFor, within } from '@weasel-js/forge/play';
|
|
2
3
|
import { type ReactNode, useLayoutEffect, useState } from 'react';
|
|
3
4
|
import { createPortal } from 'react-dom';
|
|
4
|
-
import { expect, userEvent, waitFor, within } from 'storybook/test';
|
|
5
5
|
import type { LabContribution } from '../chrome/labTypes';
|
|
6
6
|
import type { TrialContribution } from '../chrome/types';
|
|
7
7
|
import type { Instrument } from '../instrument/types';
|
|
@@ -9,7 +9,7 @@ import { Lab } from './Lab';
|
|
|
9
9
|
import './LabFit.stories.less';
|
|
10
10
|
|
|
11
11
|
// A lab must fit whatever it is mounted in without scrolling. jsdom cannot see
|
|
12
|
-
// layout, so these run in the
|
|
12
|
+
// layout, so these run in the forge-stories vitest project's real browser.
|
|
13
13
|
|
|
14
14
|
const Stub: Instrument = {
|
|
15
15
|
name: 'Stub',
|
|
@@ -43,7 +43,7 @@ const VIEWPORTS = {
|
|
|
43
43
|
|
|
44
44
|
type Host = 'reset' | 'wrapped' | 'framed';
|
|
45
45
|
|
|
46
|
-
//
|
|
46
|
+
// The frame wraps every story in divs of its own, so a fixture that needs a
|
|
47
47
|
// known ancestor chain builds that chain directly on <body> and portals into it.
|
|
48
48
|
function BodyHost({ host, children }: { host: Host; children: ReactNode }) {
|
|
49
49
|
const [mount, setMount] = useState<HTMLElement | null>(null);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@
|
|
1
|
+
import type { Meta, StoryObj } from '@weasel-js/forge';
|
|
2
2
|
import { useEffect, useRef } from 'react';
|
|
3
3
|
import type { ConfigField } from '../controls/types';
|
|
4
4
|
import { defineInstrument } from '../instrument/defineInstrument';
|