@weasel-js/routing 1.11.0 → 1.11.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/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, 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';
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);
@@ -419,6 +419,7 @@ function useToolLifecycle(opts) {
419
419
  function createDispatchCore(src) {
420
420
  const { canvas } = src;
421
421
  let routedId = null;
422
+ let over = false;
422
423
  const target = () => {
423
424
  if (routedId === null) return src.root();
424
425
  return src.views()?.targets().find((t) => t.id === routedId) ?? src.root();
@@ -461,12 +462,16 @@ function createDispatchCore(src) {
461
462
  const [x, y] = clientToCanvas(canvas, cx, cy);
462
463
  return { x, y };
463
464
  },
464
- dispatch: (ev, on) => {
465
+ dispatch: (ev, on, reach) => {
465
466
  const d = on ?? dispatcherNow();
466
467
  src.uses(d);
467
- return d.handleInput(ev, ctxNow());
468
+ return d.handleInput(ev, reach ? { ...ctxNow(), keyReach: reach } : ctxNow());
468
469
  },
469
470
  holdsKeys: () => canvas ? src.actions().holdsKeys() : src.actions().isActive(),
471
+ pointerOver: () => over,
472
+ setPointerOver: (next) => {
473
+ over = canvas !== null && next;
474
+ },
470
475
  activate: () => src.actions().activate(),
471
476
  disposed: src.disposed
472
477
  };
@@ -505,6 +510,25 @@ function dispatcherWatch(onChange) {
505
510
  };
506
511
  }
507
512
 
513
+ // src/interactions/dispatcher/underPointer.ts
514
+ function underPointer(view, mods, w) {
515
+ const affordance = view.affordanceAt?.(w) ?? void 0;
516
+ const body = view.classifyTarget?.(w);
517
+ return {
518
+ altKey: mods.altKey,
519
+ ctrlKey: mods.ctrlKey,
520
+ metaKey: mods.metaKey,
521
+ shiftKey: mods.shiftKey,
522
+ ...affordance !== void 0 ? { affordance } : {},
523
+ ...body?.body !== void 0 ? { bodyTarget: body.body } : {},
524
+ ...body?.kind !== void 0 ? { bodyKind: body.kind } : {},
525
+ ...body?.doubleClick !== void 0 ? { doubleClickBody: doubleClickBodyOf(body.doubleClick) } : {}
526
+ };
527
+ }
528
+ function doubleClickBodyOf(named) {
529
+ return { bodyTarget: named.body, ...named.kind !== void 0 ? { bodyKind: named.kind } : {} };
530
+ }
531
+
508
532
  // src/interactions/dispatcher/channels/carry.ts
509
533
  function carryInput(core) {
510
534
  const { canvas } = core;
@@ -517,6 +541,7 @@ function carryInput(core) {
517
541
  if (core.disposed()) return "unhandled";
518
542
  const event = () => {
519
543
  const w = core.toWorld(at.clientX, at.clientY);
544
+ const { affordance, bodyTarget, bodyKind } = underPointer(core.target(), at, w);
520
545
  return {
521
546
  kind: "carry",
522
547
  stage,
@@ -530,7 +555,10 @@ function carryInput(core) {
530
555
  altKey: at.altKey,
531
556
  ctrlKey: at.ctrlKey,
532
557
  metaKey: at.metaKey,
533
- shiftKey: at.shiftKey
558
+ shiftKey: at.shiftKey,
559
+ ...affordance !== void 0 ? { affordance } : {},
560
+ ...bodyTarget !== void 0 ? { bodyTarget } : {},
561
+ ...bodyKind !== void 0 ? { bodyKind } : {}
534
562
  };
535
563
  };
536
564
  const routedNow = core.routedView();
@@ -541,13 +569,12 @@ function carryInput(core) {
541
569
  return core.dispatch(event());
542
570
  }
543
571
  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))) {
572
+ if (stage === "drop" || stage === "leave") pinned.delete(pointerId);
573
+ if (stage !== "leave" && !core.dispatcherNow().inFlight().has(carryGestureId(pointerId))) {
547
574
  core.routeAt(null, at.clientX, at.clientY);
548
- return core.dispatch(event());
575
+ if (stage === "move") pinned.set(pointerId, core.routedView());
549
576
  }
550
- return core.dispatch(ev);
577
+ return core.dispatch(event());
551
578
  } finally {
552
579
  core.setRoutedView(routedNow);
553
580
  }
@@ -555,25 +582,6 @@ function carryInput(core) {
555
582
  });
556
583
  }
557
584
 
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
585
  // src/interactions/dispatcher/channels/contextMenu.ts
