@weasel-js/routing 1.10.0 → 1.10.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,6 +1,6 @@
1
- import { createDepRegistry, createDispatcher, assembleEntries, liveScope, isDev, reportRouteConflicts, offhandKeyFor, makeToolOffhandAction, buildToolOffhandBindings, dlog, ScopeNode, isTool, DEV, publishLiveDispatch, LONG_PRESS_HAPTIC_MS, multiTouchGestureId, openPointerSession, pushOwner, evaluateEnabled, validateActionId, validateActionDefaultBinding, depNodeOf, DepNode, clientToCanvas, buildDepsFromRequires, isEditableTarget, focusedControlOwnsKey, itemsFromClipboardData, itemsFromDataTransfer, itemTypesFromDataTransfer } from './chunk-4DR6AJMN.js';
1
+ import { createDepRegistry, createDispatcher, assembleEntries, liveScope, isDev, reportRouteConflicts, offhandKeyFor, makeToolOffhandAction, buildToolOffhandBindings, dlog, ScopeNode, isTool, tuneAction, pushOwner, evaluateEnabled, validateActionId, validateActionDefaultBinding, depNodeOf, DepNode, clientToCanvas, DEV, publishLiveDispatch, LONG_PRESS_HAPTIC_MS, multiTouchGestureId, openPointerSession, buildDepsFromRequires, isEditableTarget, focusedControlOwnsKey, itemsFromClipboardData, itemsFromDataTransfer, itemTypesFromDataTransfer } from './chunk-VCEIQ64H.js';
2
2
  import { createContext, useState, useContext, useEffect, useRef, useCallback, useMemo, useSyncExternalStore } from 'react';
3
- import { isApplePlatform, createPointerRecognizer, isInControlWithin, longPressEvents, stylusOf } from '@weasel-js/gestures';
3
+ import { isApplePlatform, heldPointers, createPointerRecognizer, isInControlWithin, longPressEvents, stylusOf } from '@weasel-js/gestures';
4
4
  import { useLatest, useStableByContent, sameList } from '@weasel-js/react';
5
5
  import { jsx, Fragment } from 'react/jsx-runtime';
6
6
  import { createCursorSink, resolveCursorTier } from '@weasel-js/cursor';
@@ -133,7 +133,7 @@ function useToolLifecycle(opts) {
133
133
  const next = new Set(active === null ? hotkeyStack : [active, ...hotkeyStack]);
134
134
  liveRef.current = next;
135
135
  for (const id of prev) if (!next.has(id)) fire(id, "onDeactivate");
136
- if (prevActiveRef.current !== active) onSwitchRef.current?.();
136
+ if (prevActiveRef.current !== null && prevActiveRef.current !== active) onSwitchRef.current?.();
137
137
  prevActiveRef.current = active;
138
138
  for (const id of next) if (!prev.has(id)) fire(id, "onActivate");
139
139
  });
@@ -268,6 +268,11 @@ function contextMenuInput(core, takeNativeHold) {
268
268
  };
269
269
  return onContextMenu;
270
270
  }
