@weasel-js/routing 1.6.1 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/{chunk-VEC3ACV5.js → chunk-ZGKJWZ4S.js} +774 -380
- package/dist/chunk-ZGKJWZ4S.js.map +1 -0
- package/dist/{index-CKmbNpwl.d.ts → index-BV53gfyB.d.ts} +649 -257
- package/dist/index.d.ts +2 -2
- package/dist/index.js +84 -8
- package/dist/index.js.map +1 -1
- package/dist/internal--mYnGecm.d.ts +275 -0
- package/dist/internal.d.ts +7 -0
- package/dist/internal.js +3 -0
- package/dist/internal.js.map +1 -0
- package/dist/react.d.ts +213 -344
- package/dist/react.js +456 -406
- package/dist/react.js.map +1 -1
- package/package.json +10 -5
- package/dist/chunk-VEC3ACV5.js.map +0 -1
package/dist/react.d.ts
CHANGED
|
@@ -1,296 +1,65 @@
|
|
|
1
|
-
|
|
2
|
-
export { e as DepRegistry, f as DepRegistryProvider, u as useDepRegistry, g as useDepSource, h as useOptionalDepRegistry } from './index-CKmbNpwl.js';
|
|
1
|
+
export { D as DispatcherChannels, U as UseGestureDispatcherOptions, u as useGestureDispatcher } from './internal--mYnGecm.js';
|
|
3
2
|
import * as react from 'react';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import '@weasel-js/
|
|
3
|
+
import { ReactNode, ReactElement } from 'react';
|
|
4
|
+
import { D as DepRegistry, a as DepName, b as DepSchema, S as ScopeNode, A as ActionsRegistry, c as Action, d as DepNode, K as KernelOverlay, C as Contribution, e as AnyToolOf, O as OverlayPosition, f as ScopedBinding } from './index-BV53gfyB.js';
|
|
5
|
+
import { ModeRegistry } from '@weasel-js/modes';
|
|
6
|
+
import '@weasel-js/gestures';
|
|
7
|
+
import '@weasel-js/cursor';
|
|
8
8
|
import '@weasel-js/history';
|
|
9
9
|
|
|
10
|
+
/** Provides the dep registry for a canvas. `<SceneCanvas>` mounts one; a
|
|
11
|
+
* consumer registering its own dep sources must be inside it. */
|
|
12
|
+
declare function DepRegistryProvider({ children }: {
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
}): react.JSX.Element;
|
|
15
|
+
/** The dep registry in scope. Throws outside a `<DepRegistryProvider>`. */
|
|
16
|
+
declare function useDepRegistry(): DepRegistry;
|
|
10
17
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* @internal One per view. The hook builds the `id: null` one from its flat
|
|
16
|
-
* options; `views` supplies the rest, and the hook chooses between them per
|
|
17
|
-
* event, so that it — and its listener set — still mounts exactly once.
|
|
18
|
-
*/
|
|
19
|
-
interface DispatcherViewTarget {
|
|
20
|
-
/** `null` is the root view — the whole canvas. Matches `ViewTarget.id`. */
|
|
21
|
-
id: string | null;
|
|
22
|
-
dispatcher: Dispatcher;
|
|
23
|
-
affordanceAt: UseGestureDispatcherOptions['affordanceAt'];
|
|
24
|
-
classifyTarget: UseGestureDispatcherOptions['classifyTarget'];
|
|
25
|
-
clientToWorld: UseGestureDispatcherOptions['clientToWorld'];
|
|
26
|
-
/**
|
|
27
|
-
* The deps this view answers for itself — its camera, its selection, and
|
|
28
|
-
* whatever else is per-view rather than per-canvas. Read per event; every
|
|
29
|
-
* name it does not carry still resolves through the canvas registry.
|
|
30
|
-
*
|
|
31
|
-
* This is why a view does not get a `DepRegistryProvider` of its own. The
|
|
32
|
-
* registry is where a consumer registers *sources*, and one per view would
|
|
33
|
-
* fragment that: a consumer overriding `insert` would have to know how many
|
|
34
|
-
* views exist and override each. An overlay keeps one place to register and
|
|
35
|
-
* one authority per dep, with the view claiming only what is genuinely its.
|
|
36
|
-
*
|
|
37
|
-
* Without this a gesture inside a panel pans the whole canvas and selects
|
|
38
|
-
* into the canvas's selection — correct coordinates alone do not make
|
|
39
|
-
* routing correct.
|
|
40
|
-
*/
|
|
41
|
-
deps?: () => Partial<DepSchema>;
|
|
42
|
-
/**
|
|
43
|
-
* This view's chrome-caps rule context, for the eligibility filter. The rule
|
|
44
|
-
* table is the surface's, but a rule keyed on selection or the in-flight
|
|
45
|
-
* action answers per view — so an action hidden in one panel must not
|
|
46
|
-
* decline a gesture in another. `undefined` means "nothing gates here", the
|
|
47
|
-
* same answer a surface with no mode registry gives.
|
|
48
|
-
*/
|
|
49
|
-
getRuleCtx?: () => RuleCtx | undefined;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* The part of `features/viewports`' `ViewResolver` the dispatcher needs: which
|
|
53
|
-
* view a client point belongs to, pinned to the view a gesture began in.
|
|
54
|
-
*
|
|
55
|
-
* @internal Structural on purpose — a `ViewResolver` satisfies it, and the
|
|
56
|
-
* dispatcher stays free of the viewport module.
|
|
57
|
-
*/
|
|
58
|
-
interface ViewIdResolver {
|
|
59
|
-
begin(pointerId: number, clientX: number, clientY: number): {
|
|
60
|
-
id: string | null;
|
|
61
|
-
};
|
|
62
|
-
at(pointerId: number | null, clientX: number, clientY: number): {
|
|
63
|
-
id: string | null;
|
|
64
|
-
};
|
|
65
|
-
end(pointerId: number): void;
|
|
66
|
-
}
|
|
67
|
-
/** Per-channel switches for the listeners `useGestureDispatcher` attaches to
|
|
68
|
-
* the canvas element. Omitted or `true` attaches the channel. */
|
|
69
|
-
interface DispatcherChannels {
|
|
70
|
-
/** `pointerdown`, hover `pointermove`, `pointerleave` — press, drag, click,
|
|
71
|
-
* long-press, multi-touch and the hover-cursor pump. */
|
|
72
|
-
pointer?: boolean;
|
|
73
|
-
/** `wheel`. */
|
|
74
|
-
wheel?: boolean;
|
|
75
|
-
/** WebKit's `gesturestart` / `gesturechange` / `gestureend` — Safari's
|
|
76
|
-
* trackpad pinch, dispatched as `pinch` events. */
|
|
77
|
-
pinch?: boolean;
|
|
78
|
-
/** `contextmenu`. Off leaves the browser's native menu in place. */
|
|
79
|
-
contextMenu?: boolean;
|
|
80
|
-
/** OS drop on the element plus `paste` on the window. Off leaves the
|
|
81
|
-
* element a non-drop-target and stops `weasel-dropover` being applied. */
|
|
82
|
-
ingest?: boolean;
|
|
83
|
-
}
|
|
84
|
-
/** Options for `useGestureDispatcher`: the element to listen on, the actions
|
|
85
|
-
* and tools in play, and the hooks that turn raw DOM events into the world
|
|
86
|
-
* coordinates and hit targets bindings match against. */
|
|
87
|
-
interface UseGestureDispatcherOptions {
|
|
88
|
-
/** Ref to the canvas element. Pointer/wheel/multitouch listeners attach here. */
|
|
89
|
-
canvasRef: RefObject<HTMLElement | null>;
|
|
90
|
-
/** Action registry (ActionsRegistry from registry.tsx). */
|
|
91
|
-
actions: ActionsRegistry;
|
|
92
|
-
/** Tool definitions keyed by id. Typically passes an empty Map. */
|
|
93
|
-
toolsById: ReadonlyMap<string, Tool>;
|
|
94
|
-
/** Default true. Set false to opt out of dispatcher wiring (e.g. demos that disable it). */
|
|
95
|
-
enabled?: boolean;
|
|
96
|
-
/**
|
|
97
|
-
* Observer fired whenever the dispatcher synthesizes a double click, in
|
|
98
|
-
* world coordinates. Runs BEFORE the event is dispatched and independently
|
|
99
|
-
* of which binding (if any) handles it.
|
|
100
|
-
*
|
|
101
|
-
* This is deliberately not an Action. `<SceneCanvas onDoubleClick>` is a
|
|
102
|
-
* notification — "the user double-clicked, here's what they hit" — and a
|
|
103
|
-
* notification must not compete with behavior for the gesture. As a binding
|
|
104
|
-
* it would lose to `enterPathEdit` on any body hit and silently never fire.
|
|
105
|
-
* Routing it here keeps a single definition of "double click" (the point of
|
|
106
|
-
* consolidating the kit's three detectors) without giving it
|
|
107
|
-
* first-match-wins semantics it shouldn't have.
|
|
108
|
-
*/
|
|
109
|
-
onDoubleClick?: (world: {
|
|
110
|
-
x: number;
|
|
111
|
-
y: number;
|
|
112
|
-
}) => void;
|
|
113
|
-
/**
|
|
114
|
-
* Default true. Set false to leave the window `keydown`/`keyup` listeners
|
|
115
|
-
* unattached so keyboard-bound actions never dispatch — pointer, wheel, and
|
|
116
|
-
* contextmenu channels stay live. `<SceneCanvas>` wires this to
|
|
117
|
-
* `enableKeybindings`, so opting out of keybindings disables the modern
|
|
118
|
-
* dispatcher key path as well as the legacy `useKeybindings` hook.
|
|
119
|
-
*/
|
|
120
|
-
keyboard?: boolean;
|
|
121
|
-
/**
|
|
122
|
-
* Optional affordance classifier. Called with the **world** coordinates of
|
|
123
|
-
* the pointer — this hook applies the routed view's `clientToWorld` before
|
|
124
|
-
* calling it, the same conversion that produces `InputEvent.x`/`y`, so a
|
|
125
|
-
* consumer hit-tests in one space. Returns an `AffordanceHit` when the
|
|
126
|
-
* pointer lands on a known affordance (resize handle, rotate handle, etc.)
|
|
127
|
-
* or `null` when the pointer hit open canvas.
|
|
128
|
-
*
|
|
129
|
-
* The hit is packed into `InputEvent.pointerdown.affordance` and flows
|
|
130
|
-
* through into `InvocationCtx.drag.affordance`. Action invokers that require
|
|
131
|
-
* a specific affordance (e.g. `resizeAction` requires `handle:*`) use this
|
|
132
|
-
* field as a guard in their `start` body.
|
|
133
|
-
*
|
|
134
|
-
* When omitted, `affordance` is always `undefined` — meaning only consumers
|
|
135
|
-
* that explicitly wire a classifier get affordance-gated behavior.
|
|
136
|
-
* `<SceneCanvas>` wires the full chrome→dispatcher bridge.
|
|
137
|
-
*/
|
|
138
|
-
affordanceAt?: (worldPoint: {
|
|
139
|
-
x: number;
|
|
140
|
-
y: number;
|
|
141
|
-
}) => AffordanceHit | null;
|
|
142
|
-
/**
|
|
143
|
-
* Optional body classifier. Called on every pointerdown with the world-space
|
|
144
|
-
* coordinates of the pointer. Its result is packed onto the event as
|
|
145
|
-
* `bodyTarget` + `bodyKind`, which `matchTarget` reads to resolve the
|
|
146
|
-
* string-form `TargetSpec` values in `Tool.bindings`.
|
|
147
|
-
*
|
|
148
|
-
* `body` is `'empty'` when nothing is under the pointer, `'selected-body'`
|
|
149
|
-
* when the topmost hit belongs to the current selection, or
|
|
150
|
-
* `'unselected-body'` when it belongs to a node that isn't selected. `kind`
|
|
151
|
-
* is the hit node's semantic kind, when the scene can name it.
|
|
152
|
-
*
|
|
153
|
-
* When omitted, every body-derived target form (`'empty'`,
|
|
154
|
-
* `'selected-body'`, `'unselected-body'`, `kind:<k>`, `kind:<k>:selected`)
|
|
155
|
-
* never matches — bindings using those specs are silently skipped.
|
|
156
|
-
* `<SceneCanvas>` wires this.
|
|
157
|
-
*
|
|
158
|
-
* World coordinates, on every path, for the same reason as `affordanceAt`.
|
|
159
|
-
*/
|
|
160
|
-
classifyTarget?: (worldPoint: {
|
|
161
|
-
x: number;
|
|
162
|
-
y: number;
|
|
163
|
-
}) => BodyClassification;
|
|
164
|
-
/**
|
|
165
|
-
* Optional pre-created `Dispatcher`. When provided, this hook pumps events
|
|
166
|
-
* into the supplied instance instead of creating its own. Lets a parent
|
|
167
|
-
* scope (e.g. `<SceneCanvas>`) share one dispatcher between the gesture
|
|
168
|
-
* mounter and other consumers (the preview-ghost layer).
|
|
169
|
-
*/
|
|
170
|
-
dispatcher?: Dispatcher;
|
|
171
|
-
/**
|
|
172
|
-
* Converts a client-space pointer position (e.g. `e.clientX`, `e.clientY`)
|
|
173
|
-
* to world-space coordinates. When supplied, every pointer/wheel event's
|
|
174
|
-
* `x`/`y` is converted before the dispatcher builds `InvocationCtx.world`.
|
|
175
|
-
*
|
|
176
|
-
* Without this, `ctx.world` is populated with the raw client coords, which
|
|
177
|
-
* silently breaks any action whose overlay/output uses absolute world
|
|
178
|
-
* positions (marquee, lasso polyline, world-space affordances). Actions
|
|
179
|
-
* that only read `drag.delta` are unaffected because client→world deltas
|
|
180
|
-
* are equal at scale 1, but as soon as a consumer pans/zooms the view the
|
|
181
|
-
* deltas diverge too.
|
|
182
|
-
*
|
|
183
|
-
* `<SceneCanvas>` always wires this via its canvas rect + current view.
|
|
184
|
-
* Tests / harnesses without a view can omit it and continue passing raw
|
|
185
|
-
* coordinates as before.
|
|
186
|
-
*/
|
|
187
|
-
clientToWorld?: (clientX: number, clientY: number) => {
|
|
188
|
-
x: number;
|
|
189
|
-
y: number;
|
|
190
|
-
};
|
|
191
|
-
/**
|
|
192
|
-
* Invoked once per pump event (`pointermove`, `pointerup`, `pointercancel`,
|
|
193
|
-
* `key-held` up-phase, `multitouch` move) when a dispatcher-side handle is
|
|
194
|
-
* in flight. Lets the host canvas schedule a redraw so dispatcher-only
|
|
195
|
-
* overlays (marquee, lasso, preview-ghost) repaint on each frame.
|
|
196
|
-
*
|
|
197
|
-
* Required because dispatcher actions don't go through the legacy
|
|
198
|
-
* `tools.dispatcher.onGestureChange` redraw bump that ambient tool drags
|
|
199
|
-
* relied on. Without it, the overlay layer's `draw` never runs between
|
|
200
|
-
* pointerdown and pointerup, and the chrome flashes once at gesture start
|
|
201
|
-
* and then sits frozen until the gesture ends.
|
|
202
|
-
*
|
|
203
|
-
* `<SceneCanvas>` wires this to the canvas's `requestRedraw`. Test
|
|
204
|
-
* harnesses can omit it; their dispatchers won't paint between events but
|
|
205
|
-
* that's already the test contract.
|
|
206
|
-
*/
|
|
207
|
-
requestRedraw?: () => void;
|
|
208
|
-
/**
|
|
209
|
-
* The surface's painted-cursor channel, read live because the canvas handle
|
|
210
|
-
* it lives on arrives a render after this hook runs.
|
|
211
|
-
*
|
|
212
|
-
* Given one, a cursor the CSS tier cannot express — sized in world units, or
|
|
213
|
-
* past the size the browser drops the image at — is published here and drawn
|
|
214
|
-
* by the painted-cursor layer instead. Without one, such a cursor resolves to
|
|
215
|
-
* `none` and the pointer shows nothing.
|
|
216
|
-
*/
|
|
217
|
-
paintedCursor?: () => PaintedCursorState | undefined;
|
|
218
|
-
/**
|
|
219
|
-
* Thunk returning the live `RuleCtx` for the current frame. When supplied,
|
|
220
|
-
* the dispatcher filters matched candidates by their declared
|
|
221
|
-
* `Action.eligible` rule (omitted => always eligible). `<SceneCanvas>`
|
|
222
|
-
* wires this; tests / harnesses without chrome-caps state can omit it.
|
|
223
|
-
*/
|
|
224
|
-
getRuleCtx?: () => RuleCtx | undefined;
|
|
225
|
-
/**
|
|
226
|
-
* Which of the canvas element's input channels to attach. Every one
|
|
227
|
-
* defaults to `true`; naming a channel `false` leaves its listeners
|
|
228
|
-
* unattached, so the element behaves as if the dispatcher were not there.
|
|
229
|
-
*
|
|
230
|
-
* A mount that binds two gestures should not also have to take the rest of
|
|
231
|
-
* the pipeline's side effects: `contextMenu` suppresses the native menu
|
|
232
|
-
* unconditionally, and `ingest` makes the element a valid drop target and
|
|
233
|
-
* styles it while an OS drag hovers. `@weasel-js/labkit`'s loupe mounts on
|
|
234
|
-
* a lab's own canvas host for a wheel and a held key, and turning the other
|
|
235
|
-
* three off is what keeps right-click and file drops working there.
|
|
236
|
-
*
|
|
237
|
-
* The window keyboard channel is `keyboard` instead of an entry here,
|
|
238
|
-
* because `<SceneCanvas enableKeybindings>` drives it.
|
|
239
|
-
*/
|
|
240
|
-
channels?: DispatcherChannels;
|
|
241
|
-
/**
|
|
242
|
-
* Routing for a canvas hosting more than one view: the non-root dispatch
|
|
243
|
-
* records to choose between, read fresh per event, and the resolver that
|
|
244
|
-
* chooses. Omit for a single view — then every event runs on the record the
|
|
245
|
-
* flat options above describe, which is what they are: view zero.
|
|
246
|
-
*
|
|
247
|
-
* A resolved id with no live record falls back to the root, so a view that
|
|
248
|
-
* unmounts mid-gesture degrades instead of dropping the event.
|
|
249
|
-
*
|
|
250
|
-
* Keyboard and paste have no coordinates to resolve; they run on the view
|
|
251
|
-
* the last coordinate-bearing event resolved to.
|
|
252
|
-
*
|
|
253
|
-
* @internal
|
|
254
|
-
*/
|
|
255
|
-
views?: {
|
|
256
|
-
targets: () => readonly DispatcherViewTarget[];
|
|
257
|
-
resolver: ViewIdResolver;
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
/**
|
|
261
|
-
* Attach the input pipeline to a canvas: DOM pointer, wheel, keyboard and
|
|
262
|
-
* multitouch listeners in, normalized input events out, routed through the
|
|
263
|
-
* dispatcher to whichever action a binding names.
|
|
264
|
-
*
|
|
265
|
-
* Everything it needs beyond its options — the active tool, the dep registry —
|
|
266
|
-
* comes from context, so it must be mounted inside `<SceneCanvas>`'s
|
|
267
|
-
* providers.
|
|
18
|
+
* Like `useDepRegistry`, but returns `null` when no `<DepRegistryProvider>` is
|
|
19
|
+
* in scope instead of throwing. Used by `useStandardActions` to preserve its
|
|
20
|
+
* silent-no-op contract when neither provider is present.
|
|
268
21
|
*/
|
|
269
|
-
declare function
|
|
22
|
+
declare function useOptionalDepRegistry(): DepRegistry | null;
|
|
23
|
+
/** Register a live source for `name` for the lifetime of the calling
|
|
24
|
+
* component. The `source` thunk is called at dispatch time and should
|
|
25
|
+
* return the latest value. */
|
|
26
|
+
declare function useDepSource<K extends DepName>(name: K, source: () => DepSchema[K]): void;
|
|
270
27
|
|
|
271
28
|
/**
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
29
|
+
* One node of the actions tree: the actions registered at it, over its
|
|
30
|
+
* parent's. Each id holds a stack of registrants, newest live, so a displaced
|
|
31
|
+
* registrant is restored when the one above it leaves. Every read the registry
|
|
32
|
+
* answers — `list`, `trigger`, `begin` — starts at the node's leaf.
|
|
276
33
|
*/
|
|
277
|
-
|
|
34
|
+
declare class ActionsNode extends ScopeNode<ActionsNode> {
|
|
35
|
+
private readonly contextDeps;
|
|
36
|
+
private readonly actions;
|
|
37
|
+
private readonly muted;
|
|
38
|
+
private readonly dispatchers;
|
|
39
|
+
private cache;
|
|
40
|
+
/** Called on `activate`, so a scope's dep and tool nodes move with it. */
|
|
41
|
+
readonly onActivate: (() => void)[];
|
|
42
|
+
constructor(parent: ActionsNode | null, descends: boolean, contextDeps: () => DepRegistry | null);
|
|
43
|
+
private resolve;
|
|
44
|
+
private snapshot;
|
|
45
|
+
private deps;
|
|
46
|
+
private dispatcher;
|
|
47
|
+
activate(): void;
|
|
48
|
+
readonly registry: ActionsRegistry;
|
|
49
|
+
}
|
|
278
50
|
/**
|
|
279
51
|
* @experimental
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
* `mute` calls stay inside it. Renders nothing of its own.
|
|
284
|
-
*
|
|
285
|
-
* Not a `BindingScope`: that names the tier a binding matches at (hotkey /
|
|
286
|
-
* active / ambient), which this has nothing to do with.
|
|
52
|
+
* Mounts a root `ActionsRegistry` for its lifetime. Children call
|
|
53
|
+
* `useActionsRegistry()` or `useAction()` to participate. Mounts no input
|
|
54
|
+
* listener of its own — the gesture dispatcher owns input.
|
|
287
55
|
*/
|
|
288
|
-
declare function ActionsScope({ children }: {
|
|
289
|
-
children: ReactNode;
|
|
290
|
-
}): ReactElement;
|
|
291
56
|
declare function ActionsProvider({ children }: {
|
|
292
57
|
children: ReactNode;
|
|
293
58
|
}): ReactElement;
|
|
59
|
+
/**
|
|
60
|
+
* @experimental
|
|
61
|
+
* Returns the `ActionsRegistry` in scope, or `null` when there is none.
|
|
62
|
+
*/
|
|
294
63
|
declare function useActionsRegistry(): ActionsRegistry | null;
|
|
295
64
|
/**
|
|
296
65
|
* @experimental
|
|
@@ -301,42 +70,15 @@ declare function useActionsRegistry(): ActionsRegistry | null;
|
|
|
301
70
|
*/
|
|
302
71
|
declare function useAction(action: Action): void;
|
|
303
72
|
|
|
304
|
-
/**
|
|
305
|
-
* One UI control's hold on an ongoing action — the handle a color picker, a
|
|
306
|
-
* slider or a swatch drives the action through, the way a drag drives one
|
|
307
|
-
* through a gesture.
|
|
308
|
-
*
|
|
309
|
-
* - `input(params)` opens the action on the first call and moves it on every
|
|
310
|
-
* later one: the live preview.
|
|
311
|
-
* - `commit(params?)` moves it to `params`, if given, and ends it as one
|
|
312
|
-
* undoable edit. With nothing open, a commit carrying params is a whole edit
|
|
313
|
-
* on its own — a click on a swatch, a switch of paint kind.
|
|
314
|
-
* - `cancel()` ends an open edit without keeping it.
|
|
315
|
-
*
|
|
316
|
-
* All three are no-ops when no action is reachable, so a control renders the
|
|
317
|
-
* same with or without a canvas in scope.
|
|
318
|
-
*/
|
|
319
|
-
interface OngoingAction {
|
|
320
|
-
input(params: Record<string, unknown>): void;
|
|
321
|
-
commit(params?: Record<string, unknown>): void;
|
|
322
|
-
cancel(): void;
|
|
323
|
-
}
|
|
324
|
-
/**
|
|
325
|
-
* Drive the ongoing action `actionId` from a UI control: {@link OngoingAction}.
|
|
326
|
-
*
|
|
327
|
-
* An edit left open when the control unmounts, or when `actionId` changes, is
|
|
328
|
-
* committed — it is what the user was looking at when the control went away.
|
|
329
|
-
* The returned object is stable for the life of the component.
|
|
330
|
-
*/
|
|
331
|
-
declare function useOngoingAction(actionId: string): OngoingAction;
|
|
332
|
-
|
|
333
73
|
/** Which tool is active, plus the stack of tools temporarily held active by a
|
|
334
74
|
* hotkey (space-for-hand and the like). The dispatcher reads this to decide
|
|
335
75
|
* whose bindings are in scope. */
|
|
336
76
|
interface ActiveToolContextValue {
|
|
337
|
-
active
|
|
77
|
+
/** The focused tool's id, or `null` when no tool holds the active slot —
|
|
78
|
+
* a canvas can run on ambient bindings alone. */
|
|
79
|
+
active: string | null;
|
|
338
80
|
hotkeyStack: string[];
|
|
339
|
-
setActive(id: string): void;
|
|
81
|
+
setActive(id: string | null): void;
|
|
340
82
|
pushHotkey(id: string): void;
|
|
341
83
|
/**
|
|
342
84
|
* Disengage a hotkey-held tool. With `id`, removes that tool's own entry
|
|
@@ -346,15 +88,33 @@ interface ActiveToolContextValue {
|
|
|
346
88
|
*/
|
|
347
89
|
popHotkey(id?: string): void;
|
|
348
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* One node of the tool tree. A node that `owns` a tool holds its state; one
|
|
93
|
+
* that does not — a scope inside a yoke — reads and writes its yoke's.
|
|
94
|
+
*/
|
|
95
|
+
declare class ToolNode extends ScopeNode<ToolNode> {
|
|
96
|
+
private state;
|
|
97
|
+
private value;
|
|
98
|
+
constructor(parent: ToolNode | null, descends: boolean, owns: boolean, initialActive: string | null);
|
|
99
|
+
/** The node whose tool a read here resolves to. */
|
|
100
|
+
owner(): ToolNode;
|
|
101
|
+
private update;
|
|
102
|
+
/** A stable value per owner and state, for `useSyncExternalStore`. */
|
|
103
|
+
snapshot(): ActiveToolContextValue;
|
|
104
|
+
subscribe: (listener: () => void) => (() => void);
|
|
105
|
+
}
|
|
349
106
|
/** Props for `<ActiveToolContextProvider>`. */
|
|
350
107
|
interface ActiveToolContextProviderProps {
|
|
351
108
|
children: ReactNode;
|
|
352
|
-
|
|
109
|
+
/** Seeds the active slot, and wins over the first `useTools` call's
|
|
110
|
+
* `active`. Omitted, the slot starts empty and that call seeds it. */
|
|
111
|
+
initialActive?: string | null;
|
|
353
112
|
}
|
|
354
|
-
/** Provides active-tool state
|
|
113
|
+
/** Provides root active-tool state. Every canvas below has a tool of its own;
|
|
114
|
+
* reads and writes here reach the one last used. */
|
|
355
115
|
declare function ActiveToolContextProvider({ children, initialActive, }: ActiveToolContextProviderProps): react.JSX.Element;
|
|
356
116
|
/** The active-tool state in scope. Throws outside a provider; use
|
|
357
|
-
* `
|
|
117
|
+
* `useOptionalActiveToolContext` where one is not guaranteed. */
|
|
358
118
|
declare function useActiveToolContext(): ActiveToolContextValue;
|
|
359
119
|
/**
|
|
360
120
|
* Like `useActiveToolContext`, but returns `null` when no
|
|
@@ -366,33 +126,127 @@ declare function useOptionalActiveToolContext(): ActiveToolContextValue | null;
|
|
|
366
126
|
/**
|
|
367
127
|
* Conditional `<ActiveToolContextProvider>` wrapper. Mounts a provider only
|
|
368
128
|
* when no parent provider is in scope — otherwise renders children unwrapped
|
|
369
|
-
* so
|
|
370
|
-
* existing scope. Mirrors `ActionsProviderIfRoot` / `DepRegistryProviderIfRoot`.
|
|
129
|
+
* so `<WeaselProvider>` defers to the host's existing scope.
|
|
371
130
|
*/
|
|
372
131
|
declare function ActiveToolContextProviderIfRoot({ children, }: {
|
|
373
132
|
children: ReactNode;
|
|
374
133
|
}): react.JSX.Element;
|
|
375
134
|
|
|
135
|
+
/**
|
|
136
|
+
* @experimental
|
|
137
|
+
* Input scopes and yokes: the tiers between a root registry and a canvas.
|
|
138
|
+
* Design: docs/proposals/2026-09-27-input-scopes.md.
|
|
139
|
+
*/
|
|
140
|
+
|
|
141
|
+
/** Props for `<InputScope>`. */
|
|
142
|
+
interface InputScopeProps {
|
|
143
|
+
/** The yoke this scope joins. Omitted, it sits under the registries in
|
|
144
|
+
* scope and keeps a tool of its own. */
|
|
145
|
+
yoke?: Yoke;
|
|
146
|
+
children: ReactNode;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* @experimental
|
|
150
|
+
* One canvas's input: actions, deps and a tool of its own, under whichever
|
|
151
|
+
* registries are in scope (or its yoke's). What the canvas registers stays
|
|
152
|
+
* here; what it looks up is answered here first, then by the tiers above.
|
|
153
|
+
* With nothing in scope it is a root.
|
|
154
|
+
*
|
|
155
|
+
* Chrome above calls `trigger` / `begin` / `useActiveToolContext` on its own
|
|
156
|
+
* registries, and those reach the active scope: the one last activated — by a
|
|
157
|
+
* pointerdown or wheel — or the newest where none has been.
|
|
158
|
+
*/
|
|
159
|
+
declare function InputScope({ yoke, children }: InputScopeProps): ReactElement;
|
|
160
|
+
/**
|
|
161
|
+
* @experimental
|
|
162
|
+
* Make a yoke for canvases to join, under the registries in scope where it is
|
|
163
|
+
* called. It lives as long as the calling component.
|
|
164
|
+
*/
|
|
165
|
+
declare function useYoke(): Yoke;
|
|
166
|
+
/** Props for `<Yoke>`. */
|
|
167
|
+
interface YokeProps {
|
|
168
|
+
value: Yoke;
|
|
169
|
+
children: ReactNode;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* @experimental
|
|
173
|
+
* Canvases that share input on purpose: the active tool, the gesture in
|
|
174
|
+
* flight, history and yoke-wide actions. Made once with `useYoke()` where the
|
|
175
|
+
* sharing is decided, and passed to each canvas that joins it (`yoke={yoke}`)
|
|
176
|
+
* and to `<Yoke value={yoke}>` around anything else that should reach it — a
|
|
177
|
+
* toolbar, say. The members can sit anywhere in the tree: the handle, not the
|
|
178
|
+
* tree, says who shares.
|
|
179
|
+
*/
|
|
180
|
+
interface Yoke {
|
|
181
|
+
/** @internal */
|
|
182
|
+
readonly actions: ActionsNode;
|
|
183
|
+
/** @internal */
|
|
184
|
+
readonly deps: DepNode;
|
|
185
|
+
/** @internal */
|
|
186
|
+
readonly tools: ToolNode;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* @experimental
|
|
190
|
+
* Put `value` in scope for what is not a canvas — a toolbar, a status bar — so
|
|
191
|
+
* its actions, deps and tool reads reach the yoke and, through it, the member
|
|
192
|
+
* canvas last used.
|
|
193
|
+
*/
|
|
194
|
+
declare function Yoke({ value, children }: YokeProps): ReactElement;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* One UI control's hold on an ongoing action — the handle a color picker, a
|
|
198
|
+
* slider or a swatch drives the action through, the way a drag drives one
|
|
199
|
+
* through a gesture.
|
|
200
|
+
*
|
|
201
|
+
* - `input(params)` opens the action on the first call and moves it on every
|
|
202
|
+
* later one: the live preview.
|
|
203
|
+
* - `commit(params?)` moves it to `params`, if given, and ends it as one
|
|
204
|
+
* undoable edit. With nothing open, a commit carrying params is a whole edit
|
|
205
|
+
* on its own — a click on a swatch, a switch of paint kind.
|
|
206
|
+
* - `cancel()` ends an open edit without keeping it.
|
|
207
|
+
*
|
|
208
|
+
* All three are no-ops when no action is reachable, so a control renders the
|
|
209
|
+
* same with or without a canvas in scope.
|
|
210
|
+
*/
|
|
211
|
+
interface OngoingAction {
|
|
212
|
+
input(params: Record<string, unknown>): void;
|
|
213
|
+
commit(params?: Record<string, unknown>): void;
|
|
214
|
+
cancel(): void;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Drive the ongoing action `actionId` from a UI control: {@link OngoingAction}.
|
|
218
|
+
*
|
|
219
|
+
* An edit left open when the control unmounts, or when `actionId` changes, is
|
|
220
|
+
* committed — it is what the user was looking at when the control went away.
|
|
221
|
+
* The returned object is stable for the life of the component.
|
|
222
|
+
*/
|
|
223
|
+
declare function useOngoingAction(actionId: string): OngoingAction;
|
|
224
|
+
|
|
376
225
|
/** Options for `useTools`: which tools exist, which one starts active, and
|
|
377
226
|
* which run continuously regardless of the active one. */
|
|
378
|
-
interface UseToolsOptions<TOverlay =
|
|
379
|
-
/** Initial active-slot tool id
|
|
380
|
-
|
|
227
|
+
interface UseToolsOptions<TOverlay = KernelOverlay> {
|
|
228
|
+
/** Initial active-slot tool id, which must exist in `registry`. Omit, or
|
|
229
|
+
* pass `null`, for no active tool: only hotkey and ambient bindings run. */
|
|
230
|
+
active?: string | null;
|
|
381
231
|
/** Tools eligible for the active slot or hotkey slot. The keys are the
|
|
382
232
|
* tool ids; the values are the tool records. A tool with `hotkey` set
|
|
383
233
|
* is wired into the hotkey slot whenever the engagement state matches. */
|
|
384
234
|
registry: Record<string, AnyToolOf<TOverlay>>;
|
|
385
|
-
/** Always-on
|
|
386
|
-
|
|
235
|
+
/** Always-on entries — tools or other contributions, live regardless of
|
|
236
|
+
* the active slot. */
|
|
237
|
+
ambient?: readonly Contribution<TOverlay>[];
|
|
238
|
+
/** The modes the canvas can be in, read by the dev-time route-conflict
|
|
239
|
+
* check; without a registry it checks against the kit's `DEFAULT_MODES`. */
|
|
240
|
+
modes?: ModeRegistry;
|
|
387
241
|
}
|
|
388
242
|
/** The tool registry's runtime surface: which tool is active, which is
|
|
389
243
|
* temporarily held by a hotkey, and how to change either. */
|
|
390
|
-
interface ToolsApi<TOverlay =
|
|
391
|
-
/** Current active-slot tool id. */
|
|
392
|
-
active: string;
|
|
393
|
-
/** Set the active-slot tool. The gesture dispatcher
|
|
394
|
-
* tool and cancels any in-flight handle itself. */
|
|
395
|
-
setActive: (id: string) => void;
|
|
244
|
+
interface ToolsApi<TOverlay = KernelOverlay> {
|
|
245
|
+
/** Current active-slot tool id, or `null` when no tool is active. */
|
|
246
|
+
active: string | null;
|
|
247
|
+
/** Set the active-slot tool, or `null` to clear it. The gesture dispatcher
|
|
248
|
+
* watches the active tool and cancels any in-flight handle itself. */
|
|
249
|
+
setActive: (id: string | null) => void;
|
|
396
250
|
/** Currently hotkey-engaged tool id (or `null`). Derived as the top of
|
|
397
251
|
* the hotkey stack for backwards compat with the pre-stack API. */
|
|
398
252
|
hotkeyEngaged: string | null;
|
|
@@ -400,8 +254,8 @@ interface ToolsApi<TOverlay = unknown> {
|
|
|
400
254
|
engageHotkey: (id: string) => void;
|
|
401
255
|
/** Disengage the hotkey-slot tool, if any. */
|
|
402
256
|
disengageHotkey: () => void;
|
|
403
|
-
/** All always-on
|
|
404
|
-
ambient: readonly
|
|
257
|
+
/** All always-on entries, in registration order. */
|
|
258
|
+
ambient: readonly Contribution<TOverlay>[];
|
|
405
259
|
/** Full registry — for userland UI (palette buttons, etc.). */
|
|
406
260
|
registry: Readonly<Record<string, AnyToolOf<TOverlay>>>;
|
|
407
261
|
/** Returns true if a tool with the given id is in the registry or ambient list. */
|
|
@@ -424,28 +278,35 @@ interface ToolsApi<TOverlay = unknown> {
|
|
|
424
278
|
* lives in the context so the gesture dispatcher and any sibling
|
|
425
279
|
* `useTools` calls all read the same source of truth.
|
|
426
280
|
*
|
|
427
|
-
* **First-mount-wins semantics**: if
|
|
428
|
-
*
|
|
429
|
-
* the context. Subsequent mounts respect whatever the
|
|
430
|
-
* holds (the first caller wins).
|
|
281
|
+
* **First-mount-wins semantics**: if the context's slot is unseeded (no
|
|
282
|
+
* `initialActive`, nothing set yet) on first mount, `useTools` pushes
|
|
283
|
+
* `opts.active` to the context. Subsequent mounts respect whatever the
|
|
284
|
+
* context currently holds (the first caller wins).
|
|
431
285
|
*/
|
|
432
|
-
declare function useTools<TOverlay =
|
|
286
|
+
declare function useTools<TOverlay = KernelOverlay>(opts: UseToolsOptions<TOverlay>): ToolsApi<TOverlay>;
|
|
433
287
|
|
|
434
|
-
|
|
288
|
+
/** Options for {@link useContributions}. */
|
|
289
|
+
interface UseContributionsOptions<TOverlay = KernelOverlay> {
|
|
435
290
|
/** Every registry entry, in declaration order. Order decides which of two
|
|
436
291
|
* same-specificity bindings in one scope tier wins. */
|
|
437
292
|
entries: readonly Contribution<TOverlay>[];
|
|
438
|
-
/** Desired focused entry id. First-mount-wins against
|
|
439
|
-
* `ActiveToolContext` — see `useTools` for the full semantics. */
|
|
440
|
-
focused: string;
|
|
293
|
+
/** Desired focused entry id, or `null` for none. First-mount-wins against
|
|
294
|
+
* the shared `ActiveToolContext` — see `useTools` for the full semantics. */
|
|
295
|
+
focused: string | null;
|
|
296
|
+
/** The modes the canvas can be in. The dev-time route-conflict check reads
|
|
297
|
+
* them to decide whether two gated actions can ever compete; without a
|
|
298
|
+
* registry it checks against the kit's `DEFAULT_MODES`. */
|
|
299
|
+
modes?: ModeRegistry;
|
|
441
300
|
}
|
|
442
|
-
|
|
301
|
+
/** The assembled registry {@link useContributions} returns. */
|
|
302
|
+
interface ContributionsApi<TOverlay = KernelOverlay> {
|
|
443
303
|
/** Every entry, as passed in. */
|
|
444
304
|
entries: readonly Contribution<TOverlay>[];
|
|
445
|
-
/** Currently focused entry id. */
|
|
446
|
-
focused: string;
|
|
447
|
-
/** Focus an entry. Writes through to the
|
|
448
|
-
|
|
305
|
+
/** Currently focused entry id, or `null` when nothing is focused. */
|
|
306
|
+
focused: string | null;
|
|
307
|
+
/** Focus an entry, or pass `null` to focus none. Writes through to the
|
|
308
|
+
* shared `ActiveToolContext`. */
|
|
309
|
+
setFocused: (id: string | null) => void;
|
|
449
310
|
/** Every live binding, tiered by the declaring entry's eligibility. */
|
|
450
311
|
scopedBindings(): ScopedBinding[];
|
|
451
312
|
/** Overlays of every live entry declaring `position`, ordered active, then
|
|
@@ -462,6 +323,14 @@ interface ContributionsApi<TOverlay = unknown> {
|
|
|
462
323
|
* in the context so the gesture dispatcher and every sibling caller read the
|
|
463
324
|
* same source of truth.
|
|
464
325
|
*/
|
|
465
|
-
declare function useContributions<TOverlay =
|
|
326
|
+
declare function useContributions<TOverlay = KernelOverlay>(opts: UseContributionsOptions<TOverlay>): ContributionsApi<TOverlay>;
|
|
327
|
+
/**
|
|
328
|
+
* Registers the consolidated `tool.offhand` action for every entry declaring
|
|
329
|
+
* an `offhand` trigger, into the nearest `<ActionsProvider>`; its invoker
|
|
330
|
+
* reads `params.toolId` off the matched binding. `useContributions` calls it,
|
|
331
|
+
* and so does any host that assembles its entries above its own provider —
|
|
332
|
+
* `<SceneCanvas>` does — since a null registry here registers nothing.
|
|
333
|
+
*/
|
|
334
|
+
declare function useOffhandAction(entries: readonly Contribution<unknown>[]): void;
|
|
466
335
|
|
|
467
|
-
export { ActionsProvider,
|
|
336
|
+
export { ActionsProvider, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, type ActiveToolContextProviderProps, type ActiveToolContextValue, type ContributionsApi, DepRegistry, DepRegistryProvider, InputScope, type InputScopeProps, type OngoingAction, type ToolsApi, type UseContributionsOptions, type UseToolsOptions, Yoke, type YokeProps, useAction, useActionsRegistry, useActiveToolContext, useContributions, useDepRegistry, useDepSource, useOffhandAction, useOngoingAction, useOptionalActiveToolContext, useOptionalDepRegistry, useTools, useYoke };
|