@weasel-js/routing 1.13.0 → 1.14.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.
@@ -1,514 +1,10 @@
1
+ import { NO_OVERRIDES, resolveParams, formatRoute, overriddenBindings, ActionDisabledReason, keySpecShortcut, actionBindings, canonicalModifiers, paramsMatch, VALID_MOD_NAMES, actionCursor, buildDepsFromRequires, resolveRoles, roleKeysOf, platformRoleKeys } from './chunk-EUCNVGJV.js';
2
+ import { routeGestureForSpecKind, getGestureDescriptor, parseTargetSpec, matchSpec, UNKNOWN_MIME, specificity, formatShortcut, stylusOf } from '@weasel-js/gestures';
3
+ export { GESTURE_DESCRIPTORS, LONG_PRESS_MS, activatesFocusedControl, focusedControlOwnsKey, getGestureDescriptor, isEditableTarget, isKnownGestureName, matchKey, matchModifiers, matchPhase, matchSpec, matchTarget, routeGestureForSpecKind, specKindForRouteGesture, specificity } from '@weasel-js/gestures';
1
4
  import { isDev } from '@weasel-js/react';
2
- import { createEmitter } from '@weasel-js/registry';
3
- import { RESERVED_ID_PREFIXES, RESERVED_ID_NAMES, isApplePlatform, bindingSpec, routeGestureForSpecKind, getGestureDescriptor, formatRoute, parseTargetSpec, matchSpec, canonicalModifiers, UNKNOWN_MIME, specificity, formatShortcut, VALID_MOD_NAMES, stylusOf } from '@weasel-js/gestures';
4
- export { GESTURE_DESCRIPTORS, LONG_PRESS_MS, RESERVED_ID_NAMES, RESERVED_ID_PREFIXES, ROUTE_FIELD_DEFINITIONS, ROUTE_TERMS, activatesFocusedControl, canonicalModifiers, collapseShiftPairs, describeRoute, describeRouteParts, focusedControlOwnsKey, formatPhaseAtom, formatRoute, getGestureDescriptor, isEditableTarget, isKnownGestureName, matchKey, matchModifiers, matchPhase, matchSpec, matchTarget, parseRoute, routeGestureForSpecKind, specKindForRouteGesture, specificity } from '@weasel-js/gestures';
5
5
  import { activeModeOf, NORMAL, DEFAULT_MODES, capabilitiesAllowed } from '@weasel-js/modes';
6
+ import { createEmitter } from '@weasel-js/registry';
6
7
  import { resolveCursor } from '@weasel-js/cursor';