578
586
  function contextMenuInput(core, takeNativeHold) {
579
587
  const { routeAt, toWorld, target, dispatch } = core;
@@ -618,6 +626,15 @@ function hoverCursorInput(core, paintedCursor, pointersHeld, cursorSink = () =>
618
626
  };
619
627
  let last = null;
620
628
  let facts = null;
629
+ let heldHeard = null;
630
+ const hears = () => {
631
+ if (pointersHeld() === 0) {
632
+ heldHeard = null;
633
+ return true;
634
+ }
635
+ heldHeard ??= anyBinding(ctxNow(), (s) => s.kind === "hover" && s.held !== void 0);
636
+ return heldHeard;
637
+ };
621
638
  const resolve = () => {
622
639
  if (!canvas || !last) return;
623
640
  if (pointersHeld() > 0 || dispatcherNow().inFlight().size > 0) {
@@ -635,7 +652,7 @@ function hoverCursorInput(core, paintedCursor, pointersHeld, cursorSink = () =>
635
652
  const ctx = ctxNow();
636
653
  const d = dispatcherNow();
637
654
  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));
655
+ if (ctx.trace?.recording()) ctx.trace.setLive(() => d.explain(press, ctx));
639
656
  const click = { kind: "click", x: w.x, y: w.y, pressX: w.x, pressY: w.y, ...where };
640
657
  const cursorOf = (r) => r ? actionCursor(r.action, r.params) : void 0;
641
658
  const cursor = cursorOf(d.resolveOnly(press, ctx)) ?? cursorOf(dispatcherNow().resolveOnly(click, ctx));
@@ -657,16 +674,18 @@ function hoverCursorInput(core, paintedCursor, pointersHeld, cursorSink = () =>
657
674
  const w = toWorld(at.clientX, at.clientY);
658
675
  const where = underPointer(target(), at, w);
659
676
  const from = facts ? { from: whereOf(facts.where) } : {};
677
+ const held = pointersHeld() > 0 ? { held: true } : {};
660
678
  facts = { at, where };
661
- dispatch({ kind: "hover", x: w.x, y: w.y, clientX: at.clientX, clientY: at.clientY, ...where, ...from });
679
+ dispatch({ kind: "hover", x: w.x, y: w.y, clientX: at.clientX, clientY: at.clientY, ...where, ...from, ...held });
662
680
  };
663
681
  return {
664
682
  moved(e) {
665
683
  last = hoverOf(e);
666
- if (pointersHeld() === 0) hoverAt(last);
684
+ if (hears()) hoverAt(last);
667
685
  refresh();
668
686
  },
669
687
  released(e) {
688
+ heldHeard = null;
670
689
  last = hoverOf(e);
671
690
  refreshSoon();
672
691
  },
@@ -678,7 +697,7 @@ function hoverCursorInput(core, paintedCursor, pointersHeld, cursorSink = () =>
678
697
  }
679
698
  facts = null;
680
699
  last = null;
681
- if (DEV) publishLiveDispatch(null);
700
+ ctxNow().trace?.setLive(null);
682
701
  paintedCursor()?.clearPointer();
683
702
  override.clear();
684
703
  },
@@ -687,7 +706,7 @@ function hoverCursorInput(core, paintedCursor, pointersHeld, cursorSink = () =>
687
706
  const prev = last;
688
707
  last = { ...last, altKey: e.altKey, ctrlKey: e.ctrlKey, metaKey: e.metaKey, shiftKey: e.shiftKey };
689
708
  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);
709
+ if (changed && hears()) hoverAt(last);
691
710
  refreshSoon();
692
711
  },
693
712
  clear: override.clear
@@ -792,15 +811,16 @@ function keyboardInput(core) {
792
811
  const physicalKey = (e) => e.code || e.key.toLowerCase();
793
812
  const onKeyDown = (e) => {
794
813
  if (e.defaultPrevented || isEditableTarget(e.target) || inMenu(e.target) || focusedControlOwnsKey(e)) return;
795
- if (!core.holdsKeys()) return;
814
+ const reach = { held: core.holdsKeys(), over: core.pointerOver() };
815
+ if (!reach.held && !reach.over) return;
796
816
  if (e.repeat && heldKeys.has(physicalKey(e))) {
797
817
  e.preventDefault();
798
818
  return;
799
819
  }
800
- if (press(e, { altKey: e.altKey, ctrlKey: e.ctrlKey })) return;
801
- if (typedWithAltGraph(e)) press(e, { altKey: false, ctrlKey: false });
820
+ if (press(e, { altKey: e.altKey, ctrlKey: e.ctrlKey }, reach)) return;
821
+ if (typedWithAltGraph(e)) press(e, { altKey: false, ctrlKey: false }, reach);
802
822
  };
803
- const press = (e, mods) => {
823
+ const press = (e, mods, reach) => {
804
824
  const keyEv = {
805
825
  kind: "key",
806
826
  key: e.key,
@@ -810,7 +830,7 @@ function keyboardInput(core) {
810
830
  shiftKey: e.shiftKey,
811
831
  repeat: e.repeat
812
832
  };
813
- const keyResult = dispatch(keyEv);
833
+ const keyResult = dispatch(keyEv, void 0, reach);
814
834
  const heldEv = {
815
835
  kind: "key-held",
816
836
  key: e.key,
@@ -821,7 +841,7 @@ function keyboardInput(core) {
821
841
  shiftKey: e.shiftKey
822
842
  };
823
843
  const heldOn = dispatcherNow();
824
- const heldResult = dispatch(heldEv, heldOn);
844
+ const heldResult = dispatch(heldEv, heldOn, reach);
825
845
  if (heldResult === "handled") {
826
846
  heldKeys.set(physicalKey(e), { key: e.key, ...codeOf(e), on: heldOn });
827
847
  }
@@ -887,6 +907,7 @@ var sampleOf = (e) => ({
887
907
  clientX: e.clientX,
888
908
  clientY: e.clientY,
889
909
  target: e.target,
910
+ timeStamp: e.timeStamp,
890
911
  altKey: e.altKey,
891
912
  ctrlKey: e.ctrlKey,
892
913
  metaKey: e.metaKey,
@@ -1184,6 +1205,12 @@ function attachInput(canvas, src, channels) {
1184
1205
  listeners.on(canvas, "pointerdown", () => {
1185
1206
  src.actions().activate();
1186
1207
  });
1208
+ const setOver = (on) => () => {
1209
+ core.setPointerOver(on);
1210
+ };
1211
+ listeners.on(canvas, "pointerenter", setOver(true));
1212
+ listeners.on(canvas, "pointermove", setOver(true));
1213
+ listeners.on(canvas, "pointerleave", setOver(false));
1187
1214
  if (channels.pointer) {
1188
1215
  listeners.on(window, "keydown", hover.onKey);
1189
1216
  listeners.on(window, "keyup", hover.onKey);
@@ -1191,6 +1218,14 @@ function attachInput(canvas, src, channels) {
1191
1218
  listeners.on(canvas, "pointerdown", pointer.onPointerDown);
1192
1219
  listeners.on(canvas, "pointermove", pointer.onHoverMove);
1193
1220
  listeners.on(canvas, "dblclick", pointer.onNativeDoubleClick);
1221
+ } else if (channels.hover) {
1222
+ listeners.on(window, "keydown", hover.onKey);
1223
+ listeners.on(window, "keyup", hover.onKey);
1224
+ listeners.on(canvas, "pointerleave", hover.onLeave);
1225
+ listeners.on(canvas, "pointermove", (e) => {
1226
+ core.routeAt(null, e.clientX, e.clientY);
1227
+ hover.moved(e);
1228
+ });
1194
1229
  }
1195
1230
  if (channels.wheel) {
1196
1231
  listeners.on(canvas, "wheel", wheelInput(core, pinch.live), { passive: false, capture: true });
@@ -1227,8 +1262,9 @@ function attachInput(canvas, src, channels) {
1227
1262
 
1228
1263
  // src/interactions/dispatcher/useGestureDispatcher.tsx
1229
1264
  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;
1265
+ 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
1266
  const pointerChannel = channels?.pointer !== false;
1267
+ const hoverChannel = channels?.hover ?? pointerChannel;
1232
1268
  const wheelChannel = channels?.wheel !== false;
1233
1269
  const pinchChannel = channels?.pinch !== false;
1234
1270
  const contextMenuChannel = channels?.contextMenu !== false;
@@ -1239,6 +1275,9 @@ function useGestureDispatcher(opts) {
1239
1275
  const activeTool = useActiveToolContext();
1240
1276
  const depRegistry = useDepRegistry();
1241
1277
  const [isMac] = useState(isApplePlatform);
1278
+ const [ownTrace] = useState(createDispatchTrace);
1279
+ const trace = traceOpt ?? ownTrace;
1280
+ useEffect(() => depRegistry.register("dispatchTrace", () => trace), [depRegistry, trace]);
1242
1281
  const dispatcherRef = useRef(null);
1243
1282
  if (!dispatcherRef.current) {
1244
1283
  dispatcherRef.current = dispatcherOpt ?? createDispatcher();
@@ -1254,7 +1293,8 @@ function useGestureDispatcher(opts) {
1254
1293
  activeToolId: activeTool.active,
1255
1294
  hotkeyStack: activeTool.hotkeyStack,
1256
1295
  entriesById,
1257
- isMac
1296
+ isMac,
1297
+ trace
1258
1298
  });
1259
1299
  const rootTargetRef = useLatest({
1260
1300
  id: null,
@@ -1309,6 +1349,7 @@ function useGestureDispatcher(opts) {
1309
1349
  }, {
1310
1350
  keyboard,
1311
1351
  pointer: pointerChannel,
1352
+ hover: hoverChannel,
1312
1353
  wheel: wheelChannel,
1313
1354
  pinch: pinchChannel,
1314
1355
  contextMenu: contextMenuChannel,
@@ -1320,6 +1361,7 @@ function useGestureDispatcher(opts) {
1320
1361
  keyboard,
1321
1362
  canvasRef,
1322
1363
  pointerChannel,
1364
+ hoverChannel,
1323
1365
  wheelChannel,
1324
1366
  pinchChannel,
1325
1367
  contextMenuChannel,