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