@weasel-js/routing 1.11.0 → 1.12.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,16 +1,16 @@
1
- import { U as UseGestureDispatcherOptions } from './internal-21M1rTVo.js';
2
- export { D as DispatcherChannels } from './internal-21M1rTVo.js';
1
+ import { U as UseGestureDispatcherOptions } from './internal-DejHXi0a.js';
2
+ export { D as DispatcherChannels } from './internal-DejHXi0a.js';
3
3
  import * as react from 'react';
4
4
  import { ReactNode, ReactElement } from 'react';
5
- import { D as DepRegistry, a as DepName, b as DepSchema, S as ScopeNode, A as Action, c as ActionDeps, M as MenuHost, d as ActionsRegistry, e as ActionDisabledReason, B as BoundGesture, f as ActionVariant, g as MenuDef, h as DepNode, T as ToolNode, N as NodeId, K as KernelOverlay, C as Contribution, i as Tool, O as OverlayPosition } from './index-CmfVvwWD.js';
6
- export { j as ActiveToolContextProvider, k as ActiveToolContextProviderIfRoot, l as ActiveToolContextProviderProps, m as ActiveToolContextValue, u as useActiveToolContext, n as useOptionalActiveToolContext } from './index-CmfVvwWD.js';
5
+ import { D as DepRegistry, a as DepName, b as DepSource, S as ScopeNode, A as Action, c as ActionDeps, M as MenuHost, d as ActionsRegistry, e as ActionDisabledReason, B as BoundGesture, f as ActionVariant, g as MenuDef, h as DepNode, T as ToolNode, K as KeymapDescription, N as NodeId, i as KernelOverlay, C as Contribution, j as Tool, O as OverlayPosition } from './index-CyBCsdie.js';
6
+ export { k as ActiveToolContextProvider, l as ActiveToolContextProviderIfRoot, m as ActiveToolContextProviderProps, n as ActiveToolContextValue, u as useActiveToolContext, o as useOptionalActiveToolContext } from './index-CyBCsdie.js';
7
7
  import { IconRef } from '@weasel-js/icons';
8
8
  import { ModeRegistry } from '@weasel-js/modes';
9
9
  import '@weasel-js/gestures';
10
10
  import '@weasel-js/cursor';
11
+ import '@weasel-js/geom';
11
12
  import '@weasel-js/menu';
12
13
  import '@weasel-js/prefs';
13
- import '@weasel-js/geom';
14
14
  import '@weasel-js/registry';
15
15
  import '@weasel-js/select';
16
16
 
@@ -39,8 +39,8 @@ declare function useDepRegistry(): DepRegistry;
39
39
  declare function useOptionalDepRegistry(): DepRegistry | null;
40
40
  /** Register a live source for `name` for the lifetime of the calling
41
41
  * component. The `source` thunk is called at dispatch time and should
42
- * return the latest value. */
43
- declare function useDepSource<K extends DepName>(name: K, source: () => DepSchema[K]): void;
42
+ * return the latest value; its `below` reads the source it displaced. */
43
+ declare function useDepSource<K extends DepName>(name: K, source: DepSource<K>): void;
44
44
 
45
45
  /**
46
46
  * One node of the actions tree: the actions registered at it, over its
@@ -53,6 +53,8 @@ declare class ActionsNode extends ScopeNode<ActionsNode> {
53
53
  private readonly actions;
54
54
  private readonly muted;
55
55
  private readonly tunings;
56
+ private readonly overrides;
57
+ private overrideCache;
56
58
  private readonly dispatchers;
57
59
  private cache;
58
60
  private host;
@@ -64,6 +66,8 @@ declare class ActionsNode extends ScopeNode<ActionsNode> {
64
66
  private tuningsOf;
65
67
  /** `a` under every tuning for its id from the root down to here. */
66
68
  private tuned;
69
+ /** Every binding override from the root down to here, read into specs. */
70
+ private overridesHere;
67
71
  private snapshot;
68
72
  private deps;
69
73
  /** The actions registered at this node itself, live, unmuted and tuned —
@@ -220,6 +224,10 @@ interface OngoingAction {
220
224
  */
221
225
  declare function useOngoingAction(actionId: string): OngoingAction;
222
226
 
227
+ /** The innermost keymap open on the canvas in scope, described for a hint, or
228
+ * `null` while none is. Re-renders as keymaps open and close. */
229
+ declare function useOpenKeymap(): KeymapDescription | null;
230
+
223
231
  /** World-space pointer position and the view it is over — a
224
232
  * view id, or `null` for a surface's own camera. `null` as a whole when the
225
233
  * pointer is over no publishing surface. */
