@weasel-js/routing 1.6.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.d.ts CHANGED
@@ -1,10 +1,9 @@
1
- import { D as Dispatcher, A as ActionsRegistry, T as Tool, a as AffordanceHit, R as RuleCtx, b as DepSchema, c as Action, d as AnyToolOf, O as OverlayPosition, C as Contribution, S as ScopedBinding } from './index-CKmbNpwl.js';
2
- export { e as DepRegistry, f as DepRegistryProvider, u as useDepRegistry, g as useDepSource, h as useOptionalDepRegistry } from './index-CKmbNpwl.js';
1
+ import { D as Dispatcher, A as ActionsRegistry, C as Contribution, a as AffordanceHit, R as RuleCtx, b as DepSchema, c as DepRegistry, d as DepName, S as ScopeNode, e as Action, f as DepNode, K as KernelOverlay, g as AnyToolOf, O as OverlayPosition, h as ScopedBinding } from './index-Bmq4lSHY.js';
3
2
  import * as react from 'react';
4
3
  import { RefObject, ReactNode, ReactElement } from 'react';
5
4
  import { BodyClassification } from '@weasel-js/gestures';
6
5
  import { PaintedCursorState } from '@weasel-js/cursor';
7
- import '@weasel-js/modes';
6
+ import { ModeRegistry } from '@weasel-js/modes';
8
7
  import '@weasel-js/history';
9
8
 
10
9
  /**
@@ -90,7 +89,7 @@ interface UseGestureDispatcherOptions {
90
89
  /** Action registry (ActionsRegistry from registry.tsx). */
91
90
  actions: ActionsRegistry;
92
91
  /** Tool definitions keyed by id. Typically passes an empty Map. */
93
- toolsById: ReadonlyMap<string, Tool>;
92
+ entriesById: ReadonlyMap<string, Contribution<unknown>>;
94
93
  /** Default true. Set false to opt out of dispatcher wiring (e.g. demos that disable it). */
95
94
  enabled?: boolean;
96
95
  /**
@@ -116,8 +115,14 @@ interface UseGestureDispatcherOptions {
116
115
  * contextmenu channels stay live. `<SceneCanvas>` wires this to
117
116
  * `enableKeybindings`, so opting out of keybindings disables the modern
118
117
  * dispatcher key path as well as the legacy `useKeybindings` hook.
118
+ *
119
+ * Every dispatcher hears keys on `window`, and a keystroke one of them
120
+ * claims — or anything else calls `preventDefault` on — is not dispatched by
121
+ * the rest. `'first'` listens in the capture phase, so this dispatcher gets
122
+ * the keystroke ahead of every dispatcher listening normally, whatever the
123
+ * mount order: the shape for chrome that owns a key only when it can act.
119
124
  */
120
- keyboard?: boolean;
125
+ keyboard?: boolean | 'first';
121
126
  /**
122
127
  * Optional affordance classifier. Called with the **world** coordinates of
123
128
  * the pointer — this hook applies the routed view's `clientToWorld` before
@@ -268,29 +273,59 @@ interface UseGestureDispatcherOptions {
268
273
  */
269
274
  declare function useGestureDispatcher(opts: UseGestureDispatcherOptions): void;
270
275
 
276
+ /** Provides the dep registry for a canvas. `<SceneCanvas>` mounts one; a
277
+ * consumer registering its own dep sources must be inside it. */
278
+ declare function DepRegistryProvider({ children }: {
279
+ children: ReactNode;
280
+ }): react.JSX.Element;
281
+ /** The dep registry in scope. Throws outside a `<DepRegistryProvider>`. */
282
+ declare function useDepRegistry(): DepRegistry;
271
283
  /**
272
- * @experimental
273
- * The React half of the actions registry: the context, the provider that owns
274
- * the id → descriptor stack, the mute scope, and the two hooks. The store
275
- * contract it implements is in `registry.tsx`.
284
+ * Like `useDepRegistry`, but returns `null` when no `<DepRegistryProvider>` is
285
+ * in scope instead of throwing. Used by `useStandardActions` to preserve its
286
+ * silent-no-op contract when neither provider is present.
276
287
  */
288
+ declare function useOptionalDepRegistry(): DepRegistry | null;
289
+ /** Register a live source for `name` for the lifetime of the calling
290
+ * component. The `source` thunk is called at dispatch time and should
291
+ * return the latest value. */
292
+ declare function useDepSource<K extends DepName>(name: K, source: () => DepSchema[K]): void;
277
293
 
