@weasel-js/labkit 1.4.0-pre.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.
Files changed (54) hide show
  1. package/README.md +226 -55
  2. package/dist/_dts/{CanvasStackContext-DtAn_3O-.d.ts → CanvasStackContext-LnCfqNBA.d.ts} +1 -1
  3. package/dist/_dts/{frac-Cp3NivlC.d.ts → frac-C-2c72Ij.d.ts} +9 -2
  4. package/dist/canvas/index.d.ts +73 -7
  5. package/dist/canvas/index.js +2 -2
  6. package/dist/chrome/index.d.ts +2 -2
  7. package/dist/chrome/index.js +5 -5
  8. package/dist/{chunk-LITE5YFB.js → chunk-6OEYTYML.js} +6 -6
  9. package/dist/{chunk-LITE5YFB.js.map → chunk-6OEYTYML.js.map} +1 -1
  10. package/dist/{chunk-JDDIZSLL.js → chunk-AE5CNVRU.js} +3 -3
  11. package/dist/{chunk-JDDIZSLL.js.map → chunk-AE5CNVRU.js.map} +1 -1
  12. package/dist/{chunk-EQIB2PGC.js → chunk-E2UQVZ44.js} +3 -3
  13. package/dist/{chunk-EQIB2PGC.js.map → chunk-E2UQVZ44.js.map} +1 -1
  14. package/dist/{chunk-4EY67BKJ.js → chunk-ESIQNQ6K.js} +3 -3
  15. package/dist/{chunk-4EY67BKJ.js.map → chunk-ESIQNQ6K.js.map} +1 -1
  16. package/dist/{chunk-6JMU56CU.js → chunk-HHGVISVZ.js} +4 -4
  17. package/dist/{chunk-6JMU56CU.js.map → chunk-HHGVISVZ.js.map} +1 -1
  18. package/dist/{chunk-3ZELRRBV.js → chunk-HMFODCOX.js} +86 -6
  19. package/dist/chunk-HMFODCOX.js.map +1 -0
  20. package/dist/{chunk-ROQDRXKW.js → chunk-L5QJOOLV.js} +126 -12
  21. package/dist/chunk-L5QJOOLV.js.map +1 -0
  22. package/dist/{chunk-5TCEVMPI.js → chunk-M75ZU6ZZ.js} +3 -3
  23. package/dist/{chunk-5TCEVMPI.js.map → chunk-M75ZU6ZZ.js.map} +1 -1
  24. package/dist/{chunk-3P5TTTA7.js → chunk-QB5SM4BM.js} +3 -3
  25. package/dist/{chunk-3P5TTTA7.js.map → chunk-QB5SM4BM.js.map} +1 -1
  26. package/dist/{chunk-HEORBLVT.js → chunk-RBGKL7NF.js} +3 -3
  27. package/dist/{chunk-HEORBLVT.js.map → chunk-RBGKL7NF.js.map} +1 -1
  28. package/dist/{chunk-YQJNT4XQ.js → chunk-TO2FUOKF.js} +4 -4
  29. package/dist/{chunk-YQJNT4XQ.js.map → chunk-TO2FUOKF.js.map} +1 -1
  30. package/dist/controls/index.js +3 -3
  31. package/dist/dragdrop/index.d.ts +1 -1
  32. package/dist/dragdrop/index.js +2 -2
  33. package/dist/index.d.ts +7 -52
  34. package/dist/index.js +49 -101
  35. package/dist/index.js.map +1 -1
  36. package/dist/layers/index.d.ts +2 -2
  37. package/dist/layers/index.js +3 -3
  38. package/dist/loupe/index.d.ts +3 -3
  39. package/dist/loupe/index.js +2 -2
  40. package/dist/passthrough/weasel-canvas.js +1 -1
  41. package/dist/passthrough/weasel-ui.js +2 -2
  42. package/dist/primitives/index.js +4 -4
  43. package/dist/surface/index.js +2 -2
  44. package/dist/ui/layers/index.js +2 -2
  45. package/dist/undo/index.d.ts +1 -1
  46. package/package.json +7 -7
  47. package/src/annotations/store.test.ts +72 -0
  48. package/src/annotations/store.ts +30 -0
  49. package/src/annotations/types.ts +9 -2
  50. package/src/canvas/index.ts +11 -0
  51. package/src/index.test.ts +1 -0
  52. package/src/index.ts +13 -9
  53. package/dist/chunk-3ZELRRBV.js.map +0 -1
  54. package/dist/chunk-ROQDRXKW.js.map +0 -1
