@weasel-js/labkit 1.3.0 → 1.4.0-pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +226 -55
- package/dist/_dts/CanvasStackContext-LnCfqNBA.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-C-2c72Ij.d.ts +735 -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-DCGEcMO_.d.ts → types-D6s4b7if.d.ts} +1 -1
- 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 +110 -26
- package/dist/canvas/index.js +5 -4
- package/dist/chrome/index.d.ts +9 -6
- package/dist/chrome/index.js +6 -6
- package/dist/chunk-2AYGEN57.js +32 -0
- package/dist/chunk-2AYGEN57.js.map +1 -0
- package/dist/chunk-6OEYTYML.js +669 -0
- package/dist/chunk-6OEYTYML.js.map +1 -0
- package/dist/{chunk-BMW4TDP5.js → chunk-AE5CNVRU.js} +3 -3
- package/dist/{chunk-BMW4TDP5.js.map → chunk-AE5CNVRU.js.map} +1 -1
- 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-E2UQVZ44.js +458 -0
- package/dist/chunk-E2UQVZ44.js.map +1 -0
- package/dist/{chunk-LGKRIUSW.js → chunk-ESIQNQ6K.js} +32 -7
- package/dist/chunk-ESIQNQ6K.js.map +1 -0
- package/dist/{chunk-JDROYQM3.js → chunk-HHGVISVZ.js} +130 -131
- package/dist/chunk-HHGVISVZ.js.map +1 -0
- package/dist/{chunk-BDJXIBRZ.js → chunk-HMFODCOX.js} +126 -29
- package/dist/chunk-HMFODCOX.js.map +1 -0
- package/dist/{chunk-QCICIHEO.js → chunk-L5QJOOLV.js} +839 -320
- package/dist/chunk-L5QJOOLV.js.map +1 -0
- package/dist/{chunk-3XXPU73K.js → chunk-M75ZU6ZZ.js} +9 -7
- package/dist/chunk-M75ZU6ZZ.js.map +1 -0
- package/dist/{chunk-QRYPXSGP.js → chunk-QB5SM4BM.js} +16 -8
- package/dist/chunk-QB5SM4BM.js.map +1 -0
- package/dist/chunk-RBGKL7NF.js +7128 -0
- package/dist/chunk-RBGKL7NF.js.map +1 -0
- package/dist/{chunk-WTI26YTM.js → chunk-TO2FUOKF.js} +40 -13
- package/dist/chunk-TO2FUOKF.js.map +1 -0
- package/dist/{chunk-4TMMVIDM.js → chunk-XJ6N32QP.js} +50 -6
- package/dist/chunk-XJ6N32QP.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 +9 -5
- package/dist/dragdrop/index.js +3 -3
- package/dist/index.d.ts +517 -234
- package/dist/index.js +1769 -143
- package/dist/index.js.map +1 -1
- package/dist/layers/index.d.ts +6 -5
- package/dist/layers/index.js +3 -3
- 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 +138 -91
- package/dist/passthrough/weasel-ui.js +2 -2
- package/dist/primitives/index.d.ts +3 -1
- package/dist/primitives/index.js +5 -5
- package/dist/state/index.d.ts +3 -3
- package/dist/state/index.js +2 -2
- package/dist/styles.css +229 -1
- package/dist/surface/index.d.ts +18 -2
- package/dist/surface/index.js +2 -2
- package/dist/ui/layers/index.js +2 -2
- 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 +247 -0
- package/src/annotations/store.ts +347 -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 +229 -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 +16 -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/controls/ControlPanel.test.tsx +38 -0
- package/src/controls/ControlPanel.tsx +50 -3
- package/src/dragdrop/DragDropRuntime.tsx +8 -2
- package/src/dragdrop/dragDrop.test.tsx +29 -2
- package/src/index.test.ts +23 -0
- package/src/index.ts +93 -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/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 +3 -0
- 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 -0
- 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/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.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/dist/_dts/types-BttTIent.d.ts +0 -314
- package/dist/chunk-3XXPU73K.js.map +0 -1
- package/dist/chunk-4TMMVIDM.js.map +0 -1
- package/dist/chunk-6X5RBC6G.js +0 -366
- package/dist/chunk-6X5RBC6G.js.map +0 -1
- package/dist/chunk-BDJXIBRZ.js.map +0 -1
- package/dist/chunk-CPUJ3QXL.js.map +0 -1
- package/dist/chunk-JDROYQM3.js.map +0 -1
- package/dist/chunk-LGKRIUSW.js.map +0 -1
- package/dist/chunk-QCICIHEO.js.map +0 -1
- package/dist/chunk-QRYPXSGP.js.map +0 -1
- package/dist/chunk-RGT4EJIB.js +0 -6961
- package/dist/chunk-RGT4EJIB.js.map +0 -1
- package/dist/chunk-T66HU3HX.js +0 -17
- package/dist/chunk-T66HU3HX.js.map +0 -1
- package/dist/chunk-WTI26YTM.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,52 +1,185 @@
|
|
|
1
1
|
# @weasel-js/labkit
|
|
2
2
|
|
|
3
|
-
React
|
|
3
|
+
React components for building **labs**: pages where you change parameters and
|
|
4
|
+
watch something redraw. A lab hosts one or more **instruments** — self-contained
|
|
5
|
+
experiments that own their settings and their state and render a picture from
|
|
6
|
+
both. Each open instrument gets a **trial**, one tile of the lab's workspace,
|
|
7
|
+
and the trial supplies the apparatus around it: a control panel, a toolbar, a
|
|
8
|
+
sidebar, undo, snapshots, persistence and drawing tools.
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
Reach for it when you have a visualization, a simulation or a rendering
|
|
11
|
+
algorithm and would rather not build the page around it.
|
|
6
12
|
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
Not published to npm — for now the package is intentionally local-install-only.
|
|
10
|
-
It also depends on a sibling clone of [weasel](https://github.com/orochi235/weasel),
|
|
11
|
-
so clone both side by side:
|
|
13
|
+
## Install
|
|
12
14
|
|
|
13
15
|
```bash
|
|
14
|
-
|
|
15
|
-
git clone https://github.com/orochi235/labkit.git
|
|
16
|
-
cd weasel && npm install && npm run build
|
|
17
|
-
cd ../labkit && npm install && npm run build
|
|
16
|
+
npm i @weasel-js/labkit
|
|
18
17
|
```
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
React 19 is a peer dependency (`react` and `react-dom`, `^19.0.0`). One
|
|
20
|
+
stylesheet import covers everything labkit draws, including the theme tokens and
|
|
21
|
+
the `@weasel-js/ui` components it passes through:
|
|
21
22
|
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
"@weasel-js/labkit": "file:../labkit"
|
|
25
|
-
}
|
|
23
|
+
```ts
|
|
24
|
+
import '@weasel-js/labkit/styles.css';
|
|
26
25
|
```
|
|
27
26
|
|
|
28
|
-
|
|
27
|
+
Annotations are on the `pre` tag until the next stable release:
|
|
28
|
+
`npm i @weasel-js/labkit@pre`.
|
|
29
|
+
|
|
30
|
+
## A lab
|
|
29
31
|
|
|
30
32
|
```tsx
|
|
31
|
-
import {
|
|
33
|
+
import { type ConfigOf, defineInstrument, f, Lab, localStorageAdapter } from '@weasel-js/labkit';
|
|
32
34
|
import '@weasel-js/labkit/styles.css';
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
const config = f.schema({
|
|
37
|
+
bins: f.number(20).range(5, 100).step(1).label('Bins'),
|
|
38
|
+
showAxes: f.boolean(true),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const Histogram = defineInstrument<{ samples: number[] }, ConfigOf<typeof config>>({
|
|
42
|
+
name: 'Histogram',
|
|
43
|
+
config,
|
|
44
|
+
initialState: () => ({ samples: sample(500) }),
|
|
45
|
+
render: () => null,
|
|
46
|
+
canvas: {
|
|
47
|
+
layers: [
|
|
48
|
+
{ id: 'axes', draw: drawAxes },
|
|
49
|
+
{ id: 'bars', draw: drawBars },
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
layers: { ids: ['axes', 'bars'] },
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export function App() {
|
|
35
56
|
return (
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
57
|
+
<Lab
|
|
58
|
+
instruments={[Histogram]}
|
|
59
|
+
defaultInstrument="Histogram"
|
|
60
|
+
title="Histogram"
|
|
61
|
+
storage={localStorageAdapter}
|
|
62
|
+
storageKey="histogram-lab"
|
|
63
|
+
/>
|
|
42
64
|
);
|
|
43
65
|
}
|
|
44
66
|
```
|
|
45
67
|
|
|
68
|
+
`f.schema` states the config once — values, types and controls — and the trial
|
|
69
|
+
renders the settings panel from it. `render` returns the instrument's own DOM;
|
|
70
|
+
returning `null` alongside `canvas` means the canvas layers are the whole
|
|
71
|
+
picture. `storage` keeps open trials and their state across reloads.
|
|
72
|
+
|
|
73
|
+
## Capabilities
|
|
74
|
+
|
|
75
|
+
A capability is a field on the instrument. Declaring it is what makes the trial
|
|
76
|
+
provide the corresponding chrome; they compose freely.
|
|
77
|
+
|
|
78
|
+
| Field | What the trial then provides |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| `canvas` | A stack of layered `<canvas>` elements with pan and zoom, redrawing only the layers that changed |
|
|
81
|
+
| `layers` | A sidebar list that hides and reorders those layers |
|
|
82
|
+
| `annotations` | Drawing tools, an overlay on each region you name, a Marks panel, undo and export |
|
|
83
|
+
| `loupe` | A magnifier toggle that redraws the canvas through a zoomed camera, or calls your own `render` for DOM content |
|
|
84
|
+
| `dragDrop` | A palette to drag items from, and a drop pipeline that hands you world coordinates |
|
|
85
|
+
| `undo` | Undo and redo buttons, snapshotting state on the events you name |
|
|
86
|
+
| `job` | Starts async work, aborts it on unmount and on a key change, and renders progress and a cancel control |
|
|
87
|
+
| `tools` | A tool palette and a tool slot on the trial |
|
|
88
|
+
| `chrome` | Anything else, as contributions keyed to a region |
|
|
89
|
+
|
|
90
|
+
## Annotations
|
|
91
|
+
|
|
92
|
+
An instrument names regions of itself that accept **marks** — freehand strokes,
|
|
93
|
+
lines, arrows, rectangles, ellipses and text drawn over its picture. labkit
|
|
94
|
+
supplies the tool palette, the overlay, the store, undo, persistence and export.
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
const paneRef = createRef<HTMLDivElement>();
|
|
98
|
+
|
|
99
|
+
defineInstrument<State, Config>({
|
|
100
|
+
// …
|
|
101
|
+
annotations: {
|
|
102
|
+
targets: () => [
|
|
103
|
+
{ id: 'pane', ref: paneRef, content: { w: 260, h: 180 }, positionDependsOn: ['angle'] },
|
|
104
|
+
],
|
|
105
|
+
meaning: {
|
|
106
|
+
statuses: [
|
|
107
|
+
{ id: 'open', label: 'Open', color: '#e5484d' },
|
|
108
|
+
{ id: 'fixed', label: 'Fixed', color: '#30a46c' },
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
A mark's position is stored as fractions of its target's `content` box, so it
|
|
116
|
+
stays on the same feature when the picture is rendered at a different size.
|
|
117
|
+
`positionDependsOn` names the config keys that move the picture; labkit
|
|
118
|
+
snapshots their values beside each mark, and a mark whose values have since
|
|
119
|
+
changed draws dashed and reports `isStale`. `meaning.statuses` is the optional
|
|
120
|
+
vocabulary a mark can be labelled with — a status carries its own color, which
|
|
121
|
+
the mark on the canvas follows.
|
|
122
|
+
|
|
123
|
+
Marks live in the trial's record and survive a reload. An instrument that would
|
|
124
|
+
rather keep them in a format it already owns declares `annotations.storage` with
|
|
125
|
+
a `load`/`save` pair, and labkit never writes its own slot.
|
|
126
|
+
|
|
127
|
+
`useAnnotations()` reaches the store from the instrument's `render` or from a
|
|
128
|
+
chrome contribution, and re-renders its caller as marks change:
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
const marks = useAnnotations();
|
|
132
|
+
const stale = marks.query({ target: 'pane' }).filter((m) => marks.isStale(m, config));
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`selection()` answers which marks the user has picked on the overlay, as
|
|
136
|
+
annotation ids, and `setSelection()` replaces them — the same selection
|
|
137
|
+
weasel's own click, marquee and handles drive, merged across every target.
|
|
138
|
+
A panel that opens a card for the clicked mark subscribes and re-reads it.
|
|
139
|
+
|
|
140
|
+
It also carries `get`, `hitTest`, `within`, `add`, `update`, `setMeta`,
|
|
141
|
+
`remove`, `undo`/`redo` and `capture`.
|
|
142
|
+
|
|
143
|
+
**Export.** A target declaring `base()` hands over the picture underneath its
|
|
144
|
+
marks — SVG markup, an image `src` or a canvas — and
|
|
145
|
+
`capture(target, { format, scale })` returns a Blob with the marks drawn on top,
|
|
146
|
+
as PNG or, from an SVG base, as vector SVG. Declaring `annotations` earns an
|
|
147
|
+
Export button in the toolbar; `onCapture` fires after every export, labkit's own
|
|
148
|
+
chrome included.
|
|
149
|
+
|
|
150
|
+
The pieces are exported for a host that wants to assemble them itself:
|
|
151
|
+
`createAnnotationStore`, `<AnnotationOverlay>`, `<AnnotationTargets>`,
|
|
152
|
+
`<MarkList>`, `<ExportMenu>`, `ANNOTATION_TOOLS` and `capturePlan`.
|
|
153
|
+
|
|
154
|
+
## Chrome
|
|
155
|
+
|
|
156
|
+
A trial's chrome has six named regions — `titlebar`, `toolbar`, `palette`,
|
|
157
|
+
`sidebar`, `viewport`, `status`. A `TrialContribution` names one and supplies
|
|
158
|
+
data the region lays out, or a `render` function that opts out of that layout:
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
<Lab
|
|
162
|
+
chrome={[{ id: 'seed', region: 'status', item: { text: `seed ${seed}` } }]}
|
|
163
|
+
suppress={['fps']}
|
|
164
|
+
// …
|
|
165
|
+
/>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Contributions passed to `<Lab>` apply to every trial; an instrument's own
|
|
169
|
+
`chrome` field applies to its trials only. `suppress` drops a built-in by id and
|
|
170
|
+
throws on an id that is not there.
|
|
171
|
+
|
|
172
|
+
A sidebar section can be torn out of the trial into the workspace — as a tile
|
|
173
|
+
beside the trials, or as a floating panel — with the trial still rendering into
|
|
174
|
+
it. `SidebarSection.undockable` and `undockAs` control the offer;
|
|
175
|
+
`undockPanel` / `dockPanel` move one.
|
|
176
|
+
|
|
46
177
|
## Theming
|
|
47
178
|
|
|
48
179
|
labkit ships one theme, `interstellar` — a cosmic dark and a warm parchment
|
|
49
|
-
light — as a value, not a stylesheet
|
|
180
|
+
light — as a value, not a stylesheet. `<Lab>` and `<LabShell>` apply it for you;
|
|
181
|
+
`mode` is `"auto"` (follow the OS), `"light"` or `"dark"`. Only `styles.css`
|
|
182
|
+
needs importing, and the token values arrive through the provider:
|
|
50
183
|
|
|
51
184
|
```tsx
|
|
52
185
|
import { interstellarTheme } from '@weasel-js/labkit';
|
|
@@ -55,9 +188,31 @@ import { ThemeProvider } from '@weasel-js/theme/react';
|
|
|
55
188
|
<ThemeProvider theme={interstellarTheme} mode="dark">…</ThemeProvider>
|
|
56
189
|
```
|
|
57
190
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
191
|
+
Style your own surfaces against the `--wzl-*` tokens so they follow the mode the
|
|
192
|
+
lab is in. Class names are `lk-*` and are not public API — every component takes
|
|
193
|
+
a `className`.
|
|
194
|
+
|
|
195
|
+
## Without the runtime
|
|
196
|
+
|
|
197
|
+
The presentational pieces work on their own. `<LabShell>` is a titled page
|
|
198
|
+
frame; `<Workspace>` is a tile grid that can be resized and reordered and takes
|
|
199
|
+
any children:
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
import { LabShell, Workspace } from '@weasel-js/labkit';
|
|
203
|
+
|
|
204
|
+
<LabShell title="My Lab" header={<button>+ Add</button>}>
|
|
205
|
+
<Workspace ids={['a', 'b']} resizable>
|
|
206
|
+
<MyPane id="a" />
|
|
207
|
+
<MyPane id="b" />
|
|
208
|
+
</Workspace>
|
|
209
|
+
</LabShell>;
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
`<Toolbar>`, `<Sidebar>`, `<StatusBar>`, `<FpsMeter>`, `<ScaleIndicator>`,
|
|
213
|
+
`<Legend>`, `<FloatingPanel>`, `<ZoomControl>` and the `@weasel-js/ui` property
|
|
214
|
+
rows re-exported from the root are all usable this way. `<Trial>` is not: it
|
|
215
|
+
reads the lab store and only mounts inside a `<Lab>`.
|
|
61
216
|
|
|
62
217
|
## Driving your own renderer
|
|
63
218
|
|
|
@@ -68,40 +223,56 @@ labkit and keep the GL yourself:
|
|
|
68
223
|
import { toDeviceRect, useSurfaceTile, useTiledSurface } from '@weasel-js/labkit/surface';
|
|
69
224
|
```
|
|
70
225
|
|
|
71
|
-
A trial's `view` is opaque to labkit —
|
|
72
|
-
|
|
73
|
-
|
|
226
|
+
A trial's `view` is opaque to labkit — persisted, restored on Reset and handed
|
|
227
|
+
to the instrument without being read into — so a 3D lab stores an orbit there.
|
|
228
|
+
Both cameras ship independently of `CanvasStack`: `useOrbit` for a 3D view,
|
|
229
|
+
`usePanZoom` for a 2D one over a `ViewTransform`. See `src/surface/AGENTS.md`
|
|
230
|
+
for the contract and the traps.
|
|
74
231
|
|
|
75
|
-
|
|
232
|
+
## Subpath exports
|
|
76
233
|
|
|
77
|
-
|
|
234
|
+
The root barrel carries the common surface; a subpath reaches one slice
|
|
235
|
+
directly. Several expose more than the root does, `/state` most of all.
|
|
78
236
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
237
|
+
| Subpath | |
|
|
238
|
+
| --- | --- |
|
|
239
|
+
| `@weasel-js/labkit` | Everything |
|
|
240
|
+
| `/styles.css` | The one stylesheet |
|
|
241
|
+
| `/primitives` | Toolbar, Sidebar, StatusBar, Legend, FloatingPanel, meters |
|
|
242
|
+
| `/chrome` | Regions, contribution types, built-in contributions |
|
|
243
|
+
| `/controls` | `<ControlPanel>` and the config field types |
|
|
244
|
+
| `/canvas` | `<CanvasStack>`, coordinate helpers, `usePanZoom`, `useOrbit` |
|
|
245
|
+
| `/layers` | `<LayerList>` |
|
|
246
|
+
| `/loupe` | Magnifier components and `useLoupe` |
|
|
247
|
+
| `/surface` | Tiled surface hooks for your own renderer |
|
|
248
|
+
| `/job` | `useJob` and the job capability types |
|
|
249
|
+
| `/state` | The lab store, storage adapters, `useTrialState`, serialization helpers |
|
|
250
|
+
| `/undo` | Undo stack and event bus |
|
|
251
|
+
| `/dragdrop` | `<Palette>`, `<DragGhost>`, `useDragDrop` |
|
|
252
|
+
| `/ui/layers` | `<LayerStack>`, the expandable layer-card list |
|
|
253
|
+
| `/weasel-ui`, `/weasel-canvas` | Passthroughs to `@weasel-js/ui` and `@weasel-js/core` |
|
|
88
254
|
|
|
89
255
|
## Development
|
|
90
256
|
|
|
257
|
+
labkit is a workspace package in the [weasel](https://github.com/orochi235/weasel)
|
|
258
|
+
monorepo. From the repo root, `npm install` once, then from `packages/labkit`:
|
|
259
|
+
|
|
91
260
|
```bash
|
|
92
|
-
npm
|
|
93
|
-
npm run dev
|
|
94
|
-
npm
|
|
95
|
-
npm
|
|
96
|
-
npm run
|
|
97
|
-
npm run
|
|
261
|
+
npm run dev # Vite dev server on examples/minimal
|
|
262
|
+
npm run dev:annotate # …on examples/annotate-lab (and dev:drag, dev:weasel, dev:schema)
|
|
263
|
+
npm test # Vitest
|
|
264
|
+
npm run lint # Biome, plus the class-prefix and design-token checks
|
|
265
|
+
npm run build # dist/ for publish
|
|
266
|
+
npm run docs:dev # VitePress on docs/ (docs:build to render it)
|
|
98
267
|
```
|
|
99
268
|
|
|
269
|
+
Storybook is built at the repo root: `npm run storybook` from there covers every
|
|
270
|
+
package.
|
|
271
|
+
|
|
100
272
|
## Documentation
|
|
101
273
|
|
|
102
|
-
- [Docs site](https://orochi235.github.io/labkit/)
|
|
103
|
-
- [Recipes](https://orochi235.github.io/labkit/RECIPES) — composition patterns
|
|
104
|
-
- [Agent guide](https://orochi235.github.io/labkit/AGENTS) —
|
|
105
|
-
- [Storybook](https://orochi235.github.io/
|
|
106
|
-
- [
|
|
107
|
-
- [Design spec](https://github.com/orochi235/labkit/blob/main/docs/superpowers/specs/2026-04-26-labkit-design.md)
|
|
274
|
+
- [Docs site](https://orochi235.github.io/weasel/labkit/)
|
|
275
|
+
- [Recipes](https://orochi235.github.io/weasel/labkit/RECIPES) — composition patterns
|
|
276
|
+
- [Agent guide](https://orochi235.github.io/weasel/labkit/AGENTS) — a map of the source
|
|
277
|
+
- [Storybook](https://orochi235.github.io/weasel/docs/ui/storybook/)
|
|
278
|
+
- [Design spec](https://github.com/orochi235/weasel/blob/main/packages/labkit/docs/superpowers/specs/2026-04-26-labkit-design.md)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { RefObject } from 'react';
|
|
3
|
+
import { V as ViewTransform, W as WorldFrame } from './frac-C-2c72Ij.js';
|
|
4
|
+
|
|
5
|
+
/** One layer of a canvas stack: its id, whether it is currently shown, and how
|
|
6
|
+
* it paints itself. */
|
|
7
|
+
interface CanvasLayerDescriptor {
|
|
8
|
+
id: string;
|
|
9
|
+
visible: boolean;
|
|
10
|
+
render: (ctx: CanvasRenderingContext2D, view: ViewTransform, frame: WorldFrame) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** The stack's own drawing surface, for an overlay that has to re-draw it at
|
|
14
|
+
* another camera or read back what it presented — a loupe. */
|
|
15
|
+
interface CanvasStackSurface {
|
|
16
|
+
/** The element the layers are stacked in, and the one pan/zoom listens on. */
|
|
17
|
+
element: RefObject<HTMLElement | null>;
|
|
18
|
+
/** Its measured CSS size, and the ratio the backing stores are scaled by. */
|
|
19
|
+
size: {
|
|
20
|
+
width: number;
|
|
21
|
+
height: number;
|
|
22
|
+
dpr: number;
|
|
23
|
+
};
|
|
24
|
+
/** The presented `<canvas>` per layer id. */
|
|
25
|
+
canvases: RefObject<Map<string, HTMLCanvasElement>>;
|
|
26
|
+
/** The layers as the stack is drawing them, bottom first. */
|
|
27
|
+
layers: readonly CanvasLayerDescriptor[];
|
|
28
|
+
}
|
|
29
|
+
/** What a canvas stack publishes to its children: the view, and the resolved
|
|
30
|
+
* coordinate system it is read in, so DOM overlays can place themselves in the
|
|
31
|
+
* same coordinates the layers draw in. */
|
|
32
|
+
interface CanvasStackContextValue {
|
|
33
|
+
view: ViewTransform;
|
|
34
|
+
frame: WorldFrame;
|
|
35
|
+
/** Absent from a context assembled by hand, which has no stack behind it. */
|
|
36
|
+
surface?: CanvasStackSurface;
|
|
37
|
+
}
|
|
38
|
+
/** Context carrying the surrounding canvas stack's view and world frame. */
|
|
39
|
+
declare const CanvasStackContext: react.Context<CanvasStackContextValue | null>;
|
|
40
|
+
|
|
41
|
+
export { CanvasStackContext as a };
|
|
42
|
+
export type { CanvasLayerDescriptor as C, CanvasStackContextValue as b, CanvasStackSurface as c };
|