@weasel-js/labkit 1.4.0-pre.1 → 1.4.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/dist/_dts/{CanvasStackContext-LnCfqNBA.d.ts → CanvasStackContext-BIRUVVGH.d.ts} +1 -1
- package/dist/_dts/PrefsForm.d-CgxUequc.d.ts +20 -0
- package/dist/_dts/{frac-C-2c72Ij.d.ts → frac-X7mWgd7y.d.ts} +10 -4
- package/dist/_dts/{index-BDVzvRzQ.d.ts → index-FEa1MudK.d.ts} +8 -2
- package/dist/_dts/{types-DYMaEvM5.d.ts → types-ChVJJHvk.d.ts} +3 -0
- package/dist/_dts/{types-D6s4b7if.d.ts → types-lg4TSCb2.d.ts} +3 -1
- package/dist/_dts/{useTrialState-gmMvZqPc.d.ts → useTrialState-Cdm13fvl.d.ts} +2 -1
- package/dist/_dts/weasel-canvas-FJTFi3ZZ.d.ts +5041 -0
- package/dist/canvas/index.d.ts +16 -16
- package/dist/canvas/index.js +2 -2
- package/dist/chrome/index.d.ts +6 -6
- package/dist/chrome/index.js +5 -5
- package/dist/chunk-2YILQ7Y5.js +33 -0
- package/dist/chunk-2YILQ7Y5.js.map +1 -0
- package/dist/{chunk-TO2FUOKF.js → chunk-53AZ2O2N.js} +16 -13
- package/dist/chunk-53AZ2O2N.js.map +1 -0
- package/dist/{chunk-QB5SM4BM.js → chunk-7TRFMIWX.js} +55 -41
- package/dist/chunk-7TRFMIWX.js.map +1 -0
- package/dist/{chunk-6OEYTYML.js → chunk-BMQWLKOL.js} +15 -15
- package/dist/chunk-BMQWLKOL.js.map +1 -0
- package/dist/{chunk-M75ZU6ZZ.js → chunk-D7QIPGEB.js} +3 -3
- package/dist/{chunk-M75ZU6ZZ.js.map → chunk-D7QIPGEB.js.map} +1 -1
- package/dist/{chunk-L5QJOOLV.js → chunk-DG2IFMLA.js} +1309 -273
- package/dist/chunk-DG2IFMLA.js.map +1 -0
- package/dist/{chunk-LN6JDUGB.js → chunk-HXXTULDN.js} +2 -2
- package/dist/{chunk-LN6JDUGB.js.map → chunk-HXXTULDN.js.map} +1 -1
- package/dist/{chunk-RBGKL7NF.js → chunk-J6SBFZYT.js} +1807 -1814
- package/dist/chunk-J6SBFZYT.js.map +1 -0
- package/dist/{chunk-XJ6N32QP.js → chunk-LJSIUFYD.js} +14 -30
- package/dist/chunk-LJSIUFYD.js.map +1 -0
- package/dist/{chunk-AE5CNVRU.js → chunk-MIT3ISW4.js} +37 -31
- package/dist/chunk-MIT3ISW4.js.map +1 -0
- package/dist/{chunk-HHGVISVZ.js → chunk-MSR7EVS6.js} +7 -5
- package/dist/chunk-MSR7EVS6.js.map +1 -0
- package/dist/{chunk-ESIQNQ6K.js → chunk-OFCBEZMU.js} +12 -6
- package/dist/chunk-OFCBEZMU.js.map +1 -0
- package/dist/{chunk-HMFODCOX.js → chunk-PP7ZWAMV.js} +83 -75
- package/dist/chunk-PP7ZWAMV.js.map +1 -0
- package/dist/{chunk-E2UQVZ44.js → chunk-RLOXQZW3.js} +3 -3
- package/dist/{chunk-E2UQVZ44.js.map → chunk-RLOXQZW3.js.map} +1 -1
- package/dist/controls/index.d.ts +4 -3
- package/dist/controls/index.js +3 -3
- package/dist/dragdrop/index.d.ts +5 -5
- package/dist/dragdrop/index.js +2 -2
- package/dist/index.d.ts +57 -77
- package/dist/index.js +308 -161
- package/dist/index.js.map +1 -1
- package/dist/job/index.js +1 -1
- package/dist/layers/index.d.ts +6 -6
- package/dist/layers/index.js +3 -3
- package/dist/loupe/index.d.ts +7 -13
- package/dist/loupe/index.js +2 -2
- package/dist/passthrough/weasel-canvas.d.ts +3 -343
- package/dist/passthrough/weasel-canvas.js +1 -1
- package/dist/passthrough/weasel-ui.d.ts +141 -2866
- package/dist/passthrough/weasel-ui.js +2 -2
- package/dist/primitives/index.js +4 -4
- package/dist/state/index.d.ts +3 -3
- package/dist/state/index.js +4 -2
- package/dist/state/index.js.map +1 -1
- package/dist/styles.css +74 -5
- package/dist/surface/index.d.ts +17 -2
- package/dist/surface/index.js +3 -2
- package/dist/ui/layers/index.js +2 -2
- package/dist/undo/index.d.ts +6 -5
- package/package.json +8 -8
- package/src/annotations/AnnotationOverlay.tsx +10 -6
- package/src/annotations/Annotations.less +6 -0
- package/src/annotations/Annotations.overlay.test.tsx +113 -0
- package/src/annotations/MarkList.tsx +3 -2
- package/src/canvas/CanvasStack.tsx +8 -13
- package/src/canvas/useOrbit.test.ts +93 -1
- package/src/canvas/useOrbit.ts +41 -34
- package/src/canvas/usePanZoom.test.ts +102 -1
- package/src/canvas/usePanZoom.ts +54 -36
- package/src/config/builder.test.ts +5 -0
- package/src/config/builder.ts +6 -0
- package/src/config/types.ts +1 -0
- package/src/controls/ControlPanel.test.tsx +23 -0
- package/src/controls/ControlPanel.tsx +3 -0
- package/src/job/useJob.ts +1 -1
- package/src/lab/Lab.surface.test.tsx +8 -3
- package/src/lab/LabHeader.test.tsx +12 -0
- package/src/lab/LabHeader.tsx +16 -6
- package/src/lab/Workspace.tsx +1 -1
- package/src/layers/LayerList.test.tsx +72 -1
- package/src/layers/LayerList.tsx +40 -32
- package/src/passthrough/weasel-ui.test.ts +9 -0
- package/src/passthrough/weasel-ui.ts +4 -0
- package/src/primitives/FloatingPanel.test.tsx +82 -0
- package/src/primitives/FloatingPanel.tsx +59 -41
- package/src/primitives/ZoomControl.tsx +2 -0
- package/src/state/store.ts +10 -0
- package/src/state/types.ts +3 -0
- package/src/surface/AGENTS.md +11 -1
- package/src/surface/index.ts +7 -1
- package/src/surface/useSurfaceTile.ts +20 -2
- package/src/surface/useTiledSurface.ts +4 -1
- package/src/theme/Interstellar.stories.tsx +4 -3
- package/src/theme/base.less +39 -1
- package/src/trial/Trial.less +27 -5
- package/src/trial/TrialBody.test.tsx +67 -0
- package/src/trial/TrialBody.tsx +155 -0
- package/src/trial/TrialChrome.tsx +18 -10
- package/src/trial/index.ts +2 -0
- package/dist/_dts/DrawCommand-B3bskUsC.d.ts +0 -564
- package/dist/_dts/PrefsForm-BkUJZx0A.d.ts +0 -204
- package/dist/_dts/fitViewToBounds-dZ2UDB6e.d.ts +0 -21
- package/dist/_dts/shapeKinds-Cx_rxwsa.d.ts +0 -87
- package/dist/_dts/types-C-gh9Ap-.d.ts +0 -695
- package/dist/chunk-6OEYTYML.js.map +0 -1
- package/dist/chunk-AE5CNVRU.js.map +0 -1
- package/dist/chunk-ESIQNQ6K.js.map +0 -1
- package/dist/chunk-HHGVISVZ.js.map +0 -1
- package/dist/chunk-HMFODCOX.js.map +0 -1
- package/dist/chunk-L5QJOOLV.js.map +0 -1
- package/dist/chunk-QB5SM4BM.js.map +0 -1
- package/dist/chunk-RBGKL7NF.js.map +0 -1
- package/dist/chunk-TO2FUOKF.js.map +0 -1
- package/dist/chunk-XJ6N32QP.js.map +0 -1
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import { T as ToolPrefLeaf, a as ToolPrefGroup } from '../_dts/PrefsForm-BkUJZx0A.js';
|
|
2
|
-
export { b as BuiltinPref, c as PrefBoolean, d as PrefBooleanControl, e as PrefColor, f as PrefCustom, g as PrefEnum, h as PrefEnumControl, i as PrefEnumEncoding, j as PrefKind, k as PrefNumber, l as PrefNumberControl, m as PrefNumberUnit, n as PrefObject, o as PrefPaint, p as PrefRenderContext, P as PrefRenderer, q as PrefString, r as PrefStringControl } from '../_dts/PrefsForm-BkUJZx0A.js';
|
|
3
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { M as ModeDefinition, T as ToolPrefLeaf, a as ToolPrefGroup, e as ToolsApi } from '../_dts/weasel-canvas-FJTFi3ZZ.js';
|
|
3
|
+
export { f as BuiltinPref, g as PrefBoolean, h as PrefBooleanControl, i as PrefColor, j as PrefCustom, k as PrefEnum, l as PrefEnumControl, m as PrefEnumEncoding, n as PrefKind, o as PrefNumber, p as PrefNumberControl, q as PrefNumberUnit, r as PrefObject, s as PrefPaint, t as PrefString, u as PrefStringControl } from '../_dts/weasel-canvas-FJTFi3ZZ.js';
|
|
4
4
|
import * as react from 'react';
|
|
5
|
-
import {
|
|
5
|
+
import { ReactNode, CSSProperties, ButtonHTMLAttributes, ReactElement, KeyboardEvent, RefObject, PointerEvent, RefCallback } from 'react';
|
|
6
6
|
export { LayerStack, LayerStackItem, LayerStackProps } from '../ui/layers/index.js';
|
|
7
|
-
|
|
8
|
-
import { V as View, D as DrawCommand } from '../_dts/DrawCommand-B3bskUsC.js';
|
|
9
|
-
import { B as Bounds } from '../_dts/fitViewToBounds-dZ2UDB6e.js';
|
|
10
|
-
import { K as KitInsertShape } from '../_dts/shapeKinds-Cx_rxwsa.js';
|
|
7
|
+
export { a as PrefRenderContext, P as PrefRenderer } from '../_dts/PrefsForm.d-CgxUequc.js';
|
|
11
8
|
import { TextFieldProps, ValidationResult, CheckboxProps as CheckboxProps$1, SwitchProps as SwitchProps$1, TabProps as TabProps$1, TabListProps as TabListProps$1, TabPanelProps as TabPanelProps$1, TabsProps as TabsProps$1, RadioProps as RadioProps$1, RadioGroupProps as RadioGroupProps$1, NumberFieldProps as NumberFieldProps$1, SelectProps as SelectProps$1, ListBoxItemProps, ComboBoxProps as ComboBoxProps$1, SliderProps as SliderProps$1, ModalOverlayProps, DialogProps as DialogProps$1, PopoverProps } from 'react-aria-components';
|
|
12
9
|
export { DialogTrigger as CalloutTrigger } from 'react-aria-components';
|
|
13
10
|
|
|
@@ -44,2855 +41,111 @@ declare function isDebugEnabled(namespace: string): boolean;
|
|
|
44
41
|
* Each call is prefixed with `[namespace]` so the source is searchable. */
|
|
45
42
|
declare function dlog(namespace: string, ...args: unknown[]): void;
|
|
46
43
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Canvas size in CSS pixels — passed to `draw` for layers that anchor to
|
|
125
|
-
* canvas edges (e.g. the debug overlay's layer-list panel). The GL backend
|
|
126
|
-
* supplies it explicitly so layers don't have to know about DPR.
|
|
127
|
-
*/
|
|
128
|
-
interface Dims {
|
|
129
|
-
width: number;
|
|
130
|
-
height: number;
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* A single named render sub-layer within a canvas renderer.
|
|
134
|
-
*
|
|
135
|
-
* @template TData - The data object passed to each draw call.
|
|
136
|
-
*/
|
|
137
|
-
interface RenderLayer<TData> {
|
|
138
|
-
/** Unique identifier used in visibility maps and ordering arrays. When a
|
|
139
|
-
* cache is in use, an id must identify the same logical layer across
|
|
140
|
-
* frames — reusing it for a different layer can serve cross-layer commands. */
|
|
141
|
-
id: string;
|
|
142
|
-
/** Human-readable name for UI toggles. */
|
|
143
|
-
label: string;
|
|
144
|
-
/**
|
|
145
|
-
* Emit a DrawCommand tree for the GL backend to dispatch.
|
|
146
|
-
*
|
|
147
|
-
* For world-space layers (the default), emit commands in WORLD COORDS —
|
|
148
|
-
* `drawLayers` automatically wraps them in `{ kind: 'group', transform:
|
|
149
|
-
* viewToMat3(view), ... }` before handing them to the renderer. Do NOT
|
|
150
|
-
* apply the view transform yourself.
|
|
151
|
-
*
|
|
152
|
-
* For screen-space layers (`space: 'screen'`), emit commands in CSS-pixel
|
|
153
|
-
* coords directly; `drawLayers` passes them through unchanged. If part
|
|
154
|
-
* of a screen-space layer's output needs to track the view, wrap that
|
|
155
|
-
* subset manually with `viewToMat3(view)`.
|
|
156
|
-
*/
|
|
157
|
-
draw: (data: TData, view: View, dims: Dims) => DrawCommand[];
|
|
158
|
-
/**
|
|
159
|
-
* Optional cache key. When present and a `LayerCommandCache` is supplied to
|
|
160
|
-
* `drawLayers`, the layer's previous `DrawCommand[]` is reused as long as
|
|
161
|
-
* every entry is `Object.is`-equal to the previous call's. A layer with no
|
|
162
|
-
* `deps` rebuilds on every frame.
|
|
163
|
-
*
|
|
164
|
-
* **The returned commands must be treated as immutable.** A cached tree is
|
|
165
|
-
* handed to the renderer again on later frames, so mutating a tree you
|
|
166
|
-
* previously returned corrupts the cache silently rather than erroring.
|
|
167
|
-
*
|
|
168
|
-
* **Screen-space layers are not protected against a stale `view`/`dims`
|
|
169
|
-
* the way world-space layers are** (see `space` below) — include them in
|
|
170
|
-
* `deps` if `draw` reads them.
|
|
171
|
-
*/
|
|
172
|
-
deps?: (data: TData, view: View, dims: Dims) => readonly unknown[];
|
|
173
|
-
/**
|
|
174
|
-
* Whether the layer is shown when no explicit visibility entry exists.
|
|
175
|
-
* Defaults to `true` when absent.
|
|
176
|
-
*/
|
|
177
|
-
defaultVisible?: boolean;
|
|
178
|
-
/**
|
|
179
|
-
* When true, the layer is always drawn regardless of the visibility map.
|
|
180
|
-
* Useful for layers that must never be hidden (e.g. base grid).
|
|
181
|
-
*/
|
|
182
|
-
alwaysOn?: boolean;
|
|
183
|
-
/**
|
|
184
|
-
* Coordinate space the layer draws in.
|
|
185
|
-
*
|
|
186
|
-
* - `'world'` (default): the layer's `draw` returns world-space commands;
|
|
187
|
-
* `drawLayers` wraps them in a `kind: 'group'` with `viewToMat3(view)`
|
|
188
|
-
* automatically.
|
|
189
|
-
* - `'screen'`: the layer's `draw` returns screen-space (CSS-pixel)
|
|
190
|
-
* commands; `drawLayers` passes them through unchanged. World-anchored
|
|
191
|
-
* chrome inside a screen-space layer must call `worldToScreen` or wrap
|
|
192
|
-
* the relevant subset with `viewToMat3(view)` manually.
|
|
193
|
-
*/
|
|
194
|
-
space?: 'world' | 'screen';
|
|
195
|
-
/**
|
|
196
|
-
* Optional hit-test for **consumer-attached** layers.
|
|
197
|
-
*
|
|
198
|
-
* Only layers registered through `CanvasExtensionApi.registerLayer` are
|
|
199
|
-
* hit-tested: `hitTestExtras` walks them last-registered-first on
|
|
200
|
-
* pointerdown, and `<SceneCanvas>` folds the result into its `affordanceAt`
|
|
201
|
-
* thunk ahead of the kit's own selection chrome. First non-null result
|
|
202
|
-
* wins; null means "I don't claim this hit, try the next layer."
|
|
203
|
-
*
|
|
204
|
-
* Layers that reach the draw stack some other way — a `Tool.overlay`, an
|
|
205
|
-
* entry in the `layers` map — are painted but never hit-tested, so defining
|
|
206
|
-
* `hitTest` on one has no effect. (The kit's own chrome doesn't need it: it
|
|
207
|
-
* goes through `buildAffordanceAt`.)
|
|
208
|
-
*
|
|
209
|
-
* Coordinates are world-space. The `data` arg is the layer's
|
|
210
|
-
* configured data slot (same as `draw`); `view` and `dims` mirror
|
|
211
|
-
* `draw`'s arguments.
|
|
212
|
-
*/
|
|
213
|
-
hitTest?: (worldX: number, worldY: number, data: TData, view: View, dims: Dims,
|
|
214
|
-
/** Chrome-caps visibility predicate. When supplied, the layer must
|
|
215
|
-
* not return a hit from any chrome element whose id reports
|
|
216
|
-
* `false`. Absent → every element is hittable. */
|
|
217
|
-
isVisible?: (id: string) => boolean) => LayerHit | null;
|
|
218
|
-
/**
|
|
219
|
-
* Called on every pointermove when no gesture is currently captured.
|
|
220
|
-
* Lets layers (e.g. HUD widgets) track hover state without participating
|
|
221
|
-
* in the drag pipeline. Coords are world-space; the layer is responsible
|
|
222
|
-
* for any further conversion (e.g. world→screen for screen-space layers)
|
|
223
|
-
* and for its own throttling.
|
|
224
|
-
*/
|
|
225
|
-
onUncapturedMove?: (worldX: number, worldY: number, evt: PointerEvent, view: View, dims: Dims) => void;
|
|
226
|
-
/**
|
|
227
|
-
* Called when the cursor leaves the canvas element. Lets layers clear
|
|
228
|
-
* any hover state they're holding.
|
|
229
|
-
*/
|
|
230
|
-
onUncapturedLeave?: () => void;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Facts about the device the canvas is running on.
|
|
235
|
-
*
|
|
236
|
-
* One object, recomputed when the underlying media queries change, read by
|
|
237
|
-
* two consumers: the chrome-caps rule layer (via `RuleCtx.device`) and the
|
|
238
|
-
* handle-sizing constants (via `targetScale`).
|
|
239
|
-
*
|
|
240
|
-
* Deliberately NOT a form-factor concept. There is no `isPhone` here and
|
|
241
|
-
* there should never be one: chrome layout is the consuming app's decision.
|
|
242
|
-
* The kit's job is to stop assuming a mouse.
|
|
243
|
-
*/
|
|
244
|
-
interface DeviceProfile {
|
|
245
|
-
/** `matchMedia('(pointer: coarse)')` — the primary pointer is imprecise. */
|
|
246
|
-
readonly coarsePointer: boolean;
|
|
247
|
-
/** `matchMedia('(hover: hover)')` — the primary pointer can hover. */
|
|
248
|
-
readonly canHover: boolean;
|
|
249
|
-
/** Live device pixel ratio. */
|
|
250
|
-
readonly dpr: number;
|
|
251
|
-
/** Multiplier for handle sizes and hit radii. Derived from
|
|
252
|
-
* `coarsePointer` unless explicitly overridden. */
|
|
253
|
-
readonly targetScale: number;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/** A layout container's extent in world units. */
|
|
257
|
-
type ContainerBounds = {
|
|
258
|
-
x: number;
|
|
259
|
-
y: number;
|
|
260
|
-
width: number;
|
|
261
|
-
height: number;
|
|
262
|
-
};
|
|
263
|
-
/** A child a layout strategy is arranging. */
|
|
264
|
-
interface LayoutChild<TPose> {
|
|
265
|
-
id: string;
|
|
266
|
-
pose: TPose;
|
|
267
|
-
}
|
|
268
|
-
/** One place a dragged child could land. A strategy offers these as the drag
|
|
269
|
-
* moves, a `LayoutSnap` picks between them, and the chosen one decides both
|
|
270
|
-
* the preview and the committed poses. */
|
|
271
|
-
interface DropTarget<TPose> {
|
|
272
|
-
/** Where the dragged child lands if this target is picked. */
|
|
273
|
-
pose: TPose;
|
|
274
|
-
/** Reference point for distance metrics (snap algorithms). */
|
|
275
|
-
origin: {
|
|
276
|
-
x: number;
|
|
277
|
-
y: number;
|
|
278
|
-
};
|
|
279
|
-
/** Optional axis-aligned region (world units) used by region-aware snaps
|
|
280
|
-
* (e.g. `containedThenNearest`). When present, a pointer inside this rect
|
|
281
|
-
* is treated as a containment hit on this target. Strategies that emit
|
|
282
|
-
* region-shaped targets (gutters, drop-zones) should populate this.
|
|
283
|
-
* Strategies whose targets are point-like (free-form, snap-point) can omit
|
|
284
|
-
* it and rely on `origin`-distance snaps. */
|
|
285
|
-
hitBounds?: {
|
|
286
|
-
x: number;
|
|
287
|
-
y: number;
|
|
288
|
-
width: number;
|
|
289
|
-
height: number;
|
|
290
|
-
};
|
|
291
|
-
/** Strategy-private metadata (e.g. cell coords for tile-grid). */
|
|
292
|
-
meta?: unknown;
|
|
293
|
-
}
|
|
294
|
-
/** Chooses which of a strategy's drop targets the pointer means, or `null`
|
|
295
|
-
* to reject the drop. Separate from the strategy so the same arrangement can
|
|
296
|
-
* be paired with different snapping rules. */
|
|
297
|
-
interface LayoutSnap<TPose> {
|
|
298
|
-
pickTarget(targets: DropTarget<TPose>[], pointer: {
|
|
299
|
-
x: number;
|
|
300
|
-
y: number;
|
|
301
|
-
}): DropTarget<TPose> | null;
|
|
302
|
-
}
|
|
303
|
-
/** The container a layout strategy is arranging children within. */
|
|
304
|
-
interface LayoutContainer {
|
|
305
|
-
id: string;
|
|
306
|
-
bounds: ContainerBounds;
|
|
307
|
-
}
|
|
308
|
-
/** The child currently being dragged: where it started, where the pointer
|
|
309
|
-
* currently proposes it goes, and which container it came from. */
|
|
310
|
-
interface LayoutDragged<TPose> {
|
|
311
|
-
id: string;
|
|
312
|
-
/** The pose the dragged child currently has (pre-drop). */
|
|
313
|
-
originPose: TPose;
|
|
314
|
-
/** The pose the gesture proposes (pointer-driven, pre-snap). */
|
|
315
|
-
pose: TPose;
|
|
316
|
-
sourceContainerId: string | null;
|
|
317
|
-
}
|
|
318
|
-
/**
|
|
319
|
-
* How a container arranges its children, and what happens when one is dragged
|
|
320
|
-
* into or around it.
|
|
321
|
-
*
|
|
322
|
-
* The four required methods cover the whole cycle: `childPoses` is the resting
|
|
323
|
-
* arrangement, `getDropTargets` enumerates where a drag could land,
|
|
324
|
-
* `reflowPoses` is the live preview once a target is picked, and `commitDrop`
|
|
325
|
-
* turns the result into ops so the drop is undoable.
|
|
326
|
-
*/
|
|
327
|
-
interface LayoutStrategy<TPose> {
|
|
328
|
-
childPoses(container: LayoutContainer, children: ReadonlyArray<LayoutChild<TPose>>): Map<string, TPose>;
|
|
329
|
-
getDropTargets(container: LayoutContainer, children: ReadonlyArray<LayoutChild<TPose>>, dragged: LayoutDragged<TPose>): DropTarget<TPose>[];
|
|
330
|
-
reflowPoses(container: LayoutContainer, children: ReadonlyArray<LayoutChild<TPose>>, dragged: LayoutDragged<TPose>, target: DropTarget<TPose> | null): Map<string, TPose>;
|
|
331
|
-
commitDrop(container: LayoutContainer, children: ReadonlyArray<LayoutChild<TPose>>, dragged: LayoutDragged<TPose>, target: DropTarget<TPose> | null): Op[];
|
|
332
|
-
snap: LayoutSnap<TPose>;
|
|
333
|
-
/** Optional: predicate for whether a world-space point is inside this
|
|
334
|
-
* container. When absent, callers fall back to an axis-aligned bounding-box
|
|
335
|
-
* test on the container's pose. Strategies whose containers aren't
|
|
336
|
-
* rectangular (circles, irregular zones) implement this to override the
|
|
337
|
-
* AABB default. */
|
|
338
|
-
contains?(containerPose: TPose, point: {
|
|
339
|
-
x: number;
|
|
340
|
-
y: number;
|
|
341
|
-
}): boolean;
|
|
342
|
-
/** Optional: reject a drag before any drop-target work happens. A type-aware
|
|
343
|
-
* container (a palette that only takes swatches, say) returns `false` and
|
|
344
|
-
* the drag falls through to whatever container is under it next. When
|
|
345
|
-
* absent, every drag is considered — rejection is still possible later, by
|
|
346
|
-
* `snap.pickTarget` returning null. */
|
|
347
|
-
acceptsDrop?(container: LayoutContainer, dragged: LayoutDragged<TPose>): boolean;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* Opaque clipboard payload. `items` is `unknown[]` so each app's clipboard
|
|
352
|
-
* adapter stores whatever shape it wants; the kit never inspects entries.
|
|
353
|
-
*
|
|
354
|
-
* The adapter is responsible for both producing snapshots
|
|
355
|
-
* (`snapshotSelection`) and consuming them (`commitPaste`). Type safety lives
|
|
356
|
-
* at that boundary, not in the kit.
|
|
357
|
-
*/
|
|
358
|
-
interface ClipboardSnapshot {
|
|
359
|
-
items: unknown[];
|
|
360
|
-
}
|
|
361
|
-
/**
|
|
362
|
-
* SnapTarget — where a dragged node would re-parent to if released.
|
|
363
|
-
*
|
|
364
|
-
* `slotPose` is the pose (in world coordinates) the node should snap to
|
|
365
|
-
* within the target. `metadata` is an opaque pass-through for app-specific
|
|
366
|
-
* snap details (slot index, visual hint, etc.).
|
|
367
|
-
*/
|
|
368
|
-
interface SnapTarget<TPose = unknown> {
|
|
369
|
-
parentId: string;
|
|
370
|
-
slotPose: TPose;
|
|
371
|
-
metadata?: unknown;
|
|
372
|
-
}
|
|
373
|
-
/**
|
|
374
|
-
* Narrow adapter for `useMove`. Includes optional snap-target
|
|
375
|
-
* lookup; apps without container-snapping leave it out.
|
|
376
|
-
*/
|
|
377
|
-
interface MoveAdapter<TNode extends {
|
|
378
|
-
id: string;
|
|
379
|
-
}, TPose> {
|
|
380
|
-
getNode(id: string): TNode | undefined;
|
|
381
|
-
/** Enumerate all nodes. `<Canvas>` derives a default rect-pose `pickEvery`
|
|
382
|
-
* and the scene-iteration loop from this. */
|
|
383
|
-
getNodes(): TNode[];
|
|
384
|
-
getPose(id: string): TPose;
|
|
385
|
-
/** Optional. Required only by hierarchy-aware paths: layout-pass drop
|
|
386
|
-
* targeting (`getLayout` present), nested-hit collapse
|
|
387
|
-
* (`pickTopMostHit`), and group-pose composition. Flat scenes may omit. */
|
|
388
|
-
getParent?(id: string): string | null;
|
|
389
|
-
/** Optional paint depth, read by `pickTopMostHit` to resolve two *siblings*
|
|
390
|
-
* whose bodies both cover the pointer. Without it the hit list's own order
|
|
391
|
-
* decides, which is right for a back-to-front walk and wrong for anything
|
|
392
|
-
* else. See `PickTopMostHitAdapter` for the `compareZ` alternative. */
|
|
393
|
-
getZIndex?(id: string): number | null | undefined;
|
|
394
|
-
compareZ?(a: string, b: string): number;
|
|
395
|
-
setPose(id: string, pose: TPose): void;
|
|
396
|
-
/** Optional. Used only by reparent ops (e.g. drag-into-container drops via
|
|
397
|
-
* layout strategies). Flat scenes that never reparent may omit. */
|
|
398
|
-
setParent?(id: string, parentId: string | null): void;
|
|
399
|
-
/** Optional: see SceneAdapter.applyOps. */
|
|
400
|
-
applyOps?(ops: Op[], label: string): void;
|
|
401
|
-
findSnapTarget?(draggedId: string, worldX: number, worldY: number): SnapTarget<TPose> | null;
|
|
402
|
-
/** Optional: ordered children of `parentId`, `null` for the root siblings.
|
|
403
|
-
* One contract with {@link OrderedAdapter.getChildren} — the two land on
|
|
404
|
-
* the same adapter object, and an implementation that answers only node
|
|
405
|
-
* ids returns `[]` for the root, which the ops read as "no siblings".
|
|
406
|
-
*
|
|
407
|
-
* When present (alongside the `cascadeWorldPose` option on `useMove`),
|
|
408
|
-
* dragging a node auto-cascades its descendants in the live overlay so
|
|
409
|
-
* structurally-grouped children visually follow the parent during the
|
|
410
|
-
* drag. No additional ops are generated — children's local poses don't
|
|
411
|
-
* change when the parent's local pose moves. */
|
|
412
|
-
getChildren?(parentId: string | null): string[];
|
|
413
|
-
/** Optional: layout strategy attached to a container, or null if the
|
|
414
|
-
* container uses absolute positioning (default behavior). When present,
|
|
415
|
-
* `useMove` uses the strategy to compute drop targets, sibling reflow,
|
|
416
|
-
* and the commit op batch when a drag ends over the container. */
|
|
417
|
-
getLayout?(containerId: string): LayoutStrategy<TPose> | null;
|
|
418
|
-
}
|
|
419
|
-
/**
|
|
420
|
-
* Narrow adapter for `useInsert` and `useClipboardOps`. The kit knows
|
|
421
|
-
* nothing about what tool is active or what shape to construct; it asks the
|
|
422
|
-
* adapter to produce node(s) given gesture or paste inputs.
|
|
423
|
-
*
|
|
424
|
-
* Drag-rectangle path: `commitInsert(bounds)` returns one new node or null.
|
|
425
|
-
* Clipboard paste path: `commitPaste(clipboard, offset)` returns the array of
|
|
426
|
-
* newly-materialized nodes (in order). Both empty array and array of
|
|
427
|
-
* length N are valid; the kit wraps each entry in an `InsertOp`.
|
|
428
|
-
*
|
|
429
|
-
* `snapshotSelection(ids)` builds the payload that paste later consumes.
|
|
430
|
-
* `getPasteOffset` is optional; the kit defaults to a fixed grid-cell offset
|
|
431
|
-
* supplied by the consumer (passed to `useClipboardOps` options if needed; see
|
|
432
|
-
* the hook for resolution order).
|
|
433
|
-
*/
|
|
434
|
-
interface InsertAdapter<TNode extends {
|
|
435
|
-
id: string;
|
|
436
|
-
}> {
|
|
437
|
-
/** Materialize a new node from drag-rect bounds (drag-to-insert tools).
|
|
438
|
-
* Optional — hooks that don't drive insertion (e.g. `useClone`,
|
|
439
|
-
* read-only clipboard) won't call it, and `sceneToAdapter` only fills
|
|
440
|
-
* it in when `options.commitInsert` is supplied. Hooks that *do* call
|
|
441
|
-
* it (insert tools) document the requirement at their own surface. */
|
|
442
|
-
commitInsert?(bounds: {
|
|
443
|
-
x: number;
|
|
444
|
-
y: number;
|
|
445
|
-
width: number;
|
|
446
|
-
height: number;
|
|
447
|
-
}): TNode | null;
|
|
448
|
-
/** Materialize one or more new nodes from a clipboard snapshot. Optional
|
|
449
|
-
* — required by `useClipboard.paste`, ignored by other consumers. */
|
|
450
|
-
commitPaste?(clipboard: ClipboardSnapshot, offset: {
|
|
451
|
-
dx: number;
|
|
452
|
-
dy: number;
|
|
453
|
-
}, ctx?: {
|
|
454
|
-
dropPoint?: {
|
|
455
|
-
worldX: number;
|
|
456
|
-
worldY: number;
|
|
457
|
-
};
|
|
458
|
-
}): TNode[];
|
|
459
|
-
/** Snapshot the current selection into the clipboard payload shape.
|
|
460
|
-
* Optional — required by `useClipboard.copy` / `cut` and `useClone`'s
|
|
461
|
-
* ghost capture, ignored by other consumers. */
|
|
462
|
-
snapshotSelection?(ids: string[]): ClipboardSnapshot;
|
|
463
|
-
getPasteOffset?(clipboard: ClipboardSnapshot): {
|
|
464
|
-
dx: number;
|
|
465
|
-
dy: number;
|
|
466
|
-
};
|
|
467
|
-
/** Mutator wired by `insertNode`-using ops (kit-side InsertOp). `index`
|
|
468
|
-
* carries the z-position a delete captured, so undo restores paint order;
|
|
469
|
-
* see `SceneAdapter.insertNode`. */
|
|
470
|
-
insertNode(node: TNode, index?: number): void;
|
|
471
|
-
/** Mutator wired by `setSelection` ops batched alongside paste. */
|
|
472
|
-
setSelection(ids: string[]): void;
|
|
473
|
-
/** Optional: see SceneAdapter.applyOps. */
|
|
474
|
-
applyOps?(ops: Op[], label: string): void;
|
|
475
|
-
/** Returns the current selection. Used by clone behaviors. */
|
|
476
|
-
getSelection(): string[];
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
/** Pointer position in both world and client coords. */
|
|
480
|
-
interface PointerState {
|
|
481
|
-
worldX: number;
|
|
482
|
-
worldY: number;
|
|
483
|
-
clientX: number;
|
|
484
|
-
clientY: number;
|
|
485
|
-
}
|
|
486
|
-
/**
|
|
487
|
-
* Per-gesture context passed to behaviors. `current` is the running pose
|
|
488
|
-
* map; behaviors mutate proposed poses by returning new TPose values from
|
|
489
|
-
* onMove. `scratch` is per-gesture key/value storage that resets at the
|
|
490
|
-
* next gesture start.
|
|
491
|
-
*/
|
|
492
|
-
interface GestureContext<TPose, TNode extends {
|
|
493
|
-
id: string;
|
|
494
|
-
} = {
|
|
495
|
-
id: string;
|
|
496
|
-
}> {
|
|
497
|
-
draggedIds: string[];
|
|
498
|
-
origin: Map<string, TPose>;
|
|
499
|
-
current: Map<string, TPose>;
|
|
500
|
-
snap: SnapTarget<TPose> | null;
|
|
501
|
-
modifiers: ModifierState;
|
|
502
|
-
pointer: PointerState;
|
|
503
|
-
adapter: MoveAdapter<TNode, TPose>;
|
|
504
|
-
/**
|
|
505
|
-
* Per-gesture mutable store. Keys should be namespaced by behavior name to avoid
|
|
506
|
-
* collisions: `'behaviorName'` for a single value, `'behaviorName.field'` for
|
|
507
|
-
* sub-keys. Two behaviors sharing a key will silently clobber each other.
|
|
508
|
-
*/
|
|
509
|
-
scratch: Record<string, unknown>;
|
|
510
|
-
}
|
|
511
|
-
/**
|
|
512
|
-
* Generalized base behavior. Each hook defines an alias that pins the
|
|
513
|
-
* proposed-pose shape (TProposed) and the onMove return shape (TMoveResult).
|
|
514
|
-
* onEnd is uniform: first non-undefined return wins (Op[] = commit those,
|
|
515
|
-
* null = abort, undefined = defer).
|
|
516
|
-
*
|
|
517
|
-
* `defaultTransient`: when at least one behavior in a gesture sets this true
|
|
518
|
-
* AND the hook's `options.transient` is not explicitly set, the gesture
|
|
519
|
-
* commits its ops via `adapter.applyOps(ops)` (no history entry). When
|
|
520
|
-
* `options.transient` is set explicitly, that value wins.
|
|
521
|
-
*/
|
|
522
|
-
interface ActionBehavior<TPose, TProposed, TMoveResult> {
|
|
523
|
-
defaultTransient?: boolean;
|
|
524
|
-
onStart?(ctx: GestureContext<TPose>): void;
|
|
525
|
-
onMove?(ctx: GestureContext<TPose>, proposed: TProposed): TMoveResult | void;
|
|
526
|
-
onEnd?(ctx: GestureContext<TPose>): Op[] | null | void;
|
|
527
|
-
}
|
|
528
|
-
/** Which corner/edge of the rect stays fixed during a resize. */
|
|
529
|
-
type ResizeAnchor = {
|
|
530
|
-
x: 'min' | 'max' | 'free';
|
|
531
|
-
y: 'min' | 'max' | 'free';
|
|
532
|
-
};
|
|
533
|
-
/** Minimum rect-shaped pose required by the resize machinery. */
|
|
534
|
-
interface ResizePose {
|
|
535
|
-
x: number;
|
|
536
|
-
y: number;
|
|
537
|
-
width: number;
|
|
538
|
-
height: number;
|
|
539
|
-
}
|
|
540
|
-
/** Per-frame proposed resize: pose plus the anchor pinning the opposite corner. */
|
|
541
|
-
interface ResizeProposed<TPose extends ResizePose> {
|
|
542
|
-
pose: TPose;
|
|
543
|
-
anchor: ResizeAnchor;
|
|
544
|
-
}
|
|
545
|
-
/** Per-frame result a `BoundsConstraint.onMove` can return to override the proposed pose. */
|
|
546
|
-
interface ResizeMoveResult<TPose extends ResizePose> {
|
|
547
|
-
pose?: TPose;
|
|
548
|
-
}
|
|
549
|
-
/** A bounds-frame constraint plugged into `useResize` / `resizeAction`.
|
|
550
|
-
* Reads/writes `{x,y,width,height}` and can override the proposed pose
|
|
551
|
-
* on each frame (e.g. lock-aspect, clamp-min-size, snap-to-grid). */
|
|
552
|
-
type BoundsConstraint<TPose extends ResizePose> = ActionBehavior<TPose, ResizeProposed<TPose>, ResizeMoveResult<TPose>>;
|
|
553
|
-
/** Frames a point-snap behavior can return for the hook to back-solve. */
|
|
554
|
-
type PointSnapFrame = 'dragged-corner' | 'fixed-corner' | 'center' | 'origin';
|
|
555
|
-
/** Per-frame world-space context handed to `PointSnapBehavior.onMove`.
|
|
556
|
-
* `draggedCorner` and `fixedCorner` are `null` for edge drags
|
|
557
|
-
* (`anchor.x === 'free'` or `anchor.y === 'free'`). `center` and
|
|
558
|
-
* `origin` are always present. */
|
|
559
|
-
interface PointSnapContext<TPose extends ResizePose> {
|
|
560
|
-
draggedCorner: {
|
|
561
|
-
worldX: number;
|
|
562
|
-
worldY: number;
|
|
563
|
-
} | null;
|
|
564
|
-
fixedCorner: {
|
|
565
|
-
worldX: number;
|
|
566
|
-
worldY: number;
|
|
567
|
-
} | null;
|
|
568
|
-
center: {
|
|
569
|
-
worldX: number;
|
|
570
|
-
worldY: number;
|
|
571
|
-
};
|
|
572
|
-
origin: {
|
|
573
|
-
worldX: number;
|
|
574
|
-
worldY: number;
|
|
575
|
-
};
|
|
576
|
-
rotation: number;
|
|
577
|
-
anchor: ResizeAnchor;
|
|
578
|
-
proposed: TPose;
|
|
579
|
-
modifiers: ModifierState;
|
|
580
|
-
}
|
|
581
|
-
/** Per-frame snap result. A behavior returns at most one. */
|
|
582
|
-
interface PointSnapResult {
|
|
583
|
-
frame: PointSnapFrame;
|
|
584
|
-
worldX: number;
|
|
585
|
-
worldY: number;
|
|
586
|
-
}
|
|
587
|
-
/** A point-snap behavior plugged into `useResize`'s `pointSnapBehaviors`. */
|
|
588
|
-
interface PointSnapBehavior<TPose extends ResizePose> {
|
|
589
|
-
id?: string;
|
|
590
|
-
onMove(ctx: PointSnapContext<TPose>): PointSnapResult | null | undefined;
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
/** The full vocabulary of capability tags shipped in the default preset.
|
|
594
|
-
* Apps and other consumers can add their own tags; this list is what
|
|
595
|
-
* `weasel-modes` itself uses. */
|
|
596
|
-
declare const ALL_TAGS: readonly ["navigation", "creates-selection", "creates-paths", "creates-shapes", "creates-text", "edits-anchors", "edits-text", "transforms-selection", "samples-color", "applies-fill", "edits-page"];
|
|
597
|
-
/** One capability a tool or contribution declares, and a mode allows. Any
|
|
598
|
-
* string is accepted so apps can add tags of their own; `ALL_TAGS` is the
|
|
599
|
-
* set this package ships. */
|
|
600
|
-
type CapabilityTag = (typeof ALL_TAGS)[number] | (string & {});
|
|
601
|
-
|
|
602
|
-
/** How a mode tints the workspace — the area around the page — so the user
|
|
603
|
-
* can see at a glance which mode is active. */
|
|
604
|
-
interface WorkspaceVisual {
|
|
605
|
-
tint?: string;
|
|
606
|
-
gradient?: 'top-down' | 'bottom-up';
|
|
607
|
-
intensity?: number;
|
|
608
|
-
}
|
|
609
|
-
/**
|
|
610
|
-
* A mode: an app-level editing context that narrows which tools are usable and
|
|
611
|
-
* how the workspace looks. Tools live inside modes; a tool is never "in" one.
|
|
612
|
-
*
|
|
613
|
-
* `kind` picks the lifecycle. A `soft` mode (path-edit, isolation, text-edit)
|
|
614
|
-
* is a scoped context with no commit ceremony — every edit inside it is
|
|
615
|
-
* independently undoable and `exit` is non-destructive. A `strict` mode
|
|
616
|
-
* (free-transform, crop) is a transaction: the whole session collapses to one
|
|
617
|
-
* undoable step and leaving requires an explicit `commit` or `cancel`.
|
|
618
|
-
*/
|
|
619
|
-
interface ModeDefinition {
|
|
620
|
-
id: string;
|
|
621
|
-
kind: 'soft' | 'strict';
|
|
622
|
-
/** Capability tags this mode allows beyond IMPLICIT_TAGS. */
|
|
623
|
-
allows: CapabilityTag[];
|
|
624
|
-
/** When true, out-of-target objects dim at the renderer layer. */
|
|
625
|
-
scoping: boolean;
|
|
626
|
-
workspace?: WorkspaceVisual;
|
|
627
|
-
entry?: {
|
|
628
|
-
shortcut?: string;
|
|
629
|
-
trigger?: 'double-click-target';
|
|
630
|
-
};
|
|
631
|
-
exit?: {
|
|
632
|
-
shortcut?: string;
|
|
633
|
-
};
|
|
634
|
-
commit?: {
|
|
635
|
-
shortcut?: string;
|
|
636
|
-
};
|
|
637
|
-
cancel?: {
|
|
638
|
-
shortcut?: string;
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
/** Holds the set of available modes and which one is active, and notifies
|
|
643
|
-
* subscribers when that changes. `getVersion` is a monotonic counter for
|
|
644
|
-
* render-cache invalidation. Unknown mode ids throw rather than being
|
|
645
|
-
* ignored. */
|
|
646
|
-
interface ModeRegistry {
|
|
647
|
-
current(): ModeDefinition;
|
|
648
|
-
setMode(id: string): void;
|
|
649
|
-
byId(id: string): ModeDefinition;
|
|
650
|
-
getVersion(): number;
|
|
651
|
-
subscribe(listener: () => void): () => void;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
/**
|
|
655
|
-
* Live state read by rule evaluation. Built once per frame on the consuming
|
|
656
|
-
* surface — chrome-caps, the affordance pipeline, the dispatcher's
|
|
657
|
-
* eligibility filter — and discarded.
|
|
658
|
-
*
|
|
659
|
-
* Adding a new field is additive: existing rules don't change, new
|
|
660
|
-
* selector atoms can read it.
|
|
661
|
-
*/
|
|
662
|
-
interface RuleCtx {
|
|
663
|
-
readonly focused: boolean;
|
|
664
|
-
readonly selection: readonly NodeId[];
|
|
665
|
-
readonly multiActive: boolean;
|
|
666
|
-
readonly modifiers: ModifierState;
|
|
667
|
-
readonly action: {
|
|
668
|
-
readonly kind: string | null;
|
|
669
|
-
readonly id: string | null;
|
|
670
|
-
};
|
|
671
|
-
readonly hover: NodeId | null;
|
|
672
|
-
readonly view: View;
|
|
673
|
-
/** Active mode id. `'normal'` when no non-default mode is engaged. */
|
|
674
|
-
readonly mode: string;
|
|
675
|
-
/** Capability tags allowed by the active mode (the union of
|
|
676
|
-
* `ModeDefinition.allows` plus implicit tags). The `capability:`
|
|
677
|
-
* selector reads this to determine whether a tag is permitted. */
|
|
678
|
-
readonly allowedCapabilities: ReadonlySet<CapabilityTag>;
|
|
679
|
-
/** Whether the current selection may be resized. `<SceneCanvas>` folds
|
|
680
|
-
* `selectTool.resize.resizable` over the selection (true only when every
|
|
681
|
-
* selected node is resizable). Read by the `resizable:` selector to gate
|
|
682
|
-
* `selection.resize-handles`. Absent (legacy ctx builders) is treated as
|
|
683
|
-
* resizable — back-compat: handles show unless a consumer opts a node out. */
|
|
684
|
-
readonly selectionResizable?: boolean;
|
|
685
|
-
/** Whether a path is currently in anchor-edit mode. Read by the
|
|
686
|
-
* `editingAnchors:` selector, which gates the path-edit chrome.
|
|
687
|
-
*
|
|
688
|
-
* This is deliberately a fact about state, not about permission: the
|
|
689
|
-
* anchor overlay and the anchor hit-test must agree, and the thing they
|
|
690
|
-
* must agree on is "is there an edited path right now", which no
|
|
691
|
-
* capability or mode id answers. A mode that allows `edits-anchors`
|
|
692
|
-
* with nothing being edited should draw no anchors. Absent is treated
|
|
693
|
-
* as false. */
|
|
694
|
-
readonly editingAnchors?: boolean;
|
|
695
|
-
/** Device facts — pointer coarseness, hover capability, density.
|
|
696
|
-
*
|
|
697
|
-
* Absent (legacy ctx builders) is treated as
|
|
698
|
-
* {@link DEFAULT_DEVICE_PROFILE}: a fine pointer that can hover, at
|
|
699
|
-
* density 1. That is what the kit assumed before this field existed, so
|
|
700
|
-
* an absent profile is behavior-preserving by construction. */
|
|
701
|
-
readonly device?: DeviceProfile;
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
/**
|
|
705
|
-
* A selector is a conjunction of key/value tests. Multiple keys at the same
|
|
706
|
-
* level AND together. Each key maps to a selector primitive in the evaluator.
|
|
707
|
-
*/
|
|
708
|
-
interface Selector {
|
|
709
|
-
selection?: {
|
|
710
|
-
is?: number;
|
|
711
|
-
atLeast?: number;
|
|
712
|
-
empty?: boolean;
|
|
713
|
-
};
|
|
714
|
-
mode?: string | {
|
|
715
|
-
not: string;
|
|
716
|
-
} | {
|
|
717
|
-
in: readonly string[];
|
|
718
|
-
};
|
|
719
|
-
capability?: CapabilityTag | readonly CapabilityTag[] | {
|
|
720
|
-
in: readonly CapabilityTag[];
|
|
721
|
-
} | {
|
|
722
|
-
not: CapabilityTag;
|
|
723
|
-
};
|
|
724
|
-
gesturing?: boolean;
|
|
725
|
-
actionIs?: string;
|
|
726
|
-
modifierHeld?: keyof ModifierState;
|
|
727
|
-
focused?: boolean;
|
|
728
|
-
hovering?: boolean;
|
|
729
|
-
hoveringSelected?: boolean;
|
|
730
|
-
zoomAtLeast?: number;
|
|
731
|
-
/** Matches `ctx.editingAnchors` — true while a path is in anchor-edit
|
|
732
|
-
* mode. Absent flag is treated as `false`. */
|
|
733
|
-
editingAnchors?: boolean;
|
|
734
|
-
/** Matches `ctx.selectionResizable`. Absent flag is treated as `true`
|
|
735
|
-
* (resizable), so `{ resizable: true }` passes for legacy ctx builders
|
|
736
|
-
* that don't compute it. */
|
|
737
|
-
resizable?: boolean;
|
|
738
|
-
/** Matches `ctx.device.coarsePointer` — the primary pointer is imprecise
|
|
739
|
-
* (touch, most styluses). Absent device is treated as `false`. */
|
|
740
|
-
coarsePointer?: boolean;
|
|
741
|
-
/** Matches `ctx.device.canHover` — the primary pointer can hover. Absent
|
|
742
|
-
* device is treated as `true`. */
|
|
743
|
-
canHover?: boolean;
|
|
744
|
-
}
|
|
745
|
-
/**
|
|
746
|
-
* Composable visibility/eligibility rule. Trees of `all`/`any`/`not` nodes
|
|
747
|
-
* over `Selector` leaves. `when` is the escape hatch — its closure is
|
|
748
|
-
* opaque to introspection and should be avoided when a declarative form
|
|
749
|
-
* exists. Empty `all` is true; empty `any` is false.
|
|
750
|
-
*/
|
|
751
|
-
type Rule = Selector | {
|
|
752
|
-
all: readonly Rule[];
|
|
753
|
-
} | {
|
|
754
|
-
any: readonly Rule[];
|
|
755
|
-
} | {
|
|
756
|
-
not: Rule;
|
|
757
|
-
} | {
|
|
758
|
-
when: (ctx: RuleCtx) => boolean;
|
|
759
|
-
};
|
|
760
|
-
|
|
761
|
-
/**
|
|
762
|
-
* Composable visibility predicate with fluent surface. Carries its underlying
|
|
763
|
-
* `Rule` tree at `.rule` so the resolver can introspect / share trees with
|
|
764
|
-
* the affordance pipeline and the dispatcher's eligibility filter.
|
|
765
|
-
*
|
|
766
|
-
* Callable form `cond(ctx)` evaluates the tree against ctx. The fluent
|
|
767
|
-
* methods return new Conditions wrapping new trees.
|
|
768
|
-
*
|
|
769
|
-
* **Chain semantics: strict left-to-right, no precedence.**
|
|
770
|
-
* `a.and(b).or(c)` is `(a && b) || c`; `a.or(b).and(c)` is
|
|
771
|
-
* `(a || b) && c`. Mix `.and` and `.or` only when you mean
|
|
772
|
-
* left-to-right evaluation. For grouped disjunction, name the
|
|
773
|
-
* subexpression or use the top-level `or(...)`.
|
|
774
|
-
*/
|
|
775
|
-
interface Condition {
|
|
776
|
-
(ctx: RuleCtx): boolean;
|
|
777
|
-
readonly rule: Rule;
|
|
778
|
-
/** `this && other` */
|
|
779
|
-
and(other: Condition | Rule): Condition;
|
|
780
|
-
/** `this || other` */
|
|
781
|
-
or(other: Condition | Rule): Condition;
|
|
782
|
-
/** `this && !other` */
|
|
783
|
-
andNot(other: Condition | Rule): Condition;
|
|
784
|
-
/** `this || !other` */
|
|
785
|
-
orNot(other: Condition | Rule): Condition;
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
/** Phase of a gesture lifecycle. `initial` means the tool is idle
|
|
789
|
-
* (scratch null); `engaged` means a gesture is in progress (scratch
|
|
790
|
-
* populated). The route-grammar's `[phase]` slot draws from this set. */
|
|
791
|
-
type RoutePhase = 'initial' | 'engaged';
|
|
792
|
-
|
|
793
|
-
/**
|
|
794
|
-
* Route-string grammar v3:
|
|
795
|
-
*
|
|
796
|
-
* route = phaseSlot WS gesture WS argSlot? WS targetSlot? WS modSlot?
|
|
797
|
-
* phaseSlot = '[' phaseList ']'
|
|
798
|
-
* phaseList = phaseAtom (WS ',' WS phaseAtom)*
|
|
799
|
-
* phaseAtom = (channel ':')? phaseValue -- bare phaseValue ≡ '&:phaseValue'
|
|
800
|
-
* channel = '&' | '*' | toolId -- '&' = the binding's own tool
|
|
801
|
-
* phaseValue = 'initial' | 'engaged' | '*'
|
|
802
|
-
* argSlot = '(' argValue ')' -- whitespace inside parens is significant
|
|
803
|
-
* targetSlot = '=>' WS targetValue -- omitted slot defaults to '*' for hasTarget
|
|
804
|
-
* modSlot = modAtom (WS modAtom)*
|
|
805
|
-
* modAtom = sigil modName
|
|
806
|
-
* sigil = '+' | '?' -- ! @ # $ % ^ & * reserved as id-prefix
|
|
807
|
-
* modName = 'mod' | 'shift' | 'alt' | 'ctrl' | 'meta'
|
|
808
|
-
*
|
|
809
|
-
* Shorthand: a bare phaseValue (no `:`) implies channel `&` ("this tool's
|
|
810
|
-
* own phase"). `[engaged]` ≡ `[&:engaged]`; `[*]` ≡ `[&:*]`. The truly-loose
|
|
811
|
-
* form (any channel, any phase) is `[*:*]`.
|
|
812
|
-
*
|
|
813
|
-
* Examples:
|
|
814
|
-
* [initial] click => empty +shift -- self idle
|
|
815
|
-
* [engaged] wheel -- self mid-gesture
|
|
816
|
-
* [rect:engaged] wheel -- when rect tool is mid-gesture
|
|
817
|
-
* [*:engaged] keyDown(Delete) -- when any tool is mid-gesture
|
|
818
|
-
* [initial,engaged] contextMenu => empty -- either self phase
|
|
819
|
-
* [*] click => empty -- self, any phase
|
|
820
|
-
*/
|
|
821
|
-
|
|
822
|
-
/** Channel reference for a phase atom. `'&'` = the binding's own tool;
|
|
823
|
-
* `'*'` = any tool; otherwise a registered tool id. */
|
|
824
|
-
type ChannelRef = '&' | '*' | string;
|
|
825
|
-
/** One element of a phase list: a (channel, phase) pair. The default
|
|
826
|
-
* channel (omitted in the shorthand) is `'&'`. `phase: '*'` means
|
|
827
|
-
* "any phase of the given channel". */
|
|
828
|
-
interface PhaseAtom {
|
|
829
|
-
channel: ChannelRef;
|
|
830
|
-
phase: RoutePhase | '*';
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
/**
|
|
834
|
-
* GestureSpec — describes the form of a user input event that can fire an action.
|
|
835
|
-
*
|
|
836
|
-
* Used by `Action.defaultBinding` (the action's preferred gesture) and by
|
|
837
|
-
* `GestureBinding.spec` (a tool's binding table entry). The dispatcher matches
|
|
838
|
-
* incoming input events against registered specs to determine which action to
|
|
839
|
-
* invoke.
|
|
840
|
-
*
|
|
841
|
-
* See `docs/superpowers/specs/2026-05-16-registry-unification-design.md` § "Types".
|
|
842
|
-
*/
|
|
843
|
-
/** Optional modifier-key requirement for a gesture spec.
|
|
844
|
-
*
|
|
845
|
-
* Matching semantics (strict): an omitted modifier field means the
|
|
846
|
-
* modifier MUST NOT be held — i.e., a bare `{ kind: 'key', key: 'Escape' }`
|
|
847
|
-
* matches only unmodified Escape, NOT Cmd+Escape. A `true` means the
|
|
848
|
-
* modifier MUST be held; `false` is the same as omitted (must be absent).
|
|
849
|
-
* This mirrors today's `KeyBinding` matcher and keeps conflict detection
|
|
850
|
-
* coherent.
|
|
851
|
-
*
|
|
852
|
-
* `mod` is a platform-aware shorthand: matches `metaKey` on mac, `ctrlKey`
|
|
853
|
-
* elsewhere (mirrors `KeyBinding.mod`).
|
|
854
|
-
*
|
|
855
|
-
* `shift` additionally accepts `'optional'` meaning "shifted or unshifted
|
|
856
|
-
* both acceptable" — the explicit opt-in for loose matching, used by
|
|
857
|
-
* actions like nudge whose step size depends on shift but whose firing
|
|
858
|
-
* does not. To widen other modifiers similarly, extend their type when
|
|
859
|
-
* a real consumer needs it.
|
|
860
|
-
*/
|
|
861
|
-
type ModSpec = Partial<{
|
|
862
|
-
alt: boolean | 'optional';
|
|
863
|
-
ctrl: boolean | 'optional';
|
|
864
|
-
meta: boolean | 'optional';
|
|
865
|
-
mod: boolean | 'optional';
|
|
866
|
-
shift: boolean | 'optional';
|
|
867
|
-
}>;
|
|
868
|
-
/** The predicate form of {@link TargetSpec}. `hit` is the raw target
|
|
869
|
-
* (affordance for drag, `e.target` otherwise); `bodyTarget` is the optional
|
|
870
|
-
* body-class string ('empty' | 'selected-body' | 'unselected-body') when
|
|
871
|
-
* `classifyTarget` is wired. Predicates that only need one of the two can
|
|
872
|
-
* ignore the other. */
|
|
873
|
-
interface TargetPredicate {
|
|
874
|
-
(hit: unknown, bodyTarget?: string): boolean;
|
|
875
|
-
/** `false` declares that the predicate reads `bodyTarget` only. An
|
|
876
|
-
* exclusive affordance claim bars bindings whose target doesn't consult
|
|
877
|
-
* the hit; a body predicate that declares nothing looks like it does. */
|
|
878
|
-
readsAffordance?: boolean;
|
|
879
|
-
}
|
|
880
|
-
/** Target selector for click and drag gesture specs. String forms are sugar
|
|
881
|
-
* for the kit-owned object-kind registry (TODO.md Tier 1 follow-up); until
|
|
882
|
-
* that ships, consumers can pass `{ kindOf: predicate }` to classify hits
|
|
883
|
-
* themselves.
|
|
884
|
-
*
|
|
885
|
-
* Adding a form here is a compile error in `parseTargetSpec` until
|
|
886
|
-
* `TargetSpecForm` grows a matching variant — which is in turn a compile
|
|
887
|
-
* error at every site that switches on one. */
|
|
888
|
-
type TargetSpec = 'empty' | 'selected-body' | 'unselected-body' | `kind:${string}` | `kind:${string}:selected` | `affordance:${string}` | {
|
|
889
|
-
kindOf: TargetPredicate;
|
|
890
|
-
};
|
|
891
|
-
/** Phase qualifier on a gesture spec. Restricts when the spec matches based
|
|
892
|
-
* on per-tool gesture-lifecycle state.
|
|
893
|
-
*
|
|
894
|
-
* Shorthand forms (most common case — gate on the binding's own tool):
|
|
895
|
-
* `'engaged'` → `[{ channel: '&', phase: 'engaged' }]` // self mid-gesture
|
|
896
|
-
* `'initial'` → `[{ channel: '&', phase: 'initial' }]` // self idle
|
|
897
|
-
* `'*'` → `[{ channel: '&', phase: '*' }]` // either self phase
|
|
898
|
-
*
|
|
899
|
-
* Array form for explicit channel:phase atoms — e.g. `[{ channel: 'rect',
|
|
900
|
-
* phase: 'engaged' }]` for "when the rect tool is mid-gesture, regardless of
|
|
901
|
-
* which scope I'm in." See the v3 route grammar in
|
|
902
|
-
* `@weasel-js/gestures/grammar` for the full lattice.
|
|
903
|
-
*
|
|
904
|
-
* When omitted, matches in any phase (preserves pre-phase behavior). */
|
|
905
|
-
type PhaseSpec = 'initial' | 'engaged' | '*' | readonly PhaseAtom[];
|
|
906
|
-
/** Single-keystroke gesture (keydown). */
|
|
907
|
-
interface KeySpec$1 {
|
|
908
|
-
kind: 'key';
|
|
909
|
-
/** A single key, or an array of acceptable keys (case-insensitive match). */
|
|
910
|
-
key: string | string[];
|
|
911
|
-
mods?: ModSpec;
|
|
912
|
-
phase?: PhaseSpec;
|
|
913
|
-
}
|
|
914
|
-
/** Key-held gesture (keydown opens, keyup closes). Drives "hold space for
|
|
915
|
-
* hand tool"-style interactions. */
|
|
916
|
-
interface KeyHeldSpec {
|
|
917
|
-
kind: 'key-held';
|
|
918
|
-
/** A single key, or an array of acceptable keys (case-insensitive match). */
|
|
919
|
-
key: string | string[];
|
|
920
|
-
mods?: ModSpec;
|
|
921
|
-
phase?: PhaseSpec;
|
|
922
|
-
}
|
|
923
|
-
/** Wheel-event gesture. `direction` filters by deltaY sign; default `'*'`.
|
|
924
|
-
* - `'up'` → matches only deltaY < 0
|
|
925
|
-
* - `'down'` → matches only deltaY > 0
|
|
926
|
-
* - `'*'` → matches either sign (default; universal-wildcard convention) */
|
|
927
|
-
interface WheelSpec {
|
|
928
|
-
kind: 'wheel';
|
|
929
|
-
direction?: 'up' | 'down' | '*';
|
|
930
|
-
target?: TargetSpec;
|
|
931
|
-
mods?: ModSpec;
|
|
932
|
-
phase?: PhaseSpec;
|
|
933
|
-
}
|
|
934
|
-
/** Click gesture (pointerdown + pointerup without movement past the
|
|
935
|
-
* threshold). */
|
|
936
|
-
interface ClickSpec {
|
|
937
|
-
kind: 'click';
|
|
938
|
-
target?: TargetSpec;
|
|
939
|
-
mods?: ModSpec;
|
|
940
|
-
phase?: PhaseSpec;
|
|
941
|
-
}
|
|
942
|
-
/** Double-click: two `click` events within ~500ms and ~5px of each other.
|
|
943
|
-
* Synthesized by `useGestureDispatcher`; emitted AFTER the second
|
|
944
|
-
* `click`. Bindings that want to handle a double-click should declare
|
|
945
|
-
* this kind rather than chasing two `click` events. */
|
|
946
|
-
interface DoubleClickSpec {
|
|
947
|
-
kind: 'doubleClick';
|
|
948
|
-
target?: TargetSpec;
|
|
949
|
-
mods?: ModSpec;
|
|
950
|
-
phase?: PhaseSpec;
|
|
951
|
-
}
|
|
952
|
-
/** Right-click (contextmenu) gesture. The dispatcher calls
|
|
953
|
-
* `preventDefault()` on the underlying DOM event so the native menu
|
|
954
|
-
* doesn't appear — tools/actions fully own the right-click UX. */
|
|
955
|
-
interface ContextMenuSpec {
|
|
956
|
-
kind: 'contextMenu';
|
|
957
|
-
target?: TargetSpec;
|
|
958
|
-
mods?: ModSpec;
|
|
959
|
-
phase?: PhaseSpec;
|
|
960
|
-
}
|
|
961
|
-
/** Drag gesture (pointerdown + pointermove past the threshold). */
|
|
962
|
-
interface DragSpec {
|
|
963
|
-
kind: 'drag';
|
|
964
|
-
target?: TargetSpec;
|
|
965
|
-
mods?: ModSpec;
|
|
966
|
-
phase?: PhaseSpec;
|
|
967
|
-
}
|
|
968
|
-
/**
|
|
969
|
-
* Bare pointer press, matched at down time — before the dispatcher knows
|
|
970
|
-
* whether the gesture will become a click or a drag.
|
|
971
|
-
*
|
|
972
|
-
* Reach for this only when the effect must be visible while the button is
|
|
973
|
-
* still held. Selection is the motivating case: pressing an unselected node
|
|
974
|
-
* highlights it immediately, and the drag that may follow then starts from an
|
|
975
|
-
* already-correct selection. Anything that can wait for the release belongs on
|
|
976
|
-
* a `click` spec, which does not fire on a press that turns into a drag.
|
|
977
|
-
*
|
|
978
|
-
* A matching binding does NOT own the gesture: the same press goes on to open
|
|
979
|
-
* a drag or synthesize a click as usual. Bind an immediate action here, not an
|
|
980
|
-
* ongoing one.
|
|
981
|
-
*/
|
|
982
|
-
interface PointerDownSpec {
|
|
983
|
-
kind: 'pointerDown';
|
|
984
|
-
target?: TargetSpec;
|
|
985
|
-
mods?: ModSpec;
|
|
986
|
-
phase?: PhaseSpec;
|
|
987
|
-
}
|
|
988
|
-
/**
|
|
989
|
-
* Press held past the long-press threshold without crossing the drag
|
|
990
|
-
* threshold. Synthesized by `useGestureDispatcher` from the pointer stream.
|
|
991
|
-
*
|
|
992
|
-
* Fires for `touch` and `pen` pointers only. A mouse held still for half a
|
|
993
|
-
* second is an ordinary slow click, and firing on it would produce a context
|
|
994
|
-
* menu nobody asked for.
|
|
995
|
-
*
|
|
996
|
-
* When a long-press matches no binding, the dispatcher re-dispatches it as a
|
|
997
|
-
* `contextmenu` event — so `contextMenu` bindings work under a finger with no
|
|
998
|
-
* consumer changes, while `longPress` stays independently bindable.
|
|
999
|
-
*/
|
|
1000
|
-
interface LongPressSpec {
|
|
1001
|
-
kind: 'longPress';
|
|
1002
|
-
target?: TargetSpec;
|
|
1003
|
-
mods?: ModSpec;
|
|
1004
|
-
phase?: PhaseSpec;
|
|
1005
|
-
}
|
|
1006
|
-
/** Multi-touch gesture. `fingers` is the required touch count. */
|
|
1007
|
-
interface MultiTouchSpec {
|
|
1008
|
-
kind: 'multiTouch';
|
|
1009
|
-
fingers: number;
|
|
1010
|
-
mods?: ModSpec;
|
|
1011
|
-
phase?: PhaseSpec;
|
|
1012
|
-
}
|
|
1013
|
-
/** Multi-touch tap gesture — fires when N fingers touch down then release
|
|
1014
|
-
* together without movement past the tap threshold. Synthesized by the
|
|
1015
|
-
* dispatcher from the underlying multitouch tracking. */
|
|
1016
|
-
interface MultiTouchTapSpec {
|
|
1017
|
-
kind: 'multiTouchTap';
|
|
1018
|
-
fingers: number;
|
|
1019
|
-
mods?: ModSpec;
|
|
1020
|
-
phase?: PhaseSpec;
|
|
1021
|
-
}
|
|
1022
|
-
/** OS drag-and-drop of external content onto the canvas. `types` filters by
|
|
1023
|
-
* MIME glob (`'image/*'`, `'text/plain'`); the spec matches when ANY item's
|
|
1024
|
-
* MIME matches ANY glob. Omitted or empty = matches any drop. */
|
|
1025
|
-
interface DropSpec {
|
|
1026
|
-
kind: 'drop';
|
|
1027
|
-
types?: string[];
|
|
1028
|
-
mods?: ModSpec;
|
|
1029
|
-
phase?: PhaseSpec;
|
|
1030
|
-
}
|
|
1031
|
-
/** System-clipboard paste of external content. Same `types` semantics as
|
|
1032
|
-
* {@link DropSpec} — omitted or empty = matches any paste. */
|
|
1033
|
-
interface PasteSpec {
|
|
1034
|
-
kind: 'paste';
|
|
1035
|
-
types?: string[];
|
|
1036
|
-
mods?: ModSpec;
|
|
1037
|
-
phase?: PhaseSpec;
|
|
1038
|
-
}
|
|
1039
|
-
/** The full union of supported gesture spec kinds. New invocation forms
|
|
1040
|
-
* (two-stage, modal-dialog) extend this union without touching
|
|
1041
|
-
* the `Action` type. */
|
|
1042
|
-
type GestureSpec = KeySpec$1 | KeyHeldSpec | WheelSpec | ClickSpec | DoubleClickSpec | ContextMenuSpec | DragSpec | PointerDownSpec | LongPressSpec | MultiTouchSpec | MultiTouchTapSpec | DropSpec | PasteSpec;
|
|
1043
|
-
|
|
1044
|
-
/** A 2D point in either world or screen coordinates. */
|
|
1045
|
-
interface Point2 {
|
|
1046
|
-
x: number;
|
|
1047
|
-
y: number;
|
|
1048
|
-
}
|
|
1049
|
-
/**
|
|
1050
|
-
* Information about which UI affordance was hit at pointerdown.
|
|
1051
|
-
*
|
|
1052
|
-
* Populated by the dispatcher when the `affordanceAt` thunk is provided to
|
|
1053
|
-
* `useGestureDispatcher`. Tools / action invokers that only fire on a specific
|
|
1054
|
-
* affordance (e.g. a resize handle) use this field as a guard — if the
|
|
1055
|
-
* affordance is absent or is the wrong kind, they return `{}` and let other
|
|
1056
|
-
* bindings handle the drag.
|
|
1057
|
-
*
|
|
1058
|
-
* `kind` is a discriminator string:
|
|
1059
|
-
* - `'handle:top-left'` / `'handle:top-right'` / `'handle:bottom-left'` /
|
|
1060
|
-
* `'handle:bottom-right'` — corner resize handles.
|
|
1061
|
-
* - `'rotate-handle'` — the rotation affordance.
|
|
1062
|
-
* - `'anchor:N'` — a path anchor at index N.
|
|
1063
|
-
*
|
|
1064
|
-
* `fixedPoint` is the world-space point that should remain stationary during
|
|
1065
|
-
* the gesture. For resize handles this is the opposite (diagonally fixed)
|
|
1066
|
-
* corner; for rotate it is the pivot.
|
|
1067
|
-
*
|
|
1068
|
-
* `targetIds` are the node ids this affordance belongs to.
|
|
1069
|
-
*/
|
|
1070
|
-
interface AffordanceHit {
|
|
1071
|
-
/** Discriminator string, e.g. `'handle:bottom-right'`. */
|
|
1072
|
-
kind: string;
|
|
1073
|
-
/** Id of whatever produced this hit — a kit affordance's `id`, or the
|
|
1074
|
-
* registered layer's id. Read only by the dispatcher's dead-claim warning today. */
|
|
1075
|
-
owner?: string;
|
|
1076
|
-
/** `'exclusive'` means no binding may act on this point unless its target
|
|
1077
|
-
* consults the affordance. `'shared'` (the default) competes on scope and
|
|
1078
|
-
* specificity as bindings always have. */
|
|
1079
|
-
strength?: 'exclusive' | 'shared';
|
|
1080
|
-
/** Which gestures an exclusive claim bars. Omitted bars all of them. */
|
|
1081
|
-
claimedKinds?: readonly ClaimableGesture[];
|
|
1082
|
-
/** World-space fixed/pivot point. For resize: opposite corner. For rotate: pivot. */
|
|
1083
|
-
fixedPoint?: {
|
|
1084
|
-
x: number;
|
|
1085
|
-
y: number;
|
|
1086
|
-
};
|
|
1087
|
-
/** Which nodes this affordance belongs to. */
|
|
1088
|
-
targetIds?: string[];
|
|
1089
|
-
/** Set when `kind` matches `'handle:*'`. Identifies which corner stays
|
|
1090
|
-
* fixed during a resize so consumers (resizeAction) don't re-parse `kind`.
|
|
1091
|
-
* Other affordance kinds (rotate-handle, anchor:N, controlIn:N, controlOut:N)
|
|
1092
|
-
* leave this undefined. */
|
|
1093
|
-
anchor?: ResizeAnchor;
|
|
1094
|
-
/** CSS cursor to show while the pointer hovers this affordance (no
|
|
1095
|
-
* gesture in flight). Consumed by the hover-cursor pump in
|
|
1096
|
-
* `useGestureDispatcher`; unset = the pump falls through to
|
|
1097
|
-
* action-cursor prediction, then to the active tool's cursor. */
|
|
1098
|
-
cursor?: string;
|
|
1099
|
-
/**
|
|
1100
|
-
* Free-form payload from whatever produced the hit, carried through to the
|
|
1101
|
-
* matching action untouched.
|
|
1102
|
-
*
|
|
1103
|
-
* Kit affordances describe themselves fully in the fields above and leave
|
|
1104
|
-
* this unset. It exists for affordances the kit doesn't know the shape of —
|
|
1105
|
-
* a registered layer's own chrome, where the hit-test already resolved
|
|
1106
|
-
* *which* of its pieces was hit and the action would otherwise have to
|
|
1107
|
-
* redo that work. `@weasel-js/hud` passes the hit widget here.
|
|
1108
|
-
*/
|
|
1109
|
-
payload?: unknown;
|
|
1110
|
-
}
|
|
1111
|
-
/**
|
|
1112
|
-
* One accumulated point on a drag trail: world-space position plus whatever
|
|
1113
|
-
* stylus state the originating `PointerEvent` carried.
|
|
1114
|
-
*
|
|
1115
|
-
* The stylus fields are absent for mouse/touch on browsers that don't report
|
|
1116
|
-
* them, and for synthetic events. Consumers that want pressure-driven output
|
|
1117
|
-
* (e.g. `Stroke.vertexWidths` from a pencil stroke) read them off the samples
|
|
1118
|
-
* their `insert` dep receives — see `apps/site/demos/VertexWidthsDemo.tsx`.
|
|
1119
|
-
*/
|
|
1120
|
-
interface DragSample extends Point2 {
|
|
1121
|
-
/** 0..1. Mouse/touch report 0.5 while a button is held, per the spec. */
|
|
1122
|
-
pressure?: number;
|
|
1123
|
-
/** Degrees, ±90. Zero for mouse/touch. */
|
|
1124
|
-
tiltX?: number;
|
|
1125
|
-
/** Degrees, ±90. Zero for mouse/touch. */
|
|
1126
|
-
tiltY?: number;
|
|
1127
|
-
}
|
|
1128
|
-
/** Per-invocation runtime context the dispatcher hands to an Invoker.
|
|
1129
|
-
* Gesture-kind-specific fields (`drag`, `wheel`, `multiTouch`, `key`) are
|
|
1130
|
-
* populated only for matching gesture kinds. */
|
|
1131
|
-
interface InvocationCtx {
|
|
1132
|
-
world: Point2;
|
|
1133
|
-
screen: Point2;
|
|
1134
|
-
modifiers: ModifierState;
|
|
1135
|
-
deps: ActionDeps;
|
|
1136
|
-
drag?: {
|
|
1137
|
-
start: Point2;
|
|
1138
|
-
current: Point2;
|
|
1139
|
-
delta: Point2;
|
|
1140
|
-
/**
|
|
1141
|
-
* Drag delta in client/screen coordinates (CSS pixels from the drag
|
|
1142
|
-
* origin). Use this — never `delta` — for any action whose effect
|
|
1143
|
-
* mutates the viewport itself (pan, view-zoom), because world-space
|
|
1144
|
-
* deltas become self-referential as the view shifts mid-drag.
|
|
1145
|
-
*
|
|
1146
|
-
* Populated when the dispatcher received `clientX`/`clientY` on the
|
|
1147
|
-
* underlying pointer events. Absent for legacy callers that don't
|
|
1148
|
-
* provide them.
|
|
1149
|
-
*/
|
|
1150
|
-
screenDelta?: Point2;
|
|
1151
|
-
affordance?: AffordanceHit;
|
|
1152
|
-
/**
|
|
1153
|
-
* Full pointermove history for the current drag, in world space, with
|
|
1154
|
-
* per-sample stylus state when the browser reported it.
|
|
1155
|
-
* Accumulated by the dispatcher on every `pointermove` pump event.
|
|
1156
|
-
* Available only during `onMove` and `onEnd` calls (not on `start`).
|
|
1157
|
-
* Used by `lassoSelectAction` to build its polygon vertex list and by
|
|
1158
|
-
* `insertAction`'s pencil kind to carry the freehand stroke.
|
|
1159
|
-
*/
|
|
1160
|
-
points?: DragSample[];
|
|
1161
|
-
};
|
|
1162
|
-
wheel?: {
|
|
1163
|
-
deltaX: number;
|
|
1164
|
-
deltaY: number;
|
|
1165
|
-
deltaZ: number;
|
|
1166
|
-
};
|
|
1167
|
-
multiTouch?: {
|
|
1168
|
-
centroid: Point2;
|
|
1169
|
-
spread: number;
|
|
1170
|
-
rotation: number;
|
|
1171
|
-
/**
|
|
1172
|
-
* Pinch-zoom geometry. Populated by the dispatcher when a multitouch
|
|
1173
|
-
* handle is in flight and a pointermove-pump fires.
|
|
1174
|
-
* `startSpread` is the spread at the moment the gesture began.
|
|
1175
|
-
* `currentSpread` is the spread at the current frame.
|
|
1176
|
-
*/
|
|
1177
|
-
pinch?: {
|
|
1178
|
-
startSpread: number;
|
|
1179
|
-
currentSpread: number;
|
|
1180
|
-
centroid: Point2;
|
|
1181
|
-
};
|
|
1182
|
-
};
|
|
1183
|
-
key?: {
|
|
1184
|
-
key: string;
|
|
1185
|
-
repeat: boolean;
|
|
1186
|
-
};
|
|
1187
|
-
/**
|
|
1188
|
-
* Per-invocation parameters. Populated by `ActionsRegistry.begin()` for
|
|
1189
|
-
* UI-driven ongoing actions (color picker, opacity slider) so handles can
|
|
1190
|
-
* read the current value on `start` and updated values on `onMove`. The
|
|
1191
|
-
* gesture dispatcher does not populate this field; gesture-driven actions
|
|
1192
|
-
* receive params via `BindingOpts.params` on `start` (the `opts` arg).
|
|
1193
|
-
*/
|
|
1194
|
-
params?: Record<string, unknown>;
|
|
1195
|
-
}
|
|
1196
|
-
/** Per-invocation options the dispatcher reads from a `GestureBinding`'s
|
|
1197
|
-
* `opts` field and passes to `OngoingInvoker.start`. Today carries
|
|
1198
|
-
* behaviors; extensible. */
|
|
1199
|
-
interface BindingOpts {
|
|
1200
|
-
behaviors?: ActionBehavior<unknown, unknown, unknown>[];
|
|
1201
|
-
/** Per-binding action parameters. The action's invoker reads
|
|
1202
|
-
* these via the second arg to `run` (or via InvocationCtx for ongoing
|
|
1203
|
-
* invokers, when needed). Loose typing (Record<string, unknown>) for
|
|
1204
|
-
* now; consider per-action typing later via BindingOpts<A>.
|
|
1205
|
-
*
|
|
1206
|
-
* params may also be a thunk evaluated each time the
|
|
1207
|
-
* dispatcher (or invoker) needs the value. Thunks let tools close over
|
|
1208
|
-
* refs that mutate during a gesture (e.g. polygon `sides` adjusted
|
|
1209
|
-
* mid-drag via ArrowUp). For ongoing invokers that want the latest
|
|
1210
|
-
* values at commit, the invoker can re-call the thunk inside `onEnd`
|
|
1211
|
-
* via `resolveParams(opts?.params)`. */
|
|
1212
|
-
params?: Record<string, unknown> | (() => Record<string, unknown>);
|
|
1213
|
-
}
|
|
1214
|
-
/** Convention-shaped action dependencies bag. Actions declare which
|
|
1215
|
-
* contexts they consume; the dispatcher composes them per call.
|
|
1216
|
-
* Consumer-side contexts (e.g. ColorContext) plug in by extending. */
|
|
1217
|
-
interface ActionDeps {
|
|
1218
|
-
selection?: unknown;
|
|
1219
|
-
view?: unknown;
|
|
1220
|
-
scene?: unknown;
|
|
1221
|
-
pointer?: unknown;
|
|
1222
|
-
activeTool?: unknown;
|
|
1223
|
-
[k: string]: unknown;
|
|
1224
|
-
}
|
|
1225
|
-
/**
|
|
1226
|
-
* Discriminated overlay shape returned by `OngoingHandle.overlay()`.
|
|
1227
|
-
* Dispatcher-side chrome surface for in-flight
|
|
1228
|
-
* gestures that paint non-ghost visuals. The canvas's
|
|
1229
|
-
* `useDispatcherOverlayLayer` walks every in-flight handle, calls
|
|
1230
|
-
* `overlay()`, and dispatches on `kind` to draw the appropriate shape.
|
|
1231
|
-
*
|
|
1232
|
-
* `marquee` mirrors `AreaSelectOverlay`; `lasso` mirrors `LassoSelectOverlay`.
|
|
1233
|
-
* `commands` is the generic escape hatch — actions emit arbitrary
|
|
1234
|
-
* `DrawCommand[]` for previews the typed variants can't express (insert
|
|
1235
|
-
* shape outlines, paste ghosts of synthetic nodes, custom chrome). World-
|
|
1236
|
-
* space is the default; the layer wraps in `viewToMat3` so commands track
|
|
1237
|
-
* the camera. Set `space: 'screen'` for projections you've already done
|
|
1238
|
-
* yourself (rare).
|
|
1239
|
-
*/
|
|
1240
|
-
type OngoingOverlay = {
|
|
1241
|
-
kind: 'marquee';
|
|
1242
|
-
start: {
|
|
1243
|
-
x: number;
|
|
1244
|
-
y: number;
|
|
1245
|
-
};
|
|
1246
|
-
current: {
|
|
1247
|
-
x: number;
|
|
1248
|
-
y: number;
|
|
1249
|
-
};
|
|
1250
|
-
shiftHeld: boolean;
|
|
1251
|
-
} | {
|
|
1252
|
-
kind: 'lasso';
|
|
1253
|
-
vertices: ReadonlyArray<{
|
|
1254
|
-
x: number;
|
|
1255
|
-
y: number;
|
|
1256
|
-
}>;
|
|
1257
|
-
current: {
|
|
1258
|
-
x: number;
|
|
1259
|
-
y: number;
|
|
1260
|
-
};
|
|
1261
|
-
shiftHeld: boolean;
|
|
1262
|
-
} | {
|
|
1263
|
-
kind: 'commands';
|
|
1264
|
-
commands: readonly DrawCommand[];
|
|
1265
|
-
/** Coordinate space the commands are authored in. Default `'world'`
|
|
1266
|
-
* — the layer wraps them in `viewToMat3(view)` so they track the
|
|
1267
|
-
* camera. `'screen'` emits them as-is (CSS pixels). */
|
|
1268
|
-
space?: 'world' | 'screen';
|
|
1269
|
-
} | {
|
|
1270
|
-
/**
|
|
1271
|
-
* Live insert-drag preview — dispatched by `insertAction` while the
|
|
1272
|
-
* user is dragging out a new shape. Pre-commit there is no scene node
|
|
1273
|
-
* to ghost via `previewIds()`/`previewPose()`, so insert paints its
|
|
1274
|
-
* preview through the dispatcher overlay layer instead.
|
|
1275
|
-
*
|
|
1276
|
-
* `shape` is the kit's built-in insert kind. `bounds` is the AABB of
|
|
1277
|
-
* the current drag (start/current normalized). `extras` is the
|
|
1278
|
-
* per-kind extras the action already collected — the overlay
|
|
1279
|
-
* renderer rebuilds the shape using the same path builders the
|
|
1280
|
-
* commit factory uses, so the preview matches the eventual node.
|
|
1281
|
-
*
|
|
1282
|
-
* `extras` is opaque (`unknown`) at the union level; the overlay
|
|
1283
|
-
* renderer narrows on `shape` and casts the field shape it expects.
|
|
1284
|
-
*/
|
|
1285
|
-
kind: 'insertPreview';
|
|
1286
|
-
shape: KitInsertShape;
|
|
1287
|
-
bounds: {
|
|
1288
|
-
x: number;
|
|
1289
|
-
y: number;
|
|
1290
|
-
width: number;
|
|
1291
|
-
height: number;
|
|
1292
|
-
};
|
|
1293
|
-
extras: unknown;
|
|
1294
|
-
/** World-space point to paint a small "anchor" dot at. Sells the
|
|
1295
|
-
* click point as the drag's anchor — particularly useful for
|
|
1296
|
-
* radial shapes (polygon/star) where no vertex sits on the
|
|
1297
|
-
* click point, and for any shape in center mode where the dot
|
|
1298
|
-
* marks the center the shape grows around. */
|
|
1299
|
-
anchorPoint?: {
|
|
1300
|
-
x: number;
|
|
1301
|
-
y: number;
|
|
1302
|
-
};
|
|
1303
|
-
};
|
|
1304
|
-
/** Handle returned from an `OngoingInvoker.start`. The dispatcher pumps
|
|
1305
|
-
* `onMove` on subsequent input events of the same gesture and calls
|
|
1306
|
-
* `onEnd` exactly once (with `'commit'` on natural completion or `'cancel'`
|
|
1307
|
-
* on pointercancel / blur / escape). */
|
|
1308
|
-
interface OngoingHandle {
|
|
1309
|
-
/**
|
|
1310
|
-
* Optional logical action kind — a stable, human-readable tag the
|
|
1311
|
-
* dispatcher exposes via `getActiveAction()` for chrome-visibility
|
|
1312
|
-
* rules and any other surface that wants to react to "what action
|
|
1313
|
-
* is currently in flight" without inspecting handles directly.
|
|
1314
|
-
*
|
|
1315
|
-
* Examples: `'marquee'`, `'lasso'`, `'move'`, `'resize'`, `'rotate'`,
|
|
1316
|
-
* `'pan'`, `'pinch'`.
|
|
1317
|
-
*
|
|
1318
|
-
* Distinct from the dispatcher's internal `gestureId` (`pointer-mouse`,
|
|
1319
|
-
* `key-held-Space`, etc.) which keys per-pointer state and is not
|
|
1320
|
-
* meaningful to consumers.
|
|
1321
|
-
*
|
|
1322
|
-
* When omitted, the action is "anonymous" — `getActiveAction().kind`
|
|
1323
|
-
* reports `null` even though a handle is in flight. This is fine for
|
|
1324
|
-
* actions that don't have visible chrome of their own.
|
|
1325
|
-
*/
|
|
1326
|
-
kind?: string;
|
|
1327
|
-
onMove?(ctx: InvocationCtx): void;
|
|
1328
|
-
onEnd?(ctx: InvocationCtx, reason: 'commit' | 'cancel'): void;
|
|
1329
|
-
/**
|
|
1330
|
-
* Optional preview surface — dispatcher-side ghost overlay.
|
|
1331
|
-
*
|
|
1332
|
-
* An ongoing-action implementation may populate `previewIds()` +
|
|
1333
|
-
* `previewPose(id)` to expose its in-flight preview state for the
|
|
1334
|
-
* canvas's preview-ghost layer (`usePreviewGhostLayer`) to render on
|
|
1335
|
-
* top of the committed scene during the gesture.
|
|
1336
|
-
*
|
|
1337
|
-
* Returning `null` (or omitting the method entirely) means "no preview
|
|
1338
|
-
* this gesture" — the canvas will skip this handle as a source.
|
|
1339
|
-
*
|
|
1340
|
-
* Semantics mirror the tool-side `Tool.previewIds` / `Tool.previewPose`
|
|
1341
|
-
* pair: `previewIds()` enumerates the displaced node ids; `previewPose(id)`
|
|
1342
|
-
* returns the interim pose for one of those ids (shape opaque — the
|
|
1343
|
-
* canvas casts to its `TPose` parameter). The preview-ghost layer
|
|
1344
|
-
* merges all sources via first-non-null semantics, with tool-side
|
|
1345
|
-
* previews taking precedence over dispatcher-side (preserves
|
|
1346
|
-
* backwards-compat during the registry-unification migration).
|
|
1347
|
-
*/
|
|
1348
|
-
previewIds?(): Iterable<string> | null;
|
|
1349
|
-
previewPose?(id: string): unknown | null;
|
|
1350
|
-
/**
|
|
1351
|
-
* Subset of `previewIds()` the ghost layer paints at full opacity. The
|
|
1352
|
-
* ghost alpha says "this is in flight under the pointer"; a node the
|
|
1353
|
-
* gesture merely displaces — a layout sibling reflowing into its
|
|
1354
|
-
* destination slot — is not, and reads better settled. Honored at
|
|
1355
|
-
* subtree-root granularity.
|
|
1356
|
-
*/
|
|
1357
|
-
previewOpaqueIds?(): Iterable<string> | null;
|
|
1358
|
-
/**
|
|
1359
|
-
* When `false`, the preview-ghost layer paints the ghost AND the
|
|
1360
|
-
* source node stays visible at its committed pose. Defaults to
|
|
1361
|
-
* `true` (move/resize/rotate semantics: ghost replaces the source
|
|
1362
|
-
* during the gesture). Clone overrides to `false` so the original
|
|
1363
|
-
* stays put and the ghost appears at the drag target.
|
|
1364
|
-
*/
|
|
1365
|
-
previewHidesSource?: boolean;
|
|
1366
|
-
/**
|
|
1367
|
-
* Optional per-id preview *data*. Falls back to the committed
|
|
1368
|
-
* `node.data` when null/absent. Use when the gesture mutates
|
|
1369
|
-
* `node.data` (e.g. anchor-edit on nodes that store the polygon on
|
|
1370
|
-
* `data.path`) rather than (or in addition to) the pose. The preview-
|
|
1371
|
-
* ghost layer assembles a synthetic node from `{ ...node, pose:
|
|
1372
|
-
* previewPose ?? node.pose, data: previewData ?? node.data }` before
|
|
1373
|
-
* calling the scene slot's `drawOne`.
|
|
1374
|
-
*
|
|
1375
|
-
* Sources compose first-non-null per axis: an action can emit only
|
|
1376
|
-
* `previewPose` (translation), only `previewData` (data-only edit),
|
|
1377
|
-
* or both (pose + data both change, e.g. anchor drag on a data.path
|
|
1378
|
-
* node where the bounds shift).
|
|
1379
|
-
*/
|
|
1380
|
-
previewData?(id: string): unknown | null;
|
|
1381
|
-
/**
|
|
1382
|
-
* Optional chrome surface — dispatcher-side overlay layer.
|
|
1383
|
-
*
|
|
1384
|
-
* An ongoing-action implementation may populate `overlay()` to expose a
|
|
1385
|
-
* non-ghost visual (marquee rectangle, lasso polyline) for the canvas's
|
|
1386
|
-
* `useDispatcherOverlayLayer` to paint while the gesture is in flight.
|
|
1387
|
-
* Returning `null` (or omitting the method) means "no overlay this
|
|
1388
|
-
* gesture" — the canvas will skip this handle as a chrome source.
|
|
1389
|
-
*
|
|
1390
|
-
* Distinct from the `previewIds()`/`previewPose(id)` ghost surface,
|
|
1391
|
-
* which paints displaced scene-node silhouettes. Marquee and lasso
|
|
1392
|
-
* gestures don't displace any node, but still need on-screen feedback.
|
|
1393
|
-
*/
|
|
1394
|
-
overlay?(): OngoingOverlay | null;
|
|
1395
|
-
}
|
|
1396
|
-
/** Fire-once invocation. Runs to completion synchronously (or fires off an
|
|
1397
|
-
* async side-effect; the registry doesn't wait). */
|
|
1398
|
-
interface ImmediateInvoker {
|
|
1399
|
-
timing: 'immediate';
|
|
1400
|
-
/** `params` carries the matched binding's opts.params. When
|
|
1401
|
-
* invoked via the legacy `Action.run` bridge or from the command palette
|
|
1402
|
-
* with no per-binding context, `params` is undefined; descriptors should
|
|
1403
|
-
* default to a sensible variant. */
|
|
1404
|
-
run(deps: ActionDeps, params?: Record<string, unknown>): void;
|
|
1405
|
-
}
|
|
1406
|
-
/** Phase-machine invocation. `start` opens the phase and returns the handle
|
|
1407
|
-
* the dispatcher pumps. */
|
|
1408
|
-
interface OngoingInvoker {
|
|
1409
|
-
timing: 'ongoing';
|
|
1410
|
-
start(ctx: InvocationCtx, opts?: BindingOpts): OngoingHandle;
|
|
1411
|
-
}
|
|
1412
|
-
/** Pluggable invocation strategy for an Action. Future variants
|
|
1413
|
-
* (`longPress`, `twoStage`, `modal`) extend this union without touching
|
|
1414
|
-
* the `Action` type. */
|
|
1415
|
-
type Invoker = ImmediateInvoker | OngoingInvoker;
|
|
1416
|
-
|
|
1417
|
-
/**
|
|
1418
|
-
* GestureBinding — connects a GestureSpec to an Action id (with per-binding
|
|
1419
|
-
* options). Tools own arrays of these on their `bindings` field; ambient
|
|
1420
|
-
* gesture-bindings are registered globally.
|
|
1421
|
-
*
|
|
1422
|
-
* See `docs/superpowers/specs/2026-05-16-registry-unification-design.md`.
|
|
1423
|
-
*/
|
|
1424
|
-
|
|
1425
|
-
/** An interaction: a gesture spec composed with the id of the action it
|
|
1426
|
-
* invokes. Tools declare arrays of these; the dispatcher matches an incoming
|
|
1427
|
-
* input event against them and runs the winner's action. */
|
|
1428
|
-
interface GestureBinding {
|
|
1429
|
-
spec: GestureSpec;
|
|
1430
|
-
actionId: string;
|
|
1431
|
-
opts?: BindingOpts;
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
/**
|
|
1435
|
-
* Pose composition for hierarchical scene graphs.
|
|
1436
|
-
*
|
|
1437
|
-
* As of the nesting change, `getPose(id)` on adapters returns the
|
|
1438
|
-
* **local** pose — relative to the object's direct parent. Anything in the
|
|
1439
|
-
* kit that needs to draw, hit-test, snap, or otherwise reason about world
|
|
1440
|
-
* coordinates routes through `composeWorldPose`, which walks the parent
|
|
1441
|
-
* chain and folds local poses together via a consumer-supplied `compose`.
|
|
1442
|
-
*
|
|
1443
|
-
* Pose shape is generic, so the compose strategy is too. For the common
|
|
1444
|
-
* `{x, y, width, height}` axis-aligned rect, use `composeRectPose` —
|
|
1445
|
-
* translation only, child dimensions preserved. Custom pose shapes (paths,
|
|
1446
|
-
* matrix transforms) supply their own.
|
|
1447
|
-
*
|
|
1448
|
-
* The inverse — `rebaseLocalPose` — converts a world-space pose into a
|
|
1449
|
-
* local pose under a target parent. Used when reparenting so the visual
|
|
1450
|
-
* world position of a child is preserved across the parent change.
|
|
1451
|
-
*/
|
|
1452
|
-
/** Re-exported; the declaration lives in `core/scene/types.ts`, which names
|
|
1453
|
-
* it and may not import from features. */
|
|
1454
|
-
|
|
1455
|
-
/** Consumer's pose-composition strategy for hierarchical scenes. `compose`
|
|
1456
|
-
* folds a child's pose (in parent's frame) up to the next frame; `decompose`
|
|
1457
|
-
* is its inverse. Default is IDENTITY — an absolute-pose scene where every
|
|
1458
|
-
* node already stores world coords (parent is grouping-only, no transform). */
|
|
1459
|
-
interface PoseComposition<TPose> {
|
|
1460
|
-
compose: (parent: TPose, child: TPose) => TPose;
|
|
1461
|
-
decompose: (parent: TPose, world: TPose) => TPose;
|
|
1462
|
-
}
|
|
1463
|
-
|
|
1464
|
-
/** Boolean op identifiers — five Pathfinder primaries plus Crop. */
|
|
1465
|
-
type BooleanOp = 'union' | 'intersect' | 'subtract' | 'exclude' | 'divide' | 'crop';
|
|
1466
|
-
/**
|
|
1467
|
-
* z-position descriptor for a path node. `parentId` is the direct parent
|
|
1468
|
-
* (or `null` for a top-level node); `index` is the position within that
|
|
1469
|
-
* parent's child order. Used by the optional `getZOrder` hook below to
|
|
1470
|
-
* reposition the result of a boolean op at the topmost source's slot.
|
|
1471
|
-
*/
|
|
1472
|
-
/** @internal */
|
|
1473
|
-
interface BooleanZOrder {
|
|
1474
|
-
parentId: string | null;
|
|
1475
|
-
index: number;
|
|
1476
|
-
}
|
|
1477
|
-
/** Adapter the hook and the pure core both consume. */
|
|
1478
|
-
interface BooleansAdapter {
|
|
1479
|
-
getSelection(): NodeId[];
|
|
1480
|
-
getWorldPath(id: NodeId): Path | undefined;
|
|
1481
|
-
compareZ(a: NodeId, b: NodeId): number;
|
|
1482
|
-
/**
|
|
1483
|
-
* Mint a new node from a boolean-op result `Path`. `producedBy` names the
|
|
1484
|
-
* op that synthesized it — adapters that store provenance (e.g. for a
|
|
1485
|
-
* layer-panel icon) record it; others ignore the arg.
|
|
1486
|
-
*/
|
|
1487
|
-
createPathNode(path: Path, producedBy: BooleanOp): {
|
|
1488
|
-
id: string;
|
|
1489
|
-
};
|
|
1490
|
-
/**
|
|
1491
|
-
* Optional: return the full object for an id, used by the delete ops so
|
|
1492
|
-
* their `invert` (an insert) can restore the complete object on undo.
|
|
1493
|
-
* If omitted, a `{ id }` stub is captured — undo will reinstate the id
|
|
1494
|
-
* but consumers reading other fields (path, fill, etc.) will see them as
|
|
1495
|
-
* undefined. Mirrors `DeleteAdapter.getNode`; should be provided whenever
|
|
1496
|
-
* undo over boolean ops is expected to be lossless.
|
|
1497
|
-
*/
|
|
1498
|
-
getNode?(id: NodeId): {
|
|
1499
|
-
id: string;
|
|
1500
|
-
} | undefined | null;
|
|
1501
|
-
/**
|
|
1502
|
-
* Optional: return the parent + child-index of `id` so the result of a
|
|
1503
|
-
* boolean op can be placed in the topmost source's z-slot. Adapters that
|
|
1504
|
-
* also expose `getChildren`/`setChildOrder` (the `ReorderAdapter`
|
|
1505
|
-
* contract) will have the kit emit a `createMoveToIndexOp` after the
|
|
1506
|
-
* inserts. Adapters that omit this method get v1 behavior — the result
|
|
1507
|
-
* lands wherever the adapter's plain `insertNode` defaults to.
|
|
1508
|
-
*/
|
|
1509
|
-
getZOrder?(id: NodeId): BooleanZOrder | undefined;
|
|
1510
|
-
applyOps?(ops: Op[], label?: string): void;
|
|
1511
|
-
setSelection?(ids: NodeId[]): void;
|
|
1512
|
-
insertNode?(node: {
|
|
1513
|
-
id: string;
|
|
1514
|
-
}): void;
|
|
1515
|
-
removeNode?(id: string): void;
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
/** API returned by {@link useSelection}. */
|
|
1519
|
-
interface SelectionApi {
|
|
1520
|
-
/** Current selection. Re-renders trigger when this reference changes. */
|
|
1521
|
-
current: readonly NodeId[];
|
|
1522
|
-
/** Imperative read for use inside event callbacks (avoids stale closures). */
|
|
1523
|
-
get(): NodeId[];
|
|
1524
|
-
/** Replace selection. */
|
|
1525
|
-
set(ids: NodeId[]): void;
|
|
1526
|
-
/** Add id (multi-mode appends; single-mode replaces). */
|
|
1527
|
-
add(id: NodeId): void;
|
|
1528
|
-
/** Remove id from selection. */
|
|
1529
|
-
remove(id: NodeId): void;
|
|
1530
|
-
/** Toggle id in/out of selection. */
|
|
1531
|
-
toggle(id: NodeId): void;
|
|
1532
|
-
/** Clear selection. */
|
|
1533
|
-
clear(): void;
|
|
1534
|
-
/** True if id is selected. */
|
|
1535
|
-
contains(id: NodeId): boolean;
|
|
1536
|
-
/**
|
|
1537
|
-
* Apply a click to the selection per the configured mode/extend key.
|
|
1538
|
-
* - `single`: replaces selection with `[id]`, regardless of modifiers.
|
|
1539
|
-
* - `multi`: with the extend key held, toggles `id` in/out of the selection;
|
|
1540
|
-
* otherwise replaces with `[id]`.
|
|
1541
|
-
*/
|
|
1542
|
-
applyClick(id: NodeId, modifiers: {
|
|
1543
|
-
shift: boolean;
|
|
1544
|
-
meta: boolean;
|
|
1545
|
-
ctrl: boolean;
|
|
1546
|
-
}): void;
|
|
1547
|
-
/** Pre-built methods for spreading into an adapter that needs them. */
|
|
1548
|
-
adapterMethods: {
|
|
1549
|
-
getSelection: () => NodeId[];
|
|
1550
|
-
setSelection: (ids: NodeId[]) => void;
|
|
1551
|
-
};
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
/** Context handed to every content handler for one ingest event. */
|
|
1555
|
-
interface IngestCtx {
|
|
1556
|
-
/** World-space arrival point (drop / pointed imperative ingest); `null`
|
|
1557
|
-
* for paste and point-less calls — handlers pick their own policy
|
|
1558
|
-
* (the kit image handler centers on the viewport). */
|
|
1559
|
-
point: {
|
|
1560
|
-
x: number;
|
|
1561
|
-
y: number;
|
|
1562
|
-
} | null;
|
|
1563
|
-
/** Visible canvas area in world coordinates. */
|
|
1564
|
-
viewportWorldRect(): {
|
|
1565
|
-
x: number;
|
|
1566
|
-
y: number;
|
|
1567
|
-
width: number;
|
|
1568
|
-
height: number;
|
|
1569
|
-
};
|
|
1570
|
-
/** The kit insert dep — id/layer/undoable-op supplied; the canonical way
|
|
1571
|
-
* for a handler to mint a node (`insert.commit(bounds, { kind, ... })`). */
|
|
1572
|
-
insert: InsertDep;
|
|
1573
|
-
/** Raw op commit for handlers that build their own ops. */
|
|
1574
|
-
applyOps(ops: Op[], label?: string): void;
|
|
1575
|
-
scene: Scene<unknown, string, unknown>;
|
|
1576
|
-
selection: SelectionApi;
|
|
1577
|
-
/** Consumer file→src resolver (SceneCanvas `ingestion.resolveSrc`).
|
|
1578
|
-
* When absent, the kit image handler embeds as a `data:` URI. */
|
|
1579
|
-
resolveSrc?: (file: File) => Promise<string>;
|
|
1580
|
-
/** Kit SVG-handler options (SceneCanvas `ingestion.svg`) — e.g.
|
|
1581
|
-
* `{ unpack: unpackSvgFiles }` (from `@weasel-js/svg`) to parse SVG files
|
|
1582
|
-
* into scene nodes. */
|
|
1583
|
-
svg?: SvgIngestOptions;
|
|
1584
|
-
/** Clipboard-paste seam — present when the hosting `SceneCanvas` supplied
|
|
1585
|
-
* an adapter with `commitPaste`. `reviver` comes from
|
|
1586
|
-
* `SceneCanvasProps.ingestion.clipboard`. Absent ⇒ the kit weasel-JSON
|
|
1587
|
-
* handler declines inert (dwarn, nothing ingested) — its matched items
|
|
1588
|
-
* were already consumed at match time, so they do NOT fall through;
|
|
1589
|
-
* only match-level misses flow on to other handlers. */
|
|
1590
|
-
clipboard?: ClipboardIngestCtx;
|
|
1591
|
-
/** Set to `true` by the kit weasel-JSON handler when it successfully
|
|
1592
|
-
* pastes a payload in this event. The `ctx` object is shared across all
|
|
1593
|
-
* handlers in one `runIngest` call, and higher-priority handlers' `handle`
|
|
1594
|
-
* bodies run (synchronously) before lower ones — so `kit:svg`'s
|
|
1595
|
-
* `text/plain` SVG fallback reads this to decline the SVG flavor of a copy
|
|
1596
|
-
* whose canonical weasel-JSON flavor already ingested (avoids a
|
|
1597
|
-
* double-paste when both flavors ride one clipboard event). */
|
|
1598
|
-
consumedWeaselPayload?: boolean;
|
|
1599
|
-
/** Full action-deps bag, for consumer handlers that need more. */
|
|
1600
|
-
deps: ActionDeps;
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
/**
|
|
1604
|
-
* Bridges arbitrary `TPose` shapes into the resize hook's bounds-driven math.
|
|
1605
|
-
* The hook reads bounds via `getBounds`, runs anchor-relative math on those
|
|
1606
|
-
* bounds, then asks `remapBounds` to project the result back into TPose.
|
|
1607
|
-
*
|
|
1608
|
-
* `remapBounds(pose, src, dst)` is a single operation that subsumes both
|
|
1609
|
-
* "set my own AABB to dst" (single-leaf resize) and "scale me as a leaf
|
|
1610
|
-
* inside parent's src→dst rect" (group resize) — they're the same affine
|
|
1611
|
-
* map. For rect-shaped poses the default geometry interprets the pose as
|
|
1612
|
-
* its own bounds; for Path or polygon poses the consumer supplies a
|
|
1613
|
-
* projection that knows how to read and rewrite the underlying geometry.
|
|
1614
|
-
*/
|
|
1615
|
-
interface PoseProjection<TPose> {
|
|
1616
|
-
getBounds(pose: TPose): ResizePose;
|
|
1617
|
-
remapBounds(pose: TPose, src: ResizePose, dst: ResizePose): TPose;
|
|
1618
|
-
/** Translate the pose by (dx, dy). Optional — when omitted, callers fall
|
|
1619
|
-
* back to a translation derived from `remapBounds` (origin shifted, no
|
|
1620
|
-
* scale). Path-shaped poses should provide this for performance. */
|
|
1621
|
-
translate?(pose: TPose, dx: number, dy: number): TPose;
|
|
1622
|
-
/** True iff any portion of the pose's geometry intersects `rect`. Optional
|
|
1623
|
-
* — when omitted, area-select and similar callers test against `getBounds`
|
|
1624
|
-
* AABB (looser, but correct for axis-aligned rect poses). */
|
|
1625
|
-
intersectsRect?(pose: TPose, rect: ResizePose): boolean;
|
|
1626
|
-
/** Interpolate between two poses. Optional — animation helpers fall back to
|
|
1627
|
-
* rect-shape lerp when omitted (which fails for non-rect poses). */
|
|
1628
|
-
lerp?(a: TPose, b: TPose, t: number): TPose;
|
|
1629
|
-
/** Read the pose's rotation in radians. Pivot is the AABB center
|
|
1630
|
-
* (`getBounds(pose)` center). Default 0 when omitted — descriptor
|
|
1631
|
-
* declares "this pose has no rotation." When supplied and non-zero,
|
|
1632
|
-
* `useResize` projects the drag delta into the leaf's local frame,
|
|
1633
|
-
* runs anchor math there, and translates the resulting pose so the
|
|
1634
|
-
* diagonally opposite world-space corner is pinned. */
|
|
1635
|
-
getRotation?(pose: TPose): number;
|
|
1636
|
-
/** True iff this pose shape can carry a rotation. Consulted by the
|
|
1637
|
-
* rotation affordance to decide whether to render the rotate cursor /
|
|
1638
|
-
* drag-band over a selection. When omitted, the kit assumes `true` for
|
|
1639
|
-
* back-compat — descriptors whose poses lack `x/y/width/height/rotation`
|
|
1640
|
-
* fields (e.g. polygon Paths) should return `false` so the affordance
|
|
1641
|
-
* hides instead of exposing a non-functional rotate cursor. */
|
|
1642
|
-
supportsRotation?(pose: TPose): boolean;
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
/** All easings in one bag — useful for demos / pickers. */
|
|
1646
|
-
declare const EASINGS: {
|
|
1647
|
-
readonly linear: EasingFn;
|
|
1648
|
-
readonly easeInQuad: EasingFn;
|
|
1649
|
-
readonly easeOutQuad: EasingFn;
|
|
1650
|
-
readonly easeInOutQuad: EasingFn;
|
|
1651
|
-
readonly easeInCubic: EasingFn;
|
|
1652
|
-
readonly easeOutCubic: EasingFn;
|
|
1653
|
-
readonly easeInOutCubic: EasingFn;
|
|
1654
|
-
readonly easeInQuart: EasingFn;
|
|
1655
|
-
readonly easeOutQuart: EasingFn;
|
|
1656
|
-
readonly easeInOutQuart: EasingFn;
|
|
1657
|
-
readonly easeInQuint: EasingFn;
|
|
1658
|
-
readonly easeOutQuint: EasingFn;
|
|
1659
|
-
readonly easeInOutQuint: EasingFn;
|
|
1660
|
-
readonly easeInSine: EasingFn;
|
|
1661
|
-
readonly easeOutSine: EasingFn;
|
|
1662
|
-
readonly easeInOutSine: EasingFn;
|
|
1663
|
-
readonly easeInExpo: EasingFn;
|
|
1664
|
-
readonly easeOutExpo: EasingFn;
|
|
1665
|
-
readonly easeInOutExpo: EasingFn;
|
|
1666
|
-
readonly easeInCirc: EasingFn;
|
|
1667
|
-
readonly easeOutCirc: EasingFn;
|
|
1668
|
-
readonly easeInOutCirc: EasingFn;
|
|
1669
|
-
readonly easeInBack: EasingFn;
|
|
1670
|
-
readonly easeOutBack: EasingFn;
|
|
1671
|
-
readonly easeInOutBack: EasingFn;
|
|
1672
|
-
readonly easeInElastic: EasingFn;
|
|
1673
|
-
readonly easeOutElastic: EasingFn;
|
|
1674
|
-
readonly easeInOutElastic: EasingFn;
|
|
1675
|
-
readonly easeInBounce: EasingFn;
|
|
1676
|
-
readonly easeOutBounce: EasingFn;
|
|
1677
|
-
readonly easeInOutBounce: EasingFn;
|
|
1678
|
-
};
|
|
1679
|
-
/** The name of one of the built-in easing curves. */
|
|
1680
|
-
type EasingName = keyof typeof EASINGS;
|
|
1681
|
-
|
|
1682
|
-
/** Cubic-bezier control points, CSS `cubic-bezier()` order. The curve's two
|
|
1683
|
-
* endpoints are implicit at (0,0) and (1,1). */
|
|
1684
|
-
interface BezierEasing {
|
|
1685
|
-
/** `readonly` so an `as const` preset is assignable; nothing ever writes it. */
|
|
1686
|
-
bezier: readonly [number, number, number, number];
|
|
1687
|
-
}
|
|
1688
|
-
/** An easing curve as a value: a function, the name of a built-in, or control
|
|
1689
|
-
* points. Anything an editor has to name, show or serialize must not be a bare
|
|
1690
|
-
* function, which is why the union exists. */
|
|
1691
|
-
type EasingSpec = EasingFn | EasingName | BezierEasing;
|
|
1692
|
-
|
|
1693
|
-
/** An easing curve: maps normalized progress `t ∈ [0, 1]` to eased progress.
|
|
1694
|
-
* Curves may leave the 0–1 range in the middle (back, elastic) but should
|
|
1695
|
-
* pass through 0 at 0 and 1 at 1. */
|
|
1696
|
-
type EasingFn = (t: number) => number;
|
|
1697
|
-
|
|
1698
|
-
/** Factory interpolator: built ONCE at tween start with (from, to), the returned
|
|
1699
|
-
* function is called with `t ∈ [0, 1]` each frame. Use for interpolators with
|
|
1700
|
-
* expensive setup (color-space conversion, path-string parsing) — d3-interpolate's
|
|
1701
|
-
* shape exactly. For cheap interpolations the per-tick `Interpolate<T>` form is
|
|
1702
|
-
* fine; this is the escape hatch when setup-per-tick is wasteful. */
|
|
1703
|
-
type InterpolatorFactory<T> = (from: T, to: T) => (t: number) => T;
|
|
1704
|
-
|
|
1705
|
-
/** How the camera should move. */
|
|
1706
|
-
interface ViewAnimationOptions {
|
|
1707
|
-
/** Duration in ms. Default 250. */
|
|
1708
|
-
ms?: number;
|
|
1709
|
-
/** Easing curve. Default `easeOutCubic`. */
|
|
1710
|
-
easing?: EasingSpec;
|
|
1711
|
-
/** Replace the kit's log-scale / fixed-anchor curve. */
|
|
1712
|
-
interpolator?: InterpolatorFactory<View>;
|
|
1713
|
-
/** Fires when the target is reached. Not called on cancel. */
|
|
1714
|
-
onDone?: () => void;
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
|
-
/**
|
|
1718
|
-
* @experimental
|
|
1719
|
-
* PointerContext — a tiny ambient context that publishes the world-space
|
|
1720
|
-
* position of the canvas pointer, refreshed on every `pointermove` over
|
|
1721
|
-
* the canvas. Cleared (set to `null`) on `pointerleave`.
|
|
1722
|
-
*
|
|
1723
|
-
* Why ref-based and not state-based: cursor moves fire dozens of times per
|
|
1724
|
-
* second; routing those through React state would re-render every consumer
|
|
1725
|
-
* in the tree. The context exposes a stable `pointerRef` whose `.current`
|
|
1726
|
-
* is mutated directly by the publisher, plus a thunk `getDropPoint()` that
|
|
1727
|
-
* reads it on demand. Consumers (e.g. `useClipboard`) pull via the thunk
|
|
1728
|
-
* inside their callbacks — no subscription, no re-render.
|
|
1729
|
-
*
|
|
1730
|
-
* `<SceneCanvas>` publishes automatically. `useClipboardOps` consumes when
|
|
1731
|
-
* the caller didn't pass an explicit `getDropPoint` option. Other future
|
|
1732
|
-
* hit-on-cursor consumers (drop-zone hover, context-menu anchor) can reuse
|
|
1733
|
-
* the same context.
|
|
1734
|
-
*/
|
|
1735
|
-
|
|
1736
|
-
/** @experimental World-space pointer position, or `null` when the pointer
|
|
1737
|
-
* isn't over the publishing canvas. */
|
|
1738
|
-
type PointerWorldPos = {
|
|
1739
|
-
worldX: number;
|
|
1740
|
-
worldY: number;
|
|
1741
|
-
} | null;
|
|
1742
|
-
/** @experimental */
|
|
1743
|
-
interface PointerContextValue {
|
|
1744
|
-
/** Live ref — mutate to publish, read for the latest snapshot. The
|
|
1745
|
-
* identity is stable for the lifetime of the provider. */
|
|
1746
|
-
readonly pointerRef: MutableRefObject<PointerWorldPos>;
|
|
1747
|
-
/** Convenience thunk equivalent to `() => pointerRef.current`. Stable
|
|
1748
|
-
* identity for the lifetime of the provider; safe to pass to hooks. */
|
|
1749
|
-
readonly getDropPoint: () => PointerWorldPos;
|
|
1750
|
-
}
|
|
1751
|
-
|
|
1752
|
-
/** Which tool is active, plus the stack of tools temporarily held active by a
|
|
1753
|
-
* hotkey (space-for-hand and the like). The dispatcher reads this to decide
|
|
1754
|
-
* whose bindings are in scope. */
|
|
1755
|
-
interface ActiveToolContextValue {
|
|
1756
|
-
active: string;
|
|
1757
|
-
hotkeyStack: string[];
|
|
1758
|
-
setActive(id: string): void;
|
|
1759
|
-
pushHotkey(id: string): void;
|
|
1760
|
-
popHotkey(): void;
|
|
1761
|
-
}
|
|
1762
|
-
|
|
1763
|
-
/**
|
|
1764
|
-
* `enterTextEditAction` — immediate Action descriptor for entering in-place
|
|
1765
|
-
* text editing on a selected text node.
|
|
1766
|
-
*
|
|
1767
|
-
* ## Status: REAL
|
|
1768
|
-
*
|
|
1769
|
-
* Fires via `useTextTool.bindings` when the user clicks on a
|
|
1770
|
-
* selected text node. Calls `deps.textEdit.startEdit(id)` to activate the
|
|
1771
|
-
* contenteditable overlay managed by `useTextEdit` / `useSceneTextEdit`.
|
|
1772
|
-
*
|
|
1773
|
-
* ## No defaultBinding / defaultBinding
|
|
1774
|
-
*
|
|
1775
|
-
* This action has no ambient key or gesture binding — it fires ONLY via
|
|
1776
|
-
* `useTextTool`'s `Tool.bindings` entry:
|
|
1777
|
-
*
|
|
1778
|
-
* ```ts
|
|
1779
|
-
* bindings: [
|
|
1780
|
-
* { spec: { kind: 'click', target: 'selected-body' }, actionId: 'enterTextEdit' },
|
|
1781
|
-
* ]
|
|
1782
|
-
* ```
|
|
1783
|
-
*
|
|
1784
|
-
* Keeping it binding-free avoids ambient double-fire and scopes the action to
|
|
1785
|
-
* the text tool context where `classifyTarget` is already wired.
|
|
1786
|
-
*
|
|
1787
|
-
* ## Self-guard: only act on text nodes
|
|
1788
|
-
*
|
|
1789
|
-
* The `'selected-body'` target yields a match for any selected node kind. To
|
|
1790
|
-
* avoid entering text-edit mode when the text tool happens to have a non-text
|
|
1791
|
-
* node selected, the action self-guards via an optional `isTextNode` predicate
|
|
1792
|
-
* on `TextEditDep`:
|
|
1793
|
-
*
|
|
1794
|
-
* - When `isTextNode` is absent: action fires unconditionally (the binding
|
|
1795
|
-
* spec is the real gate — consumers should only bind this action from the
|
|
1796
|
-
* text tool).
|
|
1797
|
-
* - When `isTextNode(id)` returns `false`: action is a no-op for that node.
|
|
1798
|
-
*
|
|
1799
|
-
* ### Pre-filtering at dispatch time
|
|
1800
|
-
*
|
|
1801
|
-
* `classifyTarget` now surfaces node kind, so a binding can pre-filter instead
|
|
1802
|
-
* of relying on the self-guard:
|
|
1803
|
-
*
|
|
1804
|
-
* ```ts
|
|
1805
|
-
* { spec: { kind: 'click', target: 'kind:text:selected' }, actionId: 'enterTextEdit' }
|
|
1806
|
-
* ```
|
|
1807
|
-
*
|
|
1808
|
-
* That reads the *routing trait's* kind, so it matches whatever names the
|
|
1809
|
-
* consumer registered in `<SceneCanvas routing>` — `'text'` under the kit's
|
|
1810
|
-
* inferred default. `isTextNode` stays on `TextEditDep` because it also covers
|
|
1811
|
-
* consumers who bind the broader `'selected-body'` target, and because it is
|
|
1812
|
-
* the only guard for a consumer who opted out of routing entirely.
|
|
1813
|
-
*
|
|
1814
|
-
* ## Migration plan for useTextTool
|
|
1815
|
-
*
|
|
1816
|
-
* When wiring `useTextTool` to `Tool.bindings`:
|
|
1817
|
-
*
|
|
1818
|
-
* 1. Add to `useTextTool`'s `bindings`:
|
|
1819
|
-
* ```ts
|
|
1820
|
-
* { spec: { kind: 'click', target: 'selected-body' }, actionId: 'enterTextEdit' }
|
|
1821
|
-
* ```
|
|
1822
|
-
* 2. Register a `textEdit` dep sourced from the `useTextEdit` / `useSceneTextEdit`
|
|
1823
|
-
* return value, plus an `isTextNode` predicate that checks `data.kind === 'text'`
|
|
1824
|
-
* (or however the consumer identifies text nodes).
|
|
1825
|
-
* 3. The existing `hitExisting` gate in `useTextTool`'s click route becomes
|
|
1826
|
-
* redundant — remove it in the same pass.
|
|
1827
|
-
*/
|
|
1828
|
-
|
|
1829
|
-
/**
|
|
1830
|
-
* Dep for `enterTextEditAction`.
|
|
1831
|
-
*
|
|
1832
|
-
* Wrap the return value of `useTextEdit` / `useSceneTextEdit` to source this
|
|
1833
|
-
* dep. The `isTextNode` predicate is optional — when absent the action fires
|
|
1834
|
-
* unconditionally (the binding spec acts as the gate).
|
|
1835
|
-
*
|
|
1836
|
-
* @example
|
|
1837
|
-
* ```ts
|
|
1838
|
-
* const textEdit = useSceneTextEdit({ scene, container });
|
|
1839
|
-
* useDepSource('textEdit', () => ({
|
|
1840
|
-
* startEdit: textEdit.startEdit,
|
|
1841
|
-
* isTextNode: (id) => scene.get(id as NodeId)?.data?.kind === 'text',
|
|
1842
|
-
* }));
|
|
1843
|
-
* ```
|
|
1844
|
-
*/
|
|
1845
|
-
interface TextEditDep {
|
|
1846
|
-
/**
|
|
1847
|
-
* Begin editing the node with `id`. Activates the contenteditable overlay
|
|
1848
|
-
* managed by `useTextEdit` / `useSceneTextEdit`.
|
|
1849
|
-
*/
|
|
1850
|
-
startEdit(id: string, opts?: {
|
|
1851
|
-
caret?: number | 'all';
|
|
1852
|
-
}): void;
|
|
1853
|
-
/**
|
|
1854
|
-
* Optional predicate: returns `true` when the node with `id` is a text node.
|
|
1855
|
-
* When absent the action fires on any selected node (binding spec is the gate).
|
|
1856
|
-
* When present and returning `false`, the invocation is a no-op.
|
|
1857
|
-
*/
|
|
1858
|
-
isTextNode?(id: string): boolean;
|
|
1859
|
-
}
|
|
1860
|
-
|
|
1861
|
-
/**
|
|
1862
|
-
* Consumer-supplied commit for the Slice action. `commit` receives the finite
|
|
1863
|
-
* slice segment (world coords); the consumer scans the scene, splits crossed
|
|
1864
|
-
* paths via `splitPathByLine`, and applies the result as one undoable batch.
|
|
1865
|
-
*/
|
|
1866
|
-
interface SliceDep {
|
|
1867
|
-
commit(a: Point2, b: Point2): void;
|
|
1868
|
-
}
|
|
1869
|
-
|
|
1870
|
-
/**
|
|
1871
|
-
* Clipboard dep — the imperative surface `useClipboardOps` returns.
|
|
1872
|
-
*
|
|
1873
|
-
* Consumers publish their live clipboard through `useDepSource('clipboard',
|
|
1874
|
-
* …)` from inside the `<DepRegistryProvider>` (i.e. under `<SceneCanvas>`).
|
|
1875
|
-
* The kit deliberately does not build one for them: `useClipboardOps` needs
|
|
1876
|
-
* an adapter and a selection reader that only the consumer can supply.
|
|
1877
|
-
*/
|
|
1878
|
-
interface ClipboardDep {
|
|
1879
|
-
copy(): void;
|
|
1880
|
-
paste(): void;
|
|
1881
|
-
isEmpty(): boolean;
|
|
1882
|
-
}
|
|
1883
|
-
|
|
1884
|
-
/** Optional consumer seam: given a node and the affine `m` that a pose-transform
|
|
1885
|
-
* action applied to the node's POSE, return updated `data` with the node's
|
|
1886
|
-
* data-held geometry transformed by `m`, or `null` if this node has no
|
|
1887
|
-
* data-held geometry (the kit leaves `data` alone). */
|
|
1888
|
-
interface GeometryProjection {
|
|
1889
|
-
transform(node: {
|
|
1890
|
-
id?: string;
|
|
1891
|
-
data: unknown;
|
|
1892
|
-
pose: unknown;
|
|
1893
|
-
}, m: Mat3): unknown | null;
|
|
1894
|
-
}
|
|
1895
|
-
|
|
1896
|
-
/** Minimal view API the action layer consumes. */
|
|
1897
|
-
interface ViewApi {
|
|
1898
|
-
get(): View;
|
|
1899
|
-
set(v: View): void;
|
|
1900
|
-
/** Optional recenter callback. When wired, `viewportZoomAction`'s `reset`
|
|
1901
|
-
* branch (Cmd-0) calls this instead of resetting to identity — letting
|
|
1902
|
-
* consumers re-fit the page (or other reference bounds) into the workspace.
|
|
1903
|
-
* Return the target `View` to let the action animate there; return nothing
|
|
1904
|
-
* to keep dispatching the view yourself. */
|
|
1905
|
-
recenter?(): View | void;
|
|
1906
|
-
/** Optional canvas-local host dimensions (CSS px). When wired,
|
|
1907
|
-
* `viewportZoomAction`'s keyboard branches (Cmd+= / Cmd+-) anchor at the
|
|
1908
|
-
* host center instead of the top-left origin. Null when the host isn't
|
|
1909
|
-
* measurable (unmounted). */
|
|
1910
|
-
hostSize?(): {
|
|
1911
|
-
width: number;
|
|
1912
|
-
height: number;
|
|
1913
|
-
} | null;
|
|
1914
|
-
/** Optional camera animation. `<SceneCanvas>` wires these three; a consumer
|
|
1915
|
-
* publishing their own `view` dep need not, and actions fall back to `set`. */
|
|
1916
|
-
animate?(to: View, opts?: ViewAnimationOptions): void;
|
|
1917
|
-
stopAnimation?(): void;
|
|
1918
|
-
/** Where an in-flight camera animation is heading, or null. Compute the next
|
|
1919
|
-
* discrete step from this so repeated presses compound. */
|
|
1920
|
-
animationTarget?(): View | null;
|
|
1921
|
-
}
|
|
1922
|
-
/**
|
|
1923
|
-
* Adapter dep for `areaSelectAction`.
|
|
1924
|
-
*
|
|
1925
|
-
* Provided by `<SceneCanvas>` / `<StandardActionsRegistrar>` via AABB
|
|
1926
|
-
* overlap over scene nodes. Consumers with custom hit-testing override this
|
|
1927
|
-
* dep entry in their own registrar.
|
|
1928
|
-
*/
|
|
1929
|
-
/**
|
|
1930
|
-
* Topmost-node-at-world-point dep, consumed by `moveAction` for
|
|
1931
|
-
* reparent-on-drop and available to any action that needs a single-best
|
|
1932
|
-
* pick. Mirrors the same hit-test plumbing `<SceneCanvas>` feeds to the
|
|
1933
|
-
* tool dispatcher; consumers with custom hit-testing override here.
|
|
1934
|
-
*
|
|
1935
|
-
* `exclude` is iterated once per call and treated as a set membership
|
|
1936
|
-
* test — the dep walks hits front-to-back and returns the first id not
|
|
1937
|
-
* in the exclude set. Pass moving-node roots + their descendants when
|
|
1938
|
-
* the caller wants to ignore the nodes it's manipulating.
|
|
1939
|
-
*/
|
|
1940
|
-
type NodeAtPointDep = (point: {
|
|
1941
|
-
x: number;
|
|
1942
|
-
y: number;
|
|
1943
|
-
}, exclude?: Iterable<NodeId>) => NodeId | null;
|
|
1944
|
-
/** What an area-selecting action needs: a way to ask what a region covers,
|
|
1945
|
-
* and a way to read and replace the selection. */
|
|
1946
|
-
interface AreaSelectDep {
|
|
1947
|
-
/** Return ids of all scene nodes whose AABB overlaps `bounds`. */
|
|
1948
|
-
hitTestArea(bounds: {
|
|
1949
|
-
x: number;
|
|
1950
|
-
y: number;
|
|
1951
|
-
width: number;
|
|
1952
|
-
height: number;
|
|
1953
|
-
}): NodeId[];
|
|
1954
|
-
/** Return the current selection id list. */
|
|
1955
|
-
getSelection(): NodeId[];
|
|
1956
|
-
/** Replace the current selection. */
|
|
1957
|
-
setSelection(ids: NodeId[]): void;
|
|
1958
|
-
}
|
|
1959
|
-
/**
|
|
1960
|
-
* Adapter dep for `editAnchorsAction`.
|
|
1961
|
-
*
|
|
1962
|
-
* Provides narrow read/write access to the editable polygon for a single
|
|
1963
|
-
* node. Consumers register this dep so anchor-edit actions can read/write
|
|
1964
|
-
* the polygon WITHOUT knowing whether it lives directly on the node's
|
|
1965
|
-
* pose (`pose.kind === 'polygon'`) or on `node.data.path` (the kit's
|
|
1966
|
-
* built-in pen-tool default, also WeaselDraw's shape).
|
|
1967
|
-
*
|
|
1968
|
-
* Note on live previews: in-flight edit state is surfaced through the
|
|
1969
|
-
* dispatcher's standard `OngoingHandle.previewIds/previewPose/previewData`
|
|
1970
|
-
* triple (not this dep), so chrome and preview-ghost stay in lock-step
|
|
1971
|
-
* via one source of truth.
|
|
1972
|
-
*/
|
|
1973
|
-
interface EditAnchorsDep {
|
|
1974
|
-
/** Id of the node currently being edited. Empty string means no node is
|
|
1975
|
-
* currently in edit mode — the chrome and gesture both opt out. */
|
|
1976
|
-
editingId: string;
|
|
1977
|
-
/** Enter/exit edit mode for a specific node. Pass `null` (or an empty
|
|
1978
|
-
* string) to exit. `enterPathEditAction` and `exitPathEditAction` call
|
|
1979
|
-
* this; consumers can call it directly to drive edit mode programmatically. */
|
|
1980
|
-
setEditingId(id: string | null): void;
|
|
1981
|
-
/** Returns the COMMITTED editable polygon in world coordinates, or
|
|
1982
|
-
* null if this node has no editable polygon. Does NOT consult in-
|
|
1983
|
-
* flight previews — callers that need live state read the dispatcher's
|
|
1984
|
-
* in-flight handles. */
|
|
1985
|
-
getEditablePath(id: string): unknown;
|
|
1986
|
-
/** Returns where the polygon is stored — `'pose'` when `node.pose`
|
|
1987
|
-
* IS the polygon, `'data'` when it lives on `node.data.path` with a
|
|
1988
|
-
* rect pose, or `null` when the node has no editable polygon. The
|
|
1989
|
-
* action uses this to know which preview-ghost axis to populate
|
|
1990
|
-
* (`previewPose` only / `previewData` + `previewPose` for data.path). */
|
|
1991
|
-
getStorageKind(id: string): 'pose' | 'data' | null;
|
|
1992
|
-
/** Returns the node's raw `pose` and `data` so storage-aware actions
|
|
1993
|
-
* can capture origin state at gesture-start and synthesize a matching
|
|
1994
|
-
* `previewPose` / `previewData` during `onMove`. Used by
|
|
1995
|
-
* `editAnchorsAction` for the data.path branch (rect pose + data
|
|
1996
|
-
* carrying extra fields like fill / stroke that must be preserved
|
|
1997
|
-
* through the preview). Returns null when the node is gone. */
|
|
1998
|
-
getNodeShape(id: string): {
|
|
1999
|
-
pose: unknown;
|
|
2000
|
-
data: unknown;
|
|
2001
|
-
} | null;
|
|
2002
|
-
/** Commit `worldPath` as the new value for `id`. Implementation routes
|
|
2003
|
-
* to setPose (when pose IS the polygon) or batched setPose+update
|
|
2004
|
-
* (when the polygon lives on data.path). Records one history entry
|
|
2005
|
-
* labelled `label`. */
|
|
2006
|
-
applyEdit(id: string, worldPath: unknown, label: string): void;
|
|
2007
|
-
/**
|
|
2008
|
-
* Anchors currently selected within the edited path, as **flat anchor
|
|
2009
|
-
* indices** — the same numbering `enumerateAnchors` produces and the
|
|
2010
|
-
* `anchor:N` affordance kinds carry.
|
|
2011
|
-
*
|
|
2012
|
-
* Selection is transient UI state, deliberately not part of the scene:
|
|
2013
|
-
* it is cleared whenever `editingId` changes, and any edit that
|
|
2014
|
-
* renumbers anchors (insert, delete) is responsible for leaving it
|
|
2015
|
-
* coherent. Empty means "no anchor selected" — the keyboard actions
|
|
2016
|
-
* (nudge, delete) no-op rather than acting on all anchors, matching
|
|
2017
|
-
* Illustrator.
|
|
2018
|
-
*/
|
|
2019
|
-
selectedAnchors: ReadonlySet<number>;
|
|
2020
|
-
/** Replace the anchor selection. Pass an empty iterable to clear. */
|
|
2021
|
-
setSelectedAnchors(next: Iterable<number>): void;
|
|
2022
|
-
/**
|
|
2023
|
-
* In-flight anchor-marquee rect in world coords, or null when no
|
|
2024
|
-
* marquee drag is active. Written by `marqueeAnchorsAction` and read by
|
|
2025
|
-
* the path-editing overlay — the same "ongoing action owns the preview,
|
|
2026
|
-
* chrome just draws it" split the move/resize ghosts use.
|
|
2027
|
-
*/
|
|
2028
|
-
marquee: {
|
|
2029
|
-
x: number;
|
|
2030
|
-
y: number;
|
|
2031
|
-
width: number;
|
|
2032
|
-
height: number;
|
|
2033
|
-
} | null;
|
|
2034
|
-
/** Set or clear the in-flight marquee rect. */
|
|
2035
|
-
setMarquee(rect: {
|
|
2036
|
-
x: number;
|
|
2037
|
-
y: number;
|
|
2038
|
-
width: number;
|
|
2039
|
-
height: number;
|
|
2040
|
-
} | null): void;
|
|
2041
|
-
}
|
|
2042
|
-
/**
|
|
2043
|
-
* Adapter dep for `lassoSelectAction`.
|
|
2044
|
-
*
|
|
2045
|
-
* Provides polygon-lasso hit-testing + selection read/write.
|
|
2046
|
-
* Consumers that don't implement `hitTestLasso` can omit it; the action
|
|
2047
|
-
* falls back to a bounding-box AABB test via `hitTestArea`.
|
|
2048
|
-
*/
|
|
2049
|
-
interface LassoSelectDep {
|
|
2050
|
-
/**
|
|
2051
|
-
* Hit-test against a closed polygon (vertex order CW or CCW; last→first
|
|
2052
|
-
* closing edge is implicit). Returns matching node ids.
|
|
2053
|
-
* Optional — when absent, `lassoSelectAction` falls back to AABB via
|
|
2054
|
-
* `hitTestArea`.
|
|
2055
|
-
*/
|
|
2056
|
-
hitTestLasso?(polygon: ReadonlyArray<{
|
|
2057
|
-
x: number;
|
|
2058
|
-
y: number;
|
|
2059
|
-
}>, mode: 'centers' | 'intersect' | 'enclosed'): string[];
|
|
2060
|
-
/** Return ids of nodes whose AABB overlaps the given rect (fallback). */
|
|
2061
|
-
hitTestArea(bounds: {
|
|
2062
|
-
x: number;
|
|
2063
|
-
y: number;
|
|
2064
|
-
width: number;
|
|
2065
|
-
height: number;
|
|
2066
|
-
}): string[];
|
|
2067
|
-
/** Return the current selection id list. */
|
|
2068
|
-
getSelection(): string[];
|
|
2069
|
-
/** Replace the current selection. */
|
|
2070
|
-
setSelection(ids: string[]): void;
|
|
2071
|
-
}
|
|
2072
|
-
/**
|
|
2073
|
-
* Options for the kit `image/svg+xml` content handler, threaded from
|
|
2074
|
-
* SceneCanvas's `ingestion={{ svg }}` prop.
|
|
2075
|
-
*/
|
|
2076
|
-
interface SvgIngestOptions {
|
|
2077
|
-
/** Parse dropped/pasted/picked SVG files into native scene nodes (path /
|
|
2078
|
-
* text leaves under containers mirroring the source `<g>` structure)
|
|
2079
|
-
* instead of the default single embedded-image node.
|
|
2080
|
-
*
|
|
2081
|
-
* Pass `unpackSvgFiles` from `@weasel-js/svg`:
|
|
2082
|
-
*
|
|
2083
|
-
* ```ts
|
|
2084
|
-
* import { unpackSvgFiles } from '@weasel-js/svg';
|
|
2085
|
-
* <SceneCanvas ingestion={{ svg: { unpack: unpackSvgFiles } }} />
|
|
2086
|
-
* ```
|
|
2087
|
-
*
|
|
2088
|
-
* It is injected rather than flagged on with `true` because the SVG parser
|
|
2089
|
-
* lives in `@weasel-js/svg`, which depends on this package — core importing
|
|
2090
|
-
* it back would make the two mutually dependent and unpublishable
|
|
2091
|
-
* separately. Passing the function keeps the parser out of core's bundle
|
|
2092
|
-
* for consumers who never unpack. */
|
|
2093
|
-
unpack?: SvgUnpacker;
|
|
2094
|
-
}
|
|
2095
|
-
/** Parses SVG files and inserts the resulting nodes into `ctx.scene`, as one
|
|
2096
|
-
* `applyOps` batch per file. Implemented by `unpackSvgFiles` in
|
|
2097
|
-
* `@weasel-js/svg`; see {@link SvgIngestOptions.unpack}. */
|
|
2098
|
-
type SvgUnpacker = (files: File[], ctx: IngestCtx) => Promise<void>;
|
|
2099
|
-
/**
|
|
2100
|
-
* Clipboard-paste seam consumed by the kit weasel-JSON content handler
|
|
2101
|
-
* (`IngestCtx.clipboard`). Built by `<SceneCanvas>` from its own synthesized
|
|
2102
|
-
* adapter + the `ingestion.clipboard` prop; absent when the consumer set
|
|
2103
|
-
* `ingestion.clipboard.enabled === false` or the adapter lacks `commitPaste`.
|
|
2104
|
-
* Absence makes the handler decline inert (dwarn, nothing ingested) — its
|
|
2105
|
-
* matched items were already consumed at match time and do not fall through
|
|
2106
|
-
* to other handlers.
|
|
2107
|
-
*/
|
|
2108
|
-
interface ClipboardIngestCtx {
|
|
2109
|
-
/** The hosting canvas's adapter — `commitPaste` materializes the pasted
|
|
2110
|
-
* nodes (fresh ids, offset applied); insertion still goes through ops. */
|
|
2111
|
-
adapter: InsertAdapter<{
|
|
2112
|
-
id: string;
|
|
2113
|
-
}>;
|
|
2114
|
-
/** JSON reviver for the weasel wire payload (typed arrays etc.) — from
|
|
2115
|
-
* `SceneCanvasProps.ingestion.clipboard.reviver`. */
|
|
2116
|
-
reviver?: (key: string, value: unknown) => unknown;
|
|
2117
|
-
}
|
|
2118
|
-
/**
|
|
2119
|
-
* Dep for the `ingest` action (external-content ingestion).
|
|
2120
|
-
* Sourced from `<SceneCanvas>` / `<StandardActionsRegistrar>` via
|
|
2121
|
-
* `useIngestionDepSource` — canvas rect + current view.
|
|
2122
|
-
*/
|
|
2123
|
-
interface IngestionDep {
|
|
2124
|
-
/** Visible canvas area in world coordinates. */
|
|
2125
|
-
viewportWorldRect(): {
|
|
2126
|
-
x: number;
|
|
2127
|
-
y: number;
|
|
2128
|
-
width: number;
|
|
2129
|
-
height: number;
|
|
2130
|
-
};
|
|
2131
|
-
/** Consumer file→src resolver (from SceneCanvas's `ingestion` prop).
|
|
2132
|
-
* Live accessor — read it at use time. Destructuring (or copying the
|
|
2133
|
-
* property early) snapshots the current value and won't track later
|
|
2134
|
-
* prop changes across an `await`. */
|
|
2135
|
-
resolveSrc?: (file: File) => Promise<string>;
|
|
2136
|
-
/** Kit SVG-handler options (from SceneCanvas's `ingestion` prop).
|
|
2137
|
-
* Live accessor, same caveat as `resolveSrc`. */
|
|
2138
|
-
svg?: SvgIngestOptions;
|
|
2139
|
-
/** Clipboard-paste seam for the kit weasel-JSON handler.
|
|
2140
|
-
* Live accessor, same caveat as `resolveSrc`. */
|
|
2141
|
-
clipboard?: ClipboardIngestCtx;
|
|
2142
|
-
}
|
|
2143
|
-
/**
|
|
2144
|
-
* Per-kind extra geometry passed to `InsertDep.commit`.
|
|
2145
|
-
*
|
|
2146
|
-
* Built-in tools populate a typed variant so the kit's default factory can
|
|
2147
|
-
* render the true tool params (line endpoints, polygon side count, star
|
|
2148
|
-
* geometry, pencil sample list). Consumer-defined tools may pass any
|
|
2149
|
-
* `{ kind: string; ... }` payload; the kit's factory falls back to AABB
|
|
2150
|
-
* inscription for unknown kinds.
|
|
2151
|
-
*
|
|
2152
|
-
* `bounds` is still passed alongside as a useful AABB pose hint — factories
|
|
2153
|
-
* may use it as the node's pose even when richer geometry is available.
|
|
2154
|
-
*/
|
|
2155
|
-
type InsertExtras = {
|
|
2156
|
-
kind: 'rect';
|
|
2157
|
-
} | {
|
|
2158
|
-
kind: 'ellipse';
|
|
2159
|
-
} | {
|
|
2160
|
-
kind: 'line';
|
|
2161
|
-
a: {
|
|
2162
|
-
x: number;
|
|
2163
|
-
y: number;
|
|
2164
|
-
};
|
|
2165
|
-
b: {
|
|
2166
|
-
x: number;
|
|
2167
|
-
y: number;
|
|
2168
|
-
};
|
|
2169
|
-
} | {
|
|
2170
|
-
kind: 'polygon';
|
|
2171
|
-
sides: number;
|
|
2172
|
-
rotation: number;
|
|
2173
|
-
center?: {
|
|
2174
|
-
x: number;
|
|
2175
|
-
y: number;
|
|
2176
|
-
};
|
|
2177
|
-
radius?: number;
|
|
2178
|
-
} | {
|
|
2179
|
-
kind: 'star';
|
|
2180
|
-
points: number;
|
|
2181
|
-
innerRadiusRatio: number;
|
|
2182
|
-
rotation: number;
|
|
2183
|
-
center?: {
|
|
2184
|
-
x: number;
|
|
2185
|
-
y: number;
|
|
2186
|
-
};
|
|
2187
|
-
outerRadius?: number;
|
|
2188
|
-
} | {
|
|
2189
|
-
kind: 'pencil';
|
|
2190
|
-
samples: ReadonlyArray<DragSample>;
|
|
2191
|
-
} | {
|
|
2192
|
-
kind: 'text';
|
|
2193
|
-
text?: string;
|
|
2194
|
-
} | {
|
|
2195
|
-
kind: 'image';
|
|
2196
|
-
src?: string;
|
|
2197
|
-
opacity?: number;
|
|
2198
|
-
/** Chrome-only: what the in-flight drag paints. Read by the overlay
|
|
2199
|
-
* layer, ignored by the insert dep. */
|
|
2200
|
-
preview?: 'bitmap' | 'outline';
|
|
2201
|
-
} | {
|
|
2202
|
-
kind: string;
|
|
2203
|
-
[extra: string]: unknown;
|
|
2204
|
-
};
|
|
2205
|
-
/**
|
|
2206
|
-
* World-space point snapping — grid, guides, or any consumer rule.
|
|
2207
|
-
*
|
|
2208
|
-
* Sourced by `<SceneCanvas>` from its `toolOptions.snapPoint`. Actions apply
|
|
2209
|
-
* it to the coords they ingest so the live preview and the committed
|
|
2210
|
-
* geometry agree; `insertAction` snaps the drag's start and current point.
|
|
2211
|
-
*
|
|
2212
|
-
* Optional: when the dep is absent, actions treat it as identity.
|
|
2213
|
-
*/
|
|
2214
|
-
interface SnapDep {
|
|
2215
|
-
/** Snap a world-space point. Return `p` unchanged to opt out. */
|
|
2216
|
-
point(p: {
|
|
2217
|
-
x: number;
|
|
2218
|
-
y: number;
|
|
2219
|
-
}): {
|
|
2220
|
-
x: number;
|
|
2221
|
-
y: number;
|
|
2222
|
-
};
|
|
2223
|
-
}
|
|
2224
|
-
/**
|
|
2225
|
-
* Adapter dep for `insertAction`.
|
|
2226
|
-
*
|
|
2227
|
-
* Provided by `<SceneCanvas>` / `<StandardActionsRegistrar>`. The `extras`
|
|
2228
|
-
* carry the active tool's kind + per-kind geometry. Callers
|
|
2229
|
-
* that need typed data must supply a richer `insert` dep.
|
|
2230
|
-
*/
|
|
2231
|
-
interface InsertDep {
|
|
2232
|
-
/**
|
|
2233
|
-
* Materialise a new node from the given drag-rect bounds and typed
|
|
2234
|
-
* per-kind extras. Returns the new node's id, or `null` if the consumer
|
|
2235
|
-
* rejected the insert (e.g. sub-threshold bounds, unknown kind).
|
|
2236
|
-
*/
|
|
2237
|
-
commit(bounds: {
|
|
2238
|
-
x: number;
|
|
2239
|
-
y: number;
|
|
2240
|
-
width: number;
|
|
2241
|
-
height: number;
|
|
2242
|
-
}, extras: InsertExtras): NodeId | null;
|
|
2243
|
-
}
|
|
2244
|
-
/**
|
|
2245
|
-
* Adapter dep for `resizeAction`.
|
|
2246
|
-
*
|
|
2247
|
-
* Carries the four behavior-shaping options the legacy `useResize` hook
|
|
2248
|
-
* exposed through `UseResizeOptions`: bounds-frame behaviors (e.g.
|
|
2249
|
-
* `lockAspectWithModifier`), world-space anchor-point snap behaviors (e.g.
|
|
2250
|
-
* `pointSnapToGrid`), group-expansion (`expandIds`), and pose↔bounds
|
|
2251
|
-
* projection (`geometry`).
|
|
2252
|
-
*
|
|
2253
|
-
* Optional in `DepSchema`: when absent, `resizeAction` falls back to
|
|
2254
|
-
* identity defaults (no behaviors, identity expandIds, `RECT_POSE_DESCRIPTOR`
|
|
2255
|
-
* geometry). Consumers wire the dep via `useDepSource('resizePolicy', ...)`
|
|
2256
|
-
* from any descendant of `<DepRegistryProvider>` / `<SceneCanvas>`.
|
|
2257
|
-
*
|
|
2258
|
-
* The generic is erased to `unknown` at the schema entry; consumers cast at
|
|
2259
|
-
* the call site (mirrors the `scene` entry's convention).
|
|
2260
|
-
*/
|
|
2261
|
-
interface ResizePolicy<TPose> {
|
|
2262
|
-
/** Bounds-frame constraints. Constrained to `TPose extends ResizePose` since
|
|
2263
|
-
* constraints read/write `{x,y,width,height}`. For non-rect TPose pass `[]`. */
|
|
2264
|
-
constraints: TPose extends ResizePose ? BoundsConstraint<TPose>[] : never[];
|
|
2265
|
-
/** World-space anchor-point snap behaviors. Same TPose constraint as
|
|
2266
|
-
* `constraints`. */
|
|
2267
|
-
pointSnap: TPose extends ResizePose ? PointSnapBehavior<TPose>[] : never[];
|
|
2268
|
-
/** Group-expansion at gesture start. Identity (`ids => ids`) when group
|
|
2269
|
-
* resize isn't wanted. */
|
|
2270
|
-
expandIds: (ids: string[]) => string[];
|
|
2271
|
-
/** Projection from `TPose` to bounds and back. Use `RECT_POSE_DESCRIPTOR`
|
|
2272
|
-
* for plain rect poses. */
|
|
2273
|
-
projection: PoseProjection<TPose>;
|
|
2274
|
-
}
|
|
2275
|
-
/**
|
|
2276
|
-
* Layout-strategy lookup by container id, consumed by `moveAction` to run
|
|
2277
|
-
* the drag-time reflow pass. Sourced by `<SceneCanvas>` from its `layouts`
|
|
2278
|
-
* prop. Optional: `getLayout` returns null for any container when no layout
|
|
2279
|
-
* is configured, so the reflow pass is a no-op then.
|
|
2280
|
-
*/
|
|
2281
|
-
interface LayoutDep {
|
|
2282
|
-
getLayout(containerId: string): LayoutStrategy<unknown> | null;
|
|
2283
|
-
}
|
|
2284
|
-
/**
|
|
2285
|
-
* The names an action may declare in `requires`, and what each resolves to.
|
|
2286
|
-
*
|
|
2287
|
-
* This is the whole vocabulary of things an action can reach — selection,
|
|
2288
|
-
* scene, view, history, and the rest. Consumers add their own entries by
|
|
2289
|
-
* augmenting the interface (`declare module '@weasel-js/core'`), which is what
|
|
2290
|
-
* makes a custom dep name type-check in `requires` and in the deps bag.
|
|
2291
|
-
*/
|
|
2292
|
-
interface DepSchema {
|
|
2293
|
-
/** Kit selection state — ids of currently selected nodes. */
|
|
2294
|
-
selection: SelectionApi;
|
|
2295
|
-
/** Current viewport — camera position + scale. */
|
|
2296
|
-
view: ViewApi;
|
|
2297
|
-
/**
|
|
2298
|
-
* Scene tree — structural reads + undoable mutations.
|
|
2299
|
-
*
|
|
2300
|
-
* The entry uses the fully-erased form `Scene<unknown, string, unknown>`
|
|
2301
|
-
* because `DepSchema` must be concrete. Actions that need a typed scene
|
|
2302
|
-
* should cast: `deps.scene as Scene<MyData, MyLayer, MyPose>`.
|
|
2303
|
-
*/
|
|
2304
|
-
scene: Scene<unknown, string, unknown>;
|
|
2305
|
-
/** Undo/redo history bound to the current scene. */
|
|
2306
|
-
history: History;
|
|
2307
|
-
/**
|
|
2308
|
-
* Canvas pointer position in world space.
|
|
2309
|
-
*
|
|
2310
|
-
* Exposes `pointerRef` (mutable live ref) and `getDropPoint()` thunk.
|
|
2311
|
-
* Marked `@experimental` in the source.
|
|
2312
|
-
*/
|
|
2313
|
-
pointer: PointerContextValue;
|
|
2314
|
-
/** Currently active tool id + hotkey-hold stack. */
|
|
2315
|
-
activeTool: ActiveToolContextValue;
|
|
2316
|
-
/**
|
|
2317
|
-
* Area-select dep — AABB hit-test + selection read/write.
|
|
2318
|
-
*
|
|
2319
|
-
* Sourced from `<SceneCanvas>` via AABB overlap over all scene
|
|
2320
|
-
* nodes. Override per-consumer for custom hit-testing (e.g. contain-mode,
|
|
2321
|
-
* lock-aware filtering).
|
|
2322
|
-
*/
|
|
2323
|
-
areaSelect: AreaSelectDep;
|
|
2324
|
-
/**
|
|
2325
|
-
* Topmost node at a world-space point. Sourced by `<SceneCanvas>` from
|
|
2326
|
-
* the same picker that feeds the tool dispatcher's `getNodeAtPoint`.
|
|
2327
|
-
* Optional: actions that read this (e.g. `moveAction` reparent-on-drop)
|
|
2328
|
-
* fall back to a no-op when the dep isn't registered.
|
|
2329
|
-
*/
|
|
2330
|
-
nodeAtPoint?: NodeAtPointDep;
|
|
2331
|
-
/**
|
|
2332
|
-
* Insert dep — node factory for drag-to-insert.
|
|
2333
|
-
*
|
|
2334
|
-
* Sourced from `<SceneCanvas>`. The `kind` param comes from
|
|
2335
|
-
* the active binding's `opts.params.kind`. Override per-consumer to
|
|
2336
|
-
* provide a typed node factory (e.g. with custom data payloads).
|
|
2337
|
-
*/
|
|
2338
|
-
insert: InsertDep;
|
|
2339
|
-
/**
|
|
2340
|
-
* Snap dep — world-space point snapping (grid / guides).
|
|
2341
|
-
*
|
|
2342
|
-
* Sourced by `<SceneCanvas>` from `toolOptions.snapPoint`. Optional:
|
|
2343
|
-
* absent means no snapping (identity).
|
|
2344
|
-
*/
|
|
2345
|
-
snap?: SnapDep;
|
|
2346
|
-
/**
|
|
2347
|
-
* Lasso-select dep — polygon hit-test + selection read/write.
|
|
2348
|
-
*
|
|
2349
|
-
* Sourced from `<SceneCanvas>` / `<StandardActionsRegistrar>`.
|
|
2350
|
-
* Falls back to AABB hit-test when `hitTestLasso` is absent.
|
|
2351
|
-
*/
|
|
2352
|
-
lassoSelect: LassoSelectDep;
|
|
2353
|
-
/**
|
|
2354
|
-
* Edit-anchors dep — narrow read/write of one polygon's path pose.
|
|
2355
|
-
*
|
|
2356
|
-
* Sourced from consumer. Wraps `getPose`/`setPose`/`applyOps`
|
|
2357
|
-
* for the currently-being-edited polygon node.
|
|
2358
|
-
*
|
|
2359
|
-
* The `editAnchorsAction` requires this dep to be registered when anchor
|
|
2360
|
-
* editing is active. If absent, `start` returns an empty handle (no-op).
|
|
2361
|
-
*/
|
|
2362
|
-
editAnchors: EditAnchorsDep;
|
|
2363
|
-
/**
|
|
2364
|
-
* Text-edit dep — activates the in-place text editing overlay.
|
|
2365
|
-
*
|
|
2366
|
-
* Sourced from consumer via `useTextEdit` / `useSceneTextEdit`.
|
|
2367
|
-
* The `enterTextEditAction` requires this dep to be registered by the text
|
|
2368
|
-
* tool when text editing is available.
|
|
2369
|
-
*
|
|
2370
|
-
* The optional `isTextNode` predicate guards against entering edit mode on
|
|
2371
|
-
* non-text nodes. A binding can pre-filter instead with a
|
|
2372
|
-
* `target: 'kind:text:selected'` spec; the guard remains for consumers who
|
|
2373
|
-
* bind the broader `'selected-body'` target or opted out of routing.
|
|
2374
|
-
*/
|
|
2375
|
-
textEdit: TextEditDep;
|
|
2376
|
-
/**
|
|
2377
|
-
* Resize-policy dep — bounds constraints, point-snap behaviors,
|
|
2378
|
-
* group expansion, and pose↔bounds projection for `resizeAction`.
|
|
2379
|
-
*
|
|
2380
|
-
* Optional: when omitted, `resizeAction` falls back to identity defaults
|
|
2381
|
-
* (no constraints, no snap, identity expandIds, `RECT_POSE_DESCRIPTOR`).
|
|
2382
|
-
* Consumers wire via `useDepSource('resizePolicy', ...)` or the
|
|
2383
|
-
* `useResizePolicy` helper.
|
|
2384
|
-
*/
|
|
2385
|
-
resizePolicy?: ResizePolicy<unknown>;
|
|
2386
|
-
/**
|
|
2387
|
-
* Booleans adapter — read selection ids, fetch world-space `Path`s,
|
|
2388
|
-
* compare z-order, and mint result nodes for Pathfinder ops.
|
|
2389
|
-
*
|
|
2390
|
-
* Consumers wire via `useBooleansAdapter(adapter)` (a thin wrapper
|
|
2391
|
-
* around `useDepSource('booleansAdapter', ...)`). The descriptor's
|
|
2392
|
-
* `enabled` predicate reads `deps.selection` for the count check; the
|
|
2393
|
-
* invoker reads `deps.booleansAdapter` to execute the op.
|
|
2394
|
-
*/
|
|
2395
|
-
booleansAdapter?: BooleansAdapter;
|
|
2396
|
-
/**
|
|
2397
|
-
* Gesture dispatcher control surface — exposes `cancelAll(reason)` so
|
|
2398
|
-
* actions that need to abort an in-flight handle (Escape cancels a
|
|
2399
|
-
* drag, etc.) can do so. Sourced by `<SceneCanvas>` from the
|
|
2400
|
-
* dispatcher instance it already owns.
|
|
2401
|
-
*/
|
|
2402
|
-
dispatcher?: {
|
|
2403
|
-
cancelAll(reason: 'commit' | 'cancel'): void;
|
|
2404
|
-
};
|
|
2405
|
-
/**
|
|
2406
|
-
* Layout-strategy lookup. Sourced by `<SceneCanvas>` from `layouts`.
|
|
2407
|
-
* Optional: absent (or all-null) → `moveAction` skips reflow.
|
|
2408
|
-
*/
|
|
2409
|
-
layout?: LayoutDep;
|
|
2410
|
-
/**
|
|
2411
|
-
* Slice dep — consumer-supplied commit for the Slice action.
|
|
2412
|
-
*
|
|
2413
|
-
* Receives the finite slice segment in world coordinates; the consumer
|
|
2414
|
-
* scans the scene, splits crossed paths via `splitPathByLine`, and
|
|
2415
|
-
* applies the result as one undoable batch.
|
|
2416
|
-
*
|
|
2417
|
-
* Optional: when absent, `sliceAction` is a no-op.
|
|
2418
|
-
*/
|
|
2419
|
-
slice?: SliceDep;
|
|
2420
|
-
/**
|
|
2421
|
-
* Clipboard dep — the imperative surface `useClipboardOps` returns.
|
|
2422
|
-
*
|
|
2423
|
-
* Published by the consumer (`useDepSource('clipboard', …)` from under
|
|
2424
|
-
* `<SceneCanvas>`), because `useClipboardOps` needs an adapter and a
|
|
2425
|
-
* selection reader only the consumer has. Feeds `clipboard.copy` /
|
|
2426
|
-
* `clipboard.cut`; both no-op when the dep is absent.
|
|
2427
|
-
*/
|
|
2428
|
-
clipboard?: ClipboardDep;
|
|
2429
|
-
/**
|
|
2430
|
-
* Optional consumer commit hook. When present, `moveAction` (and other
|
|
2431
|
-
* default actions) submit their committed ops through it instead of
|
|
2432
|
-
* `scene.applyBatch`, so apps with their own history integration
|
|
2433
|
-
* (checkpoint + push entry) capture the gesture as one undo entry.
|
|
2434
|
-
* When absent, commits fall back to `scene.applyBatch`.
|
|
2435
|
-
*/
|
|
2436
|
-
applyOps?: (ops: Op[], label: string) => void;
|
|
2437
|
-
/** Optional pose-composition strategy for hierarchical (local-pose) scenes.
|
|
2438
|
-
* When absent, defaults to IDENTITY (absolute-pose: nodes store world
|
|
2439
|
-
* coords). Local-pose consumers supply { compose: composeRectPose,
|
|
2440
|
-
* decompose: decomposeRectPose } (or their pose shape's equivalent). */
|
|
2441
|
-
poseComposition?: PoseComposition<unknown>;
|
|
2442
|
-
/**
|
|
2443
|
-
* Ingestion dep — canvas viewport rect + consumer file→src resolver.
|
|
2444
|
-
*
|
|
2445
|
-
* Sourced from `<SceneCanvas>` / `<StandardActionsRegistrar>` via
|
|
2446
|
-
* `useIngestionDepSource`. Feeds `ingestAction` with the world-space
|
|
2447
|
-
* viewport rect for paste-placement and image fit-clamping, and forwards
|
|
2448
|
-
* the consumer's optional `resolveSrc` seam.
|
|
2449
|
-
*
|
|
2450
|
-
* Optional: when absent, the `ingest` action no-ops (there is no
|
|
2451
|
-
* placement geometry to work with).
|
|
2452
|
-
*/
|
|
2453
|
-
ingestion?: IngestionDep;
|
|
2454
|
-
/**
|
|
2455
|
-
* Optional consumer seam for the eager-sync layer: lets pose-transform
|
|
2456
|
-
* actions (resize/move/nudge/flip — NOT rotate) ALSO rewrite a node's
|
|
2457
|
-
* data-held geometry. Given a node and the affine `m` applied to its pose,
|
|
2458
|
-
* `transform(node, m)` returns updated `data` (geometry mapped by `m`) or
|
|
2459
|
-
* `null` for nodes with no data-held geometry.
|
|
2460
|
-
*
|
|
2461
|
-
* Strictly opt-in: when absent (or when `transform` returns null), the kit
|
|
2462
|
-
* emits only the pose op and leaves `data` untouched. apps/draw wires this
|
|
2463
|
-
* to mirror `data.path` through `transformPath`. Rotate intentionally never
|
|
2464
|
-
* consults this seam (rotation lives on the pose, baked at render).
|
|
2465
|
-
*/
|
|
2466
|
-
geometryProjection?: GeometryProjection;
|
|
2467
|
-
}
|
|
2468
|
-
/**
|
|
2469
|
-
* Every dep name the registry knows about — derived from {@link DepSchema} so
|
|
2470
|
-
* the two can't drift.
|
|
2471
|
-
*
|
|
2472
|
-
* Declared here rather than beside the registry so that this `keyof` reference
|
|
2473
|
-
* resolves to the exported `DepSchema` declaration; from another module it
|
|
2474
|
-
* resolves to that module's import alias, which the API docs can't link.
|
|
2475
|
-
*/
|
|
2476
|
-
type DepName = keyof DepSchema;
|
|
2477
|
-
|
|
2478
|
-
/** Which kind of handle a recorded handle marker represents. */
|
|
2479
|
-
type HandleKind = 'corner' | 'rotation' | 'anchor';
|
|
2480
|
-
/** The geometry a hit region actually tests against, as reported to the debug
|
|
2481
|
-
* sink so the overlay can draw the real shape rather than its bounding box. */
|
|
2482
|
-
type HitShape = {
|
|
2483
|
-
kind: 'rect';
|
|
2484
|
-
x: number;
|
|
2485
|
-
y: number;
|
|
2486
|
-
width: number;
|
|
2487
|
-
height: number;
|
|
2488
|
-
rotation?: number;
|
|
2489
|
-
} | {
|
|
2490
|
-
kind: 'circle';
|
|
2491
|
-
cx: number;
|
|
2492
|
-
cy: number;
|
|
2493
|
-
r: number;
|
|
2494
|
-
} | {
|
|
2495
|
-
kind: 'path';
|
|
2496
|
-
d: Path2D;
|
|
44
|
+
declare const ICON_PATHS: {
|
|
45
|
+
readonly clone: "<path d=\"M7 7.4V4.6A1.6 1.6 0 0 1 8.6 3h6.8A1.6 1.6 0 0 1 17 4.6v6.8a1.6 1.6 0 0 1-1.6 1.6H12.6\" stroke-linecap=\"butt\"/><rect x=\"3\" y=\"7\" width=\"10\" height=\"10\" rx=\"1.6\"/><path d=\"M5.7 10.8h4.6M5.7 13.4h3\" stroke-width=\"1\"/>";
|
|
46
|
+
readonly reset: "<path d=\"M7.28 4.88A5.8 5.8 0 1 0 12.72 4.88\"/><path d=\"M13.81 7.57 12.72 4.88 15.56 4.28\"/>";
|
|
47
|
+
readonly close: "<path d=\"M5 5 15 15M15 5 5 15\" stroke-width=\"1.75\"/>";
|
|
48
|
+
readonly export: "<path d=\"M4 12.6v2.9A1.5 1.5 0 0 0 5.5 17h9a1.5 1.5 0 0 0 1.5-1.5v-2.9\"/><path d=\"M10 3.2v8.5\"/><path d=\"M11.7 9.35 10 11.7 8.3 9.35\"/><path d=\"M6.8 14.7h6.4\" stroke-width=\"1\"/>";
|
|
49
|
+
readonly zoomIn: "<circle cx=\"9\" cy=\"9\" r=\"5.4\"/><path d=\"M12.82 12.82 16.9 16.9\"/><path d=\"M9 6.7v4.6M6.7 9h4.6\"/>";
|
|
50
|
+
readonly pan: "<path d=\"M10 8.1V3.2M10 11.9V16.8M8.1 10H3.2M11.9 10H16.8\"/><path d=\"M8.26 5.27 10 3.2 11.74 5.27M11.74 14.73 10 16.8 8.26 14.73M5.27 11.74 3.2 10 5.27 8.26M14.73 8.26 16.8 10 14.73 11.74\"/><circle cx=\"10\" cy=\"10\" r=\"1.9\"/>";
|
|
51
|
+
readonly add: "<path d=\"M10 4.9v10.2M4.9 10h10.2\"/>";
|
|
52
|
+
readonly remove: "<path d=\"M4.9 10h10.2\"/>";
|
|
53
|
+
readonly delete: "<path d=\"M3.6 5.8h12.8M8 5.8V4.5A1.1 1.1 0 0 1 9.1 3.4h1.8A1.1 1.1 0 0 1 12 4.5v1.3\"/><path d=\"M5.2 5.8v9.6A1.6 1.6 0 0 0 6.8 17h6.4a1.6 1.6 0 0 0 1.6-1.6V5.8\"/><path d=\"M8.4 8.8v5.2M11.6 8.8v5.2\" stroke-width=\"1\"/>";
|
|
54
|
+
readonly sort: "<path d=\"M3.6 5.6h7.2M3.6 10h8.8M3.6 14.4h5\"/><path d=\"M15.3 5.4v9.4\"/><path d=\"M16.97 12.81 15.3 14.8 13.63 12.81\"/>";
|
|
55
|
+
readonly undo: "<path d=\"M4.4 8.4h6.8a3.6 3.6 0 0 1 0 7.2H8.6\"/><path d=\"M6.47 10.14 4.4 8.4 6.47 6.66\"/>";
|
|
56
|
+
readonly redo: "<path d=\"M15.6 8.4H8.8a3.6 3.6 0 0 0 0 7.2H11.4\"/><path d=\"M13.53 6.66 15.6 8.4 13.53 10.14\"/>";
|
|
57
|
+
readonly zoomOut: "<circle cx=\"9\" cy=\"9\" r=\"5.4\"/><path d=\"M12.82 12.82 16.9 16.9\"/><path d=\"M6.7 9h4.6\"/>";
|
|
58
|
+
readonly fit: "<path d=\"M3.2 7.2V4.4A1.2 1.2 0 0 1 4.4 3.2h2.8M12.8 3.2h2.8A1.2 1.2 0 0 1 16.8 4.4v2.8M16.8 12.8v2.8a1.2 1.2 0 0 1-1.2 1.2h-2.8M7.2 16.8H4.4a1.2 1.2 0 0 1-1.2-1.2v-2.8\"/><rect x=\"7.4\" y=\"8.2\" width=\"5.2\" height=\"3.6\" rx=\"0.8\" stroke-width=\"1\"/>";
|
|
59
|
+
readonly snapshot: "<path d=\"M3.8 8.3A1.4 1.4 0 0 1 5.2 6.9h1.9L8 5.1h4l.9 1.8h1.9A1.4 1.4 0 0 1 16.2 8.3v6A1.4 1.4 0 0 1 14.8 15.7H5.2A1.4 1.4 0 0 1 3.8 14.3z\"/><circle cx=\"10\" cy=\"11\" r=\"2.75\"/>";
|
|
60
|
+
readonly play: "<path d=\"M7.6 5.2 15.6 10 7.6 14.8Z\"/>";
|
|
61
|
+
readonly pause: "<path d=\"M7.8 5.2v9.6M12.2 5.2v9.6\"/>";
|
|
62
|
+
readonly stop: "<rect x=\"5.6\" y=\"5.6\" width=\"8.8\" height=\"8.8\" rx=\"1.4\"/>";
|
|
63
|
+
readonly step: "<path d=\"M6.4 5.4 13 10 6.4 14.6Z\"/><path d=\"M15.2 5.4v9.2\"/>";
|
|
64
|
+
readonly crosshair: "<circle cx=\"10\" cy=\"10\" r=\"5.6\"/><path d=\"M10 2.8v4.4M10 12.8v4.4M2.8 10h4.4M12.8 10h4.4\"/>";
|
|
65
|
+
readonly fullscreen: "<path d=\"M11.6 3.6h4.8v4.8M8.4 16.4H3.6v-4.8\"/><path d=\"M16.4 3.6 11.2 8.8M3.6 16.4 8.8 11.2\"/>";
|
|
66
|
+
readonly compare: "<rect x=\"3.4\" y=\"4.6\" width=\"13.2\" height=\"10.8\" rx=\"1.6\"/><path d=\"M10 4.6v10.8\" stroke-width=\"1\"/><path d=\"M5.2 7.6h3.4M5.2 10h2.6M5.2 12.4h3.4\" stroke-width=\"1\"/><path d=\"M11.4 7.6h3.4M11.4 10h3.4M11.4 12.4h1.8\" stroke-width=\"1\"/>";
|
|
67
|
+
readonly filter: "<path d=\"M3.4 4.6h13.2L11.8 11v5.4L8.2 14.8V11z\"/>";
|
|
68
|
+
readonly search: "<circle cx=\"9\" cy=\"9\" r=\"5.4\"/><path d=\"M12.82 12.82 16.9 16.9\"/>";
|
|
69
|
+
readonly loupe: "<circle cx=\"9\" cy=\"9\" r=\"5.6\"/><path d=\"M12.96 12.96 17 17\"/><rect x=\"6\" y=\"6\" width=\"6\" height=\"6\" stroke-width=\"1\"/><path d=\"M9 6v6M6 9h6\" stroke-width=\"1\"/>";
|
|
70
|
+
readonly layers: "<path d=\"M10 2.8 17.2 6.6 10 10.4 2.8 6.6z\"/><path d=\"M2.8 10 10 13.8 17.2 10\" stroke-width=\"1\"/><path d=\"M2.8 13.4 10 17.2 17.2 13.4\" stroke-width=\"1\"/>";
|
|
71
|
+
readonly lock: "<rect x=\"4.6\" y=\"9\" width=\"10.8\" height=\"8\" rx=\"1.6\"/><path d=\"M7.2 9V6.6a2.8 2.8 0 0 1 5.6 0V9\"/>";
|
|
72
|
+
readonly unlock: "<rect x=\"4.6\" y=\"9\" width=\"10.8\" height=\"8\" rx=\"1.6\"/><path d=\"M7.2 9V6.6a2.8 2.8 0 0 1 5.6 0\"/>";
|
|
73
|
+
readonly visible: "<path d=\"M2.6 10C4.6 6.6 7.1 5 10 5s5.4 1.6 7.4 5c-2 3.4-4.5 5-7.4 5s-5.4-1.6-7.4-5z\"/><circle cx=\"10\" cy=\"10\" r=\"2.2\"/>";
|
|
74
|
+
readonly hidden: "<path d=\"M2.6 10C4.6 6.6 7.1 5 10 5s5.4 1.6 7.4 5c-2 3.4-4.5 5-7.4 5s-5.4-1.6-7.4-5z\"/><circle cx=\"10\" cy=\"10\" r=\"2.2\"/><path d=\"M4.2 15.8 15.8 4.2\"/>";
|
|
75
|
+
readonly pin: "<path d=\"M8.2 3.4v5.2l-2 2.6h7.6l-2-2.6V3.4z\"/><path d=\"M7 3.4h6\"/><path d=\"M10 11.2v5.4\"/>";
|
|
76
|
+
readonly link: "<path d=\"M8.6 11.4a3.4 3.4 0 0 1 0-4.8l2.2-2.2a3.4 3.4 0 0 1 4.8 4.8l-1.1 1.1\"/><path d=\"M11.4 8.6a3.4 3.4 0 0 1 0 4.8l-2.2 2.2a3.4 3.4 0 0 1-4.8-4.8l1.1-1.1\"/>";
|
|
77
|
+
readonly collapse: "<path d=\"M14.16 4.05 10 7.8 5.84 4.05M5.84 15.95 10 12.2 14.16 15.95\"/>";
|
|
78
|
+
readonly expand: "<path d=\"M5.84 8.15 10 4.4 14.16 8.15M14.16 11.85 10 15.6 5.84 11.85\"/>";
|
|
79
|
+
readonly chevron: "<path d=\"M14.9 7.98 10 12.4 5.1 7.98\"/>";
|
|
80
|
+
readonly tune: "<path d=\"M3.4 6h13.2M3.4 10h13.2M3.4 14h13.2\" stroke-width=\"1.25\"/><circle cx=\"7\" cy=\"6\" r=\"1.8\"/><circle cx=\"12.6\" cy=\"10\" r=\"1.8\"/><circle cx=\"9\" cy=\"14\" r=\"1.8\"/>";
|
|
81
|
+
readonly grid: "<rect x=\"3.4\" y=\"3.4\" width=\"13.2\" height=\"13.2\" rx=\"1.4\"/><path d=\"M7.8 3.4v13.2M12.2 3.4v13.2M3.4 7.8h13.2M3.4 12.2h13.2\" stroke-width=\"1\"/>";
|
|
82
|
+
readonly snap: "<path d=\"M5.4 15.6V9.4a4.6 4.6 0 0 1 9.2 0v6.2h-3.2V9.4a1.4 1.4 0 0 0-2.8 0v6.2z\"/><path d=\"M5.4 12.8h3.2M11.4 12.8h3.2\" stroke-width=\"1\"/>";
|
|
83
|
+
readonly measure: "<rect x=\"2.6\" y=\"7.4\" width=\"14.8\" height=\"5.2\" rx=\"1.2\"/><path d=\"M6 7.4v2.2M9.4 7.4v3M12.8 7.4v2.2\" stroke-width=\"1\"/>";
|
|
84
|
+
readonly randomize: "<rect x=\"3.6\" y=\"3.6\" width=\"12.8\" height=\"12.8\" rx=\"2\"/><circle cx=\"7.2\" cy=\"7.2\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"10\" cy=\"10\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/><circle cx=\"12.8\" cy=\"12.8\" r=\"1\" fill=\"currentColor\" stroke=\"none\"/>";
|
|
85
|
+
readonly refresh: "<path d=\"M4.27 12.09A6.1 6.1 0 0 1 14.67 6.08\"/><path d=\"M14.67 3.38 14.67 6.08 12.01 5.61\"/><path d=\"M15.73 7.91A6.1 6.1 0 0 1 5.33 13.92\"/><path d=\"M5.33 16.62 5.33 13.92 7.99 14.39\"/>";
|
|
86
|
+
readonly info: "<circle cx=\"10\" cy=\"10\" r=\"7\"/><path d=\"M10 9.4v4.4\"/><path d=\"M10 6.5h0\"/>";
|
|
87
|
+
readonly warning: "<path d=\"M10 3.4 17.4 16.2H2.6z\"/><path d=\"M10 8.4v3.4\"/><path d=\"M10 14h0\"/>";
|
|
88
|
+
readonly error: "<circle cx=\"10\" cy=\"10\" r=\"7\"/><path d=\"M7.6 7.6 12.4 12.4M12.4 7.6 7.6 12.4\"/>";
|
|
89
|
+
readonly busy: "<path d=\"M10 3.2A6.8 6.8 0 1 1 3.2 10\"/>";
|
|
90
|
+
readonly modeLight: "<circle cx=\"10\" cy=\"10\" r=\"3.5\"/><path d=\"M15.3 10L17.7 10M13.75 6.25L15.44 4.56M10 4.7L10 2.3M6.25 6.25L4.56 4.56M4.7 10L2.3 10M6.25 13.75L4.56 15.44M10 15.3L10 17.7M13.75 13.75L15.44 15.44\"/>";
|
|
91
|
+
readonly modeDark: "<path d=\"M8.19 3.55A6.7 6.7 0 1 0 16.45 11.81A5.9 5.9 0 0 1 8.19 3.55Z\"/>";
|
|
92
|
+
readonly modeAuto: "<path d=\"M10 2.6Q10 10 17.4 10Q10 10 10 17.4Q10 10 2.6 10Q10 10 10 2.6Z\"/>";
|
|
93
|
+
readonly strokeWidth: "<path d=\"M3.4 5.6h13.2\" stroke-width=\"1\"/><path d=\"M3.4 10h13.2\" stroke-width=\"2\"/><path d=\"M3.4 15.2h13.2\" stroke-width=\"3.4\"/>";
|
|
94
|
+
readonly strokeCap: "<path d=\"M2.5 10H12\" stroke-linecap=\"butt\"/><path d=\"M12 6V14\" stroke-width=\"1\"/>";
|
|
95
|
+
readonly strokeJoin: "<path d=\"M4.4 15.5 10 5.5 15.6 15.5\" stroke-linecap=\"butt\" stroke-linejoin=\"miter\"/>";
|
|
96
|
+
readonly strokeAlign: "<circle cx=\"10\" cy=\"10\" r=\"6.2\"/>";
|
|
97
|
+
readonly capButt: "<path d=\"M5.5 5.25H14.5V14.75H5.5Z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linejoin=\"miter\"/>";
|
|
98
|
+
readonly capRound: "<path d=\"M3.13 5.25H12.13V14.75H3.13Z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linejoin=\"miter\"/><path d=\"M12.13 4.5A5.5 5.5 0 0 1 12.13 15.5Z\" fill=\"currentColor\" stroke=\"none\"/>";
|
|
99
|
+
readonly capSquare: "<path d=\"M3.13 5.25H12.13V14.75H3.13Z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linejoin=\"miter\"/><path d=\"M12.13 4.5H17.63V15.5H12.13Z\" fill=\"currentColor\" stroke=\"none\"/>";
|
|
100
|
+
readonly joinMiter: "<path d=\"M10 3.5 15.6 15.5H4.4Z\" fill=\"currentColor\" stroke=\"none\"/>";
|
|
101
|
+
readonly joinRound: "<path d=\"M4.4 15.5V12a5.6 5.6 0 0 1 11.2 0v3.5Z\" fill=\"currentColor\" stroke=\"none\"/>";
|
|
102
|
+
readonly joinBevel: "<path d=\"M6.8 8.2h6.4l2.4 7.3H4.4Z\" fill=\"currentColor\" stroke=\"none\"/>";
|
|
103
|
+
readonly alignInner: "<path d=\"M10 3.8A6.2 6.2 0 1 0 10 16.2A6.2 6.2 0 1 0 10 3.8Z\" fill=\"currentColor\" stroke=\"none\"/>";
|
|
104
|
+
readonly alignCenter: "<path d=\"M10 2.1A7.9 7.9 0 1 0 10 17.9A7.9 7.9 0 1 0 10 2.1ZM10 5.5A4.5 4.5 0 1 0 10 14.5A4.5 4.5 0 1 0 10 5.5Z\" fill=\"currentColor\" stroke=\"none\" fill-rule=\"evenodd\"/>";
|
|
105
|
+
readonly alignOuter: "<path d=\"M0 0H20V20H0ZM10 3.8A6.2 6.2 0 1 0 10 16.2A6.2 6.2 0 1 0 10 3.8Z\" fill=\"currentColor\" stroke=\"none\" fill-rule=\"evenodd\"/>";
|
|
106
|
+
readonly dashSolid: "<path d=\"M2 10h16\" stroke-width=\"3\" stroke-linecap=\"butt\"/>";
|
|
107
|
+
readonly dashDashed: "<path d=\"M2 10h16\" stroke-width=\"3\" stroke-linecap=\"butt\" stroke-dasharray=\"6 4\"/>";
|
|
108
|
+
readonly dashDotted: "<path d=\"M2 10h16\" stroke-width=\"3\" stroke-linecap=\"butt\" stroke-dasharray=\"2.5 2\"/>";
|
|
109
|
+
readonly dashCustom: "<path d=\"M2 10h16\" stroke-width=\"3\" stroke-linecap=\"butt\" stroke-dasharray=\"5 2 2 2\"/>";
|
|
110
|
+
readonly paintSolid: "<path d=\"M3.4 3.4H16.6V16.6H3.4Z\" fill=\"currentColor\" stroke=\"none\" fill-opacity=\"1\"/>";
|
|
111
|
+
readonly paintLinear: "<path d=\"M3.4 3.4H6.7V16.6H3.4Z\" fill=\"currentColor\" stroke=\"none\" fill-opacity=\"1\"/><path d=\"M6.7 3.4H10V16.6H6.7Z\" fill=\"currentColor\" stroke=\"none\" fill-opacity=\"0.68\"/><path d=\"M10 3.4H13.3V16.6H10Z\" fill=\"currentColor\" stroke=\"none\" fill-opacity=\"0.42\"/><path d=\"M13.3 3.4H16.6V16.6H13.3Z\" fill=\"currentColor\" stroke=\"none\" fill-opacity=\"0.2\"/>";
|
|
112
|
+
readonly paintRadial: "<path d=\"M3.4 3.4H16.6V16.6H3.4ZM4.95 4.95H15.05V15.05H4.95Z\" fill=\"currentColor\" fill-rule=\"evenodd\" stroke=\"none\" fill-opacity=\"0.7\"/><path d=\"M4.95 4.95H15.05V15.05H4.95ZM6.5 6.5H13.5V13.5H6.5Z\" fill=\"currentColor\" fill-rule=\"evenodd\" stroke=\"none\" fill-opacity=\"0.53\"/><path d=\"M6.5 6.5H13.5V13.5H6.5ZM8.05 8.05H11.95V11.95H8.05Z\" fill=\"currentColor\" fill-rule=\"evenodd\" stroke=\"none\" fill-opacity=\"0.37\"/><path d=\"M8.05 8.05H11.95V11.95H8.05Z\" fill=\"currentColor\" fill-rule=\"evenodd\" stroke=\"none\" fill-opacity=\"0.2\"/>";
|
|
113
|
+
readonly paintConic: "<path d=\"M10 10L16.6 3.4L16.6 16.6Z\" fill=\"currentColor\" stroke=\"none\" fill-opacity=\"1\"/><path d=\"M10 10L16.6 16.6L3.4 16.6Z\" fill=\"currentColor\" stroke=\"none\" fill-opacity=\"0.68\"/><path d=\"M10 10L3.4 16.6L3.4 3.4Z\" fill=\"currentColor\" stroke=\"none\" fill-opacity=\"0.42\"/><path d=\"M10 10L3.4 3.4L16.6 3.4Z\" fill=\"currentColor\" stroke=\"none\" fill-opacity=\"0.2\"/>";
|
|
114
|
+
readonly paintPattern: "<path d=\"M3.4 3.4L4.7 3.4L3.4 4.7Z\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M8.7 3.4L11.3 3.4L3.4 11.3L3.4 8.7Z\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M15.3 3.4L16.6 3.4L16.6 4.7L4.7 16.6L3.4 16.6L3.4 15.3Z\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M16.6 8.7L16.6 11.3L11.3 16.6L8.7 16.6Z\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M16.6 15.3L16.6 16.6L15.3 16.6Z\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M15.3 3.4L16.6 3.4L16.6 4.7Z\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M8.7 3.4L11.3 3.4L16.6 8.7L16.6 11.3Z\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M3.4 3.4L4.7 3.4L16.6 15.3L16.6 16.6L15.3 16.6L3.4 4.7Z\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M11.3 16.6L8.7 16.6L3.4 11.3L3.4 8.7Z\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M4.7 16.6L3.4 16.6L3.4 15.3Z\" fill=\"currentColor\" stroke=\"none\"/>";
|
|
115
|
+
readonly paintNone: "<path d=\"M3.4 3.4H16.6V16.6H3.4Z\"/><path d=\"M3.4 16.6L16.6 3.4\" stroke=\"var(--wzl-danger, #d94a3f)\" stroke-width=\"1.8\" stroke-linecap=\"butt\"/>";
|
|
116
|
+
readonly layoutRows: "<rect x=\"3.4\" y=\"3.4\" width=\"13.2\" height=\"3.4\" rx=\"1\" fill=\"currentColor\" stroke=\"none\"/><rect x=\"3.4\" y=\"8.3\" width=\"13.2\" height=\"3.4\" rx=\"1\" fill=\"currentColor\" stroke=\"none\"/><rect x=\"3.4\" y=\"13.2\" width=\"13.2\" height=\"3.4\" rx=\"1\" fill=\"currentColor\" stroke=\"none\"/>";
|
|
117
|
+
readonly layoutColumns: "<rect x=\"3.4\" y=\"3.4\" width=\"3.4\" height=\"13.2\" rx=\"1\" fill=\"currentColor\" stroke=\"none\"/><rect x=\"8.3\" y=\"3.4\" width=\"3.4\" height=\"13.2\" rx=\"1\" fill=\"currentColor\" stroke=\"none\"/><rect x=\"13.2\" y=\"3.4\" width=\"3.4\" height=\"13.2\" rx=\"1\" fill=\"currentColor\" stroke=\"none\"/>";
|
|
118
|
+
readonly layoutGrid: "<rect x=\"3.4\" y=\"3.4\" width=\"3.4\" height=\"3.4\" rx=\"1\" fill=\"currentColor\" stroke=\"none\"/><rect x=\"8.3\" y=\"3.4\" width=\"3.4\" height=\"3.4\" rx=\"1\" fill=\"currentColor\" stroke=\"none\"/><rect x=\"13.2\" y=\"3.4\" width=\"3.4\" height=\"3.4\" rx=\"1\" fill=\"currentColor\" stroke=\"none\"/><rect x=\"3.4\" y=\"8.3\" width=\"3.4\" height=\"3.4\" rx=\"1\" fill=\"currentColor\" stroke=\"none\"/><rect x=\"8.3\" y=\"8.3\" width=\"3.4\" height=\"3.4\" rx=\"1\" fill=\"currentColor\" stroke=\"none\"/><rect x=\"13.2\" y=\"8.3\" width=\"3.4\" height=\"3.4\" rx=\"1\" fill=\"currentColor\" stroke=\"none\"/><rect x=\"3.4\" y=\"13.2\" width=\"3.4\" height=\"3.4\" rx=\"1\" fill=\"currentColor\" stroke=\"none\"/><rect x=\"8.3\" y=\"13.2\" width=\"3.4\" height=\"3.4\" rx=\"1\" fill=\"currentColor\" stroke=\"none\"/><rect x=\"13.2\" y=\"13.2\" width=\"3.4\" height=\"3.4\" rx=\"1\" fill=\"currentColor\" stroke=\"none\"/>";
|
|
2497
119
|
};
|
|
2498
|
-
/**
|
|
2499
|
-
|
|
2500
|
-
*
|
|
2501
|
-
* Recording is push-based and cheap: hit-testers, handle painters and snap
|
|
2502
|
-
* strategies call these as they run, whether or not any overlay is watching.
|
|
2503
|
-
* Nothing here affects behavior — a sink that discards everything is a valid
|
|
2504
|
-
* sink.
|
|
2505
|
-
*/
|
|
2506
|
-
interface DebugSink {
|
|
2507
|
-
recordHitbox(id: string, kind: 'body' | 'handle' | 'rotation' | 'anchor', shape: HitShape): void;
|
|
2508
|
-
recordHandle(id: string, position: {
|
|
2509
|
-
x: number;
|
|
2510
|
-
y: number;
|
|
2511
|
-
}, kind: HandleKind): void;
|
|
2512
|
-
recordBounds(id: string, bounds: {
|
|
2513
|
-
x: number;
|
|
2514
|
-
y: number;
|
|
2515
|
-
width: number;
|
|
2516
|
-
height: number;
|
|
2517
|
-
}): void;
|
|
2518
|
-
recordOrigin(id: string, point: {
|
|
2519
|
-
x: number;
|
|
2520
|
-
y: number;
|
|
2521
|
-
}): void;
|
|
2522
|
-
recordSnapCandidate(point: {
|
|
2523
|
-
x: number;
|
|
2524
|
-
y: number;
|
|
2525
|
-
}, accepted: boolean): void;
|
|
2526
|
-
recordLayer(id: string, label: string, space: 'world' | 'screen', index: number): void;
|
|
2527
|
-
/** Clears every non-snap array. Called at the start of each Canvas render. */
|
|
2528
|
-
beginFrame(): void;
|
|
2529
|
-
/** Clears the snap array. Called at gesture end. */
|
|
2530
|
-
clearSnap(): void;
|
|
2531
|
-
}
|
|
2532
|
-
|
|
2533
|
-
/**
|
|
2534
|
-
* When an entry's bindings are live. A set, not one value: the hand tool is
|
|
2535
|
-
* palette-selectable AND engaged by holding space, and both hold at once.
|
|
2536
|
-
*/
|
|
2537
|
-
interface Eligibility {
|
|
2538
|
-
/** Selectable as the focused entry — exclusive, one at a time. */
|
|
2539
|
-
focus?: boolean;
|
|
2540
|
-
/** Also live while this key is held. */
|
|
2541
|
-
offhand?: HotkeyTrigger;
|
|
2542
|
-
/** Live regardless of what is focused. */
|
|
2543
|
-
always?: boolean;
|
|
2544
|
-
/** Live only for input this entry's own affordances produced. */
|
|
2545
|
-
claimed?: boolean;
|
|
2546
|
-
/** Modality filter, applied wherever it would otherwise be live. */
|
|
2547
|
-
capabilities?: CapabilityTag[];
|
|
2548
|
-
}
|
|
2549
|
-
/**
|
|
2550
|
-
* Where an entry's overlay sits in the layer stack, relative to the
|
|
2551
|
-
* selection chrome. `'top'` is the default and renders above everything;
|
|
2552
|
-
* the other two exist for chrome that belongs under the selection handles
|
|
2553
|
-
* (a snap-target highlight, say). With no selection overlay in the stack,
|
|
2554
|
-
* all three collapse to `'top'`.
|
|
2555
|
-
*/
|
|
2556
|
-
type OverlayPosition = 'top' | 'before-selection' | 'after-selection';
|
|
2557
|
-
/**
|
|
2558
|
-
* A registry entry: what it contributes, and when it is eligible. Every role
|
|
2559
|
-
* is optional and independent — an entry that only routes input declares only
|
|
2560
|
-
* `bindings` and `actions`.
|
|
2561
|
-
*/
|
|
2562
|
-
interface Contribution {
|
|
2563
|
-
id: string;
|
|
2564
|
-
eligibility: Eligibility;
|
|
2565
|
-
bindings?: GestureBinding[];
|
|
2566
|
-
actions?: Action[];
|
|
2567
|
-
/** One layer, or several composed in the given order. */
|
|
2568
|
-
overlay?: RenderLayer<unknown> | RenderLayer<unknown>[];
|
|
2569
|
-
/** Defaults to `'top'`. Applies to every layer in `overlay`. */
|
|
2570
|
-
overlayPosition?: OverlayPosition;
|
|
2571
|
-
presentation?: ToolPresentation;
|
|
2572
|
-
/** Reflection escape hatch — the authored form, when there was one. */
|
|
2573
|
-
def?: unknown;
|
|
2574
|
-
}
|
|
2575
|
-
|
|
2576
|
-
/**
|
|
2577
|
-
* Configurable activation-key descriptor for tools that expose their
|
|
2578
|
-
* keybinding to the host (currently Lasso and Eyedropper). Captures
|
|
2579
|
-
* only the fields meaningful to a caller-supplied tool-select key —
|
|
2580
|
-
* dispatcher-internal fields (`skipInEditable`, `enabled`,
|
|
2581
|
-
* `preventDefault`) live on `KeyBinding` in keyHelpers.ts and are
|
|
2582
|
-
* not part of the configurable surface.
|
|
2583
|
-
*/
|
|
2584
|
-
interface ToolKeybinding {
|
|
2585
|
-
/** Key or list of keys to match (case-insensitive against `event.key`). */
|
|
2586
|
-
key: string | readonly string[];
|
|
2587
|
-
/** Require Cmd (mac) / Ctrl (others). Default `false`. */
|
|
2588
|
-
mod?: boolean;
|
|
2589
|
-
/** Require Alt. Default `false`. */
|
|
2590
|
-
alt?: boolean;
|
|
2591
|
-
/**
|
|
2592
|
-
* Shift policy. `undefined`/`false` forbids shift, `true` requires
|
|
2593
|
-
* shift, `'optional'` allows either.
|
|
2594
|
-
*/
|
|
2595
|
-
shift?: boolean | 'optional';
|
|
2596
|
-
}
|
|
120
|
+
/** Every glyph name in the set. */
|
|
121
|
+
type IconName = keyof typeof ICON_PATHS;
|
|
2597
122
|
|
|
2598
|
-
/**
|
|
2599
|
-
*
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
space: boolean;
|
|
2607
|
-
}
|
|
2608
|
-
/** Per-event context passed to every channel handler. `scratch` is typed
|
|
2609
|
-
* via the tool's `TScratch` parameter; it survives across a single
|
|
2610
|
-
* gesture (pointer-down through end/cancel) and is replaced on next
|
|
2611
|
-
* gesture start by `initScratch()`. */
|
|
2612
|
-
interface ToolCtx<TScratch = unknown> {
|
|
2613
|
-
worldX: number;
|
|
2614
|
-
worldY: number;
|
|
2615
|
-
modifiers: ToolModifiers;
|
|
2616
|
-
selection: SelectionApi;
|
|
2617
|
-
/** Adapter/scene access — opaque at this layer; tools that need it
|
|
2618
|
-
* cast to a known shape. This layer doesn't constrain it. */
|
|
2619
|
-
adapter: unknown;
|
|
2620
|
-
applyOps: (ops: Op[], label: string) => void;
|
|
2621
|
-
/** Current viewport. Reflects camera-position semantics — see
|
|
2622
|
-
* `View` JSDoc. */
|
|
2623
|
-
view: View;
|
|
2624
|
-
/** Mutate the viewport. In controlled mode this calls the consumer's
|
|
2625
|
-
* `onViewChange`; in uncontrolled mode it updates Canvas's internal
|
|
2626
|
-
* state. View changes are not undoable. */
|
|
2627
|
-
setView: (next: View) => void;
|
|
2628
|
-
/** Bounding rect of the canvas element in viewport coords. Used by
|
|
2629
|
-
* zoom/pan tools to convert event clientX/clientY to canvas-relative
|
|
2630
|
-
* anchors. */
|
|
2631
|
-
canvasRect: DOMRect;
|
|
2632
|
-
/** Screen-space pointer coords relative to `canvasRect`. Useful for
|
|
2633
|
-
* viewport tools that pan/zoom in screen space (e.g. hand-pan
|
|
2634
|
-
* computes deltas in pixels, not world units). Optional — populated
|
|
2635
|
-
* by the dispatcher on pointer events; absent on keyboard events. */
|
|
2636
|
-
screenPoint?: {
|
|
2637
|
-
x: number;
|
|
2638
|
-
y: number;
|
|
2639
|
-
};
|
|
2640
|
-
/** Optional debug sink. When `<Canvas debug={...}>` is enabled, Canvas
|
|
2641
|
-
* threads its sink here so tool-internal hit math (handle hitboxes,
|
|
2642
|
-
* rotation handle, etc.) lands in the same overlay as Canvas's own
|
|
2643
|
-
* bounds/origin records. Tools should call this conditionally with `?.`. */
|
|
2644
|
-
debug?: DebugSink;
|
|
2645
|
-
scratch: TScratch;
|
|
2646
|
-
}
|
|
2647
|
-
/** Hotkey-slot trigger key. The slot is engaged while this key is held —
|
|
2648
|
-
* hence "hotkey": active as long as the key is hot. `null` (or omitted)
|
|
2649
|
-
* means the tool is not eligible for the hotkey slot. */
|
|
2650
|
-
type HotkeyTrigger = 'space' | 'alt' | 'ctrl' | 'meta' | 'shift';
|
|
2651
|
-
/** World-space AABB shape used by `previewBounds`. Alias of the kit-wide
|
|
2652
|
-
* `Bounds` type — the optional `rotation` field carries through so a tool
|
|
2653
|
-
* can report an oriented preview rect (e.g. mid-rotate). */
|
|
2654
|
-
type ToolBounds = Bounds;
|
|
2655
|
-
/** Presentation metadata for tool palettes / menus. Optional on every
|
|
2656
|
-
* tool — consumers that render a palette (`<ToolPalette>`) read these
|
|
2657
|
-
* fields to display the tool; consumers that don't can ignore them.
|
|
2658
|
-
*
|
|
2659
|
-
* Note: cursor is NOT here. `Tool.cursor` (inherited from `Contribution`)
|
|
2660
|
-
* is already plumbed through `<Canvas>` to `style.cursor` on the host. */
|
|
2661
|
-
interface ToolPresentation<TScratch = unknown> {
|
|
2662
|
-
/** Human-readable label, distinct from the `id`. Falls back to `id`. */
|
|
123
|
+
/** Shared prop shape for the icon set. Color comes from the surrounding
|
|
124
|
+
* `color` CSS property — every glyph strokes in `currentColor`. */
|
|
125
|
+
interface IconProps {
|
|
126
|
+
className?: string;
|
|
127
|
+
/** Rendered pixel size, applied to both width and height. Defaults to 20. */
|
|
128
|
+
size?: number;
|
|
129
|
+
/** Accessible name. Omit inside a button that already labels itself; the
|
|
130
|
+
* glyph is then `aria-hidden`. */
|
|
2663
131
|
label?: string;
|
|
2664
|
-
/** Inline-SVG icon component output. May be a static `ReactNode` or a
|
|
2665
|
-
* function of scratch state (rare; useful for shape-aware affordances). */
|
|
2666
|
-
icon?: react.ReactNode | ((scratch?: TScratch) => react.ReactNode);
|
|
2667
|
-
/** Palette grouping key. Tools sharing a group render contiguously
|
|
2668
|
-
* with separators between groups. Free-form string; the kit
|
|
2669
|
-
* recommends 'select' | 'shape' | 'draw' | 'type' | 'view'. */
|
|
2670
|
-
group?: string;
|
|
2671
|
-
/** Display override for the keyboard shortcut. When omitted the palette
|
|
2672
|
-
* derives one from `Tool.keybinding` via its own formatter. */
|
|
2673
|
-
shortcut?: string;
|
|
2674
|
-
}
|
|
2675
|
-
/**
|
|
2676
|
-
* The focus-declaring case of a `Contribution`: a mode the user switches
|
|
2677
|
-
* into, plus the hooks that only make sense for one (`initScratch`,
|
|
2678
|
-
* activate/deactivate, live preview, `cursor`). Everything else — bindings,
|
|
2679
|
-
* actions, overlay, presentation — is inherited.
|
|
2680
|
-
*/
|
|
2681
|
-
interface Tool<TScratch = unknown> extends Contribution {
|
|
2682
|
-
/** Optional caller-supplied key. Most built-in tools have their activation
|
|
2683
|
-
* key declared in `BUILTIN_SELECT_KEYS` in `useKeybindings.ts`; this field
|
|
2684
|
-
* is for tools that want their activation key to be configurable by the
|
|
2685
|
-
* host (currently Lasso and Eyedropper). The dynamic loop in
|
|
2686
|
-
* `useKeybindings.ts` picks this up and appends a binding entry to the
|
|
2687
|
-
* consolidated `tool.activate` action (with `opts.params.toolId` set so
|
|
2688
|
-
* the invoker knows which tool to switch to). */
|
|
2689
|
-
keybinding?: ToolKeybinding;
|
|
2690
|
-
initScratch?: () => TScratch;
|
|
2691
|
-
cursor?: string | ((ctx: ToolCtx<TScratch>) => string);
|
|
2692
|
-
onActivate?: (ctx: ToolCtx<TScratch>) => void;
|
|
2693
|
-
onDeactivate?: (ctx: ToolCtx<TScratch>) => void;
|
|
2694
|
-
/** Returns the in-flight preview pose for `id` if this tool is mid-gesture
|
|
2695
|
-
* on it; otherwise `null`. Lets `Canvas.helpersRef.getEffectivePose`
|
|
2696
|
-
* reflect live gesture state without reaching into hook internals. The
|
|
2697
|
-
* return type is `unknown` here because the Tool interface is pose-agnostic;
|
|
2698
|
-
* callers that know the pose shape (e.g. Canvas typed by `TPose`) cast at
|
|
2699
|
-
* the use site. */
|
|
2700
|
-
previewPose?: (id: string) => unknown;
|
|
2701
|
-
/** Returns the in-flight preview bounds for `id` if this tool is mid-gesture
|
|
2702
|
-
* on it; otherwise `null`. Optional companion to `previewPose` for tools that
|
|
2703
|
-
* can compute bounds without round-tripping through a geometry adapter. */
|
|
2704
|
-
previewBounds?: (id: string) => ToolBounds | null;
|
|
2705
|
-
/** Returns ids whose committed scene-render should be suppressed while this
|
|
2706
|
-
* tool is mid-gesture (e.g. cascade move's dragged + descendant ids whose
|
|
2707
|
-
* preview ghosts replace the committed pose). The standard scene slot
|
|
2708
|
-
* consults this alongside `previewPose` to avoid double-rendering. Returns
|
|
2709
|
-
* `null` when no gesture is in flight. */
|
|
2710
|
-
previewIds?: () => Iterable<string> | null;
|
|
2711
|
-
}
|
|
2712
|
-
/** Internal alias for "a Tool of any scratch type" — used in registries and
|
|
2713
|
-
* dispatchers that hold tools of heterogeneous scratch shapes. `any` is
|
|
2714
|
-
* intentional: `Tool<TScratch>` is invariant in TScratch, so `Tool<unknown>`
|
|
2715
|
-
* is too strict for containers that accept any concrete `Tool<T>`. */
|
|
2716
|
-
type AnyTool = Tool<any>;
|
|
2717
|
-
|
|
2718
|
-
/**
|
|
2719
|
-
* @experimental
|
|
2720
|
-
* A single entry in `Action.defaultBinding[]`. Either a bare `GestureSpec`
|
|
2721
|
-
* (no per-binding opts) or an object form that pairs a spec with
|
|
2722
|
-
* `BindingOpts` for parametric actions (e.g. `{ params: { axis: 'x' } }`).
|
|
2723
|
-
* Use the object form when two bindings for the same action differ only in
|
|
2724
|
-
* a runtime parameter — the dispatcher extracts `opts.params` and passes
|
|
2725
|
-
* them to `ImmediateInvoker.run` as its second argument.
|
|
2726
|
-
*/
|
|
2727
|
-
type BoundGesture = GestureSpec | {
|
|
2728
|
-
spec: GestureSpec;
|
|
2729
|
-
opts: BindingOpts;
|
|
2730
|
-
};
|
|
2731
|
-
/**
|
|
2732
|
-
* @experimental
|
|
2733
|
-
* Single registered action. v1: one binding per action.
|
|
2734
|
-
*/
|
|
2735
|
-
interface Action {
|
|
2736
|
-
id: string;
|
|
2737
|
-
label: string;
|
|
2738
|
-
/** The gesture-spec form of the binding, read by the gesture dispatcher.
|
|
2739
|
-
* May be a single `GestureSpec`, a bare `GestureSpec[]` (any-of semantics),
|
|
2740
|
-
* or a `BoundGesture[]` where each entry is either a bare `GestureSpec` or
|
|
2741
|
-
* `{ spec, opts }` — use the object form for parametric actions where two
|
|
2742
|
-
* bindings for the same action differ only by `opts.params` (e.g. `flip`
|
|
2743
|
-
* with `axis: 'x'` vs `'y'`). The dispatcher extracts `opts.params` and
|
|
2744
|
-
* passes them to `ImmediateInvoker.run` as its second argument. */
|
|
2745
|
-
defaultBinding?: GestureSpec | BoundGesture[];
|
|
2746
|
-
/** Names of the deps this action's invoker reads (keys of `DepSchema`).
|
|
2747
|
-
* The dispatcher (and `trigger`, when `requires` is present) resolves
|
|
2748
|
-
* each name against the `DepRegistry` at invocation time and passes the
|
|
2749
|
-
* resulting bag to the invoker. Dev builds warn when the invoker reads a
|
|
2750
|
-
* dep it didn't declare here — see `buildDepsFromRequires`. */
|
|
2751
|
-
requires?: readonly DepName[];
|
|
2752
|
-
/** Inline-SVG icon for palette / toolbar surfaces. Mirrors
|
|
2753
|
-
* `ToolPresentation.icon` so a generic `<ActionBar>` can render from
|
|
2754
|
-
* action metadata the same way `<ToolPalette>` renders from tool
|
|
2755
|
-
* metadata. May be a static `ReactNode` or a function (rare; useful
|
|
2756
|
-
* for state-aware icons like a "lock" toggle). */
|
|
2757
|
-
icon?: ReactNode | (() => ReactNode);
|
|
2758
|
-
/** Grouping key for palette/menu surfaces. Free-form string; the kit
|
|
2759
|
-
* ships defaults for `'align'` (six edges/centers), `'distribute'`
|
|
2760
|
-
* (two axes), and recommends `'pathfinder'` for boolean ops. */
|
|
2761
|
-
group?: string;
|
|
2762
|
-
/** Display override for the keyboard shortcut. When omitted, palette
|
|
2763
|
-
* surfaces derive a label from `defaultBinding` via their own
|
|
2764
|
-
* formatter. */
|
|
2765
|
-
shortcut?: string;
|
|
2766
|
-
/** Pluggable invocation strategy. The gesture dispatcher routes matched
|
|
2767
|
-
* bindings through `invoker.start` / `invoker.run` depending on timing.
|
|
2768
|
-
* All kit-standard descriptors ship one; consumer-supplied actions
|
|
2769
|
-
* without an invoker can still register but won't be triggered. */
|
|
2770
|
-
invoker?: Invoker;
|
|
2771
|
-
/** When set to `'hotkey'`, this action's `defaultBinding` rides the hotkey
|
|
2772
|
-
* `BindingScope` instead of the ambient scope — meaning it beats any
|
|
2773
|
-
* active-tool binding on the same input shape. Use for tool-switch
|
|
2774
|
-
* shortcuts and global held-key triggers. Default: ambient. */
|
|
2775
|
-
scope?: 'hotkey';
|
|
2776
|
-
/**
|
|
2777
|
-
* @experimental
|
|
2778
|
-
* Optional predicate the command palette consults when rendering. Return
|
|
2779
|
-
* `true` when the action is currently triggerable. Return a reason string
|
|
2780
|
-
* (e.g. `'Selection required'`) when disabled — the palette greys out
|
|
2781
|
-
* the row, skips it in keyboard nav, ignores clicks, and shows the
|
|
2782
|
-
* reason next to the label. Keystroke dispatch (the registered binding)
|
|
2783
|
-
* is unaffected; the action's own `run` should self-guard.
|
|
2784
|
-
*
|
|
2785
|
-
* **Contract:** must be pure (no side effects), fast (< 4ms in dev), and
|
|
2786
|
-
* must not throw. If a call throws or exceeds the budget in dev mode,
|
|
2787
|
-
* `evaluateEnabled` logs a one-time warning per action id; throws are
|
|
2788
|
-
* caught and treated as disabled with reason `'(predicate threw)'`.
|
|
2789
|
-
*
|
|
2790
|
-
* Snapshot-on-open semantics: the palette evaluates `enabled` once when
|
|
2791
|
-
* opened and does NOT re-evaluate on selection changes while open. Live
|
|
2792
|
-
* reactive updates are deferred — palette is short-lived.
|
|
2793
|
-
*
|
|
2794
|
-
* The reason set is a closed enum — to add a new reason, edit
|
|
2795
|
-
* `ActionDisabledReason` and the consumer's display map.
|
|
2796
|
-
*
|
|
2797
|
-
* The optional `deps` argument is the same bag passed to
|
|
2798
|
-
* `ImmediateInvoker.run`; callers (`evaluateEnabled` / the ActionBar) may
|
|
2799
|
-
* synthesize it from the surrounding `DepRegistry` so predicates can
|
|
2800
|
-
* inspect selection / scene / etc. Predicates that don't need deps just
|
|
2801
|
-
* ignore the arg.
|
|
2802
|
-
*/
|
|
2803
|
-
enabled?: (deps?: ActionDeps) => true | ActionDisabledReason;
|
|
2804
|
-
/**
|
|
2805
|
-
* Declarative eligibility rule, evaluated against the current
|
|
2806
|
-
* `RuleCtx` by the dispatcher before invoking `start()`. Omitted =
|
|
2807
|
-
* always eligible.
|
|
2808
|
-
*
|
|
2809
|
-
* Accepts either a fluent `Condition` (callable with `.rule`) or a
|
|
2810
|
-
* raw `Rule` tree; the dispatcher normalizes via `.rule` unwrap.
|
|
2811
|
-
*
|
|
2812
|
-
* Prefer `capability:`-based rules (e.g. `{ capability: 'transforms-selection' }`)
|
|
2813
|
-
* over `mode:` rules — capability rules survive new modes being added
|
|
2814
|
-
* that allow the same capability.
|
|
2815
|
-
*/
|
|
2816
|
-
eligible?: Rule | Condition;
|
|
2817
|
-
/**
|
|
2818
|
-
* CSS cursor shown while the pointer hovers a spot where this action
|
|
2819
|
-
* would win the drag. The hover-cursor pump (in `useGestureDispatcher`)
|
|
2820
|
-
* runs `Dispatcher.resolveOnly` on each idle pointermove — the same
|
|
2821
|
-
* match walk a real pointerdown takes — and applies the winning
|
|
2822
|
-
* action's `cursor`, so the hint and the actual click target stay in
|
|
2823
|
-
* sync by construction. Omitted = no override (the active tool's
|
|
2824
|
-
* `Tool.cursor` shows). Affordance hits are resolved earlier in the
|
|
2825
|
-
* pump via `AffordanceRegion.cursor` and never reach this field.
|
|
2826
|
-
*
|
|
2827
|
-
* Static string only. Prediction runs `enabled()` but cannot run the
|
|
2828
|
-
* invoker, so an action that matches yet bails at `start()` (empty
|
|
2829
|
-
* handle) may still show its cursor — keep `enabled` accurate for
|
|
2830
|
-
* actions that declare one.
|
|
2831
|
-
*/
|
|
2832
|
-
cursor?: string;
|
|
2833
|
-
/**
|
|
2834
|
-
* CSS cursor shown while THIS action's ongoing handle is in flight —
|
|
2835
|
-
* grabbing while panning, `move` while dragging a selection, `crosshair`
|
|
2836
|
-
* while pulling a marquee.
|
|
2837
|
-
*
|
|
2838
|
-
* Separate from `cursor` because the two answer different questions:
|
|
2839
|
-
* `cursor` is a prediction ("a drag from here would pan"), this is a state
|
|
2840
|
-
* ("you are panning"). An action can declare either, both, or neither;
|
|
2841
|
-
* with only `cursor` set, the hover hint holds for the duration of the
|
|
2842
|
-
* gesture.
|
|
2843
|
-
*
|
|
2844
|
-
* This is where mid-gesture cursors live now. They used to come from the
|
|
2845
|
-
* tool side — `ViewportToolDef.engaged.cursor` for a phase-gated string,
|
|
2846
|
-
* or a function-form `Tool.cursor` reading the gesture scratch out of the
|
|
2847
|
-
* tool-routing dispatcher. Both belonged to a pipeline whose whole job was
|
|
2848
|
-
* being taken over by bindings, and neither could describe a cursor for an
|
|
2849
|
-
* action a tool doesn't own.
|
|
2850
|
-
*/
|
|
2851
|
-
activeCursor?: string;
|
|
2852
132
|
}
|
|
2853
|
-
/**
|
|
2854
|
-
*
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
* canonical mapping.
|
|
2859
|
-
*/
|
|
2860
|
-
declare const ActionDisabledReason: {
|
|
2861
|
-
readonly SelectionRequired: "selection-required";
|
|
2862
|
-
readonly SceneEmpty: "scene-empty";
|
|
2863
|
-
readonly NotApplicable: "not-applicable";
|
|
2864
|
-
/** Sentinel: the predicate threw. Surfaced by `evaluateEnabled`'s catch. */
|
|
2865
|
-
readonly PredicateThrew: "predicate-threw";
|
|
2866
|
-
};
|
|
2867
|
-
/** Why an action is unavailable right now. */
|
|
2868
|
-
type ActionDisabledReason = (typeof ActionDisabledReason)[keyof typeof ActionDisabledReason];
|
|
133
|
+
/** Renders one glyph by name. The named components below are the usual way
|
|
134
|
+
* in; reach for this when the glyph is chosen at runtime. */
|
|
135
|
+
declare function Icon({ name, className, size, label }: IconProps & {
|
|
136
|
+
name: IconName;
|
|
137
|
+
}): react_jsx_runtime.JSX.Element;
|
|
2869
138
|
|
|
2870
|
-
/**
|
|
2871
|
-
*
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
hotkeyEngaged: string | null;
|
|
2881
|
-
/** Engage a hotkey-slot tool by id. */
|
|
2882
|
-
engageHotkey: (id: string) => void;
|
|
2883
|
-
/** Disengage the hotkey-slot tool, if any. */
|
|
2884
|
-
disengageHotkey: () => void;
|
|
2885
|
-
/** All always-on tools, in registration order. */
|
|
2886
|
-
ambient: readonly AnyTool[];
|
|
2887
|
-
/** Full registry — for userland UI (palette buttons, etc.). */
|
|
2888
|
-
registry: Readonly<Record<string, AnyTool>>;
|
|
2889
|
-
/** Returns true if a tool with the given id is in the registry or ambient list. */
|
|
2890
|
-
has(id: string): boolean;
|
|
2891
|
-
/** All overlay layers from currently-engaged tools (active slot, hotkey
|
|
2892
|
-
* slot if engaged, all ambient slot tools) that declare `position`.
|
|
2893
|
-
* Filters out tools with no `overlay` field. Order: active, then hotkey
|
|
2894
|
-
* (if engaged), then ambient (registration order). */
|
|
2895
|
-
getActiveOverlays(position?: OverlayPosition): RenderLayer<unknown>[];
|
|
139
|
+
/** Holds the set of available modes and which one is active, and notifies
|
|
140
|
+
* subscribers when that changes. `getVersion` is a monotonic counter for
|
|
141
|
+
* render-cache invalidation. Unknown mode ids throw rather than being
|
|
142
|
+
* ignored. */
|
|
143
|
+
interface ModeRegistry {
|
|
144
|
+
current(): ModeDefinition;
|
|
145
|
+
setMode(id: string): void;
|
|
146
|
+
byId(id: string): ModeDefinition;
|
|
147
|
+
getVersion(): number;
|
|
148
|
+
subscribe(listener: () => void): () => void;
|
|
2896
149
|
}
|
|
2897
150
|
|
|
2898
151
|
/** Props for {@link ActionBar}. */
|
|
@@ -3991,6 +1244,9 @@ type SliderProps<T extends Thumb = Thumb> = {
|
|
|
3991
1244
|
allowShiftAll?: boolean;
|
|
3992
1245
|
renderTrack?: (ctx: TrackCtx) => ReactNode;
|
|
3993
1246
|
trackHeight?: number;
|
|
1247
|
+
/** `'slim'` drives the track and thumb from the kit's slider tokens, so a
|
|
1248
|
+
* Slider matches the property rows. `trackHeight` still wins if given. */
|
|
1249
|
+
density?: 'default' | 'slim';
|
|
3994
1250
|
renderReadout?: (thumb: T, index: number) => ReactNode;
|
|
3995
1251
|
readoutPlacement?: 'none' | 'inline-after' | 'below-thumb';
|
|
3996
1252
|
ariaLabel?: string;
|
|
@@ -4430,7 +1686,7 @@ type InputProps = Omit<TextFieldProps, 'children' | 'className'> & {
|
|
|
4430
1686
|
*
|
|
4431
1687
|
* `ref` forwards to the underlying `<input>`.
|
|
4432
1688
|
*/
|
|
4433
|
-
declare const Input: react.ForwardRefExoticComponent<Omit<TextFieldProps, "
|
|
1689
|
+
declare const Input: react.ForwardRefExoticComponent<Omit<TextFieldProps, "className" | "children"> & {
|
|
4434
1690
|
label?: ReactNode;
|
|
4435
1691
|
description?: ReactNode;
|
|
4436
1692
|
errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);
|
|
@@ -4452,7 +1708,7 @@ type CheckboxProps = Omit<CheckboxProps$1, 'children' | 'className'> & {
|
|
|
4452
1708
|
* Single checkbox wrapping React Aria's Checkbox. Supports indeterminate
|
|
4453
1709
|
* via `isIndeterminate`. The label is supplied as children.
|
|
4454
1710
|
*/
|
|
4455
|
-
declare const Checkbox: react.ForwardRefExoticComponent<Omit<CheckboxProps$1, "
|
|
1711
|
+
declare const Checkbox: react.ForwardRefExoticComponent<Omit<CheckboxProps$1, "className" | "children"> & {
|
|
4456
1712
|
children?: ReactNode;
|
|
4457
1713
|
className?: string;
|
|
4458
1714
|
} & react.RefAttributes<HTMLLabelElement>>;
|
|
@@ -4472,7 +1728,7 @@ type SwitchProps = Omit<SwitchProps$1, 'children' | 'className'> & {
|
|
|
4472
1728
|
*
|
|
4473
1729
|
* `ref` forwards to the underlying label element.
|
|
4474
1730
|
*/
|
|
4475
|
-
declare const Switch: react.ForwardRefExoticComponent<Omit<SwitchProps$1, "
|
|
1731
|
+
declare const Switch: react.ForwardRefExoticComponent<Omit<SwitchProps$1, "className" | "children"> & {
|
|
4476
1732
|
children?: ReactNode;
|
|
4477
1733
|
className?: string;
|
|
4478
1734
|
} & react.RefAttributes<HTMLLabelElement>>;
|
|
@@ -4537,6 +1793,9 @@ type NumberFieldProps = Omit<NumberFieldProps$1, 'children' | 'className'> & {
|
|
|
4537
1793
|
errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);
|
|
4538
1794
|
/** Hide the up/down stepper buttons. Defaults to false. */
|
|
4539
1795
|
hideSteppers?: boolean;
|
|
1796
|
+
/** Render with no box until focused — the readout treatment the property
|
|
1797
|
+
* rows use, for a value that sits inside other chrome rather than in a form. */
|
|
1798
|
+
ghost?: boolean;
|
|
4540
1799
|
/** Native input placeholder — e.g. `'Mixed'` for a multi-selection
|
|
4541
1800
|
* editor with no shared value. */
|
|
4542
1801
|
placeholder?: string;
|
|
@@ -4550,12 +1809,15 @@ type NumberFieldProps = Omit<NumberFieldProps$1, 'children' | 'className'> & {
|
|
|
4550
1809
|
*
|
|
4551
1810
|
* `ref` forwards to the underlying `<input>`.
|
|
4552
1811
|
*/
|
|
4553
|
-
declare const NumberField: react.ForwardRefExoticComponent<Omit<NumberFieldProps$1, "
|
|
1812
|
+
declare const NumberField: react.ForwardRefExoticComponent<Omit<NumberFieldProps$1, "className" | "children"> & {
|
|
4554
1813
|
label?: ReactNode;
|
|
4555
1814
|
description?: ReactNode;
|
|
4556
1815
|
errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);
|
|
4557
1816
|
/** Hide the up/down stepper buttons. Defaults to false. */
|
|
4558
1817
|
hideSteppers?: boolean;
|
|
1818
|
+
/** Render with no box until focused — the readout treatment the property
|
|
1819
|
+
* rows use, for a value that sits inside other chrome rather than in a form. */
|
|
1820
|
+
ghost?: boolean;
|
|
4559
1821
|
/** Native input placeholder — e.g. `'Mixed'` for a multi-selection
|
|
4560
1822
|
* editor with no shared value. */
|
|
4561
1823
|
placeholder?: string;
|
|
@@ -4814,7 +2076,7 @@ interface Plot2DProps {
|
|
|
4814
2076
|
style?: CSSProperties;
|
|
4815
2077
|
/** Pointer down on the SVG. Receives both plot- and model-space coords
|
|
4816
2078
|
* pre-computed so consumers don't repeat the rect/transform dance. */
|
|
4817
|
-
onPointerDown?: (e: PointerEvent
|
|
2079
|
+
onPointerDown?: (e: PointerEvent<SVGSVGElement>, coords: Plot2DCoords) => void;
|
|
4818
2080
|
onKeyDown?: (e: KeyboardEvent<SVGSVGElement>) => void;
|
|
4819
2081
|
children?: ReactNode;
|
|
4820
2082
|
}
|
|
@@ -5079,9 +2341,20 @@ interface UseReorderDragListOptions {
|
|
|
5079
2341
|
items: LayerListItem[];
|
|
5080
2342
|
selectedIds: string[];
|
|
5081
2343
|
onReorder(ids: string[], targetIndex: number): void;
|
|
2344
|
+
/** A press that was released without ever engaging a drag — the click a
|
|
2345
|
+
* list row means by it. Fires for locked rows too, which can be selected
|
|
2346
|
+
* but not dragged. Modifiers are read at press, not at release. */
|
|
2347
|
+
onPress?(id: string, mods: PressModifiers): void;
|
|
5082
2348
|
/** Pointer-move distance (px) before pending drag engages. Default 4. */
|
|
5083
2349
|
threshold?: number;
|
|
5084
2350
|
}
|
|
2351
|
+
/** Modifier keys held when a press began. */
|
|
2352
|
+
interface PressModifiers {
|
|
2353
|
+
shiftKey: boolean;
|
|
2354
|
+
ctrlKey: boolean;
|
|
2355
|
+
metaKey: boolean;
|
|
2356
|
+
altKey: boolean;
|
|
2357
|
+
}
|
|
5085
2358
|
/**
|
|
5086
2359
|
* Live drag state for rendering feedback: which ids are being dragged and the
|
|
5087
2360
|
* insertion index the drop would use. Both `null` when no drag is engaged.
|
|
@@ -5091,18 +2364,17 @@ interface ReorderDragState {
|
|
|
5091
2364
|
targetIndex: number | null;
|
|
5092
2365
|
}
|
|
5093
2366
|
/**
|
|
5094
|
-
*
|
|
5095
|
-
* {@link ReorderDragState}.
|
|
2367
|
+
* A `ref` for the list container, an `onPointerDown` for each row, and the
|
|
2368
|
+
* live {@link ReorderDragState}. The container ref is required, not optional
|
|
2369
|
+
* decoration: it is what the drop index is measured against and what the
|
|
2370
|
+
* pointer session is opened on.
|
|
5096
2371
|
*/
|
|
5097
2372
|
interface ReorderDragHandlers {
|
|
5098
2373
|
rowProps(id: string, index: number): {
|
|
5099
|
-
onPointerDown(e: PointerEvent
|
|
2374
|
+
onPointerDown(e: PointerEvent): void;
|
|
5100
2375
|
};
|
|
5101
2376
|
containerProps: {
|
|
5102
2377
|
ref: RefCallback<HTMLElement>;
|
|
5103
|
-
onPointerMove(e: PointerEvent$1): void;
|
|
5104
|
-
onPointerUp(e: PointerEvent$1): void;
|
|
5105
|
-
onPointerCancel(e: PointerEvent$1): void;
|
|
5106
2378
|
};
|
|
5107
2379
|
state: ReorderDragState;
|
|
5108
2380
|
}
|
|
@@ -5113,8 +2385,11 @@ interface ReorderDragHandlers {
|
|
|
5113
2385
|
* drop. A drop that would leave a contiguous block where it already is does
|
|
5114
2386
|
* not call `onReorder`.
|
|
5115
2387
|
*
|
|
5116
|
-
*
|
|
5117
|
-
*
|
|
2388
|
+
* A press opens an `openPointerSession` on the *container*, which owns the
|
|
2389
|
+
* rest of the gesture: a drag that leaves the list still tracks, a release
|
|
2390
|
+
* anywhere still drops, and a release the window never delivered still ends
|
|
2391
|
+
* the drag. The container is the origin rather than the row because rows come
|
|
2392
|
+
* and go as the list re-renders, and a drag must outlive the row it grabbed.
|
|
5118
2393
|
*/
|
|
5119
2394
|
declare function useReorderDragList(opts: UseReorderDragListOptions): ReorderDragHandlers;
|
|
5120
2395
|
|
|
@@ -5191,5 +2466,5 @@ declare const MINUS_SIGN = "\u2212";
|
|
|
5191
2466
|
*/
|
|
5192
2467
|
declare function formatNumber(value: number, options?: Intl.NumberFormatOptions): string;
|
|
5193
2468
|
|
|
5194
|
-
export { ActionBar, ActionsBar, Badge, Button, Callout, Checkbox, ComboBox, ComboBoxItem, CurveEditor, DataGrid, Dialog, DragHandleGlyph, EDGE_PROFILES, Field, Input, KeyCap, KeySequence, MINUS_SIGN, NumberField, OptionsBar, Plot2D, PointPlotter, Powerline, ToolPrefGroup as PrefGroup, ToolPrefLeaf as PrefLeaf, Radio, RadioGroup, RangeSlider, Select, SelectItem, Sidebar, SidebarPanel, Slider, Switch, Tab, TabList, TabPanel, Tabs, ToggleBar, ToolButton, ToolGroup, ToolPalette, chromaAt, detectPlatform, dlog, fieldClasses, formatNumber, formatShortcut, formatShortcutParts, inferKeycapKind, isDebugEnabled, isPrefLeaf, keyGlyph, keySpecFromKey, keySpecsFromMods, oklchToHex, paintGradientTrack, prefValueAtPath, useReorderDragList, useRovingTabIndex, visiblePrefSubtree };
|
|
5195
|
-
export type { ActionBarProps, ActionsBarItem, ActionsBarProps, ActionsBarSize, ActionsBarVariant, AddPointMode, AnchorRenderProps, AxesSettings, BadgeProps, BadgeShape, BadgeSize, BadgeTone, BadgeVariant, BoundsCtx, BuiltInEdgeName, ButtonProps, ButtonSize, ButtonVariant, CalloutProps, CheckboxProps, ChromaCurve, ChromaCurvePoint, ComboBoxItemProps, ComboBoxOption, ComboBoxProps, ControlPoint, CurveDomain, CurveEditorProps, DataGridColumn, DataGridProps, DialogProps, DragHandleGlyphProps, EdgeCap, EdgeProfile, EndpointMode, FieldOrientation, FieldProps, FillSettings, GradientTrackOpts, GridSettings, InputProps, InterpolationMode, KeyCapProps, KeyCapVariant, KeySequenceProps, KeySpec, KeycapKind, LayerListItem, LogicalMod, LogicalModSpec, NumberFieldProps, OptionsBarItem, OptionsBarProps, OptionsBarSize, OptionsBarVariant, Platform, Plot2DCoords, Plot2DHandle, Plot2DProps, PointPlotterProps, PowerlineProps, PowerlineSegment, RadioGroupProps, RadioProps, RangeSliderProps, ReorderDragHandlers, ReorderDragState, RovingItem, RovingTabIndex, SelectItemProps, SelectOption, SelectProps, SidebarPanelProps, SidebarProps, SliderProps, SwitchProps, TabListProps, TabPanelProps, TabProps, TabsProps, Thumb, ThumbRenderCtx, ThumbShape, ToggleBarItem, ToggleBarProps, ToggleBarSize, ToggleBarVariant, ToolButtonProps, ToolGroupProps, ToolPaletteProps, TrackCtx, UseReorderDragListOptions, UseRovingTabIndexOptions };
|
|
2469
|
+
export { ActionBar, ActionsBar, Badge, Button, Callout, Checkbox, ComboBox, ComboBoxItem, CurveEditor, DataGrid, Dialog, DragHandleGlyph, EDGE_PROFILES, Field, ICON_PATHS, Icon, Input, KeyCap, KeySequence, MINUS_SIGN, NumberField, OptionsBar, Plot2D, PointPlotter, Powerline, ToolPrefGroup as PrefGroup, ToolPrefLeaf as PrefLeaf, Radio, RadioGroup, RangeSlider, Select, SelectItem, Sidebar, SidebarPanel, Slider, Switch, Tab, TabList, TabPanel, Tabs, ToggleBar, ToolButton, ToolGroup, ToolPalette, chromaAt, detectPlatform, dlog, fieldClasses, formatNumber, formatShortcut, formatShortcutParts, inferKeycapKind, isDebugEnabled, isPrefLeaf, keyGlyph, keySpecFromKey, keySpecsFromMods, oklchToHex, paintGradientTrack, prefValueAtPath, useReorderDragList, useRovingTabIndex, visiblePrefSubtree };
|
|
2470
|
+
export type { ActionBarProps, ActionsBarItem, ActionsBarProps, ActionsBarSize, ActionsBarVariant, AddPointMode, AnchorRenderProps, AxesSettings, BadgeProps, BadgeShape, BadgeSize, BadgeTone, BadgeVariant, BoundsCtx, BuiltInEdgeName, ButtonProps, ButtonSize, ButtonVariant, CalloutProps, CheckboxProps, ChromaCurve, ChromaCurvePoint, ComboBoxItemProps, ComboBoxOption, ComboBoxProps, ControlPoint, CurveDomain, CurveEditorProps, DataGridColumn, DataGridProps, DialogProps, DragHandleGlyphProps, EdgeCap, EdgeProfile, EndpointMode, FieldOrientation, FieldProps, FillSettings, GradientTrackOpts, GridSettings, IconName, IconProps, InputProps, InterpolationMode, KeyCapProps, KeyCapVariant, KeySequenceProps, KeySpec, KeycapKind, LayerListItem, LogicalMod, LogicalModSpec, NumberFieldProps, OptionsBarItem, OptionsBarProps, OptionsBarSize, OptionsBarVariant, Platform, Plot2DCoords, Plot2DHandle, Plot2DProps, PointPlotterProps, PowerlineProps, PowerlineSegment, RadioGroupProps, RadioProps, RangeSliderProps, ReorderDragHandlers, ReorderDragState, RovingItem, RovingTabIndex, SelectItemProps, SelectOption, SelectProps, SidebarPanelProps, SidebarProps, SliderProps, SwitchProps, TabListProps, TabPanelProps, TabProps, TabsProps, Thumb, ThumbRenderCtx, ThumbShape, ToggleBarItem, ToggleBarProps, ToggleBarSize, ToggleBarVariant, ToolButtonProps, ToolGroupProps, ToolPaletteProps, TrackCtx, UseReorderDragListOptions, UseRovingTabIndexOptions };
|