package/README.md CHANGED
@@ -1,52 +1,185 @@
1
1
  # @weasel-js/labkit
2
2
 
3
- React widgets for building self-contained interactive **lab** pages — pages with sliders, controls, and canvas-based experimentation.
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
- This is the v0.x of the library. The Lab/Trial/Instrument runtime arrives in later plans; v0.0.1 ships presentational primitives.
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
- ## Installation
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
- git clone https://github.com/orochi235/weasel.git
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
- Then point your app at the local clone:
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
- ```json
23
- "dependencies": {
24
- "@weasel-js/labkit": "file:../labkit"
25
- }
23
+ ```ts
24
+ import '@weasel-js/labkit/styles.css';
26
25
  ```
27
26
 
28
- ## Usage
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 { LabShell, Toolbar, Workspace, FpsMeter } from '@weasel-js/labkit';
33
+ import { type ConfigOf, defineInstrument, f, Lab, localStorageAdapter } from '@weasel-js/labkit';
32
34
  import '@weasel-js/labkit/styles.css';
33
35
 
34
- function MyLab() {
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
- <LabShell title="My Lab" header={<button>+ Add</button>}>
37
- <Workspace>
38
- <div>Trial 1</div>
39
- <div>Trial 2</div>
40
- </Workspace>
41
- </LabShell>
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
- `<Lab>` and `<LabShell>` do this for you; `mode` is `"auto"` (follow the OS),
59
- `"light"` or `"dark"`. Only `styles.css` needs importing — the token values
60
- arrive through the provider.
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 — it is persisted, restored on Reset and
72
- handed to the instrument without being read into — so a 3D lab stores an orbit
73
- there and gets all three. `useOrbit` is the 3D peer of `usePanZoom`.
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
- See `src/surface/AGENTS.md` for the contract and the traps.
232
+ ## Subpath exports
76
233
 
77
- ## Long-running work
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
- An instrument with work too slow for a render declares a `job`. labkit starts it,
80
- aborts it on unmount and on a key change, discards results from a superseded run,
81
- and renders progress and a cancel control into the trial chrome. Per-item failure
82
- is an event rather than a thrown error, so a run with two failed items is a
83
- partial success.
84
-
85
- ```tsx
86
- import type { JobCapability } from '@weasel-js/labkit/job';
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 install
93
- npm run dev # Vite dev server (examples/)
94
- npm run storybook # Storybook on :6006
95
- npm test # Vitest
96
- npm run lint # Biome + class-prefix check
97
- npm run build # Build dist/ for publish
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) — agent navigation guide
105
- - [Storybook](https://orochi235.github.io/labkit/storybook/)
106
- - [Speech balloon experiment](https://orochi235.github.io/labkit/storybook/?path=/story/ui-properties-propertypanel-speechballoonpanels--right-sidebar-tails) — sample implementation built on the property-panel widgets
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)
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { RefObject } from 'react';
3
- import { V as ViewTransform, W as WorldFrame } from './frac-Cp3NivlC.js';
3
+ import { V as ViewTransform, W as WorldFrame } from './frac-C-2c72Ij.js';
4
4
 
5
5
  /** One layer of a canvas stack: its id, whether it is currently shown, and how
6
6
  * it paints itself. */
@@ -671,8 +671,15 @@ interface AnnotationsApi {
671
671
  within(target: string, box: FracRect): Annotation[];
672
672
  /** Whether `a`'s position still describes the picture `config` produces. */
673
673
  isStale(a: Annotation, config: unknown): boolean;
674
- /** Fires after every mutation. No delta: the scene does not diff, so
675
- * re-query rather than expecting a change payload. */
674
+ /** The marks the user currently has selected, across every target, as
675
+ * annotation ids. Every one resolves through `get`. */
676
+ selection(): readonly string[];
677
+ /** Replace the selection. An id naming a target or a mark that is not
678
+ * there is dropped, the way `update` and `remove` ignore one. */
679
+ setSelection(ids: readonly string[]): void;
680
+ /** Fires after every mutation *and* after a selection change — weasel keeps
681
+ * a canvas's selection on the scene, so both already arrive on this one
682
+ * channel. No delta: re-query, and re-read `selection()`. */
676
683
  subscribe(fn: () => void): () => void;
677
684
  /** Whether the last mark change on any target can be taken back. Weasel
678
685
  * history is the authority; this only decides *which* target's. */
@@ -1,9 +1,9 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { ReactNode } from 'react';
3
- import { V as ViewTransform, c as WorldSpec, a2 as ViewportSize, a as Point, W as WorldFrame } from '../_dts/frac-Cp3NivlC.js';
4
- export { x as DEFAULT_FRAME, a4 as applyCamera, aa as resolveFrame } from '../_dts/frac-Cp3NivlC.js';
5
- import { C as CanvasLayerDescriptor } from '../_dts/CanvasStackContext-DtAn_3O-.js';
6
- export { a as CanvasStackContext, b as CanvasStackContextValue, c as CanvasStackSurface } from '../_dts/CanvasStackContext-DtAn_3O-.js';
2
+ import { ReactNode, WheelEvent, PointerEvent } from 'react';
3
+ import { V as ViewTransform, c as WorldSpec, a2 as ViewportSize, a as Point, W as WorldFrame } from '../_dts/frac-C-2c72Ij.js';
4
+ export { x as DEFAULT_FRAME, a4 as applyCamera, aa as resolveFrame } from '../_dts/frac-C-2c72Ij.js';
5
+ import { C as CanvasLayerDescriptor } from '../_dts/CanvasStackContext-LnCfqNBA.js';
6
+ export { a as CanvasStackContext, b as CanvasStackContextValue, c as CanvasStackSurface } from '../_dts/CanvasStackContext-LnCfqNBA.js';
7
7
  import '../_dts/types-D6s4b7if.js';
8
8
  import '../_dts/PrefsForm-BkUJZx0A.js';
9
9
  import '../_dts/types-DYMaEvM5.js';
@@ -64,5 +64,71 @@ declare function worldToScreen(world: Point, view: ViewTransform, frame?: WorldF
64
64
  /** Unproject a screen point back to world coordinates under a view. */
65
65
  declare function screenToWorld(screen: Point, view: ViewTransform, frame?: WorldFrame): Point;
66
66
 
67
- export { CanvasLayerDescriptor, CanvasStack, ViewportSize, WorldFrame, WorldSpec, centerOn, screenToWorld, worldToScreen, zoomAt };
68
- export type { CanvasStackProps, ZoomAtOptions };
67
+ /** A point in the space the instrument works in. */
68
+ interface Vec3 {
69
+ x: number;
70
+ y: number;
71
+ z: number;
72
+ }
73
+ /** An orbit camera as a trial view: where it looks from, and what it looks at.
74
+ * labkit does not turn this into a matrix — the host's renderer does. */
75
+ interface OrbitView {
76
+ yaw: number;
77
+ pitch: number;
78
+ distance: number;
79
+ target: Vec3;
80
+ }
81
+ /** Just short of the pole. At exactly ±PI/2 the azimuth is undefined and the
82
+ * camera rolls, which reads as the model jumping rather than as a limit. */
83
+ declare const PITCH_LIMIT: number;
84
+ declare function clampPitch(pitch: number): number;
85
+ declare function wrapYaw(yaw: number): number;
86
+ /** The view a drag of (dx, dy) from `start` produces. Absolute against the drag
87
+ * start, so it can be re-applied any number of times without compounding. */
88
+ declare function orbitAfterDrag(start: OrbitView, dx: number, dy: number): OrbitView;
89
+ /** Multiplicative, so one notch covers the same proportion of the distance
90
+ * whether the camera is near or far. */
91
+ declare function orbitAfterWheel(view: OrbitView, deltaY: number, minDistance: number, maxDistance: number): OrbitView;
92
+ interface UseOrbitOptions {
93
+ view: OrbitView;
94
+ onViewChange: (v: OrbitView) => void;
95
+ /** Restored on double-click. Defaults to the view the hook first saw. */
96
+ home?: OrbitView;
97
+ minDistance?: number;
98
+ maxDistance?: number;
99
+ }
100
+ interface OrbitHandlers {
101
+ onWheel: (e: WheelEvent<HTMLElement>) => void;
102
+ onPointerDown: (e: PointerEvent<HTMLElement>) => void;
103
+ onPointerMove: (e: PointerEvent<HTMLElement>) => void;
104
+ onPointerUp: (e: PointerEvent<HTMLElement>) => void;
105
+ onDoubleClick: () => void;
106
+ isDragging: () => boolean;
107
+ }
108
+ /** Pointer gestures over an orbit view: drag to turn, wheel or pinch to dolly,
109
+ * double-click to go home. The 3D peer of `usePanZoom`. */
110
+ declare function useOrbit({ view, onViewChange, home, minDistance, maxDistance, }: UseOrbitOptions): OrbitHandlers;
111
+
112
+ interface UsePanZoomOptions {
113
+ view: ViewTransform;
114
+ onViewChange: (v: ViewTransform) => void;
115
+ /** The opening `view.zoom` always stays reachable, widening these past
116
+ * whatever is passed here if it would otherwise exclude it. */
117
+ minZoom?: number;
118
+ maxZoom?: number;
119
+ /** The instrument's coordinate system, resolved against the viewport. The
120
+ * wheel anchors in this frame; omitting it anchors at the element's
121
+ * top-left, which is what labkit did before a frame could be declared. */
122
+ frame?: WorldFrame;
123
+ }
124
+ interface PanZoomHandlers {
125
+ onWheel: (e: WheelEvent<HTMLElement>) => void;
126
+ onPointerDown: (e: PointerEvent<HTMLElement>) => void;
127
+ onPointerMove: (e: PointerEvent<HTMLElement>) => void;
128
+ onPointerUp: (e: PointerEvent<HTMLElement>) => void;
129
+ isDragging: () => boolean;
130
+ }
131
+ declare function usePanZoom({ view, onViewChange, minZoom, maxZoom, frame, }: UsePanZoomOptions): PanZoomHandlers;
132
+
133
+ export { CanvasLayerDescriptor, CanvasStack, PITCH_LIMIT, ViewportSize, WorldFrame, WorldSpec, centerOn, clampPitch, orbitAfterDrag, orbitAfterWheel, screenToWorld, useOrbit, usePanZoom, worldToScreen, wrapYaw, zoomAt };
134
+ export type { CanvasStackProps, OrbitHandlers, OrbitView, PanZoomHandlers, UseOrbitOptions, UsePanZoomOptions, Vec3, ZoomAtOptions };
@@ -1,7 +1,7 @@
1
- export { CanvasStack } from '../chunk-3ZELRRBV.js';
1
+ export { CanvasStack, PITCH_LIMIT, clampPitch, orbitAfterDrag, orbitAfterWheel, useOrbit, usePanZoom, wrapYaw } from '../chunk-HMFODCOX.js';
2
2
  export { centerOn, zoomAt } from '../chunk-CTRKTLYZ.js';
3
3
  export { DEFAULT_FRAME, applyCamera, resolveFrame, screenToWorld, worldToScreen } from '../chunk-2AYGEN57.js';
4
4
  export { CanvasStackContext } from '../chunk-CQLQPQ4P.js';
5
- import '../chunk-ROQDRXKW.js';
5
+ import '../chunk-L5QJOOLV.js';
6
6
  //# sourceMappingURL=index.js.map
7
7
  //# sourceMappingURL=index.js.map
@@ -1,6 +1,6 @@
1
1
  import { c as ControlRenderer } from '../_dts/types-D6s4b7if.js';
2
- import { I as Instrument, _ as TrialChromeContext, m as TrialContribution } from '../_dts/frac-Cp3NivlC.js';
3
- export { z as IconComponent, O as SidebarSection, Q as StatusReadout, Y as ToolItem, Z as ToolbarItem, $ as TrialRegion, a1 as ViewportControl } from '../_dts/frac-Cp3NivlC.js';
2
+ import { I as Instrument, _ as TrialChromeContext, m as TrialContribution } from '../_dts/frac-C-2c72Ij.js';
3
+ export { z as IconComponent, O as SidebarSection, Q as StatusReadout, Y as ToolItem, Z as ToolbarItem, $ as TrialRegion, a1 as ViewportControl } from '../_dts/frac-C-2c72Ij.js';
4
4
  import * as react_jsx_runtime from 'react/jsx-runtime';
5
5
  import '../_dts/PrefsForm-BkUJZx0A.js';
6
6
  import 'react';
@@ -1,9 +1,9 @@
1
- export { PaletteRegion, SidebarRegion, StatusRegion, TitleBarRegion, ToolbarRegion, ViewportRegion, builtinContributions, mergeContributions, suppressContributions } from '../chunk-LITE5YFB.js';
2
- import '../chunk-6JMU56CU.js';
3
- import '../chunk-YQJNT4XQ.js';
1
+ export { PaletteRegion, SidebarRegion, StatusRegion, TitleBarRegion, ToolbarRegion, ViewportRegion, builtinContributions, mergeContributions, suppressContributions } from '../chunk-6OEYTYML.js';
2
+ import '../chunk-HHGVISVZ.js';
3
+ import '../chunk-TO2FUOKF.js';
4
4
  import '../chunk-SBXNQW4G.js';
5
- import '../chunk-HEORBLVT.js';
5
+ import '../chunk-RBGKL7NF.js';
6
6
  import '../chunk-CQLQPQ4P.js';
7
- import '../chunk-ROQDRXKW.js';
7
+ import '../chunk-L5QJOOLV.js';
8
8
  //# sourceMappingURL=index.js.map
9
9
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
- import { Toolbar, ZoomControl, ScaleIndicator, FpsMeter, useRovingTabIndex, StatusBar } from './chunk-6JMU56CU.js';
2
- import { ControlPanel } from './chunk-YQJNT4XQ.js';
3
- import { b, h2, D, O, B, k, x, A, p, _, v, j, y, n4, o3 } from './chunk-HEORBLVT.js';
4
- import { SelectIcon, PencilIcon, LineIcon, ArrowIcon, RectIcon, EllipseIcon, TextIcon } from './chunk-ROQDRXKW.js';
1
+ import { Toolbar, ZoomControl, ScaleIndicator, FpsMeter, useRovingTabIndex, StatusBar } from './chunk-HHGVISVZ.js';
2
+ import { ControlPanel } from './chunk-TO2FUOKF.js';
3
+ import { b, h2, D, O, B, k, x, A, p, _, v, j, y, n4, o3 } from './chunk-RBGKL7NF.js';
4
+ import { SelectIcon, PencilIcon, LineIcon, ArrowIcon, RectIcon, EllipseIcon, TextIcon } from './chunk-L5QJOOLV.js';
5
5
  import { createContext, useContext, useState, useId, useRef, useSyncExternalStore, useCallback } from 'react';
6
6
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
7
 
@@ -665,5 +665,5 @@ function ViewportRegion({ contributions, ctx }) {
665
665
  }
666
666
 
667
667
  export { ANNOTATION_TOOLS, ANNOTATION_WEASEL_TOOLS, AnnotationsContext, ExportMenu, MarkList, PaletteRegion, SidebarRegion, StatusRegion, TitleBarRegion, ToolbarRegion, ViewportRegion, annotationToolInfo, builtinContributions, mergeContributions, suppressContributions, useAnnotations, useAnnotationsOptional };
668
- //# sourceMappingURL=chunk-LITE5YFB.js.map
669
- //# sourceMappingURL=chunk-LITE5YFB.js.map
668
+ //# sourceMappingURL=chunk-6OEYTYML.js.map
669
+ //# sourceMappingURL=chunk-6OEYTYML.js.map