7
- import { isPrefLeaf, prefScopeIn, prefScopeUnder, prefConditionHolds, prefAliasTarget, prefLeafAt, prefAliasedLeaf, prefNumberAt, resolvePrefUnit, prefDisplayBounds, prefUnitAccepts, PREF_SCOPE, prefValueAtPath } from '@weasel-js/prefs';
8
- import { endlessAllows } from '@weasel-js/quantity';
9
-
10
- // src/interactions/actions/actionItems.ts
11
- var NO_TAGS = Object.freeze([]);
12
- function actionItems(action) {
13
- if (action.invoker?.timing !== "immediate") return [];
14
- if (!action.variants?.length) {
15
- return [{ key: action.id, action, label: action.label, icon: action.icon, tags: action.tags ?? NO_TAGS }];
16
- }
17
- return action.variants.map((v) => ({
18
- key: `${action.id}:${v.key}`,
19
- action,
20
- label: v.label,
21
- icon: v.icon ?? action.icon,
22
- tags: v.tags ? [...action.tags ?? NO_TAGS, ...v.tags] : action.tags ?? NO_TAGS,
23
- params: v.params
24
- }));
25
- }
26
- function actionItemTitle(item) {
27
- if (item.key === item.action.id) return item.label;
28
- const words = (s) => s.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
29
- const head = words(item.action.label);
30
- const label = words(item.label);
31
- for (let i = 0; i + head.length <= label.length; i++) {
32
- if (head.every((w, j) => label[i + j] === w)) return item.label;
33
- }
34
- return `${item.action.label}: ${item.label}`;
35
- }
36
-
37
- // src/interactions/actions/actionSettings.ts
38
- var EMPTY = { values: {} };
39
- function readActionSettings(action, deps) {
40
- const settings = action.settings;
41
- if (!settings) return null;
42
- try {
43
- return settings.read(deps ?? {});
44
- } catch {
45
- return EMPTY;
46
- }
47
- }
48
- function actionSettingsParams(settings, path, value) {
49
- return settings.params ? settings.params(path, value) : { [path]: value };
50
- }
51
-
52
- // src/interactions/actions/binding.ts
53
- function actionBindings(action) {
54
- const gs = action.defaultBinding;
55
- if (!gs) return [];
56
- const raw = Array.isArray(gs) ? gs : [gs];
57
- return raw.map((entry) => {
58
- const isBoundObj = !("kind" in entry);
59
- const spec = isBoundObj ? entry.spec : entry;
60
- const opts = isBoundObj ? entry.opts : void 0;
61
- return { spec, actionId: action.id, ...opts !== void 0 ? { opts } : {} };
62
- });
63
- }
64
-
65
- // src/interactions/actions/actionShortcuts.ts
66
- function keySpecShortcut(spec) {
67
- if (spec.kind !== "key" && spec.kind !== "key-held") return void 0;
68
- const key = Array.isArray(spec.key) ? spec.key[0] : spec.key;
69
- if (typeof key !== "string" || key.length === 0) return void 0;
70
- const mods = "mods" in spec ? spec.mods : void 0;
71
- return {
72
- key,
73
- mod: mods?.mod === true,
74
- alt: mods?.alt === true,
75
- shift: mods?.shift === true
76
- };
77
- }
78
- function actionShortcuts(action, params) {
79
- const out = [];
80
- const seen = /* @__PURE__ */ new Set();
81
- for (const { spec, opts } of actionBindings(action)) {
82
- if (params && !paramsMatch(opts?.params, params)) continue;
83
- const shortcut = keySpecShortcut(spec);
84
- if (!shortcut) continue;
85
- const token = `${shortcut.key}|${shortcut.mod}|${shortcut.alt}|${shortcut.shift}`;
86
- if (seen.has(token)) continue;
87
- seen.add(token);
88
- out.push(shortcut);
89
- }
90
- return out;
91
- }
92
- function paramsMatch(bound, wanted) {
93
- if (typeof bound !== "object" || bound === null) return false;
94
- return Object.entries(wanted).every(([k, v]) => bound[k] === v);
95
- }
96
-
97
- // src/interactions/actions/action.ts
98
- var ActionDisabledReason = {
99
- SelectionRequired: "selection-required",
100
- SceneEmpty: "scene-empty",
101
- NotApplicable: "not-applicable",
102
- /** Sentinel: the predicate threw. Surfaced by `evaluateEnabled`'s catch. */
103
- PredicateThrew: "predicate-threw"
104
- };
105
- function actionCursor(action, params) {
106
- const c = action?.cursor;
107
- return typeof c === "function" ? c(params) : c;
108
- }
109
- var enabledSlowWarned = /* @__PURE__ */ new Set();
110
- var enabledThrewWarned = /* @__PURE__ */ new Set();
111
- var ENABLED_BUDGET_MS = 4;
112
- function evaluateEnabled(action, deps, at) {
113
- if (!action.enabled) return { enabled: true };
114
- const dev = isDev();
115
- const now = typeof performance !== "undefined" && typeof performance.now === "function" ? () => performance.now() : () => Date.now();
116
- const start = dev ? now() : 0;
117
- try {
118
- const result = action.enabled(deps, at);
119
- if (dev) {
120
- const elapsed = now() - start;
121
- if (elapsed > ENABLED_BUDGET_MS && !enabledSlowWarned.has(action.id)) {
122
- enabledSlowWarned.add(action.id);
123
- console.warn(
124
- `weasel actions: enabled() for action "${action.id}" took ${elapsed.toFixed(2)}ms (budget ${ENABLED_BUDGET_MS}ms). The predicate must be pure and fast \u2014 see Action.enabled JSDoc.`
125
- );
126
- }
127
- }
128
- if (result === true) return { enabled: true };
129
- return { enabled: false, reason: result };
130
- } catch (e) {
131
- if (dev && !enabledThrewWarned.has(action.id)) {
132
- enabledThrewWarned.add(action.id);
133
- console.warn(
134
- `weasel actions: enabled() for action "${action.id}" threw; treating as disabled. The predicate must not throw \u2014 see Action.enabled JSDoc.`,
135
- e
136
- );
137
- }
138
- return { enabled: false, reason: ActionDisabledReason.PredicateThrew };
139
- }
140
- }
141
- function evaluateActive(action, deps) {
142
- if (!action.active) return void 0;
143
- try {
144
- return action.active(deps) === true;
145
- } catch {
146
- return false;
147
- }
148
- }
149
- var DEV = isDev();
150
- function buildDepsFromRequires(action, depRegistry) {
151
- const requires = action.requires ?? [];
152
- const entries = requires.map((name) => [name, depRegistry.get(name)]);
153
- const deps = Object.fromEntries(entries);
154
- if (DEV) {
155
- const declared = new Set(requires);
156
- return new Proxy(deps, {
157
- get(target, prop, receiver) {
158
- if (typeof prop === "string" && !declared.has(prop) && prop !== "then") {
159
- const message = `[weasel:dispatcher] action "${action.id}" read deps.${prop} but did not declare it in \`requires\`. Add \`requires: [...'${prop}']\` to the descriptor or the dep will be undefined at runtime.`;
160
- console.warn(message);
161
- throw new Error(message);
162
- }
163
- return Reflect.get(target, prop, receiver);
164
- }
165
- });
166
- }
167
- return deps;
168
- }
169
- var ScopeNode = class {
170
- /** `descends`: whether reads made here answer from the active child down —
171
- * true for a root or a yoke, which chrome reads through; false for a scope,
172
- * which answers for itself even with a scope nested inside it. */
173
- constructor(parent, descends) {
174
- this.parent = parent;
175
- this.descends = descends;
176
- this.tree = parent ? parent.tree : { version: 0, listeners: createEmitter() };
177
- }
178
- parent;
179
- descends;
180
- tree;
181
- children = [];
182
- activeChild = null;
183
- /** Bump the tree's version and tell every listener in it. */
184
- changed() {
185
- this.tree.version++;
186
- this.tree.listeners.emit();
187
- }
188
- /** Join the parent's children, newest last; returns the leave. */
189
- mount() {
190
- const parent = this.parent;
191
- if (!parent) return () => {
192
- };
193
- parent.children.push(this);
194
- this.changed();
195
- let left = false;
196
- return () => {
197
- if (left) return;
198
- left = true;
199
- const i = parent.children.lastIndexOf(this);
200
- if (i !== -1) parent.children.splice(i, 1);
201
- if (parent.activeChild === this) parent.activeChild = null;
202
- this.changed();
203
- };
204
- }
205
- /** Make this node the one its ancestors resolve through. */
206
- activate() {
207
- let moved = false;
208
- for (let n = this; n.parent; n = n.parent) {
209
- if (n.parent.activeChild !== n) {
210
- n.parent.activeChild = n;
211
- moved = true;
212
- }
213
- }
214
- if (moved) this.changed();
215
- }
216
- /** The node a lookup made here starts from: down the active children, or the
217
- * newest where nothing has been activated yet. A yoke with no members yet
218
- * is passed over — there is no canvas behind it to answer. */
219
- leaf() {
220
- let n = this;
221
- for (; ; ) {
222
- const next = n.activeChild ?? n.newestMember();
223
- if (!next) return n;
224
- n = next;
225
- }
226
- }
227
- newestMember() {
228
- for (let i = this.children.length - 1; i >= 0; i--) {
229
- const c = this.children[i];
230
- if (!(c.descends && c.children.length === 0)) return c;
231
- }
232
- return null;
233
- }
234
- /** Where a read made through this node starts. */
235
- start() {
236
- return this.descends ? this.leaf() : this;
237
- }
238
- /** Whether this node is on the path a read from the root takes. */
239
- isActive() {
240
- let root = this;
241
- while (root.parent) root = root.parent;
242
- for (const n of root.leaf().chain()) if (n === this) return true;
243
- return false;
244
- }
245
- /** This node, then each ancestor up to the root. */
246
- *chain() {
247
- for (let n = this; n; n = n.parent) yield n;
248
- }
249
- };
250
- function pushOwner(stack, value) {
251
- if (value === null) return () => {
252
- };
253
- stack.push(value);
254
- let released = false;
255
- return () => {
256
- if (released) return;
257
- released = true;
258
- const i = stack.lastIndexOf(value);
259
- if (i !== -1) stack.splice(i, 1);
260
- };
261
- }
262
-
263
- // src/interactions/actions/depNode.ts
264
- var DepNode = class extends ScopeNode {
265
- sources = /* @__PURE__ */ new Map();
266
- registry = {
267
- register: (name, source) => {
268
- const key = name;
269
- const entry = source;
270
- const stack = this.sources.get(key);
271
- if (stack) stack.push(entry);
272
- else this.sources.set(key, [entry]);
273
- this.changed();
274
- let released = false;
275
- return () => {
276
- if (released) return;
277
- released = true;
278
- const cur = this.sources.get(key);
279
- if (!cur) return;
280
- const i = cur.lastIndexOf(entry);
281
- if (i === -1) return;
282
- cur.splice(i, 1);
283
- if (cur.length === 0) this.sources.delete(key);
284
- this.changed();
285
- };
286
- },
287
- get: (name) => {
288
- const stacks = [...this.start().chain()].map((n) => n.sources.get(name) ?? []);
289
- return readFrom(stacks, 0, (stacks[0]?.length ?? 0) - 1);
290
- },
291
- subscribe: (listener) => this.tree.listeners.subscribe(listener),
292
- list: () => {
293
- const found = /* @__PURE__ */ new Map();
294
- let depth = 0;
295
- for (const n of this.start().chain()) {
296
- for (const [name, stack] of n.sources) {
297
- if (!found.has(name)) found.set(name, { name, depth, stacked: stack.length });
298
- }
299
- depth++;
300
- }
301
- return [...found.values()].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
302
- },
303
- changed: () => this.changed()
304
- };
305
- constructor(parent, descends) {
306
- super(parent, descends);
307
- NODES.set(this.registry, this);
308
- }
309
- };
310
- function readFrom(stacks, scope, index) {
311
- if (scope >= stacks.length) return void 0;
312
- if (index < 0) return readFrom(stacks, scope + 1, (stacks[scope + 1]?.length ?? 0) - 1);
313
- return stacks[scope][index](() => readFrom(stacks, scope, index - 1));
314
- }
315
- var NODES = /* @__PURE__ */ new WeakMap();
316
- function depNodeOf(registry) {
317
- return registry ? NODES.get(registry) ?? null : null;
318
- }
319
- function createDepRegistry() {
320
- return new DepNode(null, true).registry;
321
- }
322
-
323
- // src/interactions/actions/registry.ts
324
- function validateActionId(id) {
325
- if (id.length === 0) {
326
- throw new Error(`weasel: action id may not be empty`);
327
- }
328
- if (RESERVED_ID_PREFIXES.has(id[0])) {
329
- throw new Error(
330
- `weasel: action id "${id}" starts with reserved sigil "${id[0]}" (reserved set: ${[...RESERVED_ID_PREFIXES].join(" ")})`
331
- );
332
- }
333
- if (RESERVED_ID_NAMES.has(id)) {
334
- throw new Error(
335
- `weasel: action id "${id}" collides with a reserved phase keyword (reserved: ${[...RESERVED_ID_NAMES].join(", ")})`
336
- );
337
- }
338
- }
339
- function validateActionDefaultBinding(action) {
340
- const gs = action.defaultBinding;
341
- if (!gs) return;
342
- const entries = Array.isArray(gs) ? gs : [gs];
343
- for (const entry of entries) {
344
- const spec = "kind" in entry ? entry : entry.spec;
345
- const phase = spec.phase;
346
- if (phase === void 0) continue;
347
- const atoms = Array.isArray(phase) ? phase : [{ channel: "&", phase }];
348
- for (const atom of atoms) {
349
- if (atom.channel === "&") {
350
- throw new Error(
351
- `weasel: action "${action.id}" defaultBinding uses '&' channel which has no owning tool to resolve to. Use a named tool channel (e.g. '[rect:engaged]') or '*' instead, or move this binding to the tool's own Tool.bindings.`
352
- );
353
- }
354
- }
355
- }
356
- }
357
-
358
- // src/interactions/actions/invoker.ts
359
- function resolveParams(params) {
360
- if (params === void 0) return void 0;
361
- return typeof params === "function" ? params() : params;
362
- }
363
- function keysHeld(keys, modifiers, isMac) {
364
- return keys.some((k) => {
365
- if (k === "mod") return isMac ? modifiers.meta : modifiers.ctrl;
366
- return modifiers[k];
367
- });
368
- }
369
- function roleKeysOf(roles, rebinds) {
370
- if (!roles) return void 0;
371
- const out = {};
372
- for (const id of Object.keys(roles)) out[id] = rebinds?.[id] ?? roles[id].keys;
373
- return out;
374
- }
375
- function platformRoleKeys(keys, isMac) {
376
- const out = {};
377
- for (const id of Object.keys(keys)) out[id] = keys[id].map((k) => k === "mod" ? isMac ? "meta" : "ctrl" : k);
378
- return out;
379
- }
380
- function resolveRoles(keys, modifiers, isMac) {
381
- const out = {};
382
- for (const id of Object.keys(keys)) out[id] = keysHeld(keys[id], modifiers, isMac);
383
- return out;
384
- }
385
- function rebindRoles(roles, rebinds) {
386
- const out = {};
387
- for (const id of Object.keys(roles)) {
388
- out[id] = rebinds[id] ? { ...roles[id], keys: rebinds[id] } : roles[id];
389
- }
390
- return out;
391
- }
392
- function roleReader(roles) {
393
- return (ctx, id) => ctx.roles?.[id] ?? keysHeld(roles[id].keys, ctx.modifiers, isApplePlatform());
394
- }
395
-
396
- // src/interactions/actions/tuning.ts
397
- var WRAPS = /* @__PURE__ */ new WeakMap();
398
- function layered(under, over) {
399
- if (typeof under !== "function" && typeof over !== "function") return tunedParams([{ params: under }], over);
400
- return () => tunedParams([{ params: under }], resolveParams(over));
401
- }
402
- function wrapInvoker(invoker, params) {
403
- for (let w = invoker; w; w = WRAPS.get(w)?.inner) {
404
- if (WRAPS.get(w)?.params === params) return invoker;
405
- }
406
- const base = (given) => tunedParams([{ params }], given);
407
- const wrapped = invoker.timing === "immediate" ? { timing: "immediate", run: (deps, given) => invoker.run(deps, base(given)) } : {
408
- timing: "ongoing",
409
- start: (ctx, opts) => invoker.start(
410
- ctx.params ? { ...ctx, params: base(ctx.params) } : ctx,
411
- { ...opts, params: layered(params, opts?.params) }
412
- )
413
- };
414
- WRAPS.set(wrapped, { inner: invoker, params });
415
- return wrapped;
416
- }
417
- function withBindings(own, added) {
418
- const base = own === void 0 ? [] : Array.isArray(own) ? own : [own];
419
- return [...base, ...added.filter((b) => !base.includes(b))];
420
- }
421
- var CURSOR_WRAPS = /* @__PURE__ */ new WeakMap();
422
- function wrapCursor(cursor, params) {
423
- if (typeof cursor !== "function") return cursor;
424
- for (let w = cursor; typeof w === "function"; w = CURSOR_WRAPS.get(w)?.inner) {
425
- if (CURSOR_WRAPS.get(w)?.params === params) return cursor;
426
- }
427
- const wrapped = (given) => cursor(tunedParams([{ params }], given));
428
- CURSOR_WRAPS.set(wrapped, { inner: cursor, params });
429
- return wrapped;
430
- }
431
- var PREVIEW_WRAPS = /* @__PURE__ */ new WeakMap();
432
- function wrapPreview(preview, params) {
433
- if (!preview) return preview;
434
- for (let w = preview; w; w = PREVIEW_WRAPS.get(w)?.inner) {
435
- if (PREVIEW_WRAPS.get(w)?.params === params) return preview;
436
- }
437
- const wrapped = (deps, given) => preview(deps, tunedParams([{ params }], given));
438
- PREVIEW_WRAPS.set(wrapped, { inner: preview, params });
439
- return wrapped;
440
- }
441
- function tuneOnce(action, tuning) {
442
- const invoker = tuning.params && action.invoker ? wrapInvoker(action.invoker, tuning.params) : action.invoker;
443
- const cursor = tuning.params ? wrapCursor(action.cursor, tuning.params) : action.cursor;
444
- const preview = tuning.params ? wrapPreview(action.preview, tuning.params) : action.preview;
445
- const bindings = tuning.bindings?.length ? withBindings(action.defaultBinding, tuning.bindings) : null;
446
- const modifierRoles = tuning.roles && action.modifierRoles ? rebindRoles(action.modifierRoles, tuning.roles) : null;
447
- if (invoker === action.invoker && cursor === action.cursor && preview === action.preview && bindings === null && modifierRoles === null) {
448
- return action;
449
- }
450
- return {
451
- ...action,
452
- ...modifierRoles ? { modifierRoles } : {},
453
- ...invoker ? { invoker } : {},
454
- ...cursor ? { cursor } : {},
455
- ...preview ? { preview } : {},
456
- ...bindings ? { defaultBinding: bindings } : {}
457
- };
458
- }
459
- function tunedParams(tunings, given) {
460
- let out = {};
461
- for (const t of tunings) out = { ...out, ...resolveParams(t.params) };
462
- return { ...out, ...given };
463
- }
464
- function tuneAction(action, tunings) {
465
- let out = action;
466
- for (const t of tunings) out = tuneOnce(out, t);
467
- return out;
468
- }
469
- var NO_OVERRIDES = /* @__PURE__ */ new Map();
470
- function bindingId(b, ownerId, index) {
471
- return b.opts?.id ?? (ownerId === null ? `${b.actionId}@${index}` : `${ownerId}#${index}`);
472
- }
473
- function resolveBindingOverrides(maps) {
474
- if (maps.length === 0) return NO_OVERRIDES;
475
- const out = /* @__PURE__ */ new Map();
476
- for (const map of maps) {
477
- for (const [id, input] of Object.entries(map)) {
478
- try {
479
- out.set(id, bindingSpec(input));
480
- } catch (err) {
481
- console.warn(`weasel: binding override "${id}" is not a route: ${err.message}`);
482
- }
483
- }
484
- }
485
- return out;
486
- }
487
- function overridden(b, id, overrides) {
488
- if (!overrides.has(id)) return b;
489
- const spec = overrides.get(id);
490
- return spec === null ? null : { ...b, spec };
491
- }
492
- function overriddenBindings(bindings, ownerId, overrides = NO_OVERRIDES) {
493
- const out = [];
494
- bindings.forEach((b, i) => {
495
- const next = overridden(b, bindingId(b, ownerId, i), overrides);
496
- if (next) out.push(next);
497
- });
498
- return out;
499
- }
500
- function overrideActionBindings(action, overrides) {
501
- if (overrides.size === 0 || action.defaultBinding === void 0) return action;
502
- const declared = actionBindings(action);
503
- if (!declared.some((b, i) => overrides.has(bindingId(b, null, i)))) return action;
504
- const kept = [];
505
- declared.forEach((b, i) => {
506
- const id = bindingId(b, null, i);
507
- const next = overridden(b, id, overrides);
508
- if (next) kept.push({ spec: next.spec, opts: { ...next.opts, id } });
509
- });
510
- return { ...action, defaultBinding: kept };
511
- }
512
8
 
