@weasel-js/routing 1.11.1 → 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.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createDepRegistry, createDispatchTrace, createDispatcher, assembleEntries, liveScope, reportRouteConflicts, offhandKeyFor, makeToolOffhandAction, buildToolOffhandBindings, dlog, ScopeNode, tuneAction, 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-KLBH7VFF.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';
@@ -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();
@@ -427,6 +451,10 @@ function createDispatchCore(src) {
427
451
  const dispatcherNow = () => target().dispatcher;
428
452
  const routedDeps = withViewDeps(() => src.context().depRegistry, () => target().deps?.());
429
453
  const routedRuleCtx = () => target().getRuleCtx?.();
454
+ const batch = (fn) => {
455
+ const keymaps = src.context().keymaps;
456
+ return keymaps ? keymaps.batch(fn) : fn();
457
+ };
430
458
  const ctxNow = () => ({
431
459
  ...src.context(),
432
460
  depRegistry: routedDeps,
@@ -462,11 +490,13 @@ function createDispatchCore(src) {
462
490
  const [x, y] = clientToCanvas(canvas, cx, cy);
463
491
  return { x, y };
464
492
  },
465
- dispatch: (ev, on, reach) => {
493
+ dispatch: (ev, on, reach) => batch(() => {
466
494
  const d = on ?? dispatcherNow();
467
495
  src.uses(d);
468
496
  return d.handleInput(ev, reach ? { ...ctxNow(), keyReach: reach } : ctxNow());
469
- },
497
+ }),
498
+ batch,
499
+ keymaps: () => src.context().keymaps,
470
500
  holdsKeys: () => canvas ? src.actions().holdsKeys() : src.actions().isActive(),
471
501
  pointerOver: () => over,
472
502
  setPointerOver: (next) => {
@@ -797,49 +827,71 @@ function ingestInput(core) {
797
827
  return { onDragOver, onDragLeave, onDrop, onPaste };
798
828
  }
799
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
+
800
860
  // src/interactions/dispatcher/channels/keyboard.ts
801
861
  function typedWithAltGraph(e) {
802
862
  if (!e.ctrlKey || !e.altKey || e.key.length !== 1 || !e.getModifierState?.("AltGraph")) return false;
803
863
  const base = /^(?:Key|Digit)(.)$/.exec(e.code)?.[1];
804
864
  return base === void 0 || base.toLowerCase() !== e.key.toLowerCase();
805
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
+ }
806
873
  var inMenu = (t) => t instanceof Element && t.closest('[role="menu"]') !== null;
807
874
  function keyboardInput(core) {
808
875
  const { dispatch, dispatcherNow } = core;
809
876
  const heldKeys = /* @__PURE__ */ new Map();
877
+ const keymaps = keymapKeys(core.keymaps);
810
878
  const codeOf = (e) => e.code ? { code: e.code } : {};
811
879
  const physicalKey = (e) => e.code || e.key.toLowerCase();
812
880
  const onKeyDown = (e) => {
813
881
  if (e.defaultPrevented || isEditableTarget(e.target) || inMenu(e.target) || focusedControlOwnsKey(e)) return;
814
882
  const reach = { held: core.holdsKeys(), over: core.pointerOver() };
815
883
  if (!reach.held && !reach.over) return;
816
- if (e.repeat && heldKeys.has(physicalKey(e))) {
884
+ if (e.repeat && (heldKeys.has(physicalKey(e)) || keymaps.quiet(physicalKey(e)))) {
817
885
  e.preventDefault();
818
886
  return;
819
887
  }
820
- if (press(e, { altKey: e.altKey, ctrlKey: e.ctrlKey }, reach)) return;
821
- if (typedWithAltGraph(e)) press(e, { altKey: false, ctrlKey: false }, reach);
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)));
822
891
  };
823
892
  const press = (e, mods, reach) => {
824
- const keyEv = {
825
- kind: "key",
826
- key: e.key,
827
- ...codeOf(e),
828
- ...mods,
829
- metaKey: e.metaKey,
830
- shiftKey: e.shiftKey,
831
- repeat: e.repeat
832
- };
893
+ const { keyEv, heldEv } = keystrokeEvents(e, mods);
833
894
  const keyResult = dispatch(keyEv, void 0, reach);
834
- const heldEv = {
835
- kind: "key-held",
836
- key: e.key,
837
- ...codeOf(e),
838
- phase: "down",
839
- ...mods,
840
- metaKey: e.metaKey,
841
- shiftKey: e.shiftKey
842
- };
843
895
  const heldOn = dispatcherNow();
844
896
  const heldResult = dispatch(heldEv, heldOn, reach);
845
897
  if (heldResult === "handled") {
@@ -851,6 +903,7 @@ function keyboardInput(core) {
851
903
  };
852
904
  const onKeyUp = (e) => {
853
905
  const physical = physicalKey(e);
906
+ keymaps.up(e, physical);
854
907
  const down = heldKeys.get(physical);
855
908
  if (down === void 0) return;
856
909
  const ev = {
@@ -867,6 +920,7 @@ function keyboardInput(core) {
867
920
  dispatch(ev, down.on);
868
921
  };
869
922
  const releaseAll = () => {
923
+ keymaps.clear();
870
924
  const releasing = [...heldKeys.values()];
871
925
  heldKeys.clear();
872
926
  for (const { key, code, on } of releasing) {
@@ -1194,6 +1248,7 @@ function attachInput(canvas, src, channels) {
1194
1248
  pinch.end();
1195
1249
  pointer.cancelHeld();
1196
1250
  keys.releaseAll();
1251
+ src.context().keymaps?.closeAll();
1197
1252
  };
1198
1253
  const listeners = listenerSet();
1199
1254
  const keyCapture = channels.keyboard === "first";
@@ -1257,6 +1312,7 @@ function attachInput(canvas, src, channels) {
1257
1312
  held?.release();
1258
1313
  redraw.stop();
1259
1314
  for (const d of src.dispatchers()) d.cancelAll("cancel");
1315
+ src.context().keymaps?.closeAll();
1260
1316
  };
1261
1317
  }
1262
1318
 
@@ -1275,6 +1331,9 @@ function useGestureDispatcher(opts) {
1275
1331
  const activeTool = useActiveToolContext();
1276
1332
  const depRegistry = useDepRegistry();
1277
1333
  const [isMac] = useState(isApplePlatform);
1334
+ const [keymaps] = useState(createKeymapStack);
1335
+ useEffect(() => depRegistry.register("keymaps", () => keymaps), [depRegistry, keymaps]);
1336
+ const kitSettings = useKitSettings();
1278
1337
  const [ownTrace] = useState(createDispatchTrace);
1279
1338
  const trace = traceOpt ?? ownTrace;
1280
1339
  useEffect(() => depRegistry.register("dispatchTrace", () => trace), [depRegistry, trace]);
@@ -1294,7 +1353,10 @@ function useGestureDispatcher(opts) {
1294
1353
  hotkeyStack: activeTool.hotkeyStack,
1295
1354
  entriesById,
1296
1355
  isMac,
1297
- trace
1356
+ trace,
1357
+ bindingOverrides: () => registryRef.current.bindingOverrides(),
1358
+ keymaps,
1359
+ leaderUnbound: kitSettings.get("gestures.keymapUnbound")
1298
1360
  });
1299
1361
  const rootTargetRef = useLatest({
1300
1362
  id: null,
@@ -1313,7 +1375,7 @@ function useGestureDispatcher(opts) {
1313
1375
  const requestRedrawRef = useLatest(requestRedraw);
1314
1376
  const cursorRef = useLatest({ painted: paintedCursor, sink: cursorSink });
1315
1377
  const longPressRef = useLatest(longPress);
1316
- const kitSettingsRef = useLatest(useKitSettings());
1378
+ const kitSettingsRef = useLatest(kitSettings);
1317
1379
  const clicksRef = useRef({ last: null });
1318
1380
  useToolLifecycle({
1319
1381
  entriesById,
@@ -1489,6 +1551,34 @@ function useOngoingAction(actionId) {
1489
1551
  }, [edit, actionId]);
1490
1552
  return edit;
1491
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
+ }
1492
1582
  function samePos(a, b) {
1493
1583
  if (a === null || b === null) return a === b;
1494
1584
  return a.worldX === b.worldX && a.worldY === b.worldY && a.viewId === b.viewId;
@@ -1579,7 +1669,7 @@ function usePublishSelection(ids, kinds) {
1579
1669
  }
1580
1670
  }, [ctxRef, serializedRef]);
1581
1671
  }
1582
- var NO_MODES = () => () => {
1672
+ var NO_SUBSCRIBE2 = () => () => {
1583
1673
  };
1584
1674
  function useContributions(opts) {
1585
1675
  const ctx = useActiveToolContext();
@@ -1601,16 +1691,18 @@ function useContributions(opts) {
1601
1691
  }, [ctx]);
1602
1692
  const modes = opts.modes;
1603
1693
  const modeVersion = useSyncExternalStore(
1604
- modes ? modes.subscribe : NO_MODES,
1694
+ modes ? modes.subscribe : NO_SUBSCRIBE2,
1605
1695
  () => modes?.getVersion() ?? 0
1606
1696
  );
1697
+ const openKeymap = useInnermostKeymap()?.id ?? null;
1607
1698
  const snapshot = useCallback(() => assembleEntries(entries, {
1608
1699
  focusedId: focused,
1609
1700
  hotkeyStack: engaged,
1701
+ openKeymap,
1610
1702
  ...modes ? { allowedCapabilities: activeModeOf(modes.current()).allowedCapabilities } : {}
1611
1703
  // The version is what changes; `modes` is one object for the registry's life.
1612
1704
  // eslint-disable-next-line react-hooks/exhaustive-deps
1613
- }), [entries, focused, engaged, modes, modeVersion]);
1705
+ }), [entries, focused, engaged, openKeymap, modes, modeVersion]);
1614
1706
  const overlays = useCallback((position = "top") => {
1615
1707
  const { ordered, state } = snapshot();
1616
1708
  const active = [];
@@ -1633,21 +1725,28 @@ function useContributions(opts) {
1633
1725
  const modesRegistry = opts.modes;
1634
1726
  useEffect(() => {
1635
1727
  if (!isDev()) return;
1636
- const last = lastConflictSigRef.current;
1637
- if (last?.sig === entrySig && last.modes === modesRegistry) return;
1638
- lastConflictSigRef.current = { sig: entrySig, modes: modesRegistry };
1639
- const registry = [];
1640
- const ambient = [];
1641
- for (const entry of entries) {
1642
- if (entry.eligibility?.focus) registry.push(entry);
1643
- if (entry.eligibility?.always || entry.eligibility?.claimed) ambient.push(entry);
1644
- }
1645
- reportRouteConflicts({
1646
- registry,
1647
- ambient,
1648
- actions: actionsRegistry?.list() ?? [],
1649
- ...modesRegistry ? { modes: modesRegistry.list().map(activeModeOf) } : {}
1650
- });
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);
1651
1750
  }, [entrySig, entries, actionsRegistry, modesRegistry]);
1652
1751
  return useMemo(
1653
1752
  () => ({ entries, focused, setFocused, overlays }),
@@ -1768,6 +1867,6 @@ function useTools(opts) {
1768
1867
  );
1769
1868
  }
1770
1869
 
1771
- 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 };
1772
1871
  //# sourceMappingURL=react.js.map
1773
1872
  //# sourceMappingURL=react.js.map