@weasel-js/labkit 1.3.0-pre.0 → 1.4.0-pre.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/dist/_dts/CanvasStackContext-DtAn_3O-.d.ts +42 -0
- package/dist/_dts/DrawCommand-B3bskUsC.d.ts +564 -0
- package/dist/_dts/{PrefsForm-BYa6cWnO.d.ts → PrefsForm-BkUJZx0A.d.ts} +4 -1
- package/dist/_dts/fitViewToBounds-dZ2UDB6e.d.ts +21 -0
- package/dist/_dts/frac-Cp3NivlC.d.ts +728 -0
- package/dist/_dts/index-BDVzvRzQ.d.ts +236 -0
- package/dist/_dts/shapeKinds-Cx_rxwsa.d.ts +87 -0
- package/dist/_dts/{DrawCommand-DBB45NfN.d.ts → types-C-gh9Ap-.d.ts} +1 -582
- package/dist/_dts/{types-AHlQxBNN.d.ts → types-D6s4b7if.d.ts} +2 -2
- package/dist/_dts/{types-1Sdxy_Pv.d.ts → types-DYMaEvM5.d.ts} +26 -1
- package/dist/_dts/{useTrialState-CsGMjhu9.d.ts → useTrialState-gmMvZqPc.d.ts} +7 -2
- package/dist/canvas/index.d.ts +43 -25
- package/dist/canvas/index.js +5 -3
- package/dist/chrome/index.d.ts +9 -6
- package/dist/chrome/index.js +7 -6
- package/dist/chunk-2AYGEN57.js +32 -0
- package/dist/chunk-2AYGEN57.js.map +1 -0
- package/dist/{chunk-RVNN4CHQ.js → chunk-3P5TTTA7.js} +16 -8
- package/dist/chunk-3P5TTTA7.js.map +1 -0
- package/dist/{chunk-NS54R52R.js → chunk-3ZELRRBV.js} +45 -40
- package/dist/chunk-3ZELRRBV.js.map +1 -0
- package/dist/{chunk-HFDVGF4X.js → chunk-4EY67BKJ.js} +32 -7
- package/dist/chunk-4EY67BKJ.js.map +1 -0
- package/dist/chunk-5TCEVMPI.js +140 -0
- package/dist/chunk-5TCEVMPI.js.map +1 -0
- package/dist/{chunk-O7NVSCLN.js → chunk-6JMU56CU.js} +133 -134
- package/dist/chunk-6JMU56CU.js.map +1 -0
- package/dist/{chunk-CPUJ3QXL.js → chunk-CQLQPQ4P.js} +2 -2
- package/dist/chunk-CQLQPQ4P.js.map +1 -0
- package/dist/chunk-CTRKTLYZ.js +33 -0
- package/dist/chunk-CTRKTLYZ.js.map +1 -0
- package/dist/chunk-EQIB2PGC.js +458 -0
- package/dist/chunk-EQIB2PGC.js.map +1 -0
- package/dist/chunk-HEORBLVT.js +7128 -0
- package/dist/chunk-HEORBLVT.js.map +1 -0
- package/dist/{chunk-V7PRSIRQ.js → chunk-JDDIZSLL.js} +4 -4
- package/dist/chunk-JDDIZSLL.js.map +1 -0
- package/dist/chunk-LITE5YFB.js +669 -0
- package/dist/chunk-LITE5YFB.js.map +1 -0
- package/dist/{chunk-Z6HJ5FEM.js → chunk-ROQDRXKW.js} +732 -315
- package/dist/chunk-ROQDRXKW.js.map +1 -0
- package/dist/chunk-SBXNQW4G.js +3 -0
- package/dist/chunk-SBXNQW4G.js.map +1 -0
- package/dist/{chunk-4TMMVIDM.js → chunk-XJ6N32QP.js} +50 -6
- package/dist/chunk-XJ6N32QP.js.map +1 -0
- package/dist/chunk-YQJNT4XQ.js +293 -0
- package/dist/chunk-YQJNT4XQ.js.map +1 -0
- package/dist/controls/index.d.ts +4 -28
- package/dist/controls/index.js +3 -3
- package/dist/dragdrop/index.d.ts +50 -5
- package/dist/dragdrop/index.js +3 -1
- package/dist/index.d.ts +660 -302
- package/dist/index.js +1793 -587
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +6 -5
- package/dist/layers/index.js +4 -2
- package/dist/loupe/index.d.ts +187 -0
- package/dist/loupe/index.js +7 -0
- package/dist/loupe/index.js.map +1 -0
- package/dist/passthrough/weasel-canvas.d.ts +3 -1
- package/dist/passthrough/weasel-canvas.js +1 -1
- package/dist/passthrough/weasel-ui.d.ts +151 -91
- package/dist/passthrough/weasel-ui.js +3 -2
- package/dist/primitives/index.d.ts +3 -1
- package/dist/primitives/index.js +6 -5
- package/dist/state/index.d.ts +3 -3
- package/dist/state/index.js +2 -2
- package/dist/styles.css +229 -703
- package/dist/surface/index.d.ts +18 -2
- package/dist/surface/index.js +2 -2
- package/dist/ui/layers/index.d.ts +8 -2
- package/dist/ui/layers/index.js +2 -4
- package/dist/undo/index.d.ts +5 -4
- package/package.json +14 -7
- package/src/annotations/AnnotationOverlay.tsx +205 -0
- package/src/annotations/AnnotationTargets.tsx +37 -0
- package/src/annotations/Annotations.less +129 -0
- package/src/annotations/Annotations.meaning.test.tsx +111 -0
- package/src/annotations/Annotations.overlay.test.tsx +142 -0
- package/src/annotations/AnnotationsContext.ts +49 -0
- package/src/annotations/ExportMenu.test.tsx +86 -0
- package/src/annotations/ExportMenu.tsx +182 -0
- package/src/annotations/MarkList.tsx +75 -0
- package/src/annotations/capture.test.ts +140 -0
- package/src/annotations/capture.ts +237 -0
- package/src/annotations/drawOne.test.ts +56 -0
- package/src/annotations/drawOne.ts +47 -0
- package/src/annotations/frac.test.ts +59 -0
- package/src/annotations/frac.ts +66 -0
- package/src/annotations/history.test.ts +82 -0
- package/src/annotations/history.ts +118 -0
- package/src/annotations/index.ts +37 -0
- package/src/annotations/paint.test.ts +125 -0
- package/src/annotations/paint.ts +130 -0
- package/src/annotations/staleness.test.ts +56 -0
- package/src/annotations/staleness.ts +41 -0
- package/src/annotations/store.test.ts +175 -0
- package/src/annotations/store.ts +317 -0
- package/src/annotations/svgNodes.test.ts +77 -0
- package/src/annotations/svgNodes.ts +74 -0
- package/src/annotations/toolMap.test.ts +36 -0
- package/src/annotations/toolMap.ts +54 -0
- package/src/annotations/types.ts +222 -0
- package/src/annotations/view.test.ts +37 -0
- package/src/annotations/view.ts +40 -0
- package/src/canvas/AGENTS.md +55 -5
- package/src/canvas/CanvasStack.test.tsx +27 -1
- package/src/canvas/CanvasStack.tsx +29 -4
- package/src/canvas/CanvasStackContext.ts +23 -4
- package/src/canvas/camera.test.ts +78 -0
- package/src/canvas/camera.ts +62 -0
- package/src/canvas/canvasCoords.test.ts +26 -0
- package/src/canvas/canvasCoords.ts +20 -7
- package/src/canvas/index.ts +5 -1
- package/src/canvas/useLayerScheduler.ts +7 -3
- package/src/canvas/usePanZoom.test.ts +36 -5
- package/src/canvas/usePanZoom.ts +20 -15
- package/src/canvas/worldSpec.test.ts +63 -0
- package/src/canvas/worldSpec.ts +48 -0
- package/src/chrome/builtins.test.ts +31 -1
- package/src/chrome/builtins.tsx +63 -19
- package/src/chrome/regions/PaletteRegion.test.tsx +22 -1
- package/src/chrome/regions/PaletteRegion.tsx +4 -0
- package/src/chrome/regions/SidebarRegion.tsx +37 -13
- package/src/chrome/regions/ToolbarRegion.tsx +2 -1
- package/src/chrome/regions/ViewportRegion.tsx +9 -1
- package/src/chrome/regions/regions.test.tsx +6 -1
- package/src/chrome/types.ts +20 -0
- package/src/config/builder.test.ts +7 -1
- package/src/config/builder.ts +1 -5
- package/src/config/resolve.test.ts +1 -2
- package/src/controls/ControlPanel.stories.tsx +41 -2
- package/src/controls/ControlPanel.test.tsx +69 -9
- package/src/controls/ControlPanel.tsx +106 -25
- package/src/dragdrop/DragDropRuntime.tsx +8 -2
- package/src/dragdrop/dragDrop.test.tsx +29 -2
- package/src/dragdrop/index.ts +11 -0
- package/src/index.test.ts +22 -0
- package/src/index.ts +136 -4
- package/src/instrument/SineWave.smoke.test.tsx +1 -0
- package/src/instrument/types.ts +24 -1
- package/src/lab/Lab.surface.test.tsx +162 -0
- package/src/lab/Lab.tsx +102 -17
- package/src/lab/Lab.undock.test.tsx +85 -0
- package/src/lab/LabFullChrome.stories.tsx +20 -0
- package/src/lab/LabShell.less +11 -0
- package/src/lab/Workspace.less +73 -0
- package/src/lab/Workspace.surface.test.tsx +2 -0
- package/src/lab/Workspace.tsx +97 -8
- package/src/lab/index.ts +1 -1
- package/src/lab/panelHost.ts +37 -0
- package/src/layers/AGENTS.md +1 -1
- package/src/layers/LayerList.tsx +1 -1
- package/src/loupe/AGENTS.md +49 -0
- package/src/loupe/CanvasLoupe.tsx +83 -0
- package/src/loupe/DomLoupe.tsx +62 -0
- package/src/loupe/Loupe.less +38 -0
- package/src/loupe/LoupeBubble.tsx +32 -0
- package/src/loupe/TrialLoupe.tsx +93 -0
- package/src/loupe/canvasLens.test.ts +205 -0
- package/src/loupe/canvasLens.ts +141 -0
- package/src/loupe/index.ts +19 -0
- package/src/loupe/types.test.ts +29 -0
- package/src/loupe/types.ts +90 -0
- package/src/loupe/useHostSize.ts +30 -0
- package/src/loupe/useLoupe.test.tsx +197 -0
- package/src/loupe/useLoupe.ts +189 -0
- package/src/passthrough/weasel-ui.ts +25 -17
- package/src/primitives/FloatingPanel.test.tsx +76 -1
- package/src/primitives/FloatingPanel.tsx +19 -3
- package/src/primitives/ScaleIndicator.test.tsx +7 -2
- package/src/primitives/Sidebar.less +18 -0
- package/src/primitives/Toolbar.less +8 -0
- package/src/primitives/Toolbar.tsx +5 -0
- package/src/primitives/useRovingTabIndex.test.ts +21 -0
- package/src/primitives/useRovingTabIndex.ts +26 -17
- package/src/state/document.test.ts +23 -0
- package/src/state/document.ts +14 -2
- package/src/state/index.ts +2 -0
- package/src/state/store.test.ts +24 -0
- package/src/state/store.ts +35 -1
- package/src/state/types.ts +11 -0
- package/src/state/undock.test.ts +40 -0
- package/src/state/undock.ts +39 -0
- package/src/styles.less +2 -4
- package/src/surface/SurfaceContext.ts +4 -0
- package/src/surface/index.ts +8 -3
- package/src/surface/useSurfaceTile.test.tsx +2 -0
- package/src/surface/useSurfaceTile.ts +7 -1
- package/src/surface/useTiledSurface.test.tsx +58 -0
- package/src/surface/useTiledSurface.ts +38 -3
- package/src/theme/Interstellar.stories.tsx +1 -1
- package/src/trial/Trial.annotations.persist.test.tsx +166 -0
- package/src/trial/Trial.annotations.test.tsx +121 -0
- package/src/trial/Trial.canvas.test.tsx +76 -1
- package/src/trial/Trial.config.test.tsx +3 -8
- package/src/trial/Trial.less +19 -0
- package/src/trial/Trial.loupe.test.tsx +128 -0
- package/src/trial/Trial.stories.tsx +1 -0
- package/src/trial/Trial.test.tsx +12 -0
- package/src/trial/Trial.trialId.test.tsx +36 -0
- package/src/trial/Trial.tsx +215 -40
- package/src/trial/TrialChrome.tsx +35 -0
- package/src/trial/UndockedSections.tsx +65 -0
- package/src/trial/trialOps.test.ts +21 -0
- package/src/trial/trialOps.ts +5 -3
- package/src/ui/layers/index.ts +5 -1
- package/dist/_dts/index-DgcNdEdh.d.ts +0 -314
- package/dist/chunk-4TMMVIDM.js.map +0 -1
- package/dist/chunk-CPUJ3QXL.js.map +0 -1
- package/dist/chunk-HFDVGF4X.js.map +0 -1
- package/dist/chunk-NS54R52R.js.map +0 -1
- package/dist/chunk-O2BULFHX.js +0 -152
- package/dist/chunk-O2BULFHX.js.map +0 -1
- package/dist/chunk-O7NVSCLN.js.map +0 -1
- package/dist/chunk-PO6L3NSH.js +0 -366
- package/dist/chunk-PO6L3NSH.js.map +0 -1
- package/dist/chunk-PQJ5B2U2.js +0 -27
- package/dist/chunk-PQJ5B2U2.js.map +0 -1
- package/dist/chunk-RVNN4CHQ.js.map +0 -1
- package/dist/chunk-TLGRYALP.js +0 -6151
- package/dist/chunk-TLGRYALP.js.map +0 -1
- package/dist/chunk-V7PRSIRQ.js.map +0 -1
- package/dist/chunk-Z6HJ5FEM.js.map +0 -1
- package/dist/chunk-ZZAYVX4X.js +0 -505
- package/dist/chunk-ZZAYVX4X.js.map +0 -1
- package/src/primitives/DragHandleGlyph.tsx +0 -29
- package/src/ui/format.test.ts +0 -33
- package/src/ui/format.ts +0 -30
- package/src/ui/layers/LayerStack.less +0 -143
- package/src/ui/layers/LayerStack.stories.tsx +0 -45
- package/src/ui/layers/LayerStack.test.tsx +0 -170
- package/src/ui/layers/LayerStack.tsx +0 -208
- package/src/ui/properties/CheckboxRow.stories.tsx +0 -27
- package/src/ui/properties/ColorRow.stories.tsx +0 -106
- package/src/ui/properties/CurveField.less +0 -48
- package/src/ui/properties/CurveField.stories.tsx +0 -20
- package/src/ui/properties/CurveField.test.tsx +0 -127
- package/src/ui/properties/CurveField.tsx +0 -184
- package/src/ui/properties/EffectCard.tsx +0 -324
- package/src/ui/properties/Gallery.stories.tsx +0 -178
- package/src/ui/properties/NumberRow.stories.tsx +0 -65
- package/src/ui/properties/PropertyGroup.less +0 -50
- package/src/ui/properties/PropertyGroup.stories.tsx +0 -34
- package/src/ui/properties/PropertyGroup.test.tsx +0 -33
- package/src/ui/properties/PropertyGroup.tsx +0 -45
- package/src/ui/properties/PropertyList.stories.tsx +0 -83
- package/src/ui/properties/PropertyPanel.less +0 -606
- package/src/ui/properties/PropertyPanel.stories.tsx +0 -190
- package/src/ui/properties/PropertyPanel.test.tsx +0 -193
- package/src/ui/properties/PropertyPanel.tsx +0 -469
- package/src/ui/properties/PropertyRow.stories.tsx +0 -53
- package/src/ui/properties/SelectRow.stories.tsx +0 -58
- package/src/ui/properties/SliderRow.stories.tsx +0 -74
- package/src/ui/properties/SpeechBalloonPanels.stories.tsx +0 -398
- package/src/ui/properties/TextRow.stories.tsx +0 -62
- package/src/ui/properties/ToggleRow.stories.tsx +0 -57
- package/src/ui/properties/index.ts +0 -39
- package/src/ui/properties/storyLayouts.tsx +0 -36
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ToolButton, ToolGroup } from '../../passthrough/weasel-ui';
|
|
2
|
+
import { useRovingTabIndex } from '../../primitives/useRovingTabIndex';
|
|
2
3
|
import type { TrialChromeContext, TrialContribution } from '../types';
|
|
3
4
|
|
|
4
5
|
/** Props for `<PaletteRegion>`. */
|
|
@@ -10,13 +11,16 @@ export interface PaletteRegionProps {
|
|
|
10
11
|
/** A trial's tool strip. Selection lives in the trial's or the lab's tool
|
|
11
12
|
* slot; this region only reflects it. */
|
|
12
13
|
export function PaletteRegion({ contributions, ctx }: PaletteRegionProps) {
|
|
14
|
+
const { ref, onKeyDown } = useRovingTabIndex<HTMLDivElement>('vertical');
|
|
13
15
|
if (contributions.length === 0) return null;
|
|
14
16
|
return (
|
|
15
17
|
<div
|
|
18
|
+
ref={ref}
|
|
16
19
|
className="lk-palette-region"
|
|
17
20
|
role="toolbar"
|
|
18
21
|
aria-label="Tools"
|
|
19
22
|
aria-orientation="vertical"
|
|
23
|
+
onKeyDown={onKeyDown}
|
|
20
24
|
>
|
|
21
25
|
<ToolGroup orientation="vertical">
|
|
22
26
|
{contributions.map((c) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import { useState } from 'react';
|
|
3
|
-
import type { TrialChromeContext, TrialContribution } from '../types';
|
|
3
|
+
import type { SidebarSection, TrialChromeContext, TrialContribution } from '../types';
|
|
4
4
|
|
|
5
5
|
/** Props for `<SidebarRegion>`. */
|
|
6
6
|
export interface SidebarRegionProps {
|
|
@@ -11,29 +11,46 @@ export interface SidebarRegionProps {
|
|
|
11
11
|
function Section({
|
|
12
12
|
title,
|
|
13
13
|
defaultCollapsed,
|
|
14
|
+
onUndock,
|
|
14
15
|
children,
|
|
15
16
|
}: {
|
|
16
17
|
title: string;
|
|
17
18
|
defaultCollapsed: boolean;
|
|
19
|
+
onUndock?: () => void;
|
|
18
20
|
children: ReactNode;
|
|
19
21
|
}) {
|
|
20
22
|
const [collapsed, setCollapsed] = useState(defaultCollapsed);
|
|
21
23
|
return (
|
|
22
24
|
<section className="lk-sidebar-section">
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
<div className="lk-sidebar-section__bar">
|
|
26
|
+
<button
|
|
27
|
+
type="button"
|
|
28
|
+
className="lk-sidebar-section__head"
|
|
29
|
+
aria-expanded={!collapsed}
|
|
30
|
+
onClick={() => setCollapsed((c) => !c)}
|
|
31
|
+
>
|
|
32
|
+
{title}
|
|
33
|
+
</button>
|
|
34
|
+
{onUndock ? (
|
|
35
|
+
<button
|
|
36
|
+
type="button"
|
|
37
|
+
className="lk-sidebar-section__undock"
|
|
38
|
+
aria-label={`Undock ${title}`}
|
|
39
|
+
title={`Undock ${title}`}
|
|
40
|
+
onClick={onUndock}
|
|
41
|
+
>
|
|
42
|
+
⧉
|
|
43
|
+
</button>
|
|
44
|
+
) : null}
|
|
45
|
+
</div>
|
|
31
46
|
{collapsed ? null : <div className="lk-sidebar-section__body">{children}</div>}
|
|
32
47
|
</section>
|
|
33
48
|
);
|
|
34
49
|
}
|
|
35
50
|
|
|
36
|
-
/** Lays a trial's `sidebar` contributions out as titled, collapsible sections.
|
|
51
|
+
/** Lays a trial's `sidebar` contributions out as titled, collapsible sections.
|
|
52
|
+
* A section the trial has torn out is not rendered here — `Trial` portals its
|
|
53
|
+
* body into the workspace instead. */
|
|
37
54
|
export function SidebarRegion({ contributions, ctx }: SidebarRegionProps) {
|
|
38
55
|
if (contributions.length === 0) return null;
|
|
39
56
|
return (
|
|
@@ -41,13 +58,20 @@ export function SidebarRegion({ contributions, ctx }: SidebarRegionProps) {
|
|
|
41
58
|
{contributions.map((c) => {
|
|
42
59
|
if (c.render) return <div key={c.id}>{c.render(ctx)}</div>;
|
|
43
60
|
if (c.region !== 'sidebar' || !c.item) return null;
|
|
61
|
+
if (ctx.undockedPanels.includes(c.id)) return null;
|
|
62
|
+
const item = c.item as SidebarSection;
|
|
44
63
|
return (
|
|
45
64
|
<Section
|
|
46
65
|
key={c.id}
|
|
47
|
-
title={
|
|
48
|
-
defaultCollapsed={
|
|
66
|
+
title={item.title}
|
|
67
|
+
defaultCollapsed={item.defaultCollapsed ?? false}
|
|
68
|
+
onUndock={
|
|
69
|
+
item.undockable === false
|
|
70
|
+
? undefined
|
|
71
|
+
: () => ctx.undockPanel(c.id, item.undockAs ?? 'tile')
|
|
72
|
+
}
|
|
49
73
|
>
|
|
50
|
-
{
|
|
74
|
+
{item.body}
|
|
51
75
|
</Section>
|
|
52
76
|
);
|
|
53
77
|
})}
|
|
@@ -38,13 +38,14 @@ function groupsOf(contributions: readonly TrialContribution[]): Group[] {
|
|
|
38
38
|
function renderEntry(c: TrialContribution, ctx: TrialChromeContext): ReactNode {
|
|
39
39
|
if (c.render) return <span key={c.id}>{c.render(ctx)}</span>;
|
|
40
40
|
if (c.region !== 'toolbar' || !c.item) return null;
|
|
41
|
-
const { icon: Icon, label, shortcut, disabled, danger, showLabel } = c.item;
|
|
41
|
+
const { icon: Icon, label, shortcut, disabled, danger, showLabel, pressed } = c.item;
|
|
42
42
|
return (
|
|
43
43
|
<Toolbar.Button
|
|
44
44
|
key={c.id}
|
|
45
45
|
iconOnly={!showLabel}
|
|
46
46
|
variant={danger ? 'danger' : 'default'}
|
|
47
47
|
disabled={disabled}
|
|
48
|
+
pressed={pressed}
|
|
48
49
|
aria-label={label}
|
|
49
50
|
title={shortcut ? `${label} (${shortcut})` : label}
|
|
50
51
|
onClick={c.item.onActivate}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useRovingTabIndex } from '../../primitives/useRovingTabIndex';
|
|
1
2
|
import type { TrialChromeContext, TrialContribution } from '../types';
|
|
2
3
|
|
|
3
4
|
/** Props for `<ViewportRegion>`. */
|
|
@@ -11,9 +12,16 @@ export interface ViewportRegionProps {
|
|
|
11
12
|
* rather than in the toolbar, which acts on the trial itself.
|
|
12
13
|
*/
|
|
13
14
|
export function ViewportRegion({ contributions, ctx }: ViewportRegionProps) {
|
|
15
|
+
const { ref, onKeyDown } = useRovingTabIndex<HTMLDivElement>();
|
|
14
16
|
if (contributions.length === 0) return null;
|
|
15
17
|
return (
|
|
16
|
-
<div
|
|
18
|
+
<div
|
|
19
|
+
ref={ref}
|
|
20
|
+
className="lk-viewport-controls"
|
|
21
|
+
role="toolbar"
|
|
22
|
+
aria-label="View"
|
|
23
|
+
onKeyDown={onKeyDown}
|
|
24
|
+
>
|
|
17
25
|
{contributions.map((c) => {
|
|
18
26
|
if (c.render) return <span key={c.id}>{c.render(ctx)}</span>;
|
|
19
27
|
if (c.region !== 'viewport' || !c.item) return null;
|
|
@@ -6,7 +6,12 @@ import { StatusRegion } from './StatusRegion';
|
|
|
6
6
|
import { ViewportRegion } from './ViewportRegion';
|
|
7
7
|
|
|
8
8
|
const Glyph = () => <svg />;
|
|
9
|
-
const ctx = {
|
|
9
|
+
const ctx = {
|
|
10
|
+
trialId: 't1',
|
|
11
|
+
undockedPanels: [],
|
|
12
|
+
undockPanel: () => {},
|
|
13
|
+
dockPanel: () => {},
|
|
14
|
+
} as unknown as TrialChromeContext;
|
|
10
15
|
|
|
11
16
|
describe('SidebarRegion', () => {
|
|
12
17
|
it('renders nothing when empty', () => {
|
package/src/chrome/types.ts
CHANGED
|
@@ -19,6 +19,9 @@ export interface ToolbarItem {
|
|
|
19
19
|
disabled?: boolean;
|
|
20
20
|
/** Reddens on hover. For actions that discard work. */
|
|
21
21
|
danger?: boolean;
|
|
22
|
+
/** A toggle rather than a command: renders `aria-pressed` and reads as held
|
|
23
|
+
* down while true. */
|
|
24
|
+
pressed?: boolean;
|
|
22
25
|
/** Render the label beside the glyph rather than only in the tooltip. */
|
|
23
26
|
showLabel?: boolean;
|
|
24
27
|
onActivate: () => void;
|
|
@@ -37,6 +40,11 @@ export interface SidebarSection {
|
|
|
37
40
|
title: string;
|
|
38
41
|
/** Starts collapsed. The open/closed state itself is the region's. */
|
|
39
42
|
defaultCollapsed?: boolean;
|
|
43
|
+
/** Offer the tear-out control. On by default; a section that only makes
|
|
44
|
+
* sense beside its trial sets this false. */
|
|
45
|
+
undockable?: boolean;
|
|
46
|
+
/** Where the tear-out control sends it. Default `'tile'`. */
|
|
47
|
+
undockAs?: 'tile' | 'floating';
|
|
40
48
|
body: ReactNode;
|
|
41
49
|
}
|
|
42
50
|
|
|
@@ -103,6 +111,11 @@ export interface TrialChromeContext {
|
|
|
103
111
|
undo: () => void;
|
|
104
112
|
redo: () => void;
|
|
105
113
|
|
|
114
|
+
/** Whether the trial's loupe is turned on. False for an instrument that
|
|
115
|
+
* declares none, whose chrome offers no way to turn one on. */
|
|
116
|
+
loupeOn: boolean;
|
|
117
|
+
toggleLoupe: () => void;
|
|
118
|
+
|
|
106
119
|
/** The instrument's controls, resolved against the lab's rules. Always
|
|
107
120
|
* populated: a legacy `configSchema()` is adapted into the same shape. */
|
|
108
121
|
configSchema: ResolvedConfig;
|
|
@@ -112,6 +125,13 @@ export interface TrialChromeContext {
|
|
|
112
125
|
config: unknown;
|
|
113
126
|
setConfig: (key: string, value: unknown) => void;
|
|
114
127
|
|
|
128
|
+
/** Section ids this trial currently has torn out of its sidebar. */
|
|
129
|
+
undockedPanels: readonly string[];
|
|
130
|
+
/** Tear a sidebar section out into the workspace. */
|
|
131
|
+
undockPanel: (sectionId: string, as?: 'tile' | 'floating') => void;
|
|
132
|
+
/** Put a torn-out section back in the sidebar. */
|
|
133
|
+
dockPanel: (sectionId: string) => void;
|
|
134
|
+
|
|
115
135
|
savedSnapshots: SavedSnapshot[];
|
|
116
136
|
saveSnapshot: (name?: string) => void;
|
|
117
137
|
loadSnapshot: (snapshotId: string) => void;
|
|
@@ -20,7 +20,13 @@ describe('builder', () => {
|
|
|
20
20
|
it('keeps the subclass across a chain, so kind-specific methods survive', () => {
|
|
21
21
|
const n = f.number(20).label('A').range(5, 80).describe('help').step(5);
|
|
22
22
|
expect(n.kind).toBe('number');
|
|
23
|
-
expect(n.annotations).toMatchObject({
|
|
23
|
+
expect(n.annotations).toMatchObject({
|
|
24
|
+
name: 'A',
|
|
25
|
+
description: 'help',
|
|
26
|
+
min: 5,
|
|
27
|
+
max: 80,
|
|
28
|
+
step: 5,
|
|
29
|
+
});
|
|
24
30
|
});
|
|
25
31
|
|
|
26
32
|
it('keeps a custom node kind across a chain', () => {
|
package/src/config/builder.ts
CHANGED
|
@@ -29,11 +29,7 @@ abstract class BaseNode<T> implements ConfigNode<T> {
|
|
|
29
29
|
/** Clone into the same subclass. Subclasses with extra constructor
|
|
30
30
|
* arguments override this. */
|
|
31
31
|
protected with(annotations: Annotations, options: NodeOptions): this {
|
|
32
|
-
const Ctor = this.constructor as new (
|
|
33
|
-
d: T,
|
|
34
|
-
a: Annotations,
|
|
35
|
-
o: NodeOptions,
|
|
36
|
-
) => this;
|
|
32
|
+
const Ctor = this.constructor as new (d: T, a: Annotations, o: NodeOptions) => this;
|
|
37
33
|
return new Ctor(this.default, annotations, options);
|
|
38
34
|
}
|
|
39
35
|
|
|
@@ -54,8 +54,7 @@ describe('resolveConfigSchema', () => {
|
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
it('a consumer rule claims an f.value kind before the built-in', () => {
|
|
57
|
-
const colorByName: ConfigRule = (ctx) =>
|
|
58
|
-
ctx.key.endsWith('Color') ? { kind: 'color' } : null;
|
|
57
|
+
const colorByName: ConfigRule = (ctx) => (ctx.key.endsWith('Color') ? { kind: 'color' } : null);
|
|
59
58
|
const r = resolveConfigSchema(f.schema({ tintColor: f.value('#fff') }), [colorByName]);
|
|
60
59
|
expect(leafAt(r, 'tintColor').kind).toBe('color');
|
|
61
60
|
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { PropertyRow } from '@weasel-js/ui';
|
|
2
3
|
import { useState } from 'react';
|
|
3
4
|
import { f } from '../config/builder';
|
|
4
5
|
import { resolveConfigSchema } from '../config/resolve';
|
|
5
6
|
import type { ConfigRule, ControlRenderer } from '../config/types';
|
|
6
|
-
import { PropertyRow } from '../ui/properties/PropertyPanel';
|
|
7
7
|
import { ControlPanel } from './ControlPanel';
|
|
8
8
|
import type { ConfigField } from './types';
|
|
9
9
|
|
|
@@ -141,9 +141,48 @@ export const CustomControl: Story = {
|
|
|
141
141
|
),
|
|
142
142
|
};
|
|
143
143
|
|
|
144
|
+
const described = f.schema({
|
|
145
|
+
frequency: f
|
|
146
|
+
.number(2)
|
|
147
|
+
.range(0.1, 10)
|
|
148
|
+
.step(0.1)
|
|
149
|
+
.describe('Cycles per second the oscillator runs at.'),
|
|
150
|
+
amplitude: f.number(0.5).range(0, 1).step(0.05),
|
|
151
|
+
showGrid: f.boolean(true).describe('Draw the alignment grid behind the plot.'),
|
|
152
|
+
wave: f
|
|
153
|
+
.enum('sine', [
|
|
154
|
+
{ value: 'sine', label: 'Sine' },
|
|
155
|
+
{ value: 'square', label: 'Square' },
|
|
156
|
+
])
|
|
157
|
+
.describe('Waveform the generator emits.'),
|
|
158
|
+
title: f.string('My experiment'),
|
|
159
|
+
tint: f.color('#3a86ff').describe('Plot line color.'),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
/** A leaf with a `describe(...)` gets an \u24d8 beside its label whose tooltip
|
|
163
|
+
* carries the text; leaves without one stay bare. */
|
|
164
|
+
export const Described: Story = {
|
|
165
|
+
render: () => {
|
|
166
|
+
const schema = resolveConfigSchema(described, []);
|
|
167
|
+
const [config, setConfig] = useState<Record<string, unknown>>(described.defaults());
|
|
168
|
+
return (
|
|
169
|
+
<ControlPanel
|
|
170
|
+
schema={schema}
|
|
171
|
+
config={config}
|
|
172
|
+
setConfig={(key, value) => setConfig((prev) => ({ ...prev, [key as string]: value }))}
|
|
173
|
+
/>
|
|
174
|
+
);
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
|
|
144
178
|
const conditional = f.schema({
|
|
145
179
|
showGrid: f.boolean(true),
|
|
146
|
-
cellSize: f
|
|
180
|
+
cellSize: f
|
|
181
|
+
.number(20)
|
|
182
|
+
.range(5, 80)
|
|
183
|
+
.step(5)
|
|
184
|
+
.label('Grid spacing')
|
|
185
|
+
.showIf((c) => c.showGrid === true),
|
|
147
186
|
});
|
|
148
187
|
|
|
149
188
|
/** `showIf` hides the row while the value stays in config. Toggle the
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { fireEvent, render, screen } from '@testing-library/react';
|
|
2
|
-
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
3
2
|
import { TOOL_PREF_KINDS, type ToolPrefKind } from '@weasel-js/core';
|
|
4
3
|
import type { PrefLeaf } from '@weasel-js/ui';
|
|
5
|
-
import
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
6
5
|
import { f } from '../config/builder';
|
|
7
6
|
import { resolveConfigSchema } from '../config/resolve';
|
|
7
|
+
import type { ResolvedConfig } from '../config/types';
|
|
8
8
|
import { ControlPanel } from './ControlPanel';
|
|
9
9
|
import type { ConfigField } from './types';
|
|
10
10
|
|
|
@@ -188,6 +188,21 @@ describe('<ControlPanel> schema', () => {
|
|
|
188
188
|
render(<ControlPanel schema={schema} config={{}} setConfig={vi.fn()} />);
|
|
189
189
|
expect(screen.getByLabelText('Tint')).toHaveValue('#123456');
|
|
190
190
|
});
|
|
191
|
+
|
|
192
|
+
it('hangs a described leaf\u2019s description off a help affordance, and leaves an undescribed leaf bare', () => {
|
|
193
|
+
const schema = resolveConfigSchema(
|
|
194
|
+
f.schema({
|
|
195
|
+
showGrid: f.boolean(true).describe('Draw the alignment grid.'),
|
|
196
|
+
snap: f.boolean(true),
|
|
197
|
+
}),
|
|
198
|
+
[],
|
|
199
|
+
);
|
|
200
|
+
render(
|
|
201
|
+
<ControlPanel schema={schema} config={{ showGrid: true, snap: true }} setConfig={vi.fn()} />,
|
|
202
|
+
);
|
|
203
|
+
expect(screen.getByRole('button', { name: 'About Show grid' })).toBeInTheDocument();
|
|
204
|
+
expect(screen.getAllByRole('button', { name: /^About / })).toHaveLength(1);
|
|
205
|
+
});
|
|
191
206
|
});
|
|
192
207
|
|
|
193
208
|
describe('<ControlPanel> renderers', () => {
|
|
@@ -205,7 +220,7 @@ describe('<ControlPanel> renderers', () => {
|
|
|
205
220
|
expect(screen.queryByText('by-kind')).not.toBeInTheDocument();
|
|
206
221
|
});
|
|
207
222
|
|
|
208
|
-
it(
|
|
223
|
+
it("a lab renderer for the path beats the node's own .render", () => {
|
|
209
224
|
const schema = resolveConfigSchema(
|
|
210
225
|
f.schema({ tint: f.color('#ffffff').render(() => <span>by-node</span>) }),
|
|
211
226
|
[],
|
|
@@ -252,7 +267,7 @@ describe('<ControlPanel> renderers', () => {
|
|
|
252
267
|
|
|
253
268
|
it('a renderer returning null collapses the row', () => {
|
|
254
269
|
const schema = resolveConfigSchema(f.schema({ tint: f.color('#ffffff') }), []);
|
|
255
|
-
|
|
270
|
+
render(
|
|
256
271
|
<ControlPanel
|
|
257
272
|
schema={schema}
|
|
258
273
|
config={{ tint: '#ffffff' }}
|
|
@@ -260,7 +275,7 @@ describe('<ControlPanel> renderers', () => {
|
|
|
260
275
|
renderers={{ color: () => null }}
|
|
261
276
|
/>,
|
|
262
277
|
);
|
|
263
|
-
expect(
|
|
278
|
+
expect(screen.queryByText('Tint')).toBeNull();
|
|
264
279
|
});
|
|
265
280
|
});
|
|
266
281
|
|
|
@@ -274,7 +289,11 @@ describe('<ControlPanel> visibility and sections', () => {
|
|
|
274
289
|
[],
|
|
275
290
|
);
|
|
276
291
|
const { rerender } = render(
|
|
277
|
-
<ControlPanel
|
|
292
|
+
<ControlPanel
|
|
293
|
+
schema={schema}
|
|
294
|
+
config={{ showGrid: true, cellSize: 20 }}
|
|
295
|
+
setConfig={vi.fn()}
|
|
296
|
+
/>,
|
|
278
297
|
);
|
|
279
298
|
expect(screen.getByLabelText('Cell size')).toBeInTheDocument();
|
|
280
299
|
rerender(
|
|
@@ -302,11 +321,11 @@ describe('<ControlPanel> visibility and sections', () => {
|
|
|
302
321
|
f.schema({ showGrid: f.boolean(true), seed: f.number(0).section('Advanced') }),
|
|
303
322
|
[],
|
|
304
323
|
);
|
|
305
|
-
|
|
324
|
+
render(
|
|
306
325
|
<ControlPanel schema={schema} config={{ showGrid: true, seed: 0 }} setConfig={vi.fn()} />,
|
|
307
326
|
);
|
|
308
327
|
expect(screen.getByText('Advanced')).toBeInTheDocument();
|
|
309
|
-
const group =
|
|
328
|
+
const group = screen.getByRole('heading', { name: 'Advanced' }).parentElement;
|
|
310
329
|
expect(group).not.toBeNull();
|
|
311
330
|
expect(group?.textContent).toContain('Seed');
|
|
312
331
|
expect(group?.textContent).not.toContain('Show grid');
|
|
@@ -335,7 +354,10 @@ describe('<ControlPanel> built-in kind coverage', () => {
|
|
|
335
354
|
schema={schemaWith(
|
|
336
355
|
{ kind: 'paint', name: 'Fill', description: '', default: null },
|
|
337
356
|
{
|
|
338
|
-
kind: 'object',
|
|
357
|
+
kind: 'object',
|
|
358
|
+
name: 'Stroke',
|
|
359
|
+
description: '',
|
|
360
|
+
default: {},
|
|
339
361
|
children: { width: { kind: 'number', name: 'Width', description: '', default: 1 } },
|
|
340
362
|
},
|
|
341
363
|
)}
|
|
@@ -360,3 +382,41 @@ describe('<ControlPanel> built-in kind coverage', () => {
|
|
|
360
382
|
).toThrow(/spline/);
|
|
361
383
|
});
|
|
362
384
|
});
|
|
385
|
+
|
|
386
|
+
describe('<ControlPanel> packing', () => {
|
|
387
|
+
const schema = resolveConfigSchema(
|
|
388
|
+
f.schema({ width: f.number(256), title: f.string('a'), tint: f.color('#ff0000') }),
|
|
389
|
+
[],
|
|
390
|
+
);
|
|
391
|
+
const config = { width: 256, title: 'a', tint: '#ff0000' };
|
|
392
|
+
// jsdom lays out no grid, so the class the grid rules key off is the proxy
|
|
393
|
+
// for "these rows pair" — a layout assertion is not available here.
|
|
394
|
+
const list = (container: HTMLElement) => container.querySelector('.lk-control-panel');
|
|
395
|
+
const rowOf = (name: string) => screen.getByLabelText(name).closest('label');
|
|
396
|
+
|
|
397
|
+
it('packs two rows to a line by default', () => {
|
|
398
|
+
const { container } = render(
|
|
399
|
+
<ControlPanel schema={schema} config={config} setConfig={vi.fn()} />,
|
|
400
|
+
);
|
|
401
|
+
expect(list(container)?.className).toMatch(/listPairs/);
|
|
402
|
+
expect(rowOf('Width')?.className).not.toMatch(/span/);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('gives every row its own line under one-up', () => {
|
|
406
|
+
const { container } = render(
|
|
407
|
+
<ControlPanel schema={schema} config={config} setConfig={vi.fn()} pack="one-up" />,
|
|
408
|
+
);
|
|
409
|
+
expect(list(container)?.className).not.toMatch(/listPairs/);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it('spans the rows that need the width under auto, and pairs the rest', () => {
|
|
413
|
+
render(<ControlPanel schema={schema} config={config} setConfig={vi.fn()} pack="auto" />);
|
|
414
|
+
expect(rowOf('Title')?.className).toMatch(/span/);
|
|
415
|
+
expect(rowOf('Width')?.className).not.toMatch(/span/);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it('passes its layout down to the rows', () => {
|
|
419
|
+
render(<ControlPanel schema={schema} config={config} setConfig={vi.fn()} layout="inline" />);
|
|
420
|
+
expect(rowOf('Width')?.className).toMatch(/rowInline/);
|
|
421
|
+
});
|
|
422
|
+
});
|