271
+ var whereOf = ({ affordance, bodyTarget, bodyKind }) => ({
272
+ ...affordance !== void 0 ? { affordance } : {},
273
+ ...bodyTarget !== void 0 ? { bodyTarget } : {},
274
+ ...bodyKind !== void 0 ? { bodyKind } : {}
275
+ });
271
276
  var hoverOf = (e) => ({
272
277
  clientX: e.clientX,
273
278
  clientY: e.clientY,
@@ -325,8 +330,9 @@ function hoverCursorInput(core, paintedCursor, pointersHeld, cursorSink = () =>
325
330
  if (pointersHeld() === 0) {
326
331
  const w = toWorld(e.clientX, e.clientY);
327
332
  const where = underPointer(target(), last, w);
333
+ const from = facts ? { from: whereOf(facts.where) } : {};
328
334
  facts = { at: last, where };
329
- dispatch({ kind: "hover", x: w.x, y: w.y, clientX: e.clientX, clientY: e.clientY, ...where });
335
+ dispatch({ kind: "hover", x: w.x, y: w.y, clientX: e.clientX, clientY: e.clientY, ...where, ...from });
330
336
  }
331
337
  refresh();
332
338
  },
@@ -335,6 +341,12 @@ function hoverCursorInput(core, paintedCursor, pointersHeld, cursorSink = () =>
335
341
  refreshSoon();
336
342
  },
337
343
  onLeave() {
344
+ if (facts) {
345
+ const { at, where } = facts;
346
+ const w = toWorld(at.clientX, at.clientY);
347
+ dispatch({ kind: "hover", off: true, x: w.x, y: w.y, clientX: at.clientX, clientY: at.clientY, ...where, from: whereOf(where) });
348
+ }
349
+ facts = null;
338
350
  last = null;
339
351
  if (DEV) publishLiveDispatch(null);
340
352
  paintedCursor()?.clearPointer();
@@ -678,7 +690,6 @@ function pointerInput(core, opts) {
678
690
  hold.routeToPress();
679
691
  return recognizer.takeHold();
680
692
  },
681
- heldCount: () => recognizer.heldCount(),
682
693
  cancelHeld() {
683
694
  for (const session of [...sessions.values()]) session.cancel();
684
695
  },
@@ -783,6 +794,88 @@ function wheelInput(core, pinchLive) {
783
794
  return onWheel;
784
795
  }
785
796
 
797
+ // src/interactions/dispatcher/attachInput.ts
798
+ function attachInput(canvas, src, channels) {
799
+ let disposed = false;
800
+ const redraw = dispatcherWatch(src.requestRedraw);
801
+ redraw.sync(src.dispatchers());
802
+ const core = createDispatchCore({
803
+ canvas,
804
+ root: src.root,
805
+ views: src.views,
806
+ context: src.context,
807
+ actions: src.actions,
808
+ uses: () => {
809
+ redraw.sync(src.dispatchers());
810
+ },
811
+ disposed: () => disposed
812
+ });
813
+ const keys = keyboardInput(core);
814
+ const pinch = webkitPinchInput(core);
815
+ const ingest = ingestInput(core);
816
+ const held = canvas ? heldPointers(canvas) : null;
817
+ const hover = hoverCursorInput(core, src.paintedCursor, () => held?.count() ?? 0, src.cursorSink);
818
+ const pointer = pointerInput(core, {
819
+ longPress: src.longPress,
820
+ onClick: src.onClick,
821
+ onDoubleClick: src.onDoubleClick,
822
+ clicks: src.clicks,
823
+ hover
824
+ });
825
+ const onWindowBlur = () => {
826
+ pinch.end();
827
+ pointer.cancelHeld();
828
+ keys.releaseAll();
829
+ };
830
+ const listeners = listenerSet();
831
+ const keyCapture = channels.keyboard === "first";
832
+ if (channels.keyboard) {
833
+ listeners.on(window, "keydown", keys.onKeyDown, keyCapture);
834
+ listeners.on(window, "keyup", keys.onKeyUp, keyCapture);
835
+ }
836
+ listeners.on(window, "blur", onWindowBlur);
837
+ listeners.on(canvas, "pointerdown", () => {
838
+ src.actions().activate();
839
+ });
840
+ if (channels.pointer) {
841
+ listeners.on(window, "keydown", hover.onKey);
842
+ listeners.on(window, "keyup", hover.onKey);
843
+ listeners.on(canvas, "pointerleave", hover.onLeave);
844
+ listeners.on(canvas, "pointerdown", pointer.onPointerDown);
845
+ listeners.on(canvas, "pointermove", pointer.onHoverMove);
846
+ listeners.on(canvas, "dblclick", pointer.onNativeDoubleClick);
847
+ }
848
+ if (channels.wheel) {
849
+ listeners.on(canvas, "wheel", wheelInput(core, pinch.live), { passive: false, capture: true });
850
+ }
851
+ if (channels.pinch) {
852
+ listeners.on(canvas, "gesturestart", pinch.onGestureStart);
853
+ listeners.on(canvas, "gesturechange", pinch.onGestureChange);
854
+ listeners.on(canvas, "gestureend", pinch.onGestureEnd);
855
+ }
856
+ if (channels.contextMenu) {
857
+ listeners.on(canvas, "contextmenu", contextMenuInput(core, pointer.takeNativeHold));
858
+ }
859
+ if (channels.ingest) {
860
+ listeners.on(canvas, "dragenter", ingest.onDragOver);
861
+ listeners.on(canvas, "dragover", ingest.onDragOver);
862
+ listeners.on(canvas, "dragleave", ingest.onDragLeave);
863
+ listeners.on(canvas, "drop", ingest.onDrop);
864
+ listeners.on(window, "paste", ingest.onPaste);
865
+ }
866
+ return () => {
867
+ listeners.removeAll();
868
+ canvas?.classList.remove(DROPOVER_CLASS);
869
+ src.actions().dropKeys();
870
+ hover.clear();
871
+ disposed = true;
872
+ pointer.dispose();
873
+ held?.release();
874
+ redraw.stop();
875
+ for (const d of src.dispatchers()) d.cancelAll("cancel");
876
+ };
877
+ }
878
+
786
879
  // src/interactions/dispatcher/useGestureDispatcher.tsx
787
880
  function useGestureDispatcher(opts) {
788
881
  const { canvasRef, actions, entriesById, enabled = true, keyboard = true, affordanceAt, classifyTarget, dispatcher: dispatcherOpt, clientToWorld, requestRedraw, paintedCursor, cursorSink, getRuleCtx, onClick, onDoubleClick, views, channels, longPress, claimKeys = false } = opts;
@@ -840,85 +933,29 @@ function useGestureDispatcher(opts) {
840
933
  }, [enabled, claimKeys, registryRef]);
841
934
  useEffect(() => {
842
935
  if (!enabled) return;
843
- let disposed = false;
844
- const canvas = canvasRef.current;
845
- const redraw = dispatcherWatch(() => {
846
- requestRedrawRef.current?.();
847
- });
848
- redraw.sync(allDispatchers());
849
- const core = createDispatchCore({
850
- canvas,
936
+ return attachInput(canvasRef.current, {
851
937
  root: () => rootTargetRef.current,
852
938
  views: () => viewsRef.current,
853
939
  context: () => ctxRef.current,
854
940
  actions: () => registryRef.current,
855
- uses: () => {
856
- redraw.sync(allDispatchers());
941
+ dispatchers: allDispatchers,
942
+ requestRedraw: () => {
943
+ requestRedrawRef.current?.();
857
944
  },
858
- disposed: () => disposed
859
- });
860
- const keys = keyboardInput(core);
861
- const pinch = webkitPinchInput(core);
862
- const ingest = ingestInput(core);
863
- const hover = hoverCursorInput(core, () => cursorRef.current.painted?.(), () => pointer.heldCount(), () => cursorRef.current.sink?.());
864
- const pointer = pointerInput(core, {
945
+ paintedCursor: () => cursorRef.current.painted?.(),
946
+ cursorSink: () => cursorRef.current.sink?.(),
865
947
  longPress: () => longPressRef.current,
866
948
  onClick: () => onClickRef.current,
867
949
  onDoubleClick: () => onDoubleClickRef.current,
868
- clicks: clicksRef.current,
869
- hover
870
- });
871
- const onWindowBlur = () => {
872
- pinch.end();
873
- pointer.cancelHeld();
874
- keys.releaseAll();
875
- };
876
- const listeners = listenerSet();
877
- const keyCapture = keyboard === "first";
878
- if (keyboard) {
879
- listeners.on(window, "keydown", keys.onKeyDown, keyCapture);
880
- listeners.on(window, "keyup", keys.onKeyUp, keyCapture);
881
- }
882
- listeners.on(window, "blur", onWindowBlur);
883
- listeners.on(canvas, "pointerdown", () => {
884
- registryRef.current.activate();
950
+ clicks: clicksRef.current
951
+ }, {
952
+ keyboard,
953
+ pointer: pointerChannel,
954
+ wheel: wheelChannel,
955
+ pinch: pinchChannel,
956
+ contextMenu: contextMenuChannel,
957
+ ingest: ingestChannel
885
958
  });
886
- if (pointerChannel) {
887
- listeners.on(window, "keydown", hover.onKey);
888
- listeners.on(window, "keyup", hover.onKey);
889
- listeners.on(canvas, "pointerleave", hover.onLeave);
890
- listeners.on(canvas, "pointerdown", pointer.onPointerDown);
891
- listeners.on(canvas, "pointermove", pointer.onHoverMove);
892
- listeners.on(canvas, "dblclick", pointer.onNativeDoubleClick);
893
- }
894
- if (wheelChannel) {
895
- listeners.on(canvas, "wheel", wheelInput(core, pinch.live), { passive: false, capture: true });
896
- }
897
- if (pinchChannel) {
898
- listeners.on(canvas, "gesturestart", pinch.onGestureStart);
899
- listeners.on(canvas, "gesturechange", pinch.onGestureChange);
900
- listeners.on(canvas, "gestureend", pinch.onGestureEnd);
901
- }
902
- if (contextMenuChannel) {
903
- listeners.on(canvas, "contextmenu", contextMenuInput(core, pointer.takeNativeHold));
904
- }
905
- if (ingestChannel) {
906
- listeners.on(canvas, "dragenter", ingest.onDragOver);
907
- listeners.on(canvas, "dragover", ingest.onDragOver);
908
- listeners.on(canvas, "dragleave", ingest.onDragLeave);
909
- listeners.on(canvas, "drop", ingest.onDrop);
910
- listeners.on(window, "paste", ingest.onPaste);
911
- }
912
- return () => {
913
- listeners.removeAll();
914
- canvas?.classList.remove(DROPOVER_CLASS);
915
- registryRef.current.dropKeys();
916
- hover.clear();
917
- disposed = true;
918
- pointer.dispose();
919
- redraw.stop();
920
- for (const d of allDispatchers()) d.cancelAll("cancel");
921
- };
922
959
  }, [
923
960
  enabled,
924
961
  keyboard,
@@ -962,6 +999,7 @@ var ActionsNode = class extends ScopeNode {
962
999
  contextDeps;
963
1000
  actions = /* @__PURE__ */ new Map();
964
1001
  muted = /* @__PURE__ */ new Map();
1002
+ tunings = /* @__PURE__ */ new Map();
965
1003
  dispatchers = [];
966
1004
  cache = null;
967
1005
  /** Called on `activate`, so a scope's dep and tool nodes move with it. */
@@ -970,10 +1008,19 @@ var ActionsNode = class extends ScopeNode {
970
1008
  for (const n of this.chain()) {
971
1009
  if (n.muted.has(id)) return void 0;
972
1010
  const a = n.actions.get(id)?.at(-1);
973
- if (a) return a;
1011
+ if (a) return this.tuned(a);
974
1012
  }
975
1013
  return void 0;
976
1014
  }
1015
+ /** `a` under every tuning for its id from the root down to here. */
1016
+ tuned(a) {
1017
+ const tunings = [];
1018
+ for (const n of this.chain()) {
1019
+ const stack = n.tunings.get(a.id);
1020
+ if (stack) tunings.unshift(...stack);
1021
+ }
1022
+ return tunings.length ? tuneAction(a, tunings) : a;
1023
+ }
977
1024
  snapshot() {
978
1025
  const leaf = this.start();
979
1026
  const cached = this.cache;
@@ -1050,6 +1097,20 @@ var ActionsNode = class extends ScopeNode {
1050
1097
  this.changed();
1051
1098
  };
1052
1099
  },
1100
+ tune: (id, tuning) => {
1101
+ let stack = this.tunings.get(id);
1102
+ if (!stack) this.tunings.set(id, stack = []);
1103
+ const release = pushOwner(stack, tuning);
1104
+ this.changed();
1105
+ let released = false;
1106
+ return () => {
1107
+ if (released) return;
1108
+ released = true;
1109
+ release();
1110
+ if (stack.length === 0) this.tunings.delete(id);
1111
+ this.changed();
1112
+ };
1113
+ },
1053
1114
  list: () => this.snapshot(),
1054
1115
  trigger: (id, params) => {
1055
1116
  const leaf = this.start();
@@ -1177,7 +1238,20 @@ function useOngoingAction(actionId) {
1177
1238
  }
1178
1239
  };
1179
1240
  }, [regRef, idRef]);
1180
- useEffect(() => () => edit.commit(), [edit, actionId]);
1241
+ const leaving = useRef(null);
1242
+ useEffect(() => {
1243
+ const left = leaving.current;
1244
+ leaving.current = null;
1245
+ if (left !== null && left !== actionId) edit.commit();
1246
+ return () => {
1247
+ leaving.current = actionId;
1248
+ queueMicrotask(() => {
1249
+ if (leaving.current !== actionId) return;
1250
+ leaving.current = null;
1251
+ edit.commit();
1252
+ });
1253
+ };
1254
+ }, [edit, actionId]);
1181
1255
  return edit;
1182
1256
  }
1183
1257
  function samePos(a, b) {