513
9
  // src/interactions/dispatcher/inputFacts.ts
514
10
  function modifierStateOf(e) {
@@ -525,6 +21,8 @@ function worldPointOf(event) {
525
21
  return event.x !== void 0 && event.y !== void 0 ? { x: event.x, y: event.y } : void 0;
526
22
  case "carry":
527
23
  return { x: event.x, y: event.y };
24
+ case "wheel":
25
+ return event.x !== void 0 && event.y !== void 0 ? { x: event.x, y: event.y } : void 0;
528
26
  default:
529
27
  return void 0;
530
28
  }
@@ -533,6 +31,7 @@ function carriedBy(event) {
533
31
  if (event.kind === "carry") return { items: [], carry: { type: event.type, value: event.value } };
534
32
  return event.kind === "drop" || event.kind === "paste" ? { items: event.items } : void 0;
535
33
  }
34
+ var at = (x, y) => x !== void 0 && y !== void 0 ? { world: { x, y } } : {};
536
35
  function immediateParams(event, resolved) {
537
36
  switch (event.kind) {
538
37
  case "wheel":
@@ -541,6 +40,7 @@ function immediateParams(event, resolved) {
541
40
  deltaY: event.deltaY,
542
41
  clientX: event.clientX,
543
42
  clientY: event.clientY,
43
+ ...at(event.x, event.y),
544
44
  affordance: event.affordance,
545
45
  ...resolved
546
46
  };
@@ -556,11 +56,9 @@ function immediateParams(event, resolved) {
556
56
  case "click":
557
57
  case "doubleclick":
558
58
  return {
559
- worldX: event.x,
560
- worldY: event.y,
59
+ ...at(event.x, event.y),
561
60
  affordance: event.affordance,
562
- // The press point too: an action that places geometry at the click wants it.
563
- ...event.kind === "click" ? { pressX: event.pressX, pressY: event.pressY } : {},
61
+ ...event.kind === "click" && event.pressX !== void 0 && event.pressY !== void 0 ? { press: { x: event.pressX, y: event.pressY } } : {},
564
62
  mods: modifierStateOf(event),
565
63
  ...resolved
566
64
  };
@@ -568,8 +66,7 @@ function immediateParams(event, resolved) {
568
66
  // buffered copy matches `drag` specs, which are ongoing.
569
67
  case "contextmenu":
570
68
  return {
571
- worldX: event.x,
572
- worldY: event.y,
69
+ ...at(event.x, event.y),
573
70
  clientX: event.clientX,
574
71
  clientY: event.clientY,
575
72
  affordance: event.affordance,
@@ -581,8 +78,7 @@ function immediateParams(event, resolved) {
581
78
  case "pointerdown":
582
79
  case "longpress":
583
80
  return {
584
- worldX: event.x,
585
- worldY: event.y,
81
+ ...at(event.x, event.y),
586
82
  affordance: event.affordance,
587
83
  bodyTarget: event.bodyTarget,
588
84
  mods: modifierStateOf(event),
@@ -590,8 +86,7 @@ function immediateParams(event, resolved) {
590
86
  };
591
87
  case "hover":
592
88
  return {
593
- worldX: event.x,
594
- worldY: event.y,
89
+ ...at(event.x, event.y),
595
90
  clientX: event.clientX,
596
91
  clientY: event.clientY,
597
92
  affordance: event.affordance,
@@ -602,8 +97,7 @@ function immediateParams(event, resolved) {
602
97
  };
603
98
  case "carry":
604
99
  return {
605
- worldX: event.x,
606
- worldY: event.y,
100
+ ...at(event.x, event.y),
607
101
  clientX: event.clientX,
608
102
  clientY: event.clientY,
609
103
  carry: { type: event.type, value: event.value },
@@ -618,7 +112,7 @@ function immediateParams(event, resolved) {
618
112
  return {
619
113
  items: event.items,
620
114
  via: event.kind,
621
- ...event.kind === "drop" && event.x !== void 0 ? { worldX: event.x, worldY: event.y } : {},
115
+ ...event.kind === "drop" ? at(event.x, event.y) : {},
622
116
  ...resolved
623
117
  };
624
118
  default:
@@ -1251,13 +745,13 @@ function liveForKeys(binding, reach) {
1251
745
  function namesView(binding, view) {
1252
746
  return binding.opts?.views?.includes(view) ?? false;
1253
747
  }
1254
- function preferViewScoped(matches2, view) {
748
+ function preferViewScoped(matches, view) {
1255
749
  const named = [];
1256
750
  const rest = [];
1257
- for (const m of matches2) {
751
+ for (const m of matches) {
1258
752
  (namesView(m.binding, view) ? named : rest).push(m);
1259
753
  }
1260
- return named.length === 0 ? [...matches2] : [...named, ...rest];
754
+ return named.length === 0 ? [...matches] : [...named, ...rest];
1261
755
  }
1262
756
  function ruleCtxOf(ctx) {
1263
757
  const r = ctx.getRuleCtx?.();
@@ -1273,7 +767,7 @@ function isEligible(action, ruleCtx) {
1273
767
  function describeEligible(eligible) {
1274
768
  return describeRule(eligibleToRule(eligible));
1275
769
  }
1276
- function preferContextual(matches2, actionLookup, holds, view) {
770
+ function preferContextual(matches, actionLookup, holds, view) {
1277
771
  const out = [];
1278
772
  let run = [];
1279
773
  const flush = () => {
@@ -1284,7 +778,7 @@ function preferContextual(matches2, actionLookup, holds, view) {
1284
778
  out.push(...gated, ...run.filter((m) => !gated.includes(m)));
1285
779
  run = [];
1286
780
  };
1287
- for (const m of matches2) {
781
+ for (const m of matches) {
1288
782
  const head = run[0];
1289
783
  if (head && (head.scope !== m.scope || namesView(head.binding, view) !== namesView(m.binding, view) || !sameSpecificity(head.binding.spec, m.binding.spec))) flush();
1290
784
  run.push(m);
@@ -1399,6 +893,9 @@ function createGestureFacts() {
1399
893
  sample(gestureId, event) {
1400
894
  points.get(gestureId)?.push({ x: event.x, y: event.y, ...stylusOf(event) });
1401
895
  },
896
+ resumeTrail(gestureId, trail) {
897
+ if (points.has(gestureId)) points.set(gestureId, [...trail]);
898
+ },
1402
899
  turn(gestureId, event) {
1403
900
  const turn = turns.get(gestureId);
1404
901
  if (!turn || event.angle === void 0) return;
@@ -1499,6 +996,33 @@ function isEmptyOngoingHandle(h) {
1499
996
  return !h.onMove && !h.onEnd && !h.overlay && !h.previewIds && !h.previewPose;
1500
997
  }
1501
998
 
999
+ // src/interactions/dispatcher/deferredStart.ts
1000
+ function deferredStart(start, thresholdPx) {
1001
+ let inner = null;
1002
+ const onMove = (ctx) => {
1003
+ if (!inner) {
1004
+ const travel = ctx.drag?.screenDelta;
1005
+ if (!travel || Math.hypot(travel.x, travel.y) < thresholdPx) return;
1006
+ inner = start();
1007
+ }
1008
+ return inner.onMove?.(ctx);
1009
+ };
1010
+ const onEnd = (ctx, reason) => {
1011
+ inner?.onEnd?.(ctx, reason);
1012
+ };
1013
+ const read2 = (key) => {
1014
+ if (key === "onMove") return onMove;
1015
+ if (key === "onEnd") return onEnd;
1016
+ if (!inner) return void 0;
1017
+ const value = Reflect.get(inner, key);
1018
+ return typeof value === "function" ? value.bind(inner) : value;
1019
+ };
1020
+ return new Proxy({}, {
1021
+ get: (_target, key) => read2(key),
1022
+ has: (_target, key) => read2(key) !== void 0
1023
+ });
1024
+ }
1025
+
1502
1026
  // src/interactions/dispatcher/invocationCtx.ts
1503
1027
  function uiInvocationCtx(deps, params) {
1504
1028
  return {
@@ -1527,7 +1051,13 @@ function pressDrag(event) {
1527
1051
  }
1528
1052
  };
1529
1053
  }
1530
- function pumpDrag(event, facts) {
1054
+ function localDelta(toLocal, fromX, fromY, toX, toY) {
1055
+ if (!toLocal) return { x: toX - fromX, y: toY - fromY };
1056
+ const a = toLocal(fromX, fromY);
1057
+ const b = toLocal(toX, toY);
1058
+ return { x: b.x - a.x, y: b.y - a.y };
1059
+ }
1060
+ function pumpDrag(event, facts, toLocal) {
1531
1061
  const cx = event.x;
1532
1062
  const cy = event.y;
1533
1063
  const origin = facts.origin;
@@ -1544,7 +1074,7 @@ function pumpDrag(event, facts) {
1544
1074
  start: { x: ox, y: oy },
1545
1075
  current: { x: cx, y: cy },
1546
1076
  delta: { x: cx - ox, y: cy - oy },
1547
- ...hasClient ? { screenDelta: { x: eventClientX - origin.clientX, y: eventClientY - origin.clientY } } : {},
1077
+ ...hasClient ? { screenDelta: localDelta(toLocal, origin.clientX, origin.clientY, eventClientX, eventClientY) } : {},
1548
1078
  ...points !== void 0 ? { points } : {}
1549
1079
  }
1550
1080
  };
@@ -1561,7 +1091,7 @@ function multiTouchOf(event, facts) {
1561
1091
  ...startSpread !== void 0 && event.spread !== void 0 ? { pinch: { startSpread, currentSpread: spread, centroid } } : {}
1562
1092
  };
1563
1093
  }
1564
- function buildInvocationCtx(event, deps, viewId, facts = {}) {
1094
+ function buildInvocationCtx(event, deps, viewId, facts = {}, toLocal) {
1565
1095
  const base = {
1566
1096
  world: { x: 0, y: 0 },
1567
1097
  modifiers: { alt: event.altKey, ctrl: event.ctrlKey, meta: event.metaKey, shift: event.shiftKey },
@@ -1577,12 +1107,13 @@ function buildInvocationCtx(event, deps, viewId, facts = {}) {
1577
1107
  base.key = { key: event.key, repeat: false };
1578
1108
  } else if (event.kind === "wheel") {
1579
1109
  base.wheel = { deltaX: event.deltaX, deltaY: event.deltaY, deltaZ: 0 };
1110
+ if (event.x !== void 0 && event.y !== void 0) base.world = { x: event.x, y: event.y };
1580
1111
  } else if (event.kind === "pinch") {
1581
1112
  base.pinch = { scale: event.scale, rotation: event.rotation };
1582
1113
  } else if (event.kind === "pointerdown" || event.kind === "click" || event.kind === "doubleclick" || event.kind === "contextmenu") {
1583
1114
  Object.assign(base, pressDrag(event));
1584
1115
  } else if (event.kind === "pointermove" || event.kind === "pointerup") {
1585
- Object.assign(base, pumpDrag(event, facts));
1116
+ Object.assign(base, pumpDrag(event, facts, toLocal));
1586
1117
  } else if (event.kind === "multitouch") {
1587
1118
  base.multiTouch = multiTouchOf(event, facts);
1588
1119
  } else if (event.kind === "carry") {
@@ -1593,7 +1124,7 @@ function buildInvocationCtx(event, deps, viewId, facts = {}) {
1593
1124
  }
1594
1125
 
1595
1126
  // src/interactions/dispatcher/gestureRun.ts
1596
- var ctxFor = (run, event, deps, gestureId) => buildInvocationCtx(event, deps, run.viewId(), run.facts.of(gestureId));
1127
+ var ctxFor = (run, event, deps, gestureId) => buildInvocationCtx(event, deps, run.viewId(), run.facts.of(gestureId), run.toLocal());
1597
1128
  function runOnEnd(gestureId, handle, endCtx, reason) {
1598
1129
  try {
1599
1130
  handle.onEnd?.(endCtx, reason);
@@ -1606,9 +1137,25 @@ function endGesture(run, gestureId, event, reason) {
1606
1137
  const endCtx = ctxFor(run, event, {}, gestureId);
1607
1138
  const handle = run.inFlight.take(gestureId);
1608
1139
  run.facts.drop(gestureId);
1140
+ run.opened.delete(gestureId);
1609
1141
  runOnEnd(gestureId, handle, endCtx, reason);
1610
1142
  return true;
1611
1143
  }
1144
+ function endPassed(run, event) {
1145
+ const gestureId = gestureIdFor(event);
1146
+ const opened = run.opened.get(gestureId);
1147
+ if (opened === void 0) return null;
1148
+ const trail = [...run.facts.of(gestureId).points ?? []];
1149
+ endGesture(run, gestureId, event, "cancel");
1150
+ return { event: opened.event, skip: /* @__PURE__ */ new Set([...opened.passed, opened.action]), trail };
1151
+ }
1152
+ function notePassed(run, event, passed) {
1153
+ const gestureId = gestureIdFor(event);
1154
+ const opened = run.opened.get(gestureId);
1155
+ if (opened === void 0) return;
1156
+ opened.passed = /* @__PURE__ */ new Set([...opened.passed, ...passed.skip]);
1157
+ run.facts.resumeTrail(gestureId, passed.trail.slice(0, -1));
1158
+ }
1612
1159
  function endMultiTouch(run, event, reason, except) {
1613
1160
  let ended = false;
1614
1161
  for (const id of [...run.inFlight.handles.keys()]) {
@@ -1620,6 +1167,7 @@ function endAllGestures(run, reason) {
1620
1167
  const stubCtx = uiInvocationCtx({});
1621
1168
  const ending = run.inFlight.takeAll();
1622
1169
  run.facts.clear();
1170
+ run.opened.clear();
1623
1171
  for (const [gestureId, handle] of ending) runOnEnd(gestureId, handle, stubCtx, reason);
1624
1172
  }
1625
1173
  function pumpGesture(run, event) {
@@ -1635,8 +1183,8 @@ function pumpGesture(run, event) {
1635
1183
  if (!handle) return "unhandled";
1636
1184
  run.facts.sample(gestureId, event);
1637
1185
  if (!handle.onMove) return "unhandled";
1638
- handle.onMove(ctxFor(run, event, {}, gestureId));
1639
- return "handled";
1186
+ const said = handle.onMove(ctxFor(run, event, {}, gestureId));
1187
+ return said === "pass" && run.opened.has(gestureId) ? "pass" : "handled";
1640
1188
  }
1641
1189
  if (event.kind === "wheel") {
1642
1190
  for (const [gestureId, handle] of [...inFlight.handles].reverse()) {
@@ -1682,7 +1230,7 @@ function invokeAction(run, event, match, action, deps) {
1682
1230
  if (action.invoker?.timing === "immediate") {
1683
1231
  try {
1684
1232
  const params = immediateParams(event, resolveParams(match.binding.opts?.params));
1685
- action.invoker.run(deps, params);
1233
+ if (action.invoker.run(deps, params) === "pass") return "empty-handle";
1686
1234
  } catch (err) {
1687
1235
  console.error(`weasel dispatcher: action "${action.id}" invoker threw`, err);
1688
1236
  }
@@ -1699,103 +1247,22 @@ function invokeAction(run, event, match, action, deps) {
1699
1247
  const gestureId = gestureIdFor(event);
1700
1248
  const roleKeys = roleKeysOf(action.modifierRoles, match.binding.opts?.roles);
1701
1249
  run.facts.begin(gestureId, event, roleKeys && platformRoleKeys(roleKeys, run.isMac()));
1702
- const handle = action.invoker.start(ctxFor(run, event, deps, gestureId), match.binding.opts);
1250
+ const { invoker } = action;
1251
+ const pressCtx = ctxFor(run, event, deps, gestureId);
1252
+ const begin = () => invoker.start(pressCtx, match.binding.opts);
1253
+ const threshold = match.binding.opts?.dragThresholdPx ?? invoker.dragThresholdPx;
1254
+ const handle = event.kind === "pointerdown" && threshold !== void 0 && threshold > 0 ? deferredStart(begin, threshold) : begin();
1703
1255
  if (isEmptyOngoingHandle(handle)) {
1704
1256
  run.facts.drop(gestureId);
1705
1257
  return "empty-handle";
1706
1258
  }
1707
1259
  run.inFlight.open(gestureId, handle, match.ownerId, action);
1260
+ if (event.kind === "pointerdown") run.opened.set(gestureId, { event, action: action.id, passed: /* @__PURE__ */ new Set() });
1708
1261
  return "fired";
1709
1262
  }
1710
1263
  return "fired";
1711
1264
  }
1712
1265
 
1713
- // src/interactions/dispatcher/uiOngoing.ts
1714
- function createUiOngoing(inFlight, notify, getAction) {
1715
- let seq = 0;
1716
- const byAction = /* @__PURE__ */ new Map();
1717
- function commitPrior(actionId, deps) {
1718
- const prevGestureId = byAction.get(actionId);
1719
- if (prevGestureId === void 0) return;
1720
- const prevHandle = inFlight.handles.get(prevGestureId);
1721
- if (prevHandle?.onEnd) {
1722
- try {
1723
- prevHandle.onEnd(uiInvocationCtx(deps), "commit");
1724
- } catch (e) {
1725
- console.error(`weasel dispatcher: prior UI handle for "${actionId}" threw on auto-commit`, e);
1726
- }
1727
- }
1728
- inFlight.take(prevGestureId);
1729
- byAction.delete(actionId);
1730
- }
1731
- function control(actionId, gestureId, handle, deps) {
1732
- let ended = false;
1733
- const live = () => !ended && inFlight.handles.get(gestureId) === handle;
1734
- return {
1735
- gestureId,
1736
- get active() {
1737
- return live();
1738
- },
1739
- update(nextParams) {
1740
- if (!live()) return;
1741
- if (!handle.onMove) return;
1742
- try {
1743
- handle.onMove(uiInvocationCtx(deps, nextParams));
1744
- } catch (e) {
1745
- console.error(`weasel dispatcher: action "${actionId}" threw on onMove`, e);
1746
- }
1747
- notify();
1748
- },
1749
- end(reason) {
1750
- if (!live()) {
1751
- ended = true;
1752
- return;
1753
- }
1754
- ended = true;
1755
- if (handle.onEnd) {
1756
- try {
1757
- handle.onEnd(uiInvocationCtx(deps), reason);
1758
- } catch (e) {
1759
- console.error(`weasel dispatcher: action "${actionId}" threw on onEnd`, e);
1760
- }
1761
- }
1762
- inFlight.take(gestureId);
1763
- if (byAction.get(actionId) === gestureId) byAction.delete(actionId);
1764
- notify();
1765
- }
1766
- };
1767
- }
1768
- return {
1769
- begin(actionId, deps, params) {
1770
- if (!getAction) {
1771
- console.warn("weasel dispatcher: beginUiOngoing called but no getAction was provided to createDispatcher");
1772
- return null;
1773
- }
1774
- const action = getAction(actionId);
1775
- if (!action || !action.invoker || action.invoker.timing !== "ongoing") {
1776
- return null;
1777
- }
1778
- commitPrior(actionId, deps);
1779
- const gestureId = `ui-${actionId}-${++seq}`;
1780
- let handle;
1781
- try {
1782
- handle = action.invoker.start(uiInvocationCtx(deps, params), { params });
1783
- } catch (e) {
1784
- console.error(`weasel dispatcher: action "${actionId}" threw on start`, e);
1785
- return null;
1786
- }
1787
- if (isEmptyOngoingHandle(handle)) return null;
1788
- inFlight.open(gestureId, handle, null);
1789
- byAction.set(actionId, gestureId);
1790
- notify();
1791
- return control(actionId, gestureId, handle, deps);
1792
- },
1793
- clear: () => {
1794
- byAction.clear();
1795
- }
1796
- };
1797
- }
1798
-
1799
1266
  // src/interactions/dispatcher/walk.ts
1800
1267
  function assembleScopedBindings(ctx) {
1801
1268
  const allowedCapabilities = ctx.getRuleCtx?.()?.allowedCapabilities;
@@ -1959,6 +1426,104 @@ function verdictsOf(r, walked, evaluateShadowed) {
1959
1426
  return out;
1960
1427
  }
1961
1428
 
1429
+ // src/interactions/dispatcher/handOff.ts
1430
+ function handOff(run, event, ctx, invoking, engaged) {
1431
+ for (let passed = endPassed(run, event); passed !== null; passed = endPassed(run, event)) {
1432
+ const { event: press, skip } = passed;
1433
+ const invoke = invoking(press);
1434
+ walk(rank(press, ctx, engaged(), false), press, ctx, (match, action, deps) => skip.has(action.id) ? "empty-handle" : invoke(match, action, deps), false);
1435
+ notePassed(run, event, passed);
1436
+ if (pumpGesture(run, event) !== "pass") break;
1437
+ }
1438
+ return "handled";
1439
+ }
1440
+
1441
+ // src/interactions/dispatcher/uiOngoing.ts
1442
+ function createUiOngoing(inFlight, notify, getAction) {
1443
+ let seq = 0;
1444
+ const byAction = /* @__PURE__ */ new Map();
1445
+ function commitPrior(actionId, deps) {
1446
+ const prevGestureId = byAction.get(actionId);
1447
+ if (prevGestureId === void 0) return;
1448
+ const prevHandle = inFlight.handles.get(prevGestureId);
1449
+ if (prevHandle?.onEnd) {
1450
+ try {
1451
+ prevHandle.onEnd(uiInvocationCtx(deps), "commit");
1452
+ } catch (e) {
1453
+ console.error(`weasel dispatcher: prior UI handle for "${actionId}" threw on auto-commit`, e);
1454
+ }
1455
+ }
1456
+ inFlight.take(prevGestureId);
1457
+ byAction.delete(actionId);
1458
+ }
1459
+ function control(actionId, gestureId, handle, deps) {
1460
+ let ended = false;
1461
+ const live = () => !ended && inFlight.handles.get(gestureId) === handle;
1462
+ return {
1463
+ gestureId,
1464
+ get active() {
1465
+ return live();
1466
+ },
1467
+ update(nextParams) {
1468
+ if (!live()) return;
1469
+ if (!handle.onMove) return;
1470
+ try {
1471
+ handle.onMove(uiInvocationCtx(deps, nextParams));
1472
+ } catch (e) {
1473
+ console.error(`weasel dispatcher: action "${actionId}" threw on onMove`, e);
1474
+ }
1475
+ notify();
1476
+ },
1477
+ end(reason) {
1478
+ if (!live()) {
1479
+ ended = true;
1480
+ return;
1481
+ }
1482
+ ended = true;
1483
+ if (handle.onEnd) {
1484
+ try {
1485
+ handle.onEnd(uiInvocationCtx(deps), reason);
1486
+ } catch (e) {
1487
+ console.error(`weasel dispatcher: action "${actionId}" threw on onEnd`, e);
1488
+ }
1489
+ }
1490
+ inFlight.take(gestureId);
1491
+ if (byAction.get(actionId) === gestureId) byAction.delete(actionId);
1492
+ notify();
1493
+ }
1494
+ };
1495
+ }
1496
+ return {
1497
+ begin(actionId, deps, params) {
1498
+ if (!getAction) {
1499
+ console.warn("weasel dispatcher: beginUiOngoing called but no getAction was provided to createDispatcher");
1500
+ return null;
1501
+ }
1502
+ const action = getAction(actionId);
1503
+ if (!action || !action.invoker || action.invoker.timing !== "ongoing") {
1504
+ return null;
1505
+ }
1506
+ commitPrior(actionId, deps);
1507
+ const gestureId = `ui-${actionId}-${++seq}`;
1508
+ let handle;
1509
+ try {
1510
+ handle = action.invoker.start(uiInvocationCtx(deps, params), { params });
1511
+ } catch (e) {
1512
+ console.error(`weasel dispatcher: action "${actionId}" threw on start`, e);
1513
+ return null;
1514
+ }
1515
+ if (isEmptyOngoingHandle(handle)) return null;
1516
+ inFlight.open(gestureId, handle, null);
1517
+ byAction.set(actionId, gestureId);
1518
+ notify();
1519
+ return control(actionId, gestureId, handle, deps);
1520
+ },
1521
+ clear: () => {
1522
+ byAction.clear();
1523
+ }
1524
+ };
1525
+ }
1526
+
1962
1527
  // src/interactions/dispatcher/predict.ts
1963
1528
  function resolveAll(event, ctx, engaged, opts) {
1964
1529
  const evaluateShadowed = opts?.evaluateShadowed ?? false;
@@ -2019,6 +1584,10 @@ function routeThroughKeymap(event, ctx, passes) {
2019
1584
  if (verdict === "taken") return "unhandled";
2020
1585
  if (verdict === "cascaded") return passes.run("none", event);
2021
1586
  if (isPlainEscape(event)) {
1587
+ if (passes.engaged() && passes.run("none", event) === "handled") {
1588
+ stack.inputSeen();
1589
+ return "handled";
1590
+ }
2022
1591
  stack.closeInnermost();
2023
1592
  return "handled";
2024
1593
  }
@@ -2063,17 +1632,28 @@ function createDispatcher(opts) {
2063
1632
  const uiOngoing = createUiOngoing(inFlight, notify, opts?.getAction);
2064
1633
  let routedView;
2065
1634
  let routedIsMac = false;
2066
- const run = { inFlight, facts, viewId: () => routedView, isMac: () => routedIsMac };
1635
+ let routedToLocal;
1636
+ const run = {
1637
+ inFlight,
1638
+ facts,
1639
+ viewId: () => routedView,
1640
+ isMac: () => routedIsMac,
1641
+ toLocal: () => routedToLocal,
1642
+ opened: /* @__PURE__ */ new Map()
1643
+ };
2067
1644
  const invoking = (event) => (match, action, deps) => invokeAction(run, event, match, action, deps);
2068
1645
  function handleInput(event, ctx) {
2069
1646
  routedView = ctx.viewId;
2070
1647
  routedIsMac = ctx.isMac;
1648
+ routedToLocal = ctx.toLocal;
2071
1649
  const pumped = pumpGesture(run, event);
1650
+ if (pumped === "pass") return handOff(run, event, ctx, invoking, inFlight.engagedOwners);
2072
1651
  if (pumped !== void 0) return pumped;
2073
1652
  if (event.kind === "hover") return hover(event, ctx);
2074
1653
  return routeThroughKeymap(event, ctx, {
2075
1654
  run: (pass, ev) => route(ev, { ...ctx, keymapPass: pass }),
2076
- binds: (ev) => rank(ev, { ...ctx, keymapPass: "only" }, inFlight.engagedOwners(), false).ordered.length > 0
1655
+ binds: (ev) => rank(ev, { ...ctx, keymapPass: "only" }, inFlight.engagedOwners(), false).ordered.length > 0,
1656
+ engaged: () => inFlight.handles.size > 0
2077
1657
  }) ?? route(event, ctx);
2078
1658
  }
2079
1659
  function route(event, ctx) {
@@ -2255,20 +1835,20 @@ function startCarry(origin, down, payload, cb = {}) {
2255
1835
  const doc = origin.ownerDocument ?? document;
2256
1836
  const pointerId = down.pointerId;
2257
1837
  let over = null;
2258
- const leave = (at) => {
1838
+ const leave = (at2) => {
2259
1839
  const was = over;
2260
1840
  over = null;
2261
- if (was && surfaces.has(was)) was.receive("leave", payload, at, pointerId);
1841
+ if (was && surfaces.has(was)) was.receive("leave", payload, at2, pointerId);
2262
1842
  };
2263
- const step = (at) => {
2264
- const next = surfaceAt(doc, at.clientX, at.clientY);
1843
+ const step = (at2) => {
1844
+ const next = surfaceAt(doc, at2.clientX, at2.clientY);
2265
1845
  if (next === over) {
2266
- over?.receive("move", payload, at, pointerId);
1846
+ over?.receive("move", payload, at2, pointerId);
2267
1847
  return;
2268
1848
  }
2269
- leave(at);
1849
+ leave(at2);
2270
1850
  over = next;
2271
- next?.receive("enter", payload, at, pointerId);
1851
+ next?.receive("enter", payload, at2, pointerId);
2272
1852
  };
2273
1853
  let last = down;
2274
1854
  const session = openPointerSession(origin, down, {
@@ -2795,14 +2375,17 @@ function itemsFromDataTransfer(dt) {
2795
2375
  return Promise.all(reads).then(() => [...files, ...strings]);
2796
2376
  }
2797
2377
  function itemTypesFromDataTransfer(dt) {
2798
- const mimes = [];
2378
+ const pending = [];
2799
2379
  for (const item of Array.from(dt.items ?? [])) {
2800
2380
  const mime = item.type ? normalizeMime(item.type) : "";
2801
- if (item.kind === "file") mimes.push(mime && mime !== "application/x-moz-file" ? mime : UNKNOWN_MIME);
2802
- else if (keepsString(mime)) mimes.push(mime);
2381
+ if (item.kind === "file") pending.push({ pending: "file", mime: mime && mime !== "application/x-moz-file" ? mime : UNKNOWN_MIME });
2382
+ else if (keepsString(mime)) pending.push({ pending: "string", mime });
2803
2383
  }
2804
- if (mimes.length === 0 && Array.from(dt.types ?? []).includes("Files")) mimes.push(UNKNOWN_MIME);
2805
- return mimes.map((mime) => ({ mime }));
2384
+ if (pending.length === 0 && Array.from(dt.types ?? []).includes("Files")) pending.push({ pending: "file", mime: UNKNOWN_MIME });
2385
+ return pending;
2386
+ }
2387
+ function pendingKindOf(item) {
2388
+ return item.pending;
2806
2389
  }
2807
2390
  function itemsFromClipboardData(cd) {
2808
2391
  const out = [];
@@ -2862,339 +2445,6 @@ function dwarn(namespace, ...args) {
2862
2445
  console.warn(`[${namespace}]`, ...args);
2863
2446
  }
2864
2447
 
2865
- // src/menus/pickMenu.ts
2866
- var oneOf = (want, got) => want === void 0 || (Array.isArray(want) ? want.includes(got) : want === got);
2867
- function matches(when, hit) {
2868
- return oneOf(when.bodyTarget, hit.bodyTarget) && oneOf(when.bodyKind, hit.bodyKind);
2869
- }
2870
- function mergeMenuDefs(defs, activeTool) {
2871
- const live = [
2872
- ...defs.filter((d) => d.owner === null),
2873
- ...defs.filter((d) => d.owner !== null && d.owner === activeTool)
2874
- ];
2875
- const byId = /* @__PURE__ */ new Map();
2876
- for (const { def } of live) {
2877
- const prev = byId.get(def.id);
2878
- if (!prev) {
2879
- byId.set(def.id, { def: { ...def, menu: { ...def.menu, children: { ...def.menu.children } } }, parts: 1 });
2880
- continue;
2881
- }
2882
- const n = prev.parts++;
2883
- const children = prev.def.menu.children;
2884
- children[`~sep${n}`] = { kind: "separator", name: "", description: "", default: void 0 };
2885
- for (const [k, node] of Object.entries(def.menu.children)) {
2886
- children[Object.hasOwn(children, k) ? `${k}~${n}` : k] = node;
2887
- }
2888
- if (!prev.def.when && def.when) prev.def.when = def.when;
2889
- }
2890
- return [...byId.values()].map((m) => m.def);
2891
- }
2892
- function pickMenu(defs, hit) {
2893
- return defs.find((d) => d.when !== void 0 && matches(d.when, hit));
2894
- }
2895
- var SUBMENU_AS = /* @__PURE__ */ new Set(["menu", "page", "tab", "panel"]);
2896
- var noop = () => {
2897
- };
2898
- var withAccess = (access) => access ? { access } : {};
2899
- var sep = (key) => ({ kind: "separator", key });
2900
- function itemOf(ctx, it, key, label, off, access) {
2901
- const deps = ctx.src.depsFor(it.action);
2902
- const point = ctx.at ? { worldX: ctx.at.x, worldY: ctx.at.y } : {};
2903
- if (it.action.settings) return settingsOf(ctx, it, key, label, off || !evaluateEnabled(it.action, deps, ctx.at).enabled, access);
2904
- return {
2905
- kind: "item",
2906
- key,
2907
- label,
2908
- ...withAccess(access),
2909
- icon: it.icon,
2910
- shortcut: actionShortcuts(it.action, it.params)[0],
2911
- disabled: off || !evaluateEnabled(it.action, deps, ctx.at).enabled,
2912
- checked: evaluateActive(it.action, deps),
2913
- run: () => {
2914
- ctx.src.trigger(it.action.id, { ...ctx.params, ...it.params, ...point });
2915
- }
2916
- };
2917
- }
2918
- function settingsOf(ctx, it, key, label, off, access) {
2919
- const settings = it.action.settings;
2920
- const state = off ? null : readActionSettings(it.action, ctx.src.depsFor(it.action));
2921
- const mixed = new Set(state?.mixed ?? []);
2922
- const values = (path) => mixed.has(path) ? void 0 : prefValueAtPath(state?.values, path);
2923
- const onChange = (path, value) => {
2924
- const params = actionSettingsParams(settings, path, value);
2925
- if (params) ctx.src.trigger(it.action.id, { ...ctx.params, ...it.params, ...params });
2926
- };
2927
- const form = {
2928
- ...ctx,
2929
- src: { ...ctx.src, values, onChange, over: settings.schema },
2930
- root: settings.schema,
2931
- read: (path, from) => from === "item" ? void 0 : values(path)
2932
- };
2933
- const entries = state ? tidy(groupRows(form, settings.schema, "", ROOT)) : [];
2934
- return {
2935
- kind: "submenu",
2936
- key,
2937
- label,
2938
- ...it.icon ? { icon: it.icon } : {},
2939
- ...withAccess(access),
2940
- ...entries.length === 0 ? { disabled: true } : {},
2941
- entries
2942
- };
2943
- }
2944
- function itemsNamed(ctx, name) {
2945
- const action = ctx.byId.get(name);
2946
- if (action) return actionItems(action);
2947
- const colon = name.indexOf(":");
2948
- const owner = colon > 0 ? ctx.byId.get(name.slice(0, colon)) : void 0;
2949
- return owner ? actionItems(owner).filter((it) => it.key === name) : [];
2950
- }
2951
- function actionRows(ctx, leaf, key, off) {
2952
- const own = leaf.label || leaf.name;
2953
- if (leaf.action === void 0) {
2954
- if (!own) return [];
2955
- if (isDev()) console.warn(`weasel: menu row "${key}" names no registered action, so it does nothing`);
2956
- return [{ kind: "item", key, label: own, disabled: true, run: noop }];
2957
- }
2958
- const items = itemsNamed(ctx, leaf.action);
2959
- if (items.length === 1) return [itemOf(ctx, items[0], key, own || items[0].label, off, leaf.access)];
2960
- return items.map((it) => itemOf(ctx, it, `${key}/${it.key}`, it.label, off));
2961
- }
2962
- function siblingsOf(ctx, path) {
2963
- const dot = path.lastIndexOf(".");
2964
- const parent = dot < 0 ? void 0 : ctx.src.values(path.slice(0, dot));
2965
- return parent && typeof parent === "object" ? parent : void 0;
2966
- }
2967
- function booleanRows(ctx, leaf, key, path, off) {
2968
- const { values, onChange } = ctx.src;
2969
- if (!values || !onChange) return [];
2970
- const stored = values(path);
2971
- const on = leaf.encoding ? leaf.encoding.read(stored, siblingsOf(ctx, path)) : stored === true;
2972
- const write = (v) => leaf.encoding ? leaf.encoding.write(v, siblingsOf(ctx, path)) : v;
2973
- return [{
2974
- kind: "item",
2975
- key,
2976
- label: leaf.name,
2977
- ...withAccess(leaf.access),
2978
- checked: on,
2979
- disabled: off || leaf.readout === true,
2980
- run: leaf.readout ? noop : () => onChange(path, write(!on))
2981
- }];
2982
- }
2983
- function enumRows(ctx, leaf, key, path, off) {
2984
- const { values, onChange } = ctx.src;
2985
- if (!values || !onChange) return [];
2986
- const stored = values(path);
2987
- const current = leaf.encoding ? leaf.encoding.read(stored, siblingsOf(ctx, path)) : stored;
2988
- if (leaf.readout) {
2989
- const shown = leaf.options.find((o) => o.value === current)?.label ?? "\u2014";
2990
- return [{ kind: "item", key, label: `${leaf.name}: ${shown}`, disabled: true, run: noop }];
2991
- }
2992
- const write = (v) => leaf.encoding ? leaf.encoding.write(v, siblingsOf(ctx, path)) : v;
2993
- return [
2994
- sep(`${key}/<`),
2995
- ...leaf.options.map((o) => ({
2996
- kind: "item",
2997
- key: `${key}/${o.value}`,
2998
- label: o.label,
2999
- checked: o.value === current,
3000
- disabled: off || o.disabled === true,
3001
- run: () => onChange(path, write(o.value))
3002
- })),
3003
- sep(`${key}/>`)
3004
- ];
3005
- }
3006
- function numberRows(ctx, given, key, path, off) {
3007
- const { values, onChange } = ctx.src;
3008
- if (!values || !onChange) return [];
3009
- const leaf = prefNumberAt(given, values);
3010
- const unit = leaf.unit && resolvePrefUnit(leaf.unit);
3011
- const stored = values(path);
3012
- const { min, max, step, endless } = prefDisplayBounds(leaf);
3013
- const disabled = off || leaf.readout === true;
3014
- return [{
3015
- kind: "number",
3016
- key,
3017
- label: leaf.name,
3018
- ...withAccess(leaf.access),
3019
- ...leaf.icon ? { icon: leaf.icon } : {},
3020
- ...disabled ? { disabled } : {},
3021
- value: typeof stored === "number" && (Number.isFinite(stored) || endlessAllows(leaf.endless, stored)) ? unit ? unit.toDisplay(stored) : stored : void 0,
3022
- ...min === void 0 ? {} : { min },
3023
- ...max === void 0 ? {} : { max },
3024
- step,
3025
- ...unit?.suffix === void 0 ? {} : { suffix: unit.suffix },
3026
- ...leaf.display ? { display: leaf.display } : {},
3027
- ...endless ? { endless } : {},
3028
- ...leaf.infinity === void 0 ? {} : { infinity: leaf.infinity },
3029
- ...unit ? { accepts: prefUnitAccepts(unit) } : {},
3030
- set: (shown) => onChange(path, unit ? unit.fromDisplay(shown) : shown)
3031
- }];
3032
- }
3033
- var textAt = (ctx, path) => {
3034
- const v = ctx.src.values?.(path);
3035
- return typeof v === "string" && v !== "" ? v : void 0;
3036
- };
3037
- function colorRows(ctx, leaf, key, path, off) {
3038
- if (!ctx.src.values) return [];
3039
- return [{ kind: "color", key, label: leaf.name, ...withAccess(leaf.access), ...off ? { disabled: true } : {}, value: textAt(ctx, path) }];
3040
- }
3041
- function stringRows(ctx, leaf, key, path, off) {
3042
- const { values, onChange } = ctx.src;
3043
- if (!values) return [];
3044
- if (leaf.readout || !onChange) {
3045
- return [{ kind: "item", key, label: `${leaf.name}: ${textAt(ctx, path) ?? "\u2014"}`, ...withAccess(leaf.access), disabled: true, run: noop }];
3046
- }
3047
- const stored = values(path);
3048
- return [{
3049
- kind: "text",
3050
- key,
3051
- label: leaf.name,
3052
- ...withAccess(leaf.access),
3053
- ...leaf.icon ? { icon: leaf.icon } : {},
3054
- ...off ? { disabled: true } : {},
3055
- value: typeof stored === "string" ? stored : void 0,
3056
- set: (text) => onChange(path, text)
3057
- }];
3058
- }
3059
- function leafRows(ctx, leaf, key, path, off) {
3060
- switch (leaf.kind) {
3061
- case "action":
3062
- return actionRows(ctx, leaf, key, off);
3063
- case "actions": {
3064
- const group = leaf.group;
3065
- return ctx.src.actions().filter((a) => a.group === group).flatMap((a) => actionItems(a).map((it) => itemOf(ctx, it, `${key}/${it.key}`, it.label, off)));
3066
- }
3067
- case "boolean":
3068
- return booleanRows(ctx, leaf, key, path, off);
3069
- case "enum":
3070
- return enumRows(ctx, leaf, key, path, off);
3071
- case "number":
3072
- return numberRows(ctx, leaf, key, path, off);
3073
- case "color":
3074
- return colorRows(ctx, leaf, key, path, off);
3075
- case "string":
3076
- return stringRows(ctx, leaf, key, path, off);
3077
- case "separator":
3078
- return [sep(key)];
3079
- case "label":
3080
- return [{ kind: "item", key, label: leaf.name, ...withAccess(leaf.access), disabled: true, run: noop }];
3081
- case "alias": {
3082
- const alias = leaf;
3083
- const target = prefAliasTarget(alias, (p) => prefLeafAt(ctx.src.over ?? ctx.root, p));
3084
- return target ? leafRows(ctx, prefAliasedLeaf(alias, target.leaf), key, target.path, off) : [];
3085
- }
3086
- default:
3087
- return [];
3088
- }
3089
- }
3090
- var ROOT = { node: {}, scope: PREF_SCOPE, above: PREF_SCOPE };
3091
- function groupRows(ctx, group, prefix, owner) {
3092
- const { read: read2 } = ctx;
3093
- const holds = (condition) => read2 === void 0 || prefConditionHolds(condition, read2);
3094
- const out = [];
3095
- for (const [k, node] of Object.entries(group.children)) {
3096
- const path = prefix ? `${prefix}.${k}` : k;
3097
- const sits = isPrefLeaf(node) ? prefScopeIn(owner, path, holds) : owner.scope;
3098
- const scope = prefScopeUnder(sits, node, holds);
3099
- if (!scope.shown) continue;
3100
- const disabled = !scope.enabled;
3101
- const inner = { node, scope, above: sits };
3102
- if (isPrefLeaf(node)) {
3103
- out.push(...leafRows(ctx, node, path, path, disabled));
3104
- } else if (node.as !== void 0 && SUBMENU_AS.has(node.as)) {
3105
- const entries = tidy(groupRows(ctx, node, path, inner));
3106
- if (entries.length) {
3107
- out.push({ kind: "submenu", key: path, label: node.name, ...node.icon ? { icon: node.icon } : {}, ...withAccess(node.access), entries });
3108
- }
3109
- } else {
3110
- out.push(sep(`${path}/<`), ...groupRows(ctx, node, path, inner), sep(`${path}/>`));
3111
- }
3112
- }
3113
- return out;
3114
- }
3115
- function tidy(entries) {
3116
- const out = [];
3117
- for (const e of entries) {
3118
- if (e.kind === "separator" && (!out.length || out[out.length - 1].kind === "separator")) continue;
3119
- out.push(e);
3120
- }
3121
- if (out[out.length - 1]?.kind === "separator") out.pop();
3122
- return out;
3123
- }
3124
- function resolveSchemaMenu(schema, src, at, params) {
3125
- const values = src.values;
3126
- const ctx = {
3127
- src,
3128
- byId: new Map(src.actions().map((a) => [a.id, a])),
3129
- root: schema,
3130
- read: values ? (path, from) => from === "item" ? void 0 : values(path) : void 0,
3131
- at,
3132
- params
3133
- };
3134
- return tidy(groupRows(ctx, schema, "", ROOT));
3135
- }
3136
- function resolveMenu(def, src, at, params) {
3137
- return resolveSchemaMenu(def.menu, src, at, params);
3138
- }
3139
-
3140
- // src/menus/menuHost.ts
3141
- function createMenuHost(src) {
3142
- const defs = [];
3143
- const listeners = /* @__PURE__ */ new Set();
3144
- let open = null;
3145
- let seq = 0;
3146
- const emit = () => {
3147
- for (const fn of listeners) fn();
3148
- };
3149
- const close = () => {
3150
- if (!open) return;
3151
- open = null;
3152
- emit();
3153
- };
3154
- const resolve = (req) => {
3155
- const live = mergeMenuDefs(defs, src.activeTool());
3156
- const def = req.menu !== void 0 ? live.find((d) => d.id === req.menu) : req.hit ? pickMenu(live, req.hit) : void 0;
3157
- return def ? resolveMenu(def, src, req.world, req.params) : [];
3158
- };
3159
- return {
3160
- define(def, owner = null) {
3161
- const entry = { def, owner };
3162
- defs.push(entry);
3163
- return () => {
3164
- const i = defs.indexOf(entry);
3165
- if (i >= 0) defs.splice(i, 1);
3166
- };
3167
- },
3168
- open(req) {
3169
- const entries = resolve(req);
3170
- if (!entries.length) {
3171
- close();
3172
- return false;
3173
- }
3174
- open = {
3175
- seq: ++seq,
3176
- entries,
3177
- client: req.client,
3178
- world: req.world,
3179
- ...req.under ? { under: req.under } : {},
3180
- ...req.side ? { side: req.side } : {},
3181
- source: req.source ?? "pointer"
3182
- };
3183
- emit();
3184
- return true;
3185
- },
3186
- has: (req) => resolve(req).length > 0,
3187
- close,
3188
- current: () => open,
3189
- subscribe(fn) {
3190
- listeners.add(fn);
3191
- return () => {
3192
- listeners.delete(fn);
3193
- };
3194
- }
3195
- };
3196
- }
3197
-
3198
- export { ALWAYS, ActionDisabledReason, COARSE_TARGET_SCALE, DEFAULT_ALLOWED_CAPABILITIES, DEFAULT_DEVICE_PROFILE, DepNode, INGEST_STRING_MIMES, KEYMAP_HOLD, LONG_PRESS_HAPTIC_MS, NEVER, NO_OVERRIDES, PREDICATE_TARGET, ScopeNode, TOOL_OFFHAND_ID, actionBindings, actionCursor, actionItemTitle, actionItems, actionSettingsParams, actionShortcuts, addCarrySurface, admitIngestMime, anyBinding, assembleEntries, bindingId, buildDepsFromRequires, buildRouteRegistry, buildRuleCtx, buildToolOffhandBindings, carryGestureId, clientToCanvas, clientToCanvasRect, createDepRegistry, createDispatchTrace, createDispatcher, createKeymapStack, createLongPressStore, createMenuHost, createTraceRing, depNodeOf, describeKeymap, describeRule, dlog, dwarn, evaluate, evaluateActive, evaluateEnabled, findConflicts, heldKeySpec, historyKey, isDebugEnabled, isTool, itemTypesFromDataTransfer, itemsFromClipboardData, itemsFromDataTransfer, itemsFromFiles, keySpecShortcut, keymapContribution, keymapLeaderBindingId, keymapLeaderId, keymapShortcuts, keysHeld, liveScope, makeToolOffhandAction, matchBest, matchSorted, matchSortedWithBarred, matchesKeyBinding, mergeMenuDefs, modifierStateOf, multiTouchGestureId, offhandKeyFor, openPointerSession, overriddenBindings, overrideActionBindings, pickMenu, pointerGestureId, pushOwner, readActionSettings, reportRouteConflicts, resolveBindingOverrides, resolveDeviceProfile, resolveMenu, resolveParams, resolveSchemaMenu, roleReader, routesForSpec, ruleCanHoldIn, rulesExclusive, scopeBindings, startCarry, targetConsultsAffordance, tuneAction, tunedParams, validateActionDefaultBinding, validateActionId };
3199
- //# sourceMappingURL=chunk-XVWEZTUX.js.map
3200
- //# sourceMappingURL=chunk-XVWEZTUX.js.map
2448
+ export { ALWAYS, COARSE_TARGET_SCALE, DEFAULT_ALLOWED_CAPABILITIES, DEFAULT_DEVICE_PROFILE, INGEST_STRING_MIMES, KEYMAP_HOLD, LONG_PRESS_HAPTIC_MS, NEVER, PREDICATE_TARGET, TOOL_OFFHAND_ID, addCarrySurface, admitIngestMime, anyBinding, assembleEntries, buildRouteRegistry, buildRuleCtx, buildToolOffhandBindings, carryGestureId, clientToCanvas, clientToCanvasRect, createDispatchTrace, createDispatcher, createKeymapStack, createLongPressStore, createTraceRing, describeKeymap, describeRule, dlog, dwarn, evaluate, findConflicts, heldKeySpec, historyKey, immediateParams, isDebugEnabled, isTool, itemTypesFromDataTransfer, itemsFromClipboardData, itemsFromDataTransfer, itemsFromFiles, keymapContribution, keymapLeaderBindingId, keymapLeaderId, keymapShortcuts, liveScope, makeToolOffhandAction, matchBest, matchSorted, matchSortedWithBarred, matchesKeyBinding, modifierStateOf, multiTouchGestureId, offhandKeyFor, openPointerSession, pendingKindOf, pointerGestureId, reportRouteConflicts, resolveDeviceProfile, routesForSpec, ruleCanHoldIn, rulesExclusive, scopeBindings, startCarry, targetConsultsAffordance };
2449
+ //# sourceMappingURL=chunk-HR5F4LBX.js.map
2450
+ //# sourceMappingURL=chunk-HR5F4LBX.js.map