294
+ /**
295
+ * One node of the actions tree: the actions registered at it, over its
296
+ * parent's. Each id holds a stack of registrants, newest live, so a displaced
297
+ * registrant is restored when the one above it leaves. Every read the registry
298
+ * answers — `list`, `trigger`, `begin` — starts at the node's leaf.
299
+ */
300
+ declare class ActionsNode extends ScopeNode<ActionsNode> {
301
+ private readonly contextDeps;
302
+ private readonly actions;
303
+ private readonly muted;
304
+ private readonly dispatchers;
305
+ private cache;
306
+ /** Called on `activate`, so a scope's dep and tool nodes move with it. */
307
+ readonly onActivate: (() => void)[];
308
+ constructor(parent: ActionsNode | null, descends: boolean, contextDeps: () => DepRegistry | null);
309
+ private resolve;
310
+ private snapshot;
311
+ private deps;
312
+ private dispatcher;
313
+ activate(): void;
314
+ readonly registry: ActionsRegistry;
315
+ }
278
316
  /**
279
317
  * @experimental
280
- * A view of the registry in scope that can declare ids not for itself. Wrap
281
- * anything that should be able to opt out of an action — a second
282
- * `<SceneCanvas>` sharing the host's `<ActionsProvider>` mounts one — and its
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.
318
+ * Mounts a root `ActionsRegistry` for its lifetime. Children call
319
+ * `useActionsRegistry()` or `useAction()` to participate. Mounts no input
320
+ * listener of its own — the gesture dispatcher owns input.
287
321
  */
288
- declare function ActionsScope({ children }: {
289
- children: ReactNode;
290
- }): ReactElement;
291
322
  declare function ActionsProvider({ children }: {
292
323
  children: ReactNode;
293
324
  }): ReactElement;
325
+ /**
326
+ * @experimental
327
+ * Returns the `ActionsRegistry` in scope, or `null` when there is none.
328
+ */
294
329
  declare function useActionsRegistry(): ActionsRegistry | null;
295
330
  /**
296
331
  * @experimental
@@ -301,42 +336,15 @@ declare function useActionsRegistry(): ActionsRegistry | null;
301
336
  */
302
337
  declare function useAction(action: Action): void;
303
338
 
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
339
  /** Which tool is active, plus the stack of tools temporarily held active by a
334
340
  * hotkey (space-for-hand and the like). The dispatcher reads this to decide
335
341
  * whose bindings are in scope. */
336
342
  interface ActiveToolContextValue {
337
- active: string;
343
+ /** The focused tool's id, or `null` when no tool holds the active slot —
344
+ * a canvas can run on ambient bindings alone. */
345
+ active: string | null;
338
346
  hotkeyStack: string[];
339
- setActive(id: string): void;
347
+ setActive(id: string | null): void;
340
348
  pushHotkey(id: string): void;
341
349
  /**
342
350
  * Disengage a hotkey-held tool. With `id`, removes that tool's own entry
@@ -346,15 +354,33 @@ interface ActiveToolContextValue {
346
354
  */
347
355
  popHotkey(id?: string): void;
348
356
  }
357
+ /**
358
+ * One node of the tool tree. A node that `owns` a tool holds its state; one
359
+ * that does not — a scope inside a yoke — reads and writes its yoke's.
360
+ */
361
+ declare class ToolNode extends ScopeNode<ToolNode> {
362
+ private state;
363
+ private value;
364
+ constructor(parent: ToolNode | null, descends: boolean, owns: boolean, initialActive: string | null);
365
+ /** The node whose tool a read here resolves to. */
366
+ owner(): ToolNode;
367
+ private update;
368
+ /** A stable value per owner and state, for `useSyncExternalStore`. */
369
+ snapshot(): ActiveToolContextValue;
370
+ subscribe: (listener: () => void) => (() => void);
371
+ }
349
372
  /** Props for `<ActiveToolContextProvider>`. */
350
373
  interface ActiveToolContextProviderProps {
351
374
  children: ReactNode;
352
- initialActive?: string;
375
+ /** Seeds the active slot, and wins over the first `useTools` call's
376
+ * `active`. Omitted, the slot starts empty and that call seeds it. */
377
+ initialActive?: string | null;
353
378
  }
354
- /** Provides active-tool state for a canvas. `<SceneCanvas>` mounts one. */
379
+ /** Provides root active-tool state. Every canvas below has a tool of its own;
380
+ * reads and writes here reach the one last used. */
355
381
  declare function ActiveToolContextProvider({ children, initialActive, }: ActiveToolContextProviderProps): react.JSX.Element;
