@weasel-js/routing 1.13.0-rc.0 → 1.14.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,7 +1,10 @@
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-CRF3ROBB.js';
2
- import { createContext, useState, useContext, useEffect, useMemo, useRef, useCallback, useSyncExternalStore } from 'react';
1
+ import { createKeymapStack, createDispatchTrace, createDispatcher, describeKeymap, assembleEntries, liveScope, offhandKeyFor, makeToolOffhandAction, buildToolOffhandBindings, dlog, reportRouteConflicts, isTool, clientToCanvas, LONG_PRESS_HAPTIC_MS, multiTouchGestureId, openPointerSession, addCarrySurface, carryGestureId, anyBinding, isEditableTarget, focusedControlOwnsKey, itemsFromClipboardData, itemsFromDataTransfer, itemTypesFromDataTransfer, immediateParams } from './chunk-HR5F4LBX.js';
2
+ import { useDepRegistry, actionsNodeOf, useAction, useActionsRegistry, useOptionalDepRegistry, ActionsNode, MenuHostContext, DepRegistryContext, ActionsContext } from './chunk-INZ63QGX.js';
3
+ export { ActionsProvider, DepRegistryProvider, useAction, useActionsRegistry, useDepRegistry, useDepSource, useMenuHost, useMenus, useOptionalDepRegistry } from './chunk-INZ63QGX.js';
4
+ import { NO_OVERRIDES, ScopeNode, depNodeOf, DepNode, actionCursor, buildDepsFromRequires } from './chunk-EUCNVGJV.js';
5
+ import { createContext, useState, useContext, useEffect, useRef, useMemo, useCallback, useSyncExternalStore } from 'react';
3
6
  import { isApplePlatform, heldPointers, createPointerRecognizer, isInControlWithin, longPressEvents, stylusOf } from '@weasel-js/gestures';
4
- import { isDev, useLatest, useKitSettings, useStableByContent, sameList } from '@weasel-js/react';
7
+ import { useLatest, useKitSettings, useStableByContent, sameList, isDev } from '@weasel-js/react';
5
8
  import { jsx, Fragment } from 'react/jsx-runtime';
6
9
  import { createCursorSink, resolveCursorTier } from '@weasel-js/cursor';
7
10
  import { deepEqual, createEmitter } from '@weasel-js/registry';