@@ -412,4 +420,4 @@ declare function useContributions<TOverlay = KernelOverlay>(opts: UseContributio
412
420
  */
413
421
  declare function useOffhandAction(entries: readonly Contribution<unknown>[]): void;
414
422
 
415
- export { ActionsProvider, type CommandOptions, type ContributionsApi, DepRegistry, DepRegistryProvider, InputScope, type InputScopeProps, type OngoingAction, PointerContextProvider, type PointerContextValue, type PointerWorldPos, SelectionContextProvider, SelectionContextProviderIfRoot, type SelectionContextValue, type ToolsApi, type UseContributionsOptions, UseGestureDispatcherOptions, type UseToolsOptions, Yoke, type YokeProps, createPointerStore, useAction, useActionsRegistry, useCommand, useContributions, useDepRegistry, useDepSource, useGestureDispatcher, useMenuHost, useMenus, useOffhandAction, useOngoingAction, useOptionalDepRegistry, usePointerContext, usePointerPosition, usePublishSelection, useSelectionContext, useTools, useYoke };
423
+ export { ActionsProvider, type CommandOptions, type ContributionsApi, DepRegistry, DepRegistryProvider, InputScope, type InputScopeProps, type OngoingAction, PointerContextProvider, type PointerContextValue, type PointerWorldPos, SelectionContextProvider, SelectionContextProviderIfRoot, type SelectionContextValue, type ToolsApi, type UseContributionsOptions, UseGestureDispatcherOptions, type UseToolsOptions, Yoke, type YokeProps, createPointerStore, useAction, useActionsRegistry, useCommand, useContributions, useDepRegistry, useDepSource, useGestureDispatcher, useMenuHost, useMenus, useOffhandAction, useOngoingAction, useOpenKeymap, useOptionalDepRegistry, usePointerContext, usePointerPosition, usePublishSelection, useSelectionContext, useTools, useYoke };
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createDepRegistry, createDispatcher, assembleEntries, liveScope, reportRouteConflicts, offhandKeyFor, makeToolOffhandAction, buildToolOffhandBindings, dlog, ScopeNode, tuneAction, createMenuHost, pushOwner, evaluateEnabled, tunedParams, validateActionId, validateActionDefaultBinding, isTool, depNodeOf, DepNode, buildDepsFromRequires, clientToCanvas, DEV, publishLiveDispatch, LONG_PRESS_HAPTIC_MS, multiTouchGestureId, openPointerSession, addCarrySurface, gestureIdFor, isEditableTarget, focusedControlOwnsKey, itemsFromClipboardData, itemsFromDataTransfer, itemTypesFromDataTransfer, actionCursor } from './chunk-QBQQIFPI.js';
1
+ import { createDepRegistry, createKeymapStack, createDispatchTrace, createDispatcher, describeKeymap, assembleEntries, liveScope, offhandKeyFor, makeToolOffhandAction, buildToolOffhandBindings, dlog, NO_OVERRIDES, reportRouteConflicts, ScopeNode, overrideActionBindings, tuneAction, resolveBindingOverrides, createMenuHost, pushOwner, evaluateEnabled, tunedParams, validateActionId, validateActionDefaultBinding, isTool, depNodeOf, DepNode, buildDepsFromRequires, clientToCanvas, LONG_PRESS_HAPTIC_MS, multiTouchGestureId, openPointerSession, addCarrySurface, carryGestureId, anyBinding, isEditableTarget, focusedControlOwnsKey, itemsFromClipboardData, itemsFromDataTransfer, itemTypesFromDataTransfer, actionCursor } from './chunk-ARDHI47M.js';
2
2
  import { createContext, useState, useContext, useEffect, useMemo, useRef, useCallback, useSyncExternalStore } from 'react';
3
3
  import { isApplePlatform, heldPointers, createPointerRecognizer, isInControlWithin, longPressEvents, stylusOf } from '@weasel-js/gestures';
4
4
  import { isDev, useLatest, useKitSettings, useStableByContent, sameList } from '@weasel-js/react';
@@ -108,7 +108,7 @@ function useDepSource(name, source) {
108
108
  const registry = useDepRegistry();
109
109
  const sourceRef = useLatest(source);
110
110
  useEffect(() => {
111
- return registry.register(name, () => sourceRef.current());
111
+ return registry.register(name, (below) => sourceRef.current(below));
112
112
  }, [name, registry, sourceRef]);
113
113
  }
114
114
  var MenuHostContext = createContext(null);