356
382
  /** The active-tool state in scope. Throws outside a provider; use
357
- * `useActiveToolContextOptional` where one is not guaranteed. */
383
+ * `useOptionalActiveToolContext` where one is not guaranteed. */
358
384
  declare function useActiveToolContext(): ActiveToolContextValue;
359
385
  /**
360
386
  * Like `useActiveToolContext`, but returns `null` when no
@@ -366,33 +392,127 @@ declare function useOptionalActiveToolContext(): ActiveToolContextValue | null;
366
392
  /**
367
393
  * Conditional `<ActiveToolContextProvider>` wrapper. Mounts a provider only
368
394
  * when no parent provider is in scope — otherwise renders children unwrapped
369
- * so callers (e.g. `<WeaselProvider>`, `<SceneCanvas>`) defer to the host's
370
- * existing scope. Mirrors `ActionsProviderIfRoot` / `DepRegistryProviderIfRoot`.
395
+ * so `<WeaselProvider>` defers to the host's existing scope.
371
396
  */
372
397
  declare function ActiveToolContextProviderIfRoot({ children, }: {
373
398
  children: ReactNode;
374
399
  }): react.JSX.Element;
375
400
 
401
+ /**
402
+ * @experimental
403
+ * Input scopes and yokes: the tiers between a root registry and a canvas.
404
+ * Design: docs/proposals/2026-09-27-input-scopes.md.
405
+ */
406
+
407
+ /** Props for `<InputScope>`. */
408
+ interface InputScopeProps {
409
+ /** The yoke this scope joins. Omitted, it sits under the registries in
410
+ * scope and keeps a tool of its own. */
411
+ yoke?: Yoke;
412
+ children: ReactNode;
413
+ }
414
+ /**
415
+ * @experimental
416
+ * One canvas's input: actions, deps and a tool of its own, under whichever
417
+ * registries are in scope (or its yoke's). What the canvas registers stays
418
+ * here; what it looks up is answered here first, then by the tiers above.
419
+ * With nothing in scope it is a root.
420
+ *
421
+ * Chrome above calls `trigger` / `begin` / `useActiveToolContext` on its own
422
+ * registries, and those reach the active scope: the one last activated — by a
423
+ * pointerdown or wheel — or the newest where none has been.
424
+ */
425
+ declare function InputScope({ yoke, children }: InputScopeProps): ReactElement;
426
+ /**
427
+ * @experimental
428
+ * Make a yoke for canvases to join, under the registries in scope where it is
429
+ * called. It lives as long as the calling component.
430
+ */
431
+ declare function useYoke(): Yoke;
432
+ /** Props for `<Yoke>`. */
433
+ interface YokeProps {
434
+ value: Yoke;
435
+ children: ReactNode;
436
+ }
437
+ /**
438
+ * @experimental
439
+ * Canvases that share input on purpose: the active tool, the gesture in
440
+ * flight, history and yoke-wide actions. Made once with `useYoke()` where the
441
+ * sharing is decided, and passed to each canvas that joins it (`yoke={yoke}`)
442
+ * and to `<Yoke value={yoke}>` around anything else that should reach it — a
443
+ * toolbar, say. The members can sit anywhere in the tree: the handle, not the
444
+ * tree, says who shares.
445
+ */
446
+ interface Yoke {
447
+ /** @internal */
448
+ readonly actions: ActionsNode;
449
+ /** @internal */
450
+ readonly deps: DepNode;
451
+ /** @internal */
452
+ readonly tools: ToolNode;
453
+ }
454
+ /**
455
+ * @experimental
456
+ * Put `value` in scope for what is not a canvas — a toolbar, a status bar — so
457
+ * its actions, deps and tool reads reach the yoke and, through it, the member
458
+ * canvas last used.
459
+ */
460
+ declare function Yoke({ value, children }: YokeProps): ReactElement;
461
+
462
+ /**
463
+ * One UI control's hold on an ongoing action — the handle a color picker, a
464
+ * slider or a swatch drives the action through, the way a drag drives one
465
+ * through a gesture.
466
+ *
467
+ * - `input(params)` opens the action on the first call and moves it on every
468
+ * later one: the live preview.
469
+ * - `commit(params?)` moves it to `params`, if given, and ends it as one
470
+ * undoable edit. With nothing open, a commit carrying params is a whole edit
471
+ * on its own — a click on a swatch, a switch of paint kind.
472
+ * - `cancel()` ends an open edit without keeping it.
473
+ *
474
+ * All three are no-ops when no action is reachable, so a control renders the
475
+ * same with or without a canvas in scope.
476
+ */
477
+ interface OngoingAction {
478
+ input(params: Record<string, unknown>): void;
479
+ commit(params?: Record<string, unknown>): void;
480
+ cancel(): void;
481
+ }
482
+ /**
483
+ * Drive the ongoing action `actionId` from a UI control: {@link OngoingAction}.
484
+ *
485
+ * An edit left open when the control unmounts, or when `actionId` changes, is
486
+ * committed — it is what the user was looking at when the control went away.
487
+ * The returned object is stable for the life of the component.
488
+ */
489
+ declare function useOngoingAction(actionId: string): OngoingAction;
490
+
376
491
  /** Options for `useTools`: which tools exist, which one starts active, and
377
492
  * which run continuously regardless of the active one. */