@@ -89,323 +92,6 @@ function ActiveToolContextProviderIfRoot({
89
92
  if (parent !== null) return /* @__PURE__ */ jsx(Fragment, { children });
90
93
  return /* @__PURE__ */ jsx(ActiveToolContextProvider, { children });
91
94
  }
92
- var DepRegistryContext = createContext(null);
93
- function DepRegistryProvider({ children }) {
94
- const [registry] = useState(createDepRegistry);
95
- return /* @__PURE__ */ jsx(DepRegistryContext.Provider, { value: registry, children });
96
- }
97
- function useDepRegistry() {
98
- const r = useContext(DepRegistryContext);
99
- if (r === null) {
100
- throw new Error("useDepRegistry: no DepRegistryProvider in scope. Wrap your tree with <DepRegistryProvider> (typically inside <SceneCanvas>).");
101
- }
102
- return r;
103
- }
104
- function useOptionalDepRegistry() {
105
- return useContext(DepRegistryContext);
106
- }
107
- function useDepSource(name, source) {
108
- const registry = useDepRegistry();
109
- const sourceRef = useLatest(source);
110
- useEffect(() => {
111
- return registry.register(name, (below) => sourceRef.current(below));
112
- }, [name, registry, sourceRef]);
113
- }
114
- var MenuHostContext = createContext(null);
115
- function useMenuHost() {
116
- return useContext(MenuHostContext);
117
- }
118
- function useMenus(defs, owner = null) {
119
- const host = useMenuHost();
120
- useEffect(() => {
121
- if (!host) return;
122
- const releases = defs.map((d) => host.define(d, owner));
123
- return () => {
124
- for (const r of releases) r();
125
- };
126
- }, [host, defs, owner]);
127
- }
128
- var ActionsContext = createContext(null);
129
- function depsFor(a, r) {
130
- if (!r) return {};
131
- if (a.requires) return buildDepsFromRequires(a, r);
132
- return {
133
- selection: r.get("selection"),
134
- scene: r.get("scene"),
135
- history: r.get("history"),
136
- view: r.get("view"),
137
- activeTool: r.get("activeTool"),
138
- booleansAdapter: r.get("booleansAdapter")
139
- };
140
- }
141
- var ActionsNode = class extends ScopeNode {
142
- constructor(parent, descends, contextDeps) {
143
- super(parent, descends);
144
- this.contextDeps = contextDeps;
145
- NODES.set(this.registry, this);
146
- }
147
- contextDeps;
148
- actions = /* @__PURE__ */ new Map();
149
- muted = /* @__PURE__ */ new Map();
150
- tunings = /* @__PURE__ */ new Map();
151
- overrides = [];
152
- overrideCache = null;
153
- dispatchers = [];
154
- cache = null;
155
- host = null;
156
- /** Called on `activate`, so a scope's dep and tool nodes move with it. */
157
- onActivate = [];
158
- resolve(id) {
159
- for (const n of this.chain()) {
160
- if (n.muted.has(id)) return void 0;
161
- const a = n.actions.get(id)?.at(-1);
162
- if (a) return this.tuned(a);
163
- }
164
- return void 0;
165
- }
166
- /** Every tuning for `id` from the root down to here, root first. */
167
- tuningsOf(id) {
168
- const tunings = [];
169
- for (const n of this.chain()) {
170
- const stack = n.tunings.get(id);
171
- if (stack) tunings.unshift(...stack);
172
- }
173
- return tunings;
174
- }
175
- /** `a` under every tuning for its id from the root down to here. */
176
- tuned(a) {
177
- const tunings = this.tuningsOf(a.id);
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;
189
- }
190
- snapshot() {
191
- const leaf = this.start();
192
- const cached = this.cache;
193
- if (cached && cached.version === this.tree.version && cached.leaf === leaf) return cached.list;
194
- const ids = /* @__PURE__ */ new Set();
195
- for (const n of leaf.chain()) for (const id of n.actions.keys()) ids.add(id);
196
- const out = [];
197
- for (const id of ids) {
198
- const a = leaf.resolve(id);
199
- if (a) out.push(a);
200
- }
201
- const list = Object.freeze(out);
202
- this.cache = { version: this.tree.version, leaf, list };
203
- return list;
204
- }
205
- deps() {
206
- for (const n of this.chain()) {
207
- const r = n.contextDeps();
208
- if (r) return r;
209
- }
210
- return null;
211
- }
212
- /** The actions registered at this node itself, live, unmuted and tuned —
213
- * none from the scopes below it. */
214
- ownActions() {
215
- const out = [];
216
- for (const [id, stack] of this.actions) {
217
- if (stack.length > 0 && !this.muted.has(id)) out.push(this.tuned(stack.at(-1)));
218
- }
219
- return out;
220
- }
221
- /** The deps `a` is invoked with from here: the active scope's, which is
222
- * what `trigger` and `begin` run it with. */
223
- depsOf(a) {
224
- return depsFor(a, this.start().deps());
225
- }
226
- /** The menu host of this node's tree, made by its root on first ask. Rows
227
- * resolve and run against the active scope, as chrome's `trigger` does. */
228
- menuHost() {
229
- const root = [...this.chain()].at(-1);
230
- return root.host ??= createMenuHost({
231
- actions: () => root.registry.list(),
232
- depsFor: (a) => root.depsOf(a),
233
- trigger: (id, params) => root.registry.trigger(id, params),
234
- activeTool: () => root.start().deps()?.get("activeTool")?.active ?? null
235
- });
236
- }
237
- dispatcher() {
238
- for (const n of this.chain()) {
239
- const d = n.dispatchers.at(-1);
240
- if (d) return d;
241
- }
242
- return null;
243
- }
244
- activate() {
245
- keyHolder = this;
246
- super.activate();
247
- for (const f of this.onActivate) f();
248
- }
249
- registry = {
250
- register: (action) => {
251
- validateActionId(action.id);
252
- validateActionDefaultBinding(action);
253
- const stack = this.actions.get(action.id);
254
- if (stack) stack.push(action);
255
- else this.actions.set(action.id, [action]);
256
- this.changed();
257
- let released = false;
258
- return () => {
259
- if (released) return;
260
- released = true;
261
- const cur = this.actions.get(action.id);
262
- if (!cur) return;
263
- const i = cur.lastIndexOf(action);
264
- if (i === -1) return;
265
- cur.splice(i, 1);
266
- if (cur.length === 0) {
267
- queueMicrotask(() => {
268
- if (cur.length === 0 && this.actions.get(action.id) === cur) this.actions.delete(action.id);
269
- });
270
- }
271
- this.changed();
272
- };
273
- },
274
- unregister: (id) => {
275
- if (this.actions.delete(id)) this.changed();
276
- },
277
- mute: (id) => {
278
- this.muted.set(id, (this.muted.get(id) ?? 0) + 1);
279
- this.changed();
280
- let released = false;
281
- return () => {
282
- if (released) return;
283
- released = true;
284
- const held = this.muted.get(id);
285
- if (held === void 0) return;
286
- if (held > 1) this.muted.set(id, held - 1);
287
- else this.muted.delete(id);
288
- this.changed();
289
- };
290
- },
291
- tune: (id, tuning) => {
292
- let stack = this.tunings.get(id);
293
- if (!stack) this.tunings.set(id, stack = []);
294
- const release = pushOwner(stack, tuning);
295
- this.changed();
296
- let released = false;
297
- return () => {
298
- if (released) return;
299
- released = true;
300
- release();
301
- if (stack.length === 0) this.tunings.delete(id);
302
- this.changed();
303
- };
304
- },
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(),
318
- list: () => this.snapshot(),
319
- trigger: (id, params) => {
320
- const leaf = this.start();
321
- const a = leaf.resolve(id);
322
- if (!a?.invoker || a.invoker.timing !== "immediate") return false;
323
- const deps = leaf.depsOf(a);
324
- if (!evaluateEnabled(a, deps, pointOf(params)).enabled) return false;
325
- try {
326
- a.invoker.run(deps, params);
327
- } catch (err) {
328
- console.error(`weasel ActionsRegistry: action "${id}" threw`, err);
329
- }
330
- return true;
331
- },
332
- preview: (id, params) => {
333
- const leaf = this.start();
334
- const a = leaf.resolve(id);
335
- if (!a?.preview) return NO_PREVIEW;
336
- const deps = leaf.depsOf(a);
337
- if (!evaluateEnabled(a, deps, pointOf(params)).enabled) return NO_PREVIEW;
338
- let stop;
339
- try {
340
- stop = a.preview(deps, params);
341
- } catch (err) {
342
- console.error(`weasel ActionsRegistry: preview of "${id}" threw`, err);
343
- return NO_PREVIEW;
344
- }
345
- if (typeof stop !== "function") return NO_PREVIEW;
346
- let down = false;
347
- return () => {
348
- if (down) return;
349
- down = true;
350
- try {
351
- stop();
352
- } catch (err) {
353
- console.error(`weasel ActionsRegistry: preview of "${id}" threw as it ended`, err);
354
- }
355
- };
356
- },
357
- subscribe: (listener) => this.tree.listeners.subscribe(listener),
358
- setDispatcher: (d) => pushOwner(this.dispatchers, d),
359
- begin: (id, params) => {
360
- const leaf = this.start();
361
- const a = leaf.resolve(id);
362
- const disp = leaf.dispatcher();
363
- if (!a || !disp) return null;
364
- return disp.beginUiOngoing(id, leaf.depsOf(a), params);
365
- },
366
- activate: () => this.activate(),
367
- isActive: () => this.isActive(),
368
- holdsKeys: () => keyHolder === this,
369
- dropKeys: () => {
370
- if (keyHolder === this) keyHolder = null;
371
- }
372
- };
373
- };
374
- var NO_PREVIEW = () => {
375
- };
376
- function pointOf(params) {
377
- const { worldX: x, worldY: y } = params ?? {};
378
- return typeof x === "number" && typeof y === "number" ? { x, y } : void 0;
379
- }
380
- var keyHolder = null;
381
- var NODES = /* @__PURE__ */ new WeakMap();
382
- function actionsNodeOf(registry) {
383
- return registry ? NODES.get(registry) ?? null : null;
384
- }
385
- function ActionsProvider({ children }) {
386
- const depReg = useOptionalDepRegistry();
387
- const depRegRef = useLatest(depReg);
388
- const node = useMemo(() => new ActionsNode(null, true, () => depRegRef.current), [depRegRef]);
389
- return /* @__PURE__ */ jsx(ActionsContext.Provider, { value: node.registry, children: /* @__PURE__ */ jsx(MenuHostContext.Provider, { value: node.menuHost(), children }) });
390
- }
391
- var IS_DEV = isDev();
392
- function useActionsRegistry() {
393
- return useContext(ActionsContext);
394
- }
395
- function useAction(action) {
396
- const reg = useActionsRegistry();
397
- useEffect(() => {
398
- if (!reg) {
399
- if (IS_DEV) {
400
- console.warn(
401
- `useAction("${action.id}"): no <ActionsProvider> is in scope, so the action was not registered and its bindings will never fire.`
402
- );
403
- }
404
- return;
405
- }
406
- return reg.register(action);
407
- }, [reg, action]);
408
- }
409
95
  function useToolLifecycle(opts) {
410
96
  const { active, hotkeyStack } = opts;
411
97
  const liveRef = useRef(/* @__PURE__ */ new Set());
@@ -455,12 +141,16 @@ function createDispatchCore(src) {
455
141
  const keymaps = src.context().keymaps;
456
142
  return keymaps ? keymaps.batch(fn) : fn();
457
143
  };
458
- const ctxNow = () => ({
459
- ...src.context(),
460
- depRegistry: routedDeps,
461
- getRuleCtx: routedRuleCtx,
462
- viewId: target().id
463
- });
144
+ const ctxNow = () => {
145
+ const toLocal = src.clientToLocal();
146
+ return {
147
+ ...src.context(),
148
+ depRegistry: routedDeps,
149
+ getRuleCtx: routedRuleCtx,
150
+ viewId: target().id,
151
+ ...toLocal ? { toLocal } : {}
152
+ };
153
+ };
464
154
  return {
465
155
  canvas,
466
156
  target,
@@ -743,16 +433,57 @@ function hoverCursorInput(core, paintedCursor, pointersHeld, cursorSink = () =>
743
433
  };
744
434
  }
745
435
 
436
+ // src/interactions/dispatcher/channels/dropPreview.ts
437
+ function keyOf(event, winner) {
438
+ const mods = [event.altKey, event.ctrlKey, event.metaKey, event.shiftKey].map(Number).join("");
439
+ const mimes = event.items.map((it) => it.mime).join(",");
440
+ return `${winner.actionId}|${event.x},${event.y}|${mods}|${mimes}`;
441
+ }
442
+ function dropPreview() {
443
+ let key = null;
444
+ let stop = null;
445
+ const end = () => {
446
+ key = null;
447
+ const s = stop;
448
+ stop = null;
449
+ if (!s) return;
450
+ try {
451
+ s();
452
+ } catch (err) {
453
+ console.error("weasel dispatcher: a drop preview threw as it ended", err);
454
+ }
455
+ };
456
+ const show = (event, winner, ctx) => {
457
+ const next = keyOf(event, winner);
458
+ if (next === key) return;
459
+ end();
460
+ key = next;
461
+ const { action } = winner;
462
+ if (!action.preview) return;
463
+ try {
464
+ const out = action.preview(buildDepsFromRequires(action, ctx.depRegistry), immediateParams(event, winner.params));
465
+ stop = typeof out === "function" ? out : null;
466
+ } catch (err) {
467
+ console.error(`weasel dispatcher: preview of "${action.id}" threw`, err);
468
+ }
469
+ };
470
+ return { show, end };
471
+ }
472
+
746
473
  // src/interactions/dispatcher/channels/ingest.ts
747
474
  var DROPOVER_CLASS = "weasel-dropover";
748
475
  function ingestInput(core) {
749
476
  const { canvas, routeAt, toWorld, dispatch } = core;
477
+ const preview = dropPreview();
750
478
  const onDragOver = (e) => {
751
479
  const dt = e.dataTransfer;
752
- if (!dt) return;
480
+ if (!dt) {
481
+ preview.end();
482
+ return;
483
+ }
753
484
  routeAt(null, e.clientX, e.clientY);
754
485
  const w = toWorld(e.clientX, e.clientY);
755
- const wanted = core.dispatcherNow().resolveOnly({
486
+ const event = {
756
487
  kind: "drop",
757
488
  items: itemTypesFromDataTransfer(dt),
758
489
  x: w.x,
@@ -763,17 +494,25 @@ function ingestInput(core) {
763
494
  ctrlKey: e.ctrlKey,
764
495
  metaKey: e.metaKey,
765
496
  shiftKey: e.shiftKey
766
- }, core.ctxNow()) !== null;
767
- canvas?.classList.toggle(DROPOVER_CLASS, wanted);
768
- if (!wanted) return;
497
+ };
498
+ const ctx = core.ctxNow();
499
+ const winner = core.dispatcherNow().resolveOnly(event, ctx);
500
+ canvas?.classList.toggle(DROPOVER_CLASS, winner !== null);
501
+ if (!winner) {
502
+ preview.end();
503
+ return;
504
+ }
505
+ preview.show(event, winner, ctx);
769
506
  e.preventDefault();
770
507
  dt.dropEffect = "copy";
771
508
  };
772
509
  const onDragLeave = () => {
773
510
  canvas?.classList.remove(DROPOVER_CLASS);
511
+ preview.end();
774
512
  };
775
513
  const onDrop = (e) => {
776
514
  canvas?.classList.remove(DROPOVER_CLASS);
515
+ preview.end();
777
516
  const dt = e.dataTransfer;
778
517
  if (!dt) return;
779
518
  e.preventDefault();
@@ -824,7 +563,7 @@ function ingestInput(core) {
824
563
  });
825
564
  if (result === "handled") e.preventDefault();
826
565
  };
827
- return { onDragOver, onDragLeave, onDrop, onPaste };
566
+ return { onDragOver, onDragLeave, onDrop, onPaste, endPreview: preview.end };
828
567
  }
829
568
 
830
569
  // src/interactions/dispatcher/channels/keymapKeys.ts
@@ -984,7 +723,7 @@ function holdPublisher(core, longPress) {
984
723
  pointerType: pending.pointerType,
985
724
  client: { x: press.clientX, y: press.clientY },
986
725
  local: core.toCanvasLocal(press.clientX, press.clientY),
987
- world: { x: press.worldX, y: press.worldY },
726
+ world: { ...press.world },
988
727
  viewId: core.target().id,
989
728
  startedAt: pending.startedAt,
990
729
  duration: pending.duration,
@@ -1203,6 +942,8 @@ function wheelInput(core, pinchLive) {
1203
942
  deltaY: e.deltaY * toPx,
1204
943
  clientX: local.x,
1205
944
  clientY: local.y,
945
+ x: hitPoint.x,
946
+ y: hitPoint.y,
1206
947
  ...underPointer(target(), e, hitPoint)
1207
948
  };
1208
949
  const result = dispatch(ev);
@@ -1305,6 +1046,7 @@ function attachInput(canvas, src, channels) {
1305
1046
  listeners.removeAll();
1306
1047
  offCarry?.();
1307
1048
  canvas?.classList.remove(DROPOVER_CLASS);
1049
+ ingest.endPreview();
1308
1050
  src.actions().dropKeys();
1309
1051
  hover.clear();
1310
1052
  disposed = true;
@@ -1581,7 +1323,7 @@ function useOpenKeymap() {
1581
1323
  }
1582
1324
  function samePos(a, b) {
1583
1325
  if (a === null || b === null) return a === b;
1584
- return a.worldX === b.worldX && a.worldY === b.worldY && a.viewId === b.viewId;
1326
+ return a.world.x === b.world.x && a.world.y === b.world.y && a.viewId === b.viewId;
1585
1327
  }
1586
1328
  function createPointerStore() {
1587
1329
  let current = null;
@@ -1867,6 +1609,6 @@ function useTools(opts) {
1867
1609
  );
1868
1610
  }
1869
1611
 
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 };
1612
+ export { ActiveToolContextProvider, ActiveToolContextProviderIfRoot, InputScope, PointerContextProvider, SelectionContextProvider, SelectionContextProviderIfRoot, Yoke, createPointerStore, useActiveToolContext, useCommand, useContributions, useGestureDispatcher, useOffhandAction, useOngoingAction, useOpenKeymap, useOptionalActiveToolContext, usePointerContext, usePointerPosition, usePublishSelection, useSelectionContext, useTools, useYoke };
1871
1613
  //# sourceMappingURL=react.js.map
1872
1614
  //# sourceMappingURL=react.js.map