@@ -148,6 +148,8 @@ var ActionsNode = class extends ScopeNode {
148
148
  actions = /* @__PURE__ */ new Map();
149
149
  muted = /* @__PURE__ */ new Map();
150
150
  tunings = /* @__PURE__ */ new Map();
151
+ overrides = [];
152
+ overrideCache = null;
151
153
  dispatchers = [];
152
154
  cache = null;
153
155
  host = null;
@@ -173,7 +175,17 @@ var ActionsNode = class extends ScopeNode {
173
175
  /** `a` under every tuning for its id from the root down to here. */
174
176
  tuned(a) {
175
177
  const tunings = this.tuningsOf(a.id);
176
- return tunings.length ? tuneAction(a, tunings) : a;
178
+ return overrideActionBindings(tunings.length ? tuneAction(a, tunings) : a, this.overridesHere());
179
+ }
180
+ /** Every binding override from the root down to here, read into specs. */
181
+ overridesHere() {
182
+ const cached = this.overrideCache;
183
+ if (cached && cached.version === this.tree.version) return cached.resolved;
184
+ const maps = [];
185
+ for (const n of this.chain()) maps.unshift(...n.overrides);
186
+ const resolved = resolveBindingOverrides(maps);
187
+ this.overrideCache = { version: this.tree.version, resolved };
188
+ return resolved;
177
189
  }
178
190
  snapshot() {
179
191
  const leaf = this.start();
@@ -291,6 +303,18 @@ var ActionsNode = class extends ScopeNode {
291
303
  };
292
304
  },
293
305
  paramsOf: (id, given) => tunedParams(this.tuningsOf(id), given),
306
+ overrideBindings: (overrides) => {
307
+ const release = pushOwner(this.overrides, overrides);
308
+ this.changed();
309
+ let released = false;
310
+ return () => {
311
+ if (released) return;
312
+ released = true;
313
+ release();
314
+ this.changed();
315
+ };
316
+ },
317
+ bindingOverrides: () => this.start().overridesHere(),
294
318
  list: () => this.snapshot(),
295
319
  trigger: (id, params) => {
296
320
  const leaf = this.start();
@@ -419,6 +443,7 @@ function useToolLifecycle(opts) {
419
443
  function createDispatchCore(src) {
420
444
  const { canvas } = src;
421
445
  let routedId = null;
446
+ let over = false;
422
447
  const target = () => {
423
448
  if (routedId === null) return src.root();
424
449
  return src.views()?.targets().find((t) => t.id === routedId) ?? src.root();
@@ -426,6 +451,10 @@ function createDispatchCore(src) {
426
451
  const dispatcherNow = () => target().dispatcher;
427
452
  const routedDeps = withViewDeps(() => src.context().depRegistry, () => target().deps?.());
428
453
  const routedRuleCtx = () => target().getRuleCtx?.();
454
+ const batch = (fn) => {
455
+ const keymaps = src.context().keymaps;
456
+ return keymaps ? keymaps.batch(fn) : fn();
457
+ };
429
458
  const ctxNow = () => ({
430
459
  ...src.context(),
431
460
  depRegistry: routedDeps,
@@ -461,12 +490,18 @@ function createDispatchCore(src) {
461
490
  const [x, y] = clientToCanvas(canvas, cx, cy);
462
491
  return { x, y };
463
492
  },
464
- dispatch: (ev, on) => {
493
+ dispatch: (ev, on, reach) => batch(() => {
465
494
  const d = on ?? dispatcherNow();
466
495
  src.uses(d);
467
- return d.handleInput(ev, ctxNow());
468
- },
496
+ return d.handleInput(ev, reach ? { ...ctxNow(), keyReach: reach } : ctxNow());
497
+ }),
498
+ batch,
499
+ keymaps: () => src.context().keymaps,
469
500
  holdsKeys: () => canvas ? src.actions().holdsKeys() : src.actions().isActive(),
501
+ pointerOver: () => over,
502
+ setPointerOver: (next) => {
503
+ over = canvas !== null && next;
504
+ },
470
505
  activate: () => src.actions().activate(),
471
506
  disposed: src.disposed
472
507
  };
@@ -505,6 +540,25 @@ function dispatcherWatch(onChange) {
505
540
  };
506
541
  }
507
542
 
543
+ // src/interactions/dispatcher/underPointer.ts
544
+ function underPointer(view, mods, w) {
545
+ const affordance = view.affordanceAt?.(w) ?? void 0;
546
+ const body = view.classifyTarget?.(w);
547
+ return {
548
+ altKey: mods.altKey,
549
+ ctrlKey: mods.ctrlKey,
550
+ metaKey: mods.metaKey,
551
+ shiftKey: mods.shiftKey,
552
+ ...affordance !== void 0 ? { affordance } : {},
553
+ ...body?.body !== void 0 ? { bodyTarget: body.body } : {},
554
+ ...body?.kind !== void 0 ? { bodyKind: body.kind } : {},
555
+ ...body?.doubleClick !== void 0 ? { doubleClickBody: doubleClickBodyOf(body.doubleClick) } : {}
556
+ };
557
+ }
558
+ function doubleClickBodyOf(named) {
559
+ return { bodyTarget: named.body, ...named.kind !== void 0 ? { bodyKind: named.kind } : {} };
560
+ }
561
+
508
562
  // src/interactions/dispatcher/channels/carry.ts
509
563
  function carryInput(core) {
510
564
  const { canvas } = core;
@@ -517,6 +571,7 @@ function carryInput(core) {
517
571
  if (core.disposed()) return "unhandled";
518
572
  const event = () => {
519
573
  const w = core.toWorld(at.clientX, at.clientY);
574
+ const { affordance, bodyTarget, bodyKind } = underPointer(core.target(), at, w);
520
575
  return {
521
576
  kind: "carry",
522
577
  stage,
@@ -530,7 +585,10 @@ function carryInput(core) {
530
585
  altKey: at.altKey,
531
586
  ctrlKey: at.ctrlKey,
532
587
  metaKey: at.metaKey,
533
- shiftKey: at.shiftKey
588
+ shiftKey: at.shiftKey,
589
+ ...affordance !== void 0 ? { affordance } : {},
590
+ ...bodyTarget !== void 0 ? { bodyTarget } : {},
591
+ ...bodyKind !== void 0 ? { bodyKind } : {}
534
592
  };
535
593
  };
536
594
  const routedNow = core.routedView();
@@ -541,13 +599,12 @@ function carryInput(core) {
541
599
  return core.dispatch(event());
542
600
  }
543
601
  if (pinned.has(pointerId)) core.setRoutedView(pinned.get(pointerId));
544
- if (stage !== "move") pinned.delete(pointerId);
545
- const ev = event();
546
- if (stage === "drop" && !core.dispatcherNow().inFlight().has(gestureIdFor(ev))) {
602
+ if (stage === "drop" || stage === "leave") pinned.delete(pointerId);
603
+ if (stage !== "leave" && !core.dispatcherNow().inFlight().has(carryGestureId(pointerId))) {
547
604
  core.routeAt(null, at.clientX, at.clientY);
548
- return core.dispatch(event());
605
+ if (stage === "move") pinned.set(pointerId, core.routedView());
549
606
  }
550
- return core.dispatch(ev);
607
+ return core.dispatch(event());
551
608
  } finally {
552
609
  core.setRoutedView(routedNow);
553
610
  }
@@ -555,25 +612,6 @@ function carryInput(core) {
555
612
  });
556
613
  }
557
614
 
558
- // src/interactions/dispatcher/underPointer.ts
559
- function underPointer(view, mods, w) {
560
- const affordance = view.affordanceAt?.(w) ?? void 0;
561
- const body = view.classifyTarget?.(w);
562
- return {
563
- altKey: mods.altKey,
564
- ctrlKey: mods.ctrlKey,
565
- metaKey: mods.metaKey,
566
- shiftKey: mods.shiftKey,
567
- ...affordance !== void 0 ? { affordance } : {},
568
- ...body?.body !== void 0 ? { bodyTarget: body.body } : {},
569
- ...body?.kind !== void 0 ? { bodyKind: body.kind } : {},
570
- ...body?.doubleClick !== void 0 ? { doubleClickBody: doubleClickBodyOf(body.doubleClick) } : {}
571
- };
572
- }
573
- function doubleClickBodyOf(named) {
574
- return { bodyTarget: named.body, ...named.kind !== void 0 ? { bodyKind: named.kind } : {} };
575
- }
576
-
577
615
  // src/interactions/dispatcher/channels/contextMenu.ts
578
616
  function contextMenuInput(core, takeNativeHold) {
579
617
  const { routeAt, toWorld, target, dispatch } = core;
@@ -618,6 +656,15 @@ function hoverCursorInput(core, paintedCursor, pointersHeld, cursorSink = () =>
618
656
  };
619
657
  let last = null;
620
658
  let facts = null;
659
+ let heldHeard = null;
660
+ const hears = () => {
661
+ if (pointersHeld() === 0) {
662
+ heldHeard = null;
663
+ return true;
664
+ }
665
+ heldHeard ??= anyBinding(ctxNow(), (s) => s.kind === "hover" && s.held !== void 0);
666
+ return heldHeard;
667
+ };
621
668
  const resolve = () => {
622
669
  if (!canvas || !last) return;
623
670
  if (pointersHeld() > 0 || dispatcherNow().inFlight().size > 0) {
@@ -635,7 +682,7 @@ function hoverCursorInput(core, paintedCursor, pointersHeld, cursorSink = () =>
635
682
  const ctx = ctxNow();
636
683
  const d = dispatcherNow();
637
684
  const press = { kind: "pointerdown", x: w.x, y: w.y, clientX: h.clientX, clientY: h.clientY, ...where };
638
- if (DEV) publishLiveDispatch(() => d.explain(press, ctx));
685
+ if (ctx.trace?.recording()) ctx.trace.setLive(() => d.explain(press, ctx));
639
686
  const click = { kind: "click", x: w.x, y: w.y, pressX: w.x, pressY: w.y, ...where };
640
687
  const cursorOf = (r) => r ? actionCursor(r.action, r.params) : void 0;
641
688
  const cursor = cursorOf(d.resolveOnly(press, ctx)) ?? cursorOf(dispatcherNow().resolveOnly(click, ctx));
@@ -657,16 +704,18 @@ function hoverCursorInput(core, paintedCursor, pointersHeld, cursorSink = () =>
657
704
  const w = toWorld(at.clientX, at.clientY);
658
705
  const where = underPointer(target(), at, w);
659
706
  const from = facts ? { from: whereOf(facts.where) } : {};
707
+ const held = pointersHeld() > 0 ? { held: true } : {};
660
708
  facts = { at, where };
661
- dispatch({ kind: "hover", x: w.x, y: w.y, clientX: at.clientX, clientY: at.clientY, ...where, ...from });
709
+ dispatch({ kind: "hover", x: w.x, y: w.y, clientX: at.clientX, clientY: at.clientY, ...where, ...from, ...held });
662
710
  };
663
711
  return {
664
712
  moved(e) {
665
713
  last = hoverOf(e);
666
- if (pointersHeld() === 0) hoverAt(last);
714
+ if (hears()) hoverAt(last);
667
715
  refresh();
668
716
  },
669
717
  released(e) {
718
+ heldHeard = null;
670
719
  last = hoverOf(e);
671
720
  refreshSoon();
672
721
  },
@@ -678,7 +727,7 @@ function hoverCursorInput(core, paintedCursor, pointersHeld, cursorSink = () =>
678
727
  }
679
728
  facts = null;
680
729
  last = null;
681
- if (DEV) publishLiveDispatch(null);
730
+ ctxNow().trace?.setLive(null);
682
731
  paintedCursor()?.clearPointer();
683
732
  override.clear();
684
733
  },
@@ -687,7 +736,7 @@ function hoverCursorInput(core, paintedCursor, pointersHeld, cursorSink = () =>
687
736
  const prev = last;
688
737
  last = { ...last, altKey: e.altKey, ctrlKey: e.ctrlKey, metaKey: e.metaKey, shiftKey: e.shiftKey };
689
738
  const changed = prev.altKey !== last.altKey || prev.ctrlKey !== last.ctrlKey || prev.metaKey !== last.metaKey || prev.shiftKey !== last.shiftKey;
690
- if (changed && pointersHeld() === 0) hoverAt(last);
739
+ if (changed && hears()) hoverAt(last);
691
740
  refreshSoon();
692
741
  },
693
742
  clear: override.clear
@@ -778,50 +827,73 @@ function ingestInput(core) {
778
827
  return { onDragOver, onDragLeave, onDrop, onPaste };
779
828
  }
780
829
 
830
+ // src/interactions/dispatcher/channels/keymapKeys.ts
831
+ function keymapKeys(stack) {
832
+ const leaders = /* @__PURE__ */ new Map();
833
+ const quiet = /* @__PURE__ */ new Set();
834
+ const release = (physical) => {
835
+ if (leaders.delete(physical)) stack()?.release(`key:${physical}`);
836
+ };
837
+ return {
838
+ quiet: (physical) => leaders.has(physical) || quiet.has(physical),
839
+ press: (e, physical, run) => {
840
+ const before = stack()?.innermost() ?? null;
841
+ run();
842
+ const s = stack();
843
+ if (s?.holdOpened(`key:${physical}`)) leaders.set(physical, e.metaKey);
844
+ else if (before !== null && s?.innermost() !== before) quiet.add(physical);
845
+ },
846
+ up: (e, physical) => {
847
+ quiet.delete(physical);
848
+ release(physical);
849
+ if (e.key === "Meta") {
850
+ for (const [k, withCmd] of [...leaders]) if (withCmd) release(k);
851
+ }
852
+ },
853
+ clear: () => {
854
+ leaders.clear();
855
+ quiet.clear();
856
+ }
857
+ };
858
+ }
859
+
781
860
  // src/interactions/dispatcher/channels/keyboard.ts
782
861
  function typedWithAltGraph(e) {
783
862
  if (!e.ctrlKey || !e.altKey || e.key.length !== 1 || !e.getModifierState?.("AltGraph")) return false;
784
863
  const base = /^(?:Key|Digit)(.)$/.exec(e.code)?.[1];
785
864
  return base === void 0 || base.toLowerCase() !== e.key.toLowerCase();
786
865
  }
866
+ function keystrokeEvents(e, mods) {
867
+ const common = { key: e.key, ...e.code ? { code: e.code } : {}, ...mods, metaKey: e.metaKey, shiftKey: e.shiftKey };
868
+ return {
869
+ keyEv: { kind: "key", ...common, repeat: e.repeat },
870
+ heldEv: { kind: "key-held", ...common, phase: "down" }
871
+ };
872
+ }
787
873
  var inMenu = (t) => t instanceof Element && t.closest('[role="menu"]') !== null;
788
874
  function keyboardInput(core) {
789
875
  const { dispatch, dispatcherNow } = core;
790
876
  const heldKeys = /* @__PURE__ */ new Map();
877
+ const keymaps = keymapKeys(core.keymaps);
791
878
  const codeOf = (e) => e.code ? { code: e.code } : {};
792
879
  const physicalKey = (e) => e.code || e.key.toLowerCase();
793
880
  const onKeyDown = (e) => {
794
881
  if (e.defaultPrevented || isEditableTarget(e.target) || inMenu(e.target) || focusedControlOwnsKey(e)) return;
795
- if (!core.holdsKeys()) return;
796
- if (e.repeat && heldKeys.has(physicalKey(e))) {
882
+ const reach = { held: core.holdsKeys(), over: core.pointerOver() };
883
+ if (!reach.held && !reach.over) return;
884
+ if (e.repeat && (heldKeys.has(physicalKey(e)) || keymaps.quiet(physicalKey(e)))) {
797
885
  e.preventDefault();
798
886
  return;
799
887
  }
800
- if (press(e, { altKey: e.altKey, ctrlKey: e.ctrlKey })) return;
801
- if (typedWithAltGraph(e)) press(e, { altKey: false, ctrlKey: false });
888
+ const altGr = typedWithAltGraph(e);
889
+ const typed = { altKey: false, ctrlKey: false };
890
+ keymaps.press(e, physicalKey(e), () => core.batch(() => altGr && core.keymaps()?.innermost() ? press(e, typed, reach) : press(e, { altKey: e.altKey, ctrlKey: e.ctrlKey }, reach) || altGr && press(e, typed, reach)));
802
891
  };
803
- const press = (e, mods) => {
804
- const keyEv = {
805
- kind: "key",
806
- key: e.key,
807
- ...codeOf(e),
808
- ...mods,
809
- metaKey: e.metaKey,
810
- shiftKey: e.shiftKey,
811
- repeat: e.repeat
812
- };
813
- const keyResult = dispatch(keyEv);
814
- const heldEv = {
815
- kind: "key-held",
816
- key: e.key,
817
- ...codeOf(e),
818
- phase: "down",
819
- ...mods,
820
- metaKey: e.metaKey,
821
- shiftKey: e.shiftKey
822
- };
892
+ const press = (e, mods, reach) => {
893
+ const { keyEv, heldEv } = keystrokeEvents(e, mods);
894
+ const keyResult = dispatch(keyEv, void 0, reach);
823
895
  const heldOn = dispatcherNow();
824
- const heldResult = dispatch(heldEv, heldOn);
896
+ const heldResult = dispatch(heldEv, heldOn, reach);
825
897
  if (heldResult === "handled") {
826
898
  heldKeys.set(physicalKey(e), { key: e.key, ...codeOf(e), on: heldOn });
827
899
  }
@@ -831,6 +903,7 @@ function keyboardInput(core) {
831
903
  };
832
904
  const onKeyUp = (e) => {
833
905
  const physical = physicalKey(e);
906
+ keymaps.up(e, physical);
834
907
  const down = heldKeys.get(physical);
835
908
  if (down === void 0) return;
836
909
  const ev = {
@@ -847,6 +920,7 @@ function keyboardInput(core) {
847
920
  dispatch(ev, down.on);
848
921
  };
849
922
  const releaseAll = () => {
923
+ keymaps.clear();
850
924
  const releasing = [...heldKeys.values()];
851
925
  heldKeys.clear();
852
926
  for (const { key, code, on } of releasing) {
@@ -887,6 +961,7 @@ var sampleOf = (e) => ({
887
961
  clientX: e.clientX,
888
962
  clientY: e.clientY,
889
963
  target: e.target,
964
+ timeStamp: e.timeStamp,
890
965
  altKey: e.altKey,
891
966
  ctrlKey: e.ctrlKey,
892
967
  metaKey: e.metaKey,
@@ -1173,6 +1248,7 @@ function attachInput(canvas, src, channels) {
1173
1248
  pinch.end();
1174
1249
  pointer.cancelHeld();
1175
1250
  keys.releaseAll();
1251
+ src.context().keymaps?.closeAll();
1176
1252
  };
1177
1253
  const listeners = listenerSet();
1178
1254
  const keyCapture = channels.keyboard === "first";
@@ -1184,6 +1260,12 @@ function attachInput(canvas, src, channels) {
1184
1260
  listeners.on(canvas, "pointerdown", () => {
1185
1261
  src.actions().activate();
1186
1262
  });
1263
+ const setOver = (on) => () => {
1264
+ core.setPointerOver(on);
1265
+ };
1266
+ listeners.on(canvas, "pointerenter", setOver(true));
1267
+ listeners.on(canvas, "pointermove", setOver(true));
1268
+ listeners.on(canvas, "pointerleave", setOver(false));
1187
1269
  if (channels.pointer) {
1188
1270
  listeners.on(window, "keydown", hover.onKey);
1189
1271
  listeners.on(window, "keyup", hover.onKey);
@@ -1191,6 +1273,14 @@ function attachInput(canvas, src, channels) {
1191
1273
  listeners.on(canvas, "pointerdown", pointer.onPointerDown);
1192
1274
  listeners.on(canvas, "pointermove", pointer.onHoverMove);
1193
1275
  listeners.on(canvas, "dblclick", pointer.onNativeDoubleClick);
1276
+ } else if (channels.hover) {
1277
+ listeners.on(window, "keydown", hover.onKey);
1278
+ listeners.on(window, "keyup", hover.onKey);
1279
+ listeners.on(canvas, "pointerleave", hover.onLeave);
1280
+ listeners.on(canvas, "pointermove", (e) => {
1281
+ core.routeAt(null, e.clientX, e.clientY);
1282
+ hover.moved(e);
1283
+ });
1194
1284
  }
1195
1285
  if (channels.wheel) {
1196
1286
  listeners.on(canvas, "wheel", wheelInput(core, pinch.live), { passive: false, capture: true });
@@ -1222,13 +1312,15 @@ function attachInput(canvas, src, channels) {
1222
1312
  held?.release();
1223
1313
  redraw.stop();
1224
1314
  for (const d of src.dispatchers()) d.cancelAll("cancel");
1315
+ src.context().keymaps?.closeAll();
1225
1316
  };
1226
1317
  }
1227
1318
 
1228
1319
  // src/interactions/dispatcher/useGestureDispatcher.tsx
1229
1320
  function useGestureDispatcher(opts) {
1230
- const { canvasRef, actions, entriesById, enabled = true, keyboard = true, affordanceAt, classifyTarget, dispatcher: dispatcherOpt, clientToWorld, clientToLocal, requestRedraw, paintedCursor, cursorSink, getRuleCtx, onClick, onDoubleClick, views, channels, longPress, claimKeys = false, hears = "scope" } = opts;
1321
+ const { canvasRef, actions, entriesById, enabled = true, keyboard = true, affordanceAt, classifyTarget, dispatcher: dispatcherOpt, trace: traceOpt, clientToWorld, clientToLocal, requestRedraw, paintedCursor, cursorSink, getRuleCtx, onClick, onDoubleClick, views, channels, longPress, claimKeys = false, hears = "scope" } = opts;
1231
1322
  const pointerChannel = channels?.pointer !== false;
1323
+ const hoverChannel = channels?.hover ?? pointerChannel;
1232
1324
  const wheelChannel = channels?.wheel !== false;
1233
1325
  const pinchChannel = channels?.pinch !== false;
1234
1326
  const contextMenuChannel = channels?.contextMenu !== false;
@@ -1239,6 +1331,12 @@ function useGestureDispatcher(opts) {
1239
1331
  const activeTool = useActiveToolContext();
1240
1332
  const depRegistry = useDepRegistry();
1241
1333
  const [isMac] = useState(isApplePlatform);
1334
+ const [keymaps] = useState(createKeymapStack);
1335
+ useEffect(() => depRegistry.register("keymaps", () => keymaps), [depRegistry, keymaps]);
1336
+ const kitSettings = useKitSettings();
1337
+ const [ownTrace] = useState(createDispatchTrace);
1338
+ const trace = traceOpt ?? ownTrace;
1339
+ useEffect(() => depRegistry.register("dispatchTrace", () => trace), [depRegistry, trace]);
1242
1340
  const dispatcherRef = useRef(null);
1243
1341
  if (!dispatcherRef.current) {
1244
1342
  dispatcherRef.current = dispatcherOpt ?? createDispatcher();
@@ -1254,7 +1352,11 @@ function useGestureDispatcher(opts) {
1254
1352
  activeToolId: activeTool.active,
1255
1353
  hotkeyStack: activeTool.hotkeyStack,
1256
1354
  entriesById,
1257
- isMac
1355
+ isMac,
1356
+ trace,
1357
+ bindingOverrides: () => registryRef.current.bindingOverrides(),
1358
+ keymaps,
1359
+ leaderUnbound: kitSettings.get("gestures.keymapUnbound")
1258
1360
  });
1259
1361
  const rootTargetRef = useLatest({
1260
1362
  id: null,
@@ -1273,7 +1375,7 @@ function useGestureDispatcher(opts) {
1273
1375
  const requestRedrawRef = useLatest(requestRedraw);
1274
1376
  const cursorRef = useLatest({ painted: paintedCursor, sink: cursorSink });
1275
1377
  const longPressRef = useLatest(longPress);
1276
- const kitSettingsRef = useLatest(useKitSettings());
1378
+ const kitSettingsRef = useLatest(kitSettings);
1277
1379
  const clicksRef = useRef({ last: null });
1278
1380
  useToolLifecycle({
1279
1381
  entriesById,
@@ -1309,6 +1411,7 @@ function useGestureDispatcher(opts) {
1309
1411
  }, {
1310
1412
  keyboard,
1311
1413
  pointer: pointerChannel,
1414
+ hover: hoverChannel,
1312
1415
  wheel: wheelChannel,
1313
1416
  pinch: pinchChannel,
1314
1417
  contextMenu: contextMenuChannel,
@@ -1320,6 +1423,7 @@ function useGestureDispatcher(opts) {
1320
1423
  keyboard,
1321
1424
  canvasRef,
1322
1425
  pointerChannel,
1426
+ hoverChannel,
1323
1427
  wheelChannel,
1324
1428
  pinchChannel,
1325
1429
  contextMenuChannel,
@@ -1447,6 +1551,34 @@ function useOngoingAction(actionId) {
1447
1551
  }, [edit, actionId]);
1448
1552
  return edit;
1449
1553
  }
1554
+ function useInnermostKeymap() {
1555
+ const deps = useOptionalDepRegistry();
1556
+ const subscribe = useCallback((notify) => {
1557
+ if (!deps) return () => {
1558
+ };
1559
+ let leaveStack = deps.get("keymaps")?.subscribe(notify);
1560
+ const leaveDeps = deps.subscribe?.(() => {
1561
+ leaveStack?.();
1562
+ leaveStack = deps.get("keymaps")?.subscribe(notify);
1563
+ notify();
1564
+ });
1565
+ return () => {
1566
+ leaveStack?.();
1567
+ leaveDeps?.();
1568
+ };
1569
+ }, [deps]);
1570
+ return useSyncExternalStore(subscribe, () => deps?.get("keymaps")?.innermost() ?? null);
1571
+ }
1572
+
1573
+ // src/keymaps/useOpenKeymap.ts
1574
+ function useOpenKeymap() {
1575
+ const registry = useActionsRegistry();
1576
+ const open = useInnermostKeymap();
1577
+ return useMemo(
1578
+ () => open ? describeKeymap(open, registry?.list() ?? [], registry?.bindingOverrides()) : null,
1579
+ [open, registry]
1580
+ );
1581
+ }
1450
1582
  function samePos(a, b) {
1451
1583
  if (a === null || b === null) return a === b;
1452
1584
  return a.worldX === b.worldX && a.worldY === b.worldY && a.viewId === b.viewId;
@@ -1537,7 +1669,7 @@ function usePublishSelection(ids, kinds) {
1537
1669
  }
1538
1670
  }, [ctxRef, serializedRef]);
1539
1671
  }
1540
- var NO_MODES = () => () => {
1672
+ var NO_SUBSCRIBE2 = () => () => {
1541
1673
  };
1542
1674
  function useContributions(opts) {
1543
1675
  const ctx = useActiveToolContext();
@@ -1559,16 +1691,18 @@ function useContributions(opts) {
1559
1691
  }, [ctx]);
1560
1692
  const modes = opts.modes;
1561
1693
  const modeVersion = useSyncExternalStore(
1562
- modes ? modes.subscribe : NO_MODES,
1694
+ modes ? modes.subscribe : NO_SUBSCRIBE2,
1563
1695
  () => modes?.getVersion() ?? 0
1564
1696
  );
1697
+ const openKeymap = useInnermostKeymap()?.id ?? null;
1565
1698
  const snapshot = useCallback(() => assembleEntries(entries, {
1566
1699
  focusedId: focused,
1567
1700
  hotkeyStack: engaged,
1701
+ openKeymap,
1568
1702
  ...modes ? { allowedCapabilities: activeModeOf(modes.current()).allowedCapabilities } : {}
1569
1703
  // The version is what changes; `modes` is one object for the registry's life.
1570
1704
  // eslint-disable-next-line react-hooks/exhaustive-deps
1571
- }), [entries, focused, engaged, modes, modeVersion]);
1705
+ }), [entries, focused, engaged, openKeymap, modes, modeVersion]);
1572
1706
  const overlays = useCallback((position = "top") => {
1573
1707
  const { ordered, state } = snapshot();
1574
1708
  const active = [];
@@ -1591,21 +1725,28 @@ function useContributions(opts) {
1591
1725
  const modesRegistry = opts.modes;
1592
1726
  useEffect(() => {
1593
1727
  if (!isDev()) return;
1594
- const last = lastConflictSigRef.current;
1595
- if (last?.sig === entrySig && last.modes === modesRegistry) return;
1596
- lastConflictSigRef.current = { sig: entrySig, modes: modesRegistry };
1597
- const registry = [];
1598
- const ambient = [];
1599
- for (const entry of entries) {
1600
- if (entry.eligibility?.focus) registry.push(entry);
1601
- if (entry.eligibility?.always || entry.eligibility?.claimed) ambient.push(entry);
1602
- }
1603
- reportRouteConflicts({
1604
- registry,
1605
- ambient,
1606
- actions: actionsRegistry?.list() ?? [],
1607
- ...modesRegistry ? { modes: modesRegistry.list().map(activeModeOf) } : {}
1608
- });
1728
+ const check = () => {
1729
+ const overrides = actionsRegistry?.bindingOverrides() ?? NO_OVERRIDES;
1730
+ const sig = `${entrySig}|${JSON.stringify([...overrides])}`;
1731
+ const last = lastConflictSigRef.current;
1732
+ if (last?.sig === sig && last.modes === modesRegistry) return;
1733
+ lastConflictSigRef.current = { sig, modes: modesRegistry };
1734
+ const registry = [];
1735
+ const ambient = [];
1736
+ for (const entry of entries) {
1737
+ if (entry.eligibility?.focus) registry.push(entry);
1738
+ if (entry.eligibility?.always || entry.eligibility?.claimed) ambient.push(entry);
1739
+ }
1740
+ reportRouteConflicts({
1741
+ registry,
1742
+ ambient,
1743
+ actions: actionsRegistry?.list() ?? [],
1744
+ overrides,
1745
+ ...modesRegistry ? { modes: modesRegistry.list().map(activeModeOf) } : {}
1746
+ });
1747
+ };
1748
+ check();
1749
+ return actionsRegistry?.subscribe(check);
1609
1750
  }, [entrySig, entries, actionsRegistry, modesRegistry]);
1610
1751
  return useMemo(
1611
1752
  () => ({ entries, focused, setFocused, overlays }),
@@ -1726,6 +1867,6 @@ function useTools(opts) {
1726
1867
  );
1727
1868
  }
1728
1869
 
1729
- export { ActionsProvider, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, DepRegistryProvider, InputScope, PointerContextProvider, SelectionContextProvider, SelectionContextProviderIfRoot, Yoke, createPointerStore, useAction, useActionsRegistry, useActiveToolContext, useCommand, useContributions, useDepRegistry, useDepSource, useGestureDispatcher, useMenuHost, useMenus, useOffhandAction, useOngoingAction, useOptionalActiveToolContext, useOptionalDepRegistry, usePointerContext, usePointerPosition, usePublishSelection, useSelectionContext, useTools, useYoke };
1870
+ export { ActionsProvider, ActiveToolContextProvider, ActiveToolContextProviderIfRoot, DepRegistryProvider, InputScope, PointerContextProvider, SelectionContextProvider, SelectionContextProviderIfRoot, Yoke, createPointerStore, useAction, useActionsRegistry, useActiveToolContext, useCommand, useContributions, useDepRegistry, useDepSource, useGestureDispatcher, useMenuHost, useMenus, useOffhandAction, useOngoingAction, useOpenKeymap, useOptionalActiveToolContext, useOptionalDepRegistry, usePointerContext, usePointerPosition, usePublishSelection, useSelectionContext, useTools, useYoke };
1730
1871
  //# sourceMappingURL=react.js.map
1731
1872
  //# sourceMappingURL=react.js.map