378
- interface UseToolsOptions<TOverlay = unknown> {
379
- /** Initial active-slot tool id. Must exist in `registry`. */
380
- active: string;
493
+ interface UseToolsOptions<TOverlay = KernelOverlay> {
494
+ /** Initial active-slot tool id, which must exist in `registry`. Omit, or
495
+ * pass `null`, for no active tool: only hotkey and ambient bindings run. */
496
+ active?: string | null;
381
497
  /** Tools eligible for the active slot or hotkey slot. The keys are the
382
498
  * tool ids; the values are the tool records. A tool with `hotkey` set
383
499
  * is wired into the hotkey slot whenever the engagement state matches. */
384
500
  registry: Record<string, AnyToolOf<TOverlay>>;
385
- /** Always-on tools — listen continuously regardless of active slot. */
386
- ambient?: AnyToolOf<TOverlay>[];
501
+ /** Always-on entries — tools or other contributions, live regardless of
502
+ * the active slot. */
503
+ ambient?: readonly Contribution<TOverlay>[];
504
+ /** The modes the canvas can be in, read by the dev-time route-conflict
505
+ * check; without a registry it checks against the kit's `DEFAULT_MODES`. */
506
+ modes?: ModeRegistry;
387
507
  }
388
508
  /** The tool registry's runtime surface: which tool is active, which is
389
509
  * temporarily held by a hotkey, and how to change either. */
390
- interface ToolsApi<TOverlay = unknown> {
391
- /** Current active-slot tool id. */
392
- active: string;
393
- /** Set the active-slot tool. The gesture dispatcher watches the active
394
- * tool and cancels any in-flight handle itself. */
395
- setActive: (id: string) => void;
510
+ interface ToolsApi<TOverlay = KernelOverlay> {
511
+ /** Current active-slot tool id, or `null` when no tool is active. */
512
+ active: string | null;
513
+ /** Set the active-slot tool, or `null` to clear it. The gesture dispatcher
514
+ * watches the active tool and cancels any in-flight handle itself. */
515
+ setActive: (id: string | null) => void;
396
516
  /** Currently hotkey-engaged tool id (or `null`). Derived as the top of
397
517
  * the hotkey stack for backwards compat with the pre-stack API. */
398
518
  hotkeyEngaged: string | null;
@@ -400,8 +520,8 @@ interface ToolsApi<TOverlay = unknown> {
400
520
  engageHotkey: (id: string) => void;
401
521
  /** Disengage the hotkey-slot tool, if any. */
402
522
  disengageHotkey: () => void;
403
- /** All always-on tools, in registration order. */
404
- ambient: readonly AnyToolOf<TOverlay>[];
523
+ /** All always-on entries, in registration order. */
524
+ ambient: readonly Contribution<TOverlay>[];
405
525
  /** Full registry — for userland UI (palette buttons, etc.). */
406
526
  registry: Readonly<Record<string, AnyToolOf<TOverlay>>>;
407
527
  /** Returns true if a tool with the given id is in the registry or ambient list. */
@@ -424,28 +544,33 @@ interface ToolsApi<TOverlay = unknown> {
424
544
  * lives in the context so the gesture dispatcher and any sibling
425
545
  * `useTools` calls all read the same source of truth.
426
546
  *
427
- * **First-mount-wins semantics**: if `opts.active` differs from the context
428
- * default (`'select'`) on first mount, `useTools` pushes `opts.active` to
429
- * the context. Subsequent mounts respect whatever the context currently
430
- * holds (the first caller wins).
547
+ * **First-mount-wins semantics**: if the context's slot is unseeded (no
548
+ * `initialActive`, nothing set yet) on first mount, `useTools` pushes
549
+ * `opts.active` to the context. Subsequent mounts respect whatever the
550
+ * context currently holds (the first caller wins).
431
551
  */
432
- declare function useTools<TOverlay = unknown>(opts: UseToolsOptions<TOverlay>): ToolsApi<TOverlay>;
552
+ declare function useTools<TOverlay = KernelOverlay>(opts: UseToolsOptions<TOverlay>): ToolsApi<TOverlay>;
433
553
 
434
- interface UseContributionsOptions<TOverlay = unknown> {
554
+ interface UseContributionsOptions<TOverlay = KernelOverlay> {
435
555
  /** Every registry entry, in declaration order. Order decides which of two
436
556
  * same-specificity bindings in one scope tier wins. */
437
557
  entries: readonly Contribution<TOverlay>[];
438
- /** Desired focused entry id. First-mount-wins against the shared
439
- * `ActiveToolContext` — see `useTools` for the full semantics. */
440
- focused: string;
558
+ /** Desired focused entry id, or `null` for none. First-mount-wins against
559
+ * the shared `ActiveToolContext` — see `useTools` for the full semantics. */
560
+ focused: string | null;
561
+ /** The modes the canvas can be in. The dev-time route-conflict check reads
562
+ * them to decide whether two gated actions can ever compete; without a
563
+ * registry it checks against the kit's `DEFAULT_MODES`. */
564
+ modes?: ModeRegistry;
441
565
  }
442
- interface ContributionsApi<TOverlay = unknown> {
566
+ interface ContributionsApi<TOverlay = KernelOverlay> {
443
567
  /** Every entry, as passed in. */
444
568
  entries: readonly Contribution<TOverlay>[];
445
- /** Currently focused entry id. */
446
- focused: string;
447
- /** Focus an entry. Writes through to the shared `ActiveToolContext`. */
448
- setFocused: (id: string) => void;
569
+ /** Currently focused entry id, or `null` when nothing is focused. */
570
+ focused: string | null;
571
+ /** Focus an entry, or pass `null` to focus none. Writes through to the
572
+ * shared `ActiveToolContext`. */
573
+ setFocused: (id: string | null) => void;
449
574
  /** Every live binding, tiered by the declaring entry's eligibility. */
450
575
  scopedBindings(): ScopedBinding[];
451
576
  /** Overlays of every live entry declaring `position`, ordered active, then
@@ -462,6 +587,14 @@ interface ContributionsApi<TOverlay = unknown> {
462
587
  * in the context so the gesture dispatcher and every sibling caller read the
463
588
  * same source of truth.
464
589
  */
465
- declare function useContributions<TOverlay = unknown>(opts: UseContributionsOptions<TOverlay>): ContributionsApi<TOverlay>;
590
+ declare function useContributions<TOverlay = KernelOverlay>(opts: UseContributionsOptions<TOverlay>): ContributionsApi<TOverlay>;
591
+ /**
592
+ * Registers the consolidated `tool.offhand` action for every entry declaring
593
+ * an `offhand` trigger, into the nearest `<ActionsProvider>`; its invoker
594
+ * reads `params.toolId` off the matched binding. `useContributions` calls it,
595
+ * and so does any host that assembles its entries above its own provider —
596
+ * `<SceneCanvas>` does — since a null registry here registers nothing.
597
+ */
598
+ declare function useOffhandAction(entries: readonly Contribution<unknown>[]): void;
466
599
 
467
- export { ActionsProvider, ActionsScope, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, type ActiveToolContextProviderProps, type ActiveToolContextValue, type ContributionsApi, type DispatcherChannels, type DispatcherViewTarget, type OngoingAction, type ToolsApi, type UseContributionsOptions, type UseGestureDispatcherOptions, type UseToolsOptions, type ViewIdResolver, useAction, useActionsRegistry, useActiveToolContext, useContributions, useGestureDispatcher, useOngoingAction, useOptionalActiveToolContext, useTools };
600
+ export { ActionsProvider, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, type ActiveToolContextProviderProps, type ActiveToolContextValue, type ContributionsApi, DepRegistry, DepRegistryProvider, type DispatcherChannels, type DispatcherViewTarget, InputScope, type InputScopeProps, type OngoingAction, type ToolsApi, type UseContributionsOptions, type UseGestureDispatcherOptions, type UseToolsOptions, type ViewIdResolver, Yoke, type YokeProps, useAction, useActionsRegistry, useActiveToolContext, useContributions, useDepRegistry, useDepSource, useGestureDispatcher, useOffhandAction, useOngoingAction, useOptionalActiveToolContext, useOptionalDepRegistry, useTools, useYoke };