@weasel-js/routing 1.9.4 → 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/{chunk-Y5RURZBH.js → chunk-VCEIQ64H.js} +1017 -851
- package/dist/chunk-VCEIQ64H.js.map +1 -0
- package/dist/{index-gArM2tbt.d.ts → index-DkOqUxow.d.ts} +370 -596
- package/dist/index.d.ts +3 -3
- package/dist/index.js +16 -64
- package/dist/index.js.map +1 -1
- package/dist/{internal-B0SsznMG.d.ts → internal-Boc2I3zn.d.ts} +38 -67
- package/dist/internal.d.ts +4 -4
- package/dist/react.d.ts +123 -34
- package/dist/react.js +688 -751
- package/dist/react.js.map +1 -1
- package/package.json +9 -8
- package/dist/chunk-Y5RURZBH.js.map +0 -1
|
@@ -1,8 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { createEmitter } from '@weasel-js/registry';
|
|
2
|
+
import { RESERVED_ID_PREFIXES, RESERVED_ID_NAMES, routeGestureForSpecKind, getGestureDescriptor, formatRoute, parseTargetSpec, matchSpec, canonicalModifiers, UNKNOWN_MIME, specificity, VALID_MOD_NAMES, stylusOf } from '@weasel-js/gestures';
|
|
3
|
+
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';
|
|
4
|
+
import { activeModeOf, NORMAL, DEFAULT_MODES, capabilitiesAllowed } from '@weasel-js/modes';
|
|
4
5
|
import { resolveCursor } from '@weasel-js/cursor';
|
|
5
6
|
|
|
7
|
+
// src/interactions/actions/action.ts
|
|
8
|
+
var ActionDisabledReason = {
|
|
9
|
+
SelectionRequired: "selection-required",
|
|
10
|
+
SceneEmpty: "scene-empty",
|
|
11
|
+
NotApplicable: "not-applicable",
|
|
12
|
+
/** Sentinel: the predicate threw. Surfaced by `evaluateEnabled`'s catch. */
|
|
13
|
+
PredicateThrew: "predicate-threw"
|
|
14
|
+
};
|
|
15
|
+
|
|
6
16
|
// src/interactions/actions/binding.ts
|
|
7
17
|
function actionBindings(action) {
|
|
8
18
|
const gs = action.defaultBinding;
|
|
@@ -16,6 +26,53 @@ function actionBindings(action) {
|
|
|
16
26
|
});
|
|
17
27
|
}
|
|
18
28
|
|
|
29
|
+
// src/devFlag.ts
|
|
30
|
+
function isDev() {
|
|
31
|
+
return typeof process !== "undefined" ? process.env?.NODE_ENV !== "production" : true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/interactions/actions/actionEnabled.ts
|
|
35
|
+
var enabledSlowWarned = /* @__PURE__ */ new Set();
|
|
36
|
+
var enabledThrewWarned = /* @__PURE__ */ new Set();
|
|
37
|
+
var ENABLED_BUDGET_MS = 4;
|
|
38
|
+
function evaluateEnabled(action, deps) {
|
|
39
|
+
if (!action.enabled) return { enabled: true };
|
|
40
|
+
const dev = isDev();
|
|
41
|
+
const now = typeof performance !== "undefined" && typeof performance.now === "function" ? () => performance.now() : () => Date.now();
|
|
42
|
+
const start = dev ? now() : 0;
|
|
43
|
+
try {
|
|
44
|
+
const result = action.enabled(deps);
|
|
45
|
+
if (dev) {
|
|
46
|
+
const elapsed = now() - start;
|
|
47
|
+
if (elapsed > ENABLED_BUDGET_MS && !enabledSlowWarned.has(action.id)) {
|
|
48
|
+
enabledSlowWarned.add(action.id);
|
|
49
|
+
console.warn(
|
|
50
|
+
`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.`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (result === true) return { enabled: true };
|
|
55
|
+
return { enabled: false, reason: result };
|
|
56
|
+
} catch (e) {
|
|
57
|
+
if (dev && !enabledThrewWarned.has(action.id)) {
|
|
58
|
+
enabledThrewWarned.add(action.id);
|
|
59
|
+
console.warn(
|
|
60
|
+
`weasel actions: enabled() for action "${action.id}" threw; treating as disabled. The predicate must not throw \u2014 see Action.enabled JSDoc.`,
|
|
61
|
+
e
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return { enabled: false, reason: ActionDisabledReason.PredicateThrew };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function evaluateActive(action, deps) {
|
|
68
|
+
if (!action.active) return void 0;
|
|
69
|
+
try {
|
|
70
|
+
return action.active(deps) === true;
|
|
71
|
+
} catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
19
76
|
// src/interactions/actions/buildDeps.ts
|
|
20
77
|
var DEV = (() => {
|
|
21
78
|
try {
|
|
@@ -43,8 +100,6 @@ function buildDepsFromRequires(action, depRegistry) {
|
|
|
43
100
|
}
|
|
44
101
|
return deps;
|
|
45
102
|
}
|
|
46
|
-
|
|
47
|
-
// src/interactions/actions/scopeNode.ts
|
|
48
103
|
var ScopeNode = class {
|
|
49
104
|
/** `descends`: whether reads made here answer from the active child down —
|
|
50
105
|
* true for a root or a yoke, which chrome reads through; false for a scope,
|
|
@@ -52,7 +107,7 @@ var ScopeNode = class {
|
|
|
52
107
|
constructor(parent, descends) {
|
|
53
108
|
this.parent = parent;
|
|
54
109
|
this.descends = descends;
|
|
55
|
-
this.tree = parent ? parent.tree : { version: 0, listeners:
|
|
110
|
+
this.tree = parent ? parent.tree : { version: 0, listeners: createEmitter() };
|
|
56
111
|
}
|
|
57
112
|
parent;
|
|
58
113
|
descends;
|
|
@@ -62,13 +117,7 @@ var ScopeNode = class {
|
|
|
62
117
|
/** Bump the tree's version and tell every listener in it. */
|
|
63
118
|
changed() {
|
|
64
119
|
this.tree.version++;
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
listener();
|
|
68
|
-
} catch (err) {
|
|
69
|
-
console.error("weasel input scope: subscriber threw", err);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
120
|
+
this.tree.listeners.emit();
|
|
72
121
|
}
|
|
73
122
|
/** Join the parent's children, newest last; returns the leave. */
|
|
74
123
|
mount() {
|
|
@@ -176,12 +225,7 @@ var DepNode = class extends ScopeNode {
|
|
|
176
225
|
}
|
|
177
226
|
return void 0;
|
|
178
227
|
},
|
|
179
|
-
subscribe: (listener) =>
|
|
180
|
-
this.tree.listeners.add(listener);
|
|
181
|
-
return () => {
|
|
182
|
-
this.tree.listeners.delete(listener);
|
|
183
|
-
};
|
|
184
|
-
},
|
|
228
|
+
subscribe: (listener) => this.tree.listeners.subscribe(listener),
|
|
185
229
|
changed: () => this.changed()
|
|
186
230
|
};
|
|
187
231
|
constructor(parent, descends) {
|
|
@@ -238,6 +282,47 @@ function resolveParams(params) {
|
|
|
238
282
|
return typeof params === "function" ? params() : params;
|
|
239
283
|
}
|
|
240
284
|
|
|
285
|
+
// src/interactions/actions/tuning.ts
|
|
286
|
+
var WRAPS = /* @__PURE__ */ new WeakMap();
|
|
287
|
+
function layered(under, over) {
|
|
288
|
+
if (typeof under !== "function" && typeof over !== "function") return { ...under, ...over };
|
|
289
|
+
return () => ({ ...resolveParams(under), ...resolveParams(over) });
|
|
290
|
+
}
|
|
291
|
+
function wrapInvoker(invoker, params) {
|
|
292
|
+
for (let w = invoker; w; w = WRAPS.get(w)?.inner) {
|
|
293
|
+
if (WRAPS.get(w)?.params === params) return invoker;
|
|
294
|
+
}
|
|
295
|
+
const base = (given) => ({ ...resolveParams(params), ...given });
|
|
296
|
+
const wrapped = invoker.timing === "immediate" ? { timing: "immediate", run: (deps, given) => invoker.run(deps, base(given)) } : {
|
|
297
|
+
timing: "ongoing",
|
|
298
|
+
start: (ctx, opts) => invoker.start(
|
|
299
|
+
ctx.params ? { ...ctx, params: base(ctx.params) } : ctx,
|
|
300
|
+
{ ...opts, params: layered(params, opts?.params) }
|
|
301
|
+
)
|
|
302
|
+
};
|
|
303
|
+
WRAPS.set(wrapped, { inner: invoker, params });
|
|
304
|
+
return wrapped;
|
|
305
|
+
}
|
|
306
|
+
function withBindings(own, added) {
|
|
307
|
+
const base = own === void 0 ? [] : Array.isArray(own) ? own : [own];
|
|
308
|
+
return [...base, ...added.filter((b) => !base.includes(b))];
|
|
309
|
+
}
|
|
310
|
+
function tuneOnce(action, tuning) {
|
|
311
|
+
const invoker = tuning.params && action.invoker ? wrapInvoker(action.invoker, tuning.params) : action.invoker;
|
|
312
|
+
const bindings = tuning.bindings?.length ? withBindings(action.defaultBinding, tuning.bindings) : null;
|
|
313
|
+
if (invoker === action.invoker && bindings === null) return action;
|
|
314
|
+
return {
|
|
315
|
+
...action,
|
|
316
|
+
...invoker ? { invoker } : {},
|
|
317
|
+
...bindings ? { defaultBinding: bindings } : {}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function tuneAction(action, tunings) {
|
|
321
|
+
let out = action;
|
|
322
|
+
for (const t of tunings) out = tuneOnce(out, t);
|
|
323
|
+
return out;
|
|
324
|
+
}
|
|
325
|
+
|
|
241
326
|
// src/interactions/actions/toolOffhand.ts
|
|
242
327
|
var TOOL_OFFHAND_ID = "tool.offhand";
|
|
243
328
|
function offhandKeyFor(trigger) {
|
|
@@ -257,8 +342,12 @@ function offhandKeyFor(trigger) {
|
|
|
257
342
|
}
|
|
258
343
|
}
|
|
259
344
|
function buildToolOffhandBindings(specs) {
|
|
260
|
-
return specs.map(({ toolId, key }) => ({
|
|
345
|
+
return specs.map(({ toolId, key }) => ({
|
|
346
|
+
spec: { ...heldKeySpec(key), phase: BETWEEN_GESTURES },
|
|
347
|
+
opts: { params: { toolId } }
|
|
348
|
+
}));
|
|
261
349
|
}
|
|
350
|
+
var BETWEEN_GESTURES = [{ channel: "*", phase: "initial" }];
|
|
262
351
|
function heldKeySpec(key) {
|
|
263
352
|
const isModifier = [key].flat().some((k) => MODIFIER_KEYS.has(k));
|
|
264
353
|
return { kind: "key-held", key, mods: { ...isModifier ? ANY_MODS : SHIFT_OR_ALT } };
|
|
@@ -289,56 +378,95 @@ function makeToolOffhandAction(bindings) {
|
|
|
289
378
|
requires: ["activeTool"]
|
|
290
379
|
};
|
|
291
380
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
function liveScope(id, eligibility, state) {
|
|
295
|
-
const tags = eligibility.capabilities;
|
|
296
|
-
if (tags && tags.length > 0 && state.allows && !state.allows(tags)) return null;
|
|
297
|
-
if (state.engagedIds?.has(id)) return "hotkey";
|
|
298
|
-
if (eligibility.focus && state.focusedId === id) return "active";
|
|
299
|
-
if (eligibility.always || eligibility.claimed) return "ambient";
|
|
300
|
-
return null;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// src/contributions/assemble.ts
|
|
304
|
-
function scopeBindings(entries, state) {
|
|
381
|
+
var PREDICATE_TARGET = "predicate";
|
|
382
|
+
function buildRouteRegistry(entries) {
|
|
305
383
|
const out = [];
|
|
306
|
-
for (const
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
384
|
+
for (const owner of entries) {
|
|
385
|
+
for (const binding of owner.bindings ?? []) {
|
|
386
|
+
const row = entryFor(owner.id, binding.spec, binding.actionId);
|
|
387
|
+
if (!row) continue;
|
|
388
|
+
const { views, params, yields } = binding.opts ?? {};
|
|
389
|
+
out.push({
|
|
390
|
+
...row,
|
|
391
|
+
...views === void 0 ? {} : { views },
|
|
392
|
+
...params === void 0 ? {} : { params },
|
|
393
|
+
...yields ? { yields: true } : {}
|
|
394
|
+
});
|
|
311
395
|
}
|
|
312
396
|
}
|
|
313
397
|
return out;
|
|
314
398
|
}
|
|
315
|
-
function
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
399
|
+
function routesForSpec(spec) {
|
|
400
|
+
const gesture = routeGestureForSpecKind(spec.kind);
|
|
401
|
+
if (!gesture) return [];
|
|
402
|
+
const descriptor = getGestureDescriptor(gesture);
|
|
403
|
+
const modifiers = parseModSpec("mods" in spec ? spec.mods : void 0);
|
|
404
|
+
const phases = phaseAtomsForSpec("phase" in spec ? spec.phase : void 0);
|
|
405
|
+
const target = descriptor.hasTarget ? routeTargetForSpec("target" in spec ? spec.target : void 0) ?? "*" : void 0;
|
|
406
|
+
const args = descriptor.arg ? routeArgsForSpec(spec, descriptor.arg.default) : [void 0];
|
|
407
|
+
return args.map((arg) => formatRoute({ phases, gesture, arg, target, modifiers }));
|
|
408
|
+
}
|
|
409
|
+
function entryFor(ownerId, spec, actionId) {
|
|
410
|
+
const gesture = routeGestureForSpecKind(spec.kind);
|
|
411
|
+
if (!gesture) return null;
|
|
412
|
+
const descriptor = getGestureDescriptor(gesture);
|
|
326
413
|
return {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
414
|
+
ownerId,
|
|
415
|
+
actionId,
|
|
416
|
+
gesture,
|
|
417
|
+
phase: phaseOf("phase" in spec ? spec.phase : void 0),
|
|
418
|
+
modifiers: parseModSpec("mods" in spec ? spec.mods : void 0),
|
|
419
|
+
arg: descriptor.arg ? argOf(spec, descriptor.arg.default) : void 0,
|
|
420
|
+
...descriptor.arg ? argAlternativesOf(spec, descriptor.arg.default) : {},
|
|
421
|
+
target: descriptor.hasTarget ? routeTargetForSpec("target" in spec ? spec.target : void 0) : void 0,
|
|
422
|
+
spec
|
|
333
423
|
};
|
|
334
424
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
425
|
+
function phaseOf(phase) {
|
|
426
|
+
if (phase === void 0 || phase === "*") return "any";
|
|
427
|
+
if (phase === "initial" || phase === "engaged") return phase;
|
|
428
|
+
const distinct = new Set(phase.map((atom) => atom.phase));
|
|
429
|
+
if (distinct.size !== 1) return "any";
|
|
430
|
+
const only = [...distinct][0];
|
|
431
|
+
return only === "initial" || only === "engaged" ? only : "any";
|
|
432
|
+
}
|
|
433
|
+
function parseModSpec(mods) {
|
|
434
|
+
if (!mods) return {};
|
|
435
|
+
const out = {};
|
|
436
|
+
for (const [name, req] of Object.entries(mods)) {
|
|
437
|
+
if (req === true) out[name] = "required";
|
|
438
|
+
else if (req === "optional") out[name] = "optional";
|
|
439
|
+
}
|
|
440
|
+
return out;
|
|
441
|
+
}
|
|
442
|
+
function phaseAtomsForSpec(phase) {
|
|
443
|
+
if (phase === void 0) return [{ channel: "*", phase: "*" }];
|
|
444
|
+
if (phase === "initial" || phase === "engaged" || phase === "*") {
|
|
445
|
+
return [{ channel: "&", phase }];
|
|
446
|
+
}
|
|
447
|
+
return phase;
|
|
448
|
+
}
|
|
449
|
+
function routeTargetForSpec(target) {
|
|
450
|
+
if (target === void 0) return void 0;
|
|
451
|
+
return typeof target === "string" ? target : PREDICATE_TARGET;
|
|
452
|
+
}
|
|
453
|
+
function routeArgsForSpec(spec, fallback) {
|
|
454
|
+
if ("key" in spec) return Array.isArray(spec.key) ? spec.key : [spec.key];
|
|
455
|
+
if ("fingers" in spec) return [String(spec.fingers)];
|
|
456
|
+
if ("direction" in spec) return [spec.direction ?? fallback];
|
|
457
|
+
if (spec.kind === "hover") return [spec.motion ?? fallback];
|
|
458
|
+
if ("types" in spec) return [spec.types?.length ? spec.types.join("|") : fallback];
|
|
459
|
+
return [fallback];
|
|
460
|
+
}
|
|
461
|
+
function argOf(spec, fallback) {
|
|
462
|
+
const args = routeArgsForSpec(spec, fallback);
|
|
463
|
+
return args.length > 1 ? args.join("|") : args[0];
|
|
464
|
+
}
|
|
465
|
+
function argAlternativesOf(spec, fallback) {
|
|
466
|
+
const types = "types" in spec ? spec.types : void 0;
|
|
467
|
+
const alts = types?.length ? types : routeArgsForSpec(spec, fallback).filter((a) => a !== void 0);
|
|
468
|
+
return alts.length > 1 ? { argAlternatives: alts } : {};
|
|
339
469
|
}
|
|
340
|
-
|
|
341
|
-
// src/interactions/dispatcher/matcher.ts
|
|
342
470
|
var SCOPE_PRIORITY = ["hotkey", "active", "ambient"];
|
|
343
471
|
function targetConsultsAffordance(specTarget) {
|
|
344
472
|
if (specTarget === void 0) return false;
|
|
@@ -446,89 +574,6 @@ function matchSortedWithBarred(e, bindings, isMac, engagedChannels, opts = {}) {
|
|
|
446
574
|
return { matches: out, barred, ...owner !== void 0 ? { claimOwner: owner } : {} };
|
|
447
575
|
}
|
|
448
576
|
var EMPTY_ENGAGED = /* @__PURE__ */ new Set();
|
|
449
|
-
var PREDICATE_TARGET = "predicate";
|
|
450
|
-
function buildRouteRegistry(entries) {
|
|
451
|
-
const out = [];
|
|
452
|
-
for (const owner of entries) {
|
|
453
|
-
for (const binding of owner.bindings ?? []) {
|
|
454
|
-
const row = entryFor(owner.id, binding.spec, binding.actionId);
|
|
455
|
-
if (!row) continue;
|
|
456
|
-
const views = binding.opts?.views;
|
|
457
|
-
out.push(views === void 0 ? row : { ...row, views });
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
return out;
|
|
461
|
-
}
|
|
462
|
-
function routesForSpec(spec) {
|
|
463
|
-
const gesture = routeGestureForSpecKind(spec.kind);
|
|
464
|
-
if (!gesture) return [];
|
|
465
|
-
const descriptor = getGestureDescriptor(gesture);
|
|
466
|
-
const modifiers = parseModSpec("mods" in spec ? spec.mods : void 0);
|
|
467
|
-
const phases = phaseAtomsForSpec("phase" in spec ? spec.phase : void 0);
|
|
468
|
-
const target = descriptor.hasTarget ? routeTargetForSpec("target" in spec ? spec.target : void 0) ?? "*" : void 0;
|
|
469
|
-
const args = descriptor.arg ? routeArgsForSpec(spec, descriptor.arg.default) : [void 0];
|
|
470
|
-
return args.map((arg) => formatRoute({ phases, gesture, arg, target, modifiers }));
|
|
471
|
-
}
|
|
472
|
-
function entryFor(ownerId, spec, actionId) {
|
|
473
|
-
const gesture = routeGestureForSpecKind(spec.kind);
|
|
474
|
-
if (!gesture) return null;
|
|
475
|
-
const descriptor = getGestureDescriptor(gesture);
|
|
476
|
-
return {
|
|
477
|
-
ownerId,
|
|
478
|
-
actionId,
|
|
479
|
-
gesture,
|
|
480
|
-
phase: phaseOf("phase" in spec ? spec.phase : void 0),
|
|
481
|
-
modifiers: parseModSpec("mods" in spec ? spec.mods : void 0),
|
|
482
|
-
arg: descriptor.arg ? argOf(spec, descriptor.arg.default) : void 0,
|
|
483
|
-
...descriptor.arg ? argAlternativesOf(spec, descriptor.arg.default) : {},
|
|
484
|
-
target: descriptor.hasTarget ? routeTargetForSpec("target" in spec ? spec.target : void 0) : void 0,
|
|
485
|
-
spec
|
|
486
|
-
};
|
|
487
|
-
}
|
|
488
|
-
function phaseOf(phase) {
|
|
489
|
-
if (phase === void 0 || phase === "*") return "any";
|
|
490
|
-
if (phase === "initial" || phase === "engaged") return phase;
|
|
491
|
-
const distinct = new Set(phase.map((atom) => atom.phase));
|
|
492
|
-
if (distinct.size !== 1) return "any";
|
|
493
|
-
const only = [...distinct][0];
|
|
494
|
-
return only === "initial" || only === "engaged" ? only : "any";
|
|
495
|
-
}
|
|
496
|
-
function parseModSpec(mods) {
|
|
497
|
-
if (!mods) return {};
|
|
498
|
-
const out = {};
|
|
499
|
-
for (const [name, req] of Object.entries(mods)) {
|
|
500
|
-
if (req === true) out[name] = "required";
|
|
501
|
-
else if (req === "optional") out[name] = "optional";
|
|
502
|
-
}
|
|
503
|
-
return out;
|
|
504
|
-
}
|
|
505
|
-
function phaseAtomsForSpec(phase) {
|
|
506
|
-
if (phase === void 0) return [{ channel: "*", phase: "*" }];
|
|
507
|
-
if (phase === "initial" || phase === "engaged" || phase === "*") {
|
|
508
|
-
return [{ channel: "&", phase }];
|
|
509
|
-
}
|
|
510
|
-
return phase;
|
|
511
|
-
}
|
|
512
|
-
function routeTargetForSpec(target) {
|
|
513
|
-
if (target === void 0) return void 0;
|
|
514
|
-
return typeof target === "string" ? target : PREDICATE_TARGET;
|
|
515
|
-
}
|
|
516
|
-
function routeArgsForSpec(spec, fallback) {
|
|
517
|
-
if ("key" in spec) return Array.isArray(spec.key) ? spec.key : [spec.key];
|
|
518
|
-
if ("fingers" in spec) return [String(spec.fingers)];
|
|
519
|
-
if ("direction" in spec) return [spec.direction ?? fallback];
|
|
520
|
-
if ("types" in spec) return [spec.types?.length ? spec.types.join("|") : fallback];
|
|
521
|
-
return [fallback];
|
|
522
|
-
}
|
|
523
|
-
function argOf(spec, fallback) {
|
|
524
|
-
const args = routeArgsForSpec(spec, fallback);
|
|
525
|
-
return args.length > 1 ? args.join("|") : args[0];
|
|
526
|
-
}
|
|
527
|
-
function argAlternativesOf(spec, fallback) {
|
|
528
|
-
const types = "types" in spec ? spec.types : void 0;
|
|
529
|
-
const alts = types?.length ? types : routeArgsForSpec(spec, fallback).filter((a) => a !== void 0);
|
|
530
|
-
return alts.length > 1 ? { argAlternatives: alts } : {};
|
|
531
|
-
}
|
|
532
577
|
|
|
533
578
|
// src/eligibility/deviceProfile.ts
|
|
534
579
|
var COARSE_TARGET_SCALE = 1.75;
|
|
@@ -788,9 +833,9 @@ function recordTrace(entry) {
|
|
|
788
833
|
traceLog.push(entry);
|
|
789
834
|
if (traceLog.length > TRACE_LIMIT) traceLog.shift();
|
|
790
835
|
}
|
|
791
|
-
function publishLiveDispatch(
|
|
836
|
+
function publishLiveDispatch(explain2) {
|
|
792
837
|
if (!DEV2 || typeof window === "undefined") return;
|
|
793
|
-
window.__weaselDispatchLive__ =
|
|
838
|
+
window.__weaselDispatchLive__ = explain2;
|
|
794
839
|
}
|
|
795
840
|
function recordModeSwitch(mode, from, to, detail) {
|
|
796
841
|
recordTrace({
|
|
@@ -803,33 +848,168 @@ function recordModeSwitch(mode, from, to, detail) {
|
|
|
803
848
|
});
|
|
804
849
|
}
|
|
805
850
|
|
|
806
|
-
// src/interactions/dispatcher/
|
|
807
|
-
function
|
|
808
|
-
return {
|
|
851
|
+
// src/interactions/dispatcher/gestureIds.ts
|
|
852
|
+
function pointerGestureId(pointerId) {
|
|
853
|
+
return `pointer-${pointerId ?? "mouse"}`;
|
|
809
854
|
}
|
|
810
|
-
function
|
|
811
|
-
|
|
812
|
-
case "pointerdown":
|
|
813
|
-
case "longpress":
|
|
814
|
-
case "click":
|
|
815
|
-
case "doubleclick":
|
|
816
|
-
case "contextmenu":
|
|
817
|
-
return event.x !== void 0 && event.y !== void 0 ? { x: event.x, y: event.y } : void 0;
|
|
818
|
-
default:
|
|
819
|
-
return void 0;
|
|
820
|
-
}
|
|
855
|
+
function multiTouchGestureId(fingers) {
|
|
856
|
+
return `multitouch-${fingers}`;
|
|
821
857
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
function liveInView(binding, view) {
|
|
825
|
-
const views = binding.opts?.views;
|
|
826
|
-
return views === void 0 || views.includes(view);
|
|
858
|
+
function isMultiTouchGestureId(gestureId) {
|
|
859
|
+
return gestureId.startsWith("multitouch-");
|
|
827
860
|
}
|
|
828
|
-
function
|
|
829
|
-
|
|
861
|
+
function gestureIdFor(event) {
|
|
862
|
+
if (event.kind === "key-held") {
|
|
863
|
+
return `key-held-${event.code ?? event.key}`;
|
|
864
|
+
}
|
|
865
|
+
if (event.kind === "pointerdown" || event.kind === "pointermove" || event.kind === "pointerup" || event.kind === "pointercancel") {
|
|
866
|
+
return pointerGestureId(event.pointerId);
|
|
867
|
+
}
|
|
868
|
+
if (event.kind === "multitouch") {
|
|
869
|
+
return multiTouchGestureId(event.fingers);
|
|
870
|
+
}
|
|
871
|
+
return `ongoing-${event.kind}`;
|
|
830
872
|
}
|
|
831
|
-
|
|
832
|
-
|
|
873
|
+
|
|
874
|
+
// src/contributions/eligibility.ts
|
|
875
|
+
function liveScope(id, eligibility, state) {
|
|
876
|
+
const tags = eligibility.capabilities;
|
|
877
|
+
if (tags && state.allows && !state.allows(tags)) return null;
|
|
878
|
+
if (state.engagedIds?.has(id)) return "hotkey";
|
|
879
|
+
if (eligibility.focus && state.focusedId === id) return "active";
|
|
880
|
+
if (eligibility.always || eligibility.claimed) return "ambient";
|
|
881
|
+
return null;
|
|
882
|
+
}
|
|
883
|
+
function scopeBindings(entries, state) {
|
|
884
|
+
const out = [];
|
|
885
|
+
for (const entry of entries) {
|
|
886
|
+
const scope = liveScope(entry.id, entry.eligibility ?? {}, state);
|
|
887
|
+
if (scope === null) continue;
|
|
888
|
+
for (const binding of entry.bindings ?? []) {
|
|
889
|
+
out.push({ binding, scope, ownerId: entry.id });
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return out;
|
|
893
|
+
}
|
|
894
|
+
function assembleEntries(entries, inputs) {
|
|
895
|
+
const all = [...entries];
|
|
896
|
+
const ordered = [];
|
|
897
|
+
for (let i = inputs.hotkeyStack.length - 1; i >= 0; i--) {
|
|
898
|
+
const entry = all.find((e) => e.id === inputs.hotkeyStack[i]);
|
|
899
|
+
if (entry && !ordered.includes(entry)) ordered.push(entry);
|
|
900
|
+
}
|
|
901
|
+
for (const entry of all) {
|
|
902
|
+
if (!ordered.includes(entry)) ordered.push(entry);
|
|
903
|
+
}
|
|
904
|
+
const allowed = inputs.allowedCapabilities;
|
|
905
|
+
return {
|
|
906
|
+
ordered,
|
|
907
|
+
state: {
|
|
908
|
+
focusedId: inputs.focusedId,
|
|
909
|
+
engagedIds: new Set(inputs.hotkeyStack),
|
|
910
|
+
...allowed ? { allows: (tags) => capabilitiesAllowed(allowed, tags) } : {}
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// src/interactions/dispatcher/inputFacts.ts
|
|
916
|
+
function modifiersOf(e) {
|
|
917
|
+
return { alt: e.altKey, ctrl: e.ctrlKey, meta: e.metaKey, shift: e.shiftKey };
|
|
918
|
+
}
|
|
919
|
+
function worldPointOf(event) {
|
|
920
|
+
switch (event.kind) {
|
|
921
|
+
case "pointerdown":
|
|
922
|
+
case "hover":
|
|
923
|
+
case "longpress":
|
|
924
|
+
case "click":
|
|
925
|
+
case "doubleclick":
|
|
926
|
+
case "contextmenu":
|
|
927
|
+
return event.x !== void 0 && event.y !== void 0 ? { x: event.x, y: event.y } : void 0;
|
|
928
|
+
default:
|
|
929
|
+
return void 0;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
function carriedBy(event) {
|
|
933
|
+
return event.kind === "drop" || event.kind === "paste" ? { items: event.items } : void 0;
|
|
934
|
+
}
|
|
935
|
+
function immediateParams(event, resolved) {
|
|
936
|
+
switch (event.kind) {
|
|
937
|
+
case "wheel":
|
|
938
|
+
return {
|
|
939
|
+
deltaX: event.deltaX,
|
|
940
|
+
deltaY: event.deltaY,
|
|
941
|
+
clientX: event.clientX,
|
|
942
|
+
clientY: event.clientY,
|
|
943
|
+
affordance: event.affordance,
|
|
944
|
+
...resolved
|
|
945
|
+
};
|
|
946
|
+
case "pinch":
|
|
947
|
+
return {
|
|
948
|
+
scale: event.scale,
|
|
949
|
+
rotation: event.rotation,
|
|
950
|
+
clientX: event.clientX,
|
|
951
|
+
clientY: event.clientY,
|
|
952
|
+
affordance: event.affordance,
|
|
953
|
+
...resolved
|
|
954
|
+
};
|
|
955
|
+
case "click":
|
|
956
|
+
case "doubleclick":
|
|
957
|
+
return {
|
|
958
|
+
worldX: event.x,
|
|
959
|
+
worldY: event.y,
|
|
960
|
+
affordance: event.affordance,
|
|
961
|
+
// The press point too: an action that places geometry at the click wants it.
|
|
962
|
+
...event.kind === "click" ? { pressX: event.pressX, pressY: event.pressY } : {},
|
|
963
|
+
mods: modifiersOf(event),
|
|
964
|
+
...resolved
|
|
965
|
+
};
|
|
966
|
+
// Only a `stage: 'press'` pointerdown reaches an immediate invoker: the
|
|
967
|
+
// buffered copy matches `drag` specs, which are ongoing.
|
|
968
|
+
case "pointerdown":
|
|
969
|
+
case "contextmenu":
|
|
970
|
+
case "longpress":
|
|
971
|
+
return {
|
|
972
|
+
worldX: event.x,
|
|
973
|
+
worldY: event.y,
|
|
974
|
+
affordance: event.affordance,
|
|
975
|
+
bodyTarget: event.bodyTarget,
|
|
976
|
+
mods: modifiersOf(event),
|
|
977
|
+
...resolved
|
|
978
|
+
};
|
|
979
|
+
case "hover":
|
|
980
|
+
return {
|
|
981
|
+
worldX: event.x,
|
|
982
|
+
worldY: event.y,
|
|
983
|
+
clientX: event.clientX,
|
|
984
|
+
clientY: event.clientY,
|
|
985
|
+
affordance: event.affordance,
|
|
986
|
+
bodyTarget: event.bodyTarget,
|
|
987
|
+
mods: modifiersOf(event),
|
|
988
|
+
...resolved
|
|
989
|
+
};
|
|
990
|
+
case "drop":
|
|
991
|
+
case "paste":
|
|
992
|
+
return {
|
|
993
|
+
items: event.items,
|
|
994
|
+
via: event.kind,
|
|
995
|
+
...event.kind === "drop" && event.x !== void 0 ? { worldX: event.x, worldY: event.y } : {},
|
|
996
|
+
...resolved
|
|
997
|
+
};
|
|
998
|
+
default:
|
|
999
|
+
return resolved;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// src/interactions/dispatcher/ranking.ts
|
|
1004
|
+
function liveInView(binding, view) {
|
|
1005
|
+
const views = binding.opts?.views;
|
|
1006
|
+
return views === void 0 || views.includes(view);
|
|
1007
|
+
}
|
|
1008
|
+
function namesView(binding, view) {
|
|
1009
|
+
return binding.opts?.views?.includes(view) ?? false;
|
|
1010
|
+
}
|
|
1011
|
+
function preferViewScoped(matches, view) {
|
|
1012
|
+
const named = [];
|
|
833
1013
|
const rest = [];
|
|
834
1014
|
for (const m of matches) {
|
|
835
1015
|
(namesView(m.binding, view) ? named : rest).push(m);
|
|
@@ -948,609 +1128,332 @@ function buildDispatchRecord(r, walked, event, ctx, predicted, outcome) {
|
|
|
948
1128
|
outcome
|
|
949
1129
|
};
|
|
950
1130
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
const inFlightHandles = /* @__PURE__ */ new Map();
|
|
959
|
-
const inFlightOwners = /* @__PURE__ */ new Map();
|
|
960
|
-
const inFlightActions = /* @__PURE__ */ new Map();
|
|
961
|
-
const dragOrigins = /* @__PURE__ */ new Map();
|
|
962
|
-
const subscribers = /* @__PURE__ */ new Set();
|
|
963
|
-
let version = 0;
|
|
964
|
-
function notify() {
|
|
965
|
-
version++;
|
|
966
|
-
for (const fn of subscribers) fn();
|
|
967
|
-
}
|
|
968
|
-
const dragPoints = /* @__PURE__ */ new Map();
|
|
969
|
-
const pinchStartSpreads = /* @__PURE__ */ new Map();
|
|
970
|
-
let uiOngoingSeq = 0;
|
|
971
|
-
const uiOngoingByAction = /* @__PURE__ */ new Map();
|
|
972
|
-
function isEmptyOngoingHandle(h) {
|
|
973
|
-
return !h.onMove && !h.onEnd && !h.overlay && !h.previewIds && !h.previewPose;
|
|
974
|
-
}
|
|
975
|
-
let routedView;
|
|
976
|
-
function buildUiInvocationCtx(deps, params) {
|
|
977
|
-
return {
|
|
978
|
-
world: { x: 0, y: 0 },
|
|
979
|
-
modifiers: { alt: false, ctrl: false, meta: false, shift: false },
|
|
980
|
-
deps,
|
|
981
|
-
...params !== void 0 ? { params } : {}
|
|
982
|
-
};
|
|
983
|
-
}
|
|
984
|
-
function buildInvocationCtx(event, deps, gestureId) {
|
|
985
|
-
const modifiers = {
|
|
986
|
-
alt: event.altKey,
|
|
987
|
-
ctrl: event.ctrlKey,
|
|
988
|
-
meta: event.metaKey,
|
|
989
|
-
shift: event.shiftKey
|
|
990
|
-
};
|
|
991
|
-
const base = {
|
|
992
|
-
world: { x: 0, y: 0 },
|
|
993
|
-
modifiers,
|
|
994
|
-
deps,
|
|
995
|
-
...routedView !== void 0 ? { viewId: routedView } : {}
|
|
996
|
-
};
|
|
997
|
-
const clientPoint = (ev) => {
|
|
998
|
-
const cx = ev.clientX;
|
|
999
|
-
const cy = ev.clientY;
|
|
1000
|
-
return cx !== void 0 && cy !== void 0 ? { x: cx, y: cy } : void 0;
|
|
1001
|
-
};
|
|
1002
|
-
const screen = clientPoint(event);
|
|
1003
|
-
if (screen) base.screen = screen;
|
|
1004
|
-
if (event.kind === "key") {
|
|
1005
|
-
base.key = { key: event.key, repeat: event.repeat ?? false };
|
|
1006
|
-
} else if (event.kind === "key-held") {
|
|
1007
|
-
base.key = { key: event.key, repeat: false };
|
|
1008
|
-
} else if (event.kind === "wheel") {
|
|
1009
|
-
base.wheel = { deltaX: event.deltaX, deltaY: event.deltaY, deltaZ: 0 };
|
|
1010
|
-
} else if (event.kind === "pinch") {
|
|
1011
|
-
base.pinch = { scale: event.scale, rotation: event.rotation };
|
|
1012
|
-
} else if (event.kind === "pointerdown" || event.kind === "click" || event.kind === "doubleclick" || event.kind === "contextmenu") {
|
|
1013
|
-
const sx = event.x ?? (event.kind === "click" ? event.pressX : void 0) ?? 0;
|
|
1014
|
-
const sy = event.y ?? (event.kind === "click" ? event.pressY : void 0) ?? 0;
|
|
1015
|
-
base.world = { x: sx, y: sy };
|
|
1016
|
-
const affordance = event.kind === "pointerdown" ? event.affordance : void 0;
|
|
1017
|
-
base.drag = {
|
|
1018
|
-
start: { x: sx, y: sy },
|
|
1019
|
-
current: { x: sx, y: sy },
|
|
1020
|
-
delta: { x: 0, y: 0 },
|
|
1021
|
-
...affordance !== void 0 ? { affordance } : {}
|
|
1022
|
-
};
|
|
1023
|
-
} else if (event.kind === "pointermove" || event.kind === "pointerup") {
|
|
1024
|
-
const cx = event.x;
|
|
1025
|
-
const cy = event.y;
|
|
1026
|
-
base.world = { x: cx, y: cy };
|
|
1027
|
-
const origin = gestureId ? dragOrigins.get(gestureId) : void 0;
|
|
1028
|
-
const ox = origin?.x ?? cx;
|
|
1029
|
-
const oy = origin?.y ?? cy;
|
|
1030
|
-
const live = gestureId ? dragPoints.get(gestureId) : void 0;
|
|
1031
|
-
const points = live && event.kind === "pointerup" ? [...live] : live;
|
|
1032
|
-
const eventClientX = event.clientX;
|
|
1033
|
-
const eventClientY = event.clientY;
|
|
1034
|
-
const hasClient = eventClientX !== void 0 && eventClientY !== void 0 && origin?.clientX !== void 0 && origin?.clientY !== void 0;
|
|
1035
|
-
base.drag = {
|
|
1036
|
-
start: { x: ox, y: oy },
|
|
1037
|
-
current: { x: cx, y: cy },
|
|
1038
|
-
delta: { x: cx - ox, y: cy - oy },
|
|
1039
|
-
...hasClient ? { screenDelta: { x: eventClientX - origin.clientX, y: eventClientY - origin.clientY } } : {},
|
|
1040
|
-
...points !== void 0 ? { points } : {}
|
|
1041
|
-
};
|
|
1042
|
-
} else if (event.kind === "multitouch") {
|
|
1043
|
-
const centroid = event.centroid ?? { x: 0, y: 0 };
|
|
1044
|
-
const spread = event.spread ?? 1;
|
|
1045
|
-
const startSpread = gestureId ? pinchStartSpreads.get(gestureId) : void 0;
|
|
1046
|
-
base.multiTouch = {
|
|
1047
|
-
centroid,
|
|
1048
|
-
spread,
|
|
1049
|
-
rotation: 0,
|
|
1050
|
-
...startSpread !== void 0 && event.spread !== void 0 ? { pinch: { startSpread, currentSpread: spread, centroid } } : {}
|
|
1051
|
-
};
|
|
1052
|
-
}
|
|
1053
|
-
return base;
|
|
1054
|
-
}
|
|
1055
|
-
function gestureIdFor(event) {
|
|
1056
|
-
if (event.kind === "key-held") {
|
|
1057
|
-
return `key-held-${event.code ?? event.key}`;
|
|
1058
|
-
}
|
|
1059
|
-
if (event.kind === "pointerdown" || event.kind === "pointermove" || event.kind === "pointerup" || event.kind === "pointercancel") {
|
|
1060
|
-
return pointerGestureId(event.pointerId);
|
|
1061
|
-
}
|
|
1062
|
-
if (event.kind === "multitouch") {
|
|
1063
|
-
return `multitouch-${event.fingers}`;
|
|
1064
|
-
}
|
|
1065
|
-
return `ongoing-${event.kind}`;
|
|
1066
|
-
}
|
|
1067
|
-
function assembleScopedBindings(ctx) {
|
|
1068
|
-
const allowedCapabilities = ctx.getRuleCtx?.()?.allowedCapabilities;
|
|
1069
|
-
const { ordered, state } = assembleEntries(ctx.entriesById.values(), {
|
|
1070
|
-
focusedId: ctx.activeToolId,
|
|
1071
|
-
hotkeyStack: ctx.hotkeyStack,
|
|
1072
|
-
...allowedCapabilities ? { allowedCapabilities } : {}
|
|
1073
|
-
});
|
|
1074
|
-
const view = ctx.viewId ?? null;
|
|
1075
|
-
const result = scopeBindings(ordered, state).filter((sb) => liveInView(sb.binding, view));
|
|
1076
|
-
for (const action of ctx.actions.list()) {
|
|
1077
|
-
const targetScope = action.scope === "hotkey" ? "hotkey" : "ambient";
|
|
1078
|
-
for (const binding of actionBindings(action)) {
|
|
1079
|
-
if (!liveInView(binding, view)) continue;
|
|
1080
|
-
result.push({ binding, scope: targetScope, ownerId: null });
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
return result;
|
|
1084
|
-
}
|
|
1085
|
-
function snapshotEngagedChannels() {
|
|
1086
|
-
if (inFlightOwners.size === 0) return EMPTY_ENGAGED2;
|
|
1087
|
-
const out = /* @__PURE__ */ new Set();
|
|
1088
|
-
for (const owner of inFlightOwners.values()) {
|
|
1089
|
-
if (owner != null) out.add(owner);
|
|
1090
|
-
}
|
|
1091
|
-
return out;
|
|
1092
|
-
}
|
|
1093
|
-
function buildActionMap(registry) {
|
|
1094
|
-
const map = /* @__PURE__ */ new Map();
|
|
1095
|
-
for (const action of registry.list()) {
|
|
1096
|
-
map.set(action.id, action);
|
|
1097
|
-
}
|
|
1098
|
-
return map;
|
|
1099
|
-
}
|
|
1100
|
-
function runOnEnd(gestureId, handle, endCtx, reason) {
|
|
1101
|
-
try {
|
|
1102
|
-
handle.onEnd?.(endCtx, reason);
|
|
1103
|
-
} catch (err) {
|
|
1104
|
-
console.error(`weasel dispatcher: gesture "${gestureId}" threw on onEnd`, err);
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
function endGesture(gestureId, event, reason) {
|
|
1108
|
-
const handle = inFlightHandles.get(gestureId);
|
|
1109
|
-
if (!handle) return false;
|
|
1110
|
-
const endCtx = buildInvocationCtx(event, {}, gestureId);
|
|
1111
|
-
inFlightHandles.delete(gestureId);
|
|
1112
|
-
inFlightOwners.delete(gestureId);
|
|
1113
|
-
inFlightActions.delete(gestureId);
|
|
1114
|
-
dragOrigins.delete(gestureId);
|
|
1115
|
-
dragPoints.delete(gestureId);
|
|
1116
|
-
pinchStartSpreads.delete(gestureId);
|
|
1117
|
-
runOnEnd(gestureId, handle, endCtx, reason);
|
|
1118
|
-
return true;
|
|
1119
|
-
}
|
|
1120
|
-
function handleInput(event, ctx) {
|
|
1121
|
-
routedView = ctx.viewId;
|
|
1122
|
-
if (event.kind === "key-held" && event.phase === "up") {
|
|
1123
|
-
const gestureId = gestureIdFor(event);
|
|
1124
|
-
return endGesture(gestureId, event, "commit") ? "handled" : "unhandled";
|
|
1125
|
-
}
|
|
1126
|
-
if (event.kind === "key-held" && event.phase === "down") {
|
|
1127
|
-
const gestureId = gestureIdFor(event);
|
|
1128
|
-
if (inFlightHandles.has(gestureId)) {
|
|
1129
|
-
return "handled";
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
if (event.kind === "pointermove") {
|
|
1133
|
-
const gestureId = gestureIdFor(event);
|
|
1134
|
-
const handle = inFlightHandles.get(gestureId);
|
|
1135
|
-
if (!handle) return "unhandled";
|
|
1136
|
-
const pts = dragPoints.get(gestureId);
|
|
1137
|
-
if (pts) {
|
|
1138
|
-
pts.push({
|
|
1139
|
-
x: event.x,
|
|
1140
|
-
y: event.y,
|
|
1141
|
-
...event.pressure !== void 0 ? { pressure: event.pressure } : {},
|
|
1142
|
-
...event.tiltX !== void 0 ? { tiltX: event.tiltX } : {},
|
|
1143
|
-
...event.tiltY !== void 0 ? { tiltY: event.tiltY } : {}
|
|
1144
|
-
});
|
|
1145
|
-
}
|
|
1146
|
-
if (handle.onMove) {
|
|
1147
|
-
const moveCtx = buildInvocationCtx(event, {}, gestureId);
|
|
1148
|
-
handle.onMove(moveCtx);
|
|
1149
|
-
return "handled";
|
|
1150
|
-
}
|
|
1151
|
-
return "unhandled";
|
|
1152
|
-
}
|
|
1153
|
-
if (event.kind === "pointerup") {
|
|
1154
|
-
const gestureId = gestureIdFor(event);
|
|
1155
|
-
return endGesture(gestureId, event, "commit") ? "handled" : "unhandled";
|
|
1156
|
-
}
|
|
1157
|
-
if (event.kind === "pointercancel") {
|
|
1158
|
-
const gestureId = gestureIdFor(event);
|
|
1159
|
-
return endGesture(gestureId, event, "cancel") ? "handled" : "unhandled";
|
|
1160
|
-
}
|
|
1161
|
-
if (event.kind === "multitouch" && event.end !== void 0) {
|
|
1162
|
-
let ended = false;
|
|
1163
|
-
for (const id of [...inFlightHandles.keys()]) {
|
|
1164
|
-
if (id.startsWith("multitouch-")) ended = endGesture(id, event, event.end) || ended;
|
|
1165
|
-
}
|
|
1166
|
-
return ended ? "handled" : "unhandled";
|
|
1167
|
-
}
|
|
1168
|
-
if (event.kind === "multitouch" && event.centroid !== void 0) {
|
|
1169
|
-
const gestureId = gestureIdFor(event);
|
|
1170
|
-
const handle = inFlightHandles.get(gestureId);
|
|
1171
|
-
if (handle?.onMove) {
|
|
1172
|
-
const moveCtx = buildInvocationCtx(event, {}, gestureId);
|
|
1173
|
-
handle.onMove(moveCtx);
|
|
1174
|
-
return "handled";
|
|
1175
|
-
}
|
|
1176
|
-
for (const id of [...inFlightHandles.keys()]) {
|
|
1177
|
-
if (id.startsWith("multitouch-") && id !== gestureId) endGesture(id, event, "cancel");
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
const r = rank(event, ctx, DEV2);
|
|
1181
|
-
const walked = walk(r, event, ctx, (match, action, deps) => invoke(event, match, action, deps), false);
|
|
1182
|
-
const outcome = walked.stop === "fired" ? "handled" : "unhandled";
|
|
1183
|
-
if (DEV2) recordTrace(buildDispatchRecord(r, walked, event, ctx, false, outcome));
|
|
1184
|
-
return outcome;
|
|
1185
|
-
}
|
|
1186
|
-
function invoke(event, match, action, deps) {
|
|
1187
|
-
if (action.invoker?.timing === "immediate") {
|
|
1188
|
-
try {
|
|
1189
|
-
const resolved = resolveParams(match.binding.opts?.params);
|
|
1190
|
-
let params;
|
|
1191
|
-
if (event.kind === "wheel") {
|
|
1192
|
-
params = {
|
|
1193
|
-
deltaX: event.deltaX,
|
|
1194
|
-
deltaY: event.deltaY,
|
|
1195
|
-
clientX: event.clientX,
|
|
1196
|
-
clientY: event.clientY,
|
|
1197
|
-
affordance: event.affordance,
|
|
1198
|
-
...resolved
|
|
1199
|
-
};
|
|
1200
|
-
} else if (event.kind === "pinch") {
|
|
1201
|
-
params = {
|
|
1202
|
-
scale: event.scale,
|
|
1203
|
-
rotation: event.rotation,
|
|
1204
|
-
clientX: event.clientX,
|
|
1205
|
-
clientY: event.clientY,
|
|
1206
|
-
affordance: event.affordance,
|
|
1207
|
-
...resolved
|
|
1208
|
-
};
|
|
1209
|
-
} else if (event.kind === "click" || event.kind === "doubleclick") {
|
|
1210
|
-
params = {
|
|
1211
|
-
worldX: event.x,
|
|
1212
|
-
worldY: event.y,
|
|
1213
|
-
affordance: event.affordance,
|
|
1214
|
-
// Press point as well as release point — an action that places
|
|
1215
|
-
// geometry at the click wants the former. See `ClickEvent`.
|
|
1216
|
-
...event.kind === "click" ? { pressX: event.pressX, pressY: event.pressY } : {},
|
|
1217
|
-
mods: modifiersOf(event),
|
|
1218
|
-
...resolved
|
|
1219
|
-
};
|
|
1220
|
-
} else if (event.kind === "contextmenu" || event.kind === "longpress") {
|
|
1221
|
-
params = {
|
|
1222
|
-
worldX: event.x,
|
|
1223
|
-
worldY: event.y,
|
|
1224
|
-
affordance: event.affordance,
|
|
1225
|
-
bodyTarget: event.bodyTarget,
|
|
1226
|
-
mods: modifiersOf(event),
|
|
1227
|
-
...resolved
|
|
1228
|
-
};
|
|
1229
|
-
} else if (event.kind === "pointerdown") {
|
|
1230
|
-
params = {
|
|
1231
|
-
worldX: event.x,
|
|
1232
|
-
worldY: event.y,
|
|
1233
|
-
affordance: event.affordance,
|
|
1234
|
-
bodyTarget: event.bodyTarget,
|
|
1235
|
-
mods: modifiersOf(event),
|
|
1236
|
-
...resolved
|
|
1237
|
-
};
|
|
1238
|
-
} else if (event.kind === "drop" || event.kind === "paste") {
|
|
1239
|
-
params = {
|
|
1240
|
-
items: event.items,
|
|
1241
|
-
via: event.kind,
|
|
1242
|
-
...event.kind === "drop" && event.x !== void 0 ? { worldX: event.x, worldY: event.y } : {},
|
|
1243
|
-
...resolved
|
|
1244
|
-
};
|
|
1245
|
-
} else {
|
|
1246
|
-
params = resolved;
|
|
1247
|
-
}
|
|
1248
|
-
action.invoker.run(deps, params);
|
|
1249
|
-
} catch (err) {
|
|
1250
|
-
console.error(`weasel dispatcher: action "${action.id}" invoker threw`, err);
|
|
1251
|
-
}
|
|
1252
|
-
return "fired";
|
|
1253
|
-
}
|
|
1254
|
-
if (action.invoker?.timing === "ongoing") {
|
|
1255
|
-
if (event.kind === "pointerdown" && event.stage === "press") {
|
|
1256
|
-
console.error(
|
|
1257
|
-
`weasel dispatcher: action "${action.id}" has an ongoing invoker but is bound to a pointerDown spec, which fires at press time. Bind it to a drag spec, or give the action an immediate invoker.`
|
|
1258
|
-
);
|
|
1259
|
-
return "misbound";
|
|
1260
|
-
}
|
|
1261
|
-
const gestureId = gestureIdFor(event);
|
|
1131
|
+
function createGestureFacts() {
|
|
1132
|
+
const origins = /* @__PURE__ */ new Map();
|
|
1133
|
+
const points = /* @__PURE__ */ new Map();
|
|
1134
|
+
const startSpreads = /* @__PURE__ */ new Map();
|
|
1135
|
+
const turns = /* @__PURE__ */ new Map();
|
|
1136
|
+
return {
|
|
1137
|
+
begin(gestureId, event) {
|
|
1262
1138
|
if (event.kind === "pointerdown") {
|
|
1263
|
-
|
|
1139
|
+
origins.set(gestureId, {
|
|
1264
1140
|
x: event.x ?? 0,
|
|
1265
1141
|
y: event.y ?? 0,
|
|
1266
1142
|
...event.clientX !== void 0 ? { clientX: event.clientX } : {},
|
|
1267
1143
|
...event.clientY !== void 0 ? { clientY: event.clientY } : {}
|
|
1268
1144
|
});
|
|
1269
|
-
|
|
1270
|
-
x: event.x ?? 0,
|
|
1271
|
-
y: event.y ?? 0,
|
|
1272
|
-
...event.pressure !== void 0 ? { pressure: event.pressure } : {},
|
|
1273
|
-
...event.tiltX !== void 0 ? { tiltX: event.tiltX } : {},
|
|
1274
|
-
...event.tiltY !== void 0 ? { tiltY: event.tiltY } : {}
|
|
1275
|
-
}]);
|
|
1145
|
+
points.set(gestureId, [{ x: event.x ?? 0, y: event.y ?? 0, ...stylusOf(event) }]);
|
|
1276
1146
|
}
|
|
1277
1147
|
if (event.kind === "multitouch" && event.spread !== void 0) {
|
|
1278
|
-
|
|
1148
|
+
startSpreads.set(gestureId, event.spread);
|
|
1279
1149
|
}
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
if (isEmptyOngoingHandle(handle)) {
|
|
1283
|
-
if (event.kind === "pointerdown") {
|
|
1284
|
-
dragOrigins.delete(gestureId);
|
|
1285
|
-
dragPoints.delete(gestureId);
|
|
1286
|
-
}
|
|
1287
|
-
if (event.kind === "multitouch") {
|
|
1288
|
-
pinchStartSpreads.delete(gestureId);
|
|
1289
|
-
}
|
|
1290
|
-
return "empty-handle";
|
|
1150
|
+
if (event.kind === "multitouch" && event.angle !== void 0) {
|
|
1151
|
+
turns.set(gestureId, { last: event.angle, total: 0 });
|
|
1291
1152
|
}
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
let h = held.get(a);
|
|
1315
|
-
if (h === void 0) held.set(a, h = isEligible(a, ruleCtx));
|
|
1316
|
-
return h;
|
|
1317
|
-
};
|
|
1318
|
-
const ordered = ruleCtx ? preferContextual(sorted, (id) => actionMap.get(id), holds, view) : sorted;
|
|
1319
|
-
return {
|
|
1320
|
-
ordered,
|
|
1321
|
-
barred: outcome.barred,
|
|
1322
|
-
...outcome.claimOwner !== void 0 ? { claimOwner: outcome.claimOwner } : {},
|
|
1323
|
-
actionMap,
|
|
1324
|
-
ruleCtx,
|
|
1325
|
-
eligible: (m) => {
|
|
1326
|
-
const a = actionMap.get(m.binding.actionId);
|
|
1327
|
-
return a === void 0 || holds(a);
|
|
1328
|
-
}
|
|
1329
|
-
};
|
|
1330
|
-
}
|
|
1331
|
-
function walk(r, event, ctx, attempt, evaluateShadowed) {
|
|
1332
|
-
const steps = [];
|
|
1333
|
-
const tried = /* @__PURE__ */ new Set();
|
|
1334
|
-
let stop = "exhausted";
|
|
1335
|
-
let winner = null;
|
|
1336
|
-
for (const match of r.ordered) {
|
|
1337
|
-
if (!r.eligible(match)) continue;
|
|
1338
|
-
const push = (step) => {
|
|
1339
|
-
steps.push({ match, step });
|
|
1153
|
+
},
|
|
1154
|
+
sample(gestureId, event) {
|
|
1155
|
+
points.get(gestureId)?.push({ x: event.x, y: event.y, ...stylusOf(event) });
|
|
1156
|
+
},
|
|
1157
|
+
turn(gestureId, event) {
|
|
1158
|
+
const turn = turns.get(gestureId);
|
|
1159
|
+
if (!turn || event.angle === void 0) return;
|
|
1160
|
+
const step = event.angle - turn.last;
|
|
1161
|
+
turn.total += step - 360 * Math.round(step / 360);
|
|
1162
|
+
turn.last = event.angle;
|
|
1163
|
+
},
|
|
1164
|
+
of(gestureId) {
|
|
1165
|
+
if (gestureId === void 0) return {};
|
|
1166
|
+
const origin = origins.get(gestureId);
|
|
1167
|
+
const trail = points.get(gestureId);
|
|
1168
|
+
const startSpread = startSpreads.get(gestureId);
|
|
1169
|
+
const turn = turns.get(gestureId);
|
|
1170
|
+
return {
|
|
1171
|
+
...origin !== void 0 ? { origin } : {},
|
|
1172
|
+
...trail !== void 0 ? { points: trail } : {},
|
|
1173
|
+
...startSpread !== void 0 ? { startSpread } : {},
|
|
1174
|
+
...turn !== void 0 ? { rotation: turn.total } : {}
|
|
1340
1175
|
};
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1176
|
+
},
|
|
1177
|
+
drop(gestureId) {
|
|
1178
|
+
origins.delete(gestureId);
|
|
1179
|
+
points.delete(gestureId);
|
|
1180
|
+
startSpreads.delete(gestureId);
|
|
1181
|
+
turns.delete(gestureId);
|
|
1182
|
+
},
|
|
1183
|
+
clear() {
|
|
1184
|
+
origins.clear();
|
|
1185
|
+
points.clear();
|
|
1186
|
+
startSpreads.clear();
|
|
1187
|
+
turns.clear();
|
|
1188
|
+
}
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
var EMPTY_ENGAGED2 = /* @__PURE__ */ new Set();
|
|
1192
|
+
function createInFlightGestures() {
|
|
1193
|
+
const handles = /* @__PURE__ */ new Map();
|
|
1194
|
+
const owners = /* @__PURE__ */ new Map();
|
|
1195
|
+
const actions = /* @__PURE__ */ new Map();
|
|
1196
|
+
const forget = (gestureId) => {
|
|
1197
|
+
handles.delete(gestureId);
|
|
1198
|
+
owners.delete(gestureId);
|
|
1199
|
+
actions.delete(gestureId);
|
|
1200
|
+
};
|
|
1201
|
+
return {
|
|
1202
|
+
handles,
|
|
1203
|
+
open(gestureId, handle, ownerId, action) {
|
|
1204
|
+
handles.set(gestureId, handle);
|
|
1205
|
+
owners.set(gestureId, ownerId);
|
|
1206
|
+
if (action) actions.set(gestureId, action);
|
|
1207
|
+
},
|
|
1208
|
+
take(gestureId) {
|
|
1209
|
+
const handle = handles.get(gestureId);
|
|
1210
|
+
forget(gestureId);
|
|
1211
|
+
return handle;
|
|
1212
|
+
},
|
|
1213
|
+
takeAll() {
|
|
1214
|
+
const all = [...handles];
|
|
1215
|
+
handles.clear();
|
|
1216
|
+
owners.clear();
|
|
1217
|
+
actions.clear();
|
|
1218
|
+
return all;
|
|
1219
|
+
},
|
|
1220
|
+
engagedOwners() {
|
|
1221
|
+
if (owners.size === 0) return EMPTY_ENGAGED2;
|
|
1222
|
+
const out = /* @__PURE__ */ new Set();
|
|
1223
|
+
for (const owner of owners.values()) {
|
|
1224
|
+
if (owner != null) out.add(owner);
|
|
1370
1225
|
}
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1226
|
+
return out;
|
|
1227
|
+
},
|
|
1228
|
+
cursor() {
|
|
1229
|
+
let cursor = null;
|
|
1230
|
+
for (const [id, handle] of handles) {
|
|
1231
|
+
const action = actions.get(id);
|
|
1232
|
+
const spec = handle.cursor?.() ?? action?.activeCursor ?? action?.cursor;
|
|
1233
|
+
if (spec !== void 0 && resolveCursor(spec)) cursor = spec;
|
|
1376
1234
|
}
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1235
|
+
return cursor;
|
|
1236
|
+
},
|
|
1237
|
+
latest() {
|
|
1238
|
+
let lastId = null;
|
|
1239
|
+
let lastHandle = null;
|
|
1240
|
+
for (const [id, handle] of handles) {
|
|
1241
|
+
lastId = id;
|
|
1242
|
+
lastHandle = handle;
|
|
1381
1243
|
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
winner = { match, action };
|
|
1244
|
+
if (lastHandle === null) return { kind: null, id: null };
|
|
1245
|
+
return { kind: lastHandle.kind ?? null, id: lastId };
|
|
1385
1246
|
}
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
} else if (verdict.kind === "would-fire") {
|
|
1418
|
-
verdict = { kind: "shadowed" };
|
|
1419
|
-
}
|
|
1420
|
-
out.push({
|
|
1421
|
-
actionId,
|
|
1422
|
-
action,
|
|
1423
|
-
binding: match.binding,
|
|
1424
|
-
scope: match.scope,
|
|
1425
|
-
ownerId: match.ownerId,
|
|
1426
|
-
specificity: specificity(match.binding.spec),
|
|
1427
|
-
verdict
|
|
1428
|
-
});
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
function isEmptyOngoingHandle(h) {
|
|
1250
|
+
return !h.onMove && !h.onEnd && !h.overlay && !h.previewIds && !h.previewPose;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
// src/interactions/dispatcher/invocationCtx.ts
|
|
1254
|
+
function uiInvocationCtx(deps, params) {
|
|
1255
|
+
return {
|
|
1256
|
+
world: { x: 0, y: 0 },
|
|
1257
|
+
modifiers: { alt: false, ctrl: false, meta: false, shift: false },
|
|
1258
|
+
deps,
|
|
1259
|
+
...params !== void 0 ? { params } : {}
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
function clientPoint(ev) {
|
|
1263
|
+
const cx = ev.clientX;
|
|
1264
|
+
const cy = ev.clientY;
|
|
1265
|
+
return cx !== void 0 && cy !== void 0 ? { x: cx, y: cy } : void 0;
|
|
1266
|
+
}
|
|
1267
|
+
function pressDrag(event) {
|
|
1268
|
+
const sx = event.x ?? (event.kind === "click" ? event.pressX : void 0) ?? 0;
|
|
1269
|
+
const sy = event.y ?? (event.kind === "click" ? event.pressY : void 0) ?? 0;
|
|
1270
|
+
const affordance = event.kind === "pointerdown" ? event.affordance : void 0;
|
|
1271
|
+
return {
|
|
1272
|
+
world: { x: sx, y: sy },
|
|
1273
|
+
drag: {
|
|
1274
|
+
start: { x: sx, y: sy },
|
|
1275
|
+
current: { x: sx, y: sy },
|
|
1276
|
+
delta: { x: 0, y: 0 },
|
|
1277
|
+
...affordance !== void 0 ? { affordance } : {}
|
|
1429
1278
|
}
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
modifiers: { alt: false, ctrl: false, meta: false, shift: false },
|
|
1452
|
-
deps: {}
|
|
1453
|
-
};
|
|
1454
|
-
const ending = [...inFlightHandles];
|
|
1455
|
-
inFlightHandles.clear();
|
|
1456
|
-
inFlightOwners.clear();
|
|
1457
|
-
inFlightActions.clear();
|
|
1458
|
-
uiOngoingByAction.clear();
|
|
1459
|
-
dragOrigins.clear();
|
|
1460
|
-
dragPoints.clear();
|
|
1461
|
-
pinchStartSpreads.clear();
|
|
1462
|
-
for (const [gestureId, handle] of ending) runOnEnd(gestureId, handle, stubCtx, reason);
|
|
1463
|
-
}
|
|
1464
|
-
function inFlight() {
|
|
1465
|
-
return inFlightHandles;
|
|
1466
|
-
}
|
|
1467
|
-
function inFlightCursor() {
|
|
1468
|
-
let cursor = null;
|
|
1469
|
-
for (const action of inFlightActions.values()) {
|
|
1470
|
-
const spec = action.activeCursor ?? action.cursor;
|
|
1471
|
-
if (spec !== void 0 && resolveCursor(spec)) cursor = spec;
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
function pumpDrag(event, facts) {
|
|
1282
|
+
const cx = event.x;
|
|
1283
|
+
const cy = event.y;
|
|
1284
|
+
const origin = facts.origin;
|
|
1285
|
+
const ox = origin?.x ?? cx;
|
|
1286
|
+
const oy = origin?.y ?? cy;
|
|
1287
|
+
const live = facts.points;
|
|
1288
|
+
const points = live && event.kind === "pointerup" ? [...live] : live;
|
|
1289
|
+
const eventClientX = event.clientX;
|
|
1290
|
+
const eventClientY = event.clientY;
|
|
1291
|
+
const hasClient = eventClientX !== void 0 && eventClientY !== void 0 && origin?.clientX !== void 0 && origin?.clientY !== void 0;
|
|
1292
|
+
return {
|
|
1293
|
+
world: { x: cx, y: cy },
|
|
1294
|
+
drag: {
|
|
1295
|
+
start: { x: ox, y: oy },
|
|
1296
|
+
current: { x: cx, y: cy },
|
|
1297
|
+
delta: { x: cx - ox, y: cy - oy },
|
|
1298
|
+
...hasClient ? { screenDelta: { x: eventClientX - origin.clientX, y: eventClientY - origin.clientY } } : {},
|
|
1299
|
+
...points !== void 0 ? { points } : {}
|
|
1472
1300
|
}
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
function multiTouchOf(event, facts) {
|
|
1304
|
+
const centroid = event.centroid ?? { x: 0, y: 0 };
|
|
1305
|
+
const spread = event.spread ?? 1;
|
|
1306
|
+
const { startSpread } = facts;
|
|
1307
|
+
return {
|
|
1308
|
+
centroid,
|
|
1309
|
+
spread,
|
|
1310
|
+
rotation: facts.rotation ?? 0,
|
|
1311
|
+
// Pinch geometry, on a move of a running gesture.
|
|
1312
|
+
...startSpread !== void 0 && event.spread !== void 0 ? { pinch: { startSpread, currentSpread: spread, centroid } } : {}
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
function buildInvocationCtx(event, deps, viewId, facts = {}) {
|
|
1316
|
+
const base = {
|
|
1317
|
+
world: { x: 0, y: 0 },
|
|
1318
|
+
modifiers: { alt: event.altKey, ctrl: event.ctrlKey, meta: event.metaKey, shift: event.shiftKey },
|
|
1319
|
+
deps,
|
|
1320
|
+
...viewId !== void 0 ? { viewId } : {}
|
|
1321
|
+
};
|
|
1322
|
+
const screen = clientPoint(event);
|
|
1323
|
+
if (screen) base.screen = screen;
|
|
1324
|
+
if (event.kind === "key") {
|
|
1325
|
+
base.key = { key: event.key, repeat: event.repeat ?? false };
|
|
1326
|
+
} else if (event.kind === "key-held") {
|
|
1327
|
+
base.key = { key: event.key, repeat: false };
|
|
1328
|
+
} else if (event.kind === "wheel") {
|
|
1329
|
+
base.wheel = { deltaX: event.deltaX, deltaY: event.deltaY, deltaZ: 0 };
|
|
1330
|
+
} else if (event.kind === "pinch") {
|
|
1331
|
+
base.pinch = { scale: event.scale, rotation: event.rotation };
|
|
1332
|
+
} else if (event.kind === "pointerdown" || event.kind === "click" || event.kind === "doubleclick" || event.kind === "contextmenu") {
|
|
1333
|
+
Object.assign(base, pressDrag(event));
|
|
1334
|
+
} else if (event.kind === "pointermove" || event.kind === "pointerup") {
|
|
1335
|
+
Object.assign(base, pumpDrag(event, facts));
|
|
1336
|
+
} else if (event.kind === "multitouch") {
|
|
1337
|
+
base.multiTouch = multiTouchOf(event, facts);
|
|
1338
|
+
}
|
|
1339
|
+
return base;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
// src/interactions/dispatcher/gestureRun.ts
|
|
1343
|
+
var ctxFor = (run, event, deps, gestureId) => buildInvocationCtx(event, deps, run.viewId(), run.facts.of(gestureId));
|
|
1344
|
+
function runOnEnd(gestureId, handle, endCtx, reason) {
|
|
1345
|
+
try {
|
|
1346
|
+
handle.onEnd?.(endCtx, reason);
|
|
1347
|
+
} catch (err) {
|
|
1348
|
+
console.error(`weasel dispatcher: gesture "${gestureId}" threw on onEnd`, err);
|
|
1486
1349
|
}
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1350
|
+
}
|
|
1351
|
+
function endGesture(run, gestureId, event, reason) {
|
|
1352
|
+
if (!run.inFlight.handles.has(gestureId)) return false;
|
|
1353
|
+
const endCtx = ctxFor(run, event, {}, gestureId);
|
|
1354
|
+
const handle = run.inFlight.take(gestureId);
|
|
1355
|
+
run.facts.drop(gestureId);
|
|
1356
|
+
runOnEnd(gestureId, handle, endCtx, reason);
|
|
1357
|
+
return true;
|
|
1358
|
+
}
|
|
1359
|
+
function endMultiTouch(run, event, reason, except) {
|
|
1360
|
+
let ended = false;
|
|
1361
|
+
for (const id of [...run.inFlight.handles.keys()]) {
|
|
1362
|
+
if (isMultiTouchGestureId(id) && id !== except) ended = endGesture(run, id, event, reason) || ended;
|
|
1363
|
+
}
|
|
1364
|
+
return ended;
|
|
1365
|
+
}
|
|
1366
|
+
function endAllGestures(run, reason) {
|
|
1367
|
+
const stubCtx = uiInvocationCtx({});
|
|
1368
|
+
const ending = run.inFlight.takeAll();
|
|
1369
|
+
run.facts.clear();
|
|
1370
|
+
for (const [gestureId, handle] of ending) runOnEnd(gestureId, handle, stubCtx, reason);
|
|
1371
|
+
}
|
|
1372
|
+
function pumpGesture(run, event) {
|
|
1373
|
+
const { inFlight } = run;
|
|
1374
|
+
const ended = (reason) => endGesture(run, gestureIdFor(event), event, reason) ? "handled" : "unhandled";
|
|
1375
|
+
if (event.kind === "key-held" && event.phase === "up") return ended("commit");
|
|
1376
|
+
if (event.kind === "key-held" && event.phase === "down") {
|
|
1377
|
+
return inFlight.handles.has(gestureIdFor(event)) ? "handled" : void 0;
|
|
1378
|
+
}
|
|
1379
|
+
if (event.kind === "pointermove") {
|
|
1380
|
+
const gestureId = gestureIdFor(event);
|
|
1381
|
+
const handle = inFlight.handles.get(gestureId);
|
|
1382
|
+
if (!handle) return "unhandled";
|
|
1383
|
+
run.facts.sample(gestureId, event);
|
|
1384
|
+
if (!handle.onMove) return "unhandled";
|
|
1385
|
+
handle.onMove(ctxFor(run, event, {}, gestureId));
|
|
1386
|
+
return "handled";
|
|
1387
|
+
}
|
|
1388
|
+
if (event.kind === "pointerup") return ended("commit");
|
|
1389
|
+
if (event.kind === "pointercancel") return ended("cancel");
|
|
1390
|
+
if (event.kind === "multitouch" && event.end !== void 0) {
|
|
1391
|
+
return endMultiTouch(run, event, event.end) ? "handled" : "unhandled";
|
|
1392
|
+
}
|
|
1393
|
+
if (event.kind === "multitouch" && event.centroid !== void 0) {
|
|
1394
|
+
const gestureId = gestureIdFor(event);
|
|
1395
|
+
const handle = inFlight.handles.get(gestureId);
|
|
1396
|
+
if (handle?.onMove) {
|
|
1397
|
+
run.facts.turn(gestureId, event);
|
|
1398
|
+
handle.onMove(ctxFor(run, event, {}, gestureId));
|
|
1399
|
+
return "handled";
|
|
1493
1400
|
}
|
|
1494
|
-
|
|
1495
|
-
return { kind: lastHandle.kind ?? null, id: lastId };
|
|
1401
|
+
endMultiTouch(run, event, "cancel", gestureId);
|
|
1496
1402
|
}
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
} finally {
|
|
1502
|
-
if (before > 0 || inFlightHandles.size > 0) notify();
|
|
1503
|
-
}
|
|
1504
|
-
};
|
|
1505
|
-
const cancelAllWithNotify = (reason) => {
|
|
1506
|
-
const before = inFlightHandles.size;
|
|
1403
|
+
return void 0;
|
|
1404
|
+
}
|
|
1405
|
+
function invokeAction(run, event, match, action, deps) {
|
|
1406
|
+
if (action.invoker?.timing === "immediate") {
|
|
1507
1407
|
try {
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1408
|
+
const params = immediateParams(event, resolveParams(match.binding.opts?.params));
|
|
1409
|
+
action.invoker.run(deps, params);
|
|
1410
|
+
} catch (err) {
|
|
1411
|
+
console.error(`weasel dispatcher: action "${action.id}" invoker threw`, err);
|
|
1511
1412
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
if (
|
|
1516
|
-
|
|
1517
|
-
|
|
1413
|
+
return "fired";
|
|
1414
|
+
}
|
|
1415
|
+
if (action.invoker?.timing === "ongoing") {
|
|
1416
|
+
if (event.kind === "hover" || event.kind === "pointerdown" && event.stage === "press") {
|
|
1417
|
+
const spec = event.kind === "hover" ? "hover spec, which has no press to own" : "pointerDown spec, which fires at press time";
|
|
1418
|
+
console.error(
|
|
1419
|
+
`weasel dispatcher: action "${action.id}" has an ongoing invoker but is bound to a ${spec}. Bind it to a drag spec, or give the action an immediate invoker.`
|
|
1420
|
+
);
|
|
1421
|
+
return "misbound";
|
|
1518
1422
|
}
|
|
1519
|
-
const
|
|
1520
|
-
|
|
1521
|
-
|
|
1423
|
+
const gestureId = gestureIdFor(event);
|
|
1424
|
+
run.facts.begin(gestureId, event);
|
|
1425
|
+
const handle = action.invoker.start(ctxFor(run, event, deps, gestureId), match.binding.opts);
|
|
1426
|
+
if (isEmptyOngoingHandle(handle)) {
|
|
1427
|
+
run.facts.drop(gestureId);
|
|
1428
|
+
return "empty-handle";
|
|
1522
1429
|
}
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1430
|
+
run.inFlight.open(gestureId, handle, match.ownerId, action);
|
|
1431
|
+
return "fired";
|
|
1432
|
+
}
|
|
1433
|
+
return "fired";
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
// src/interactions/dispatcher/uiOngoing.ts
|
|
1437
|
+
function createUiOngoing(inFlight, notify, getAction) {
|
|
1438
|
+
let seq = 0;
|
|
1439
|
+
const byAction = /* @__PURE__ */ new Map();
|
|
1440
|
+
function commitPrior(actionId, deps) {
|
|
1441
|
+
const prevGestureId = byAction.get(actionId);
|
|
1442
|
+
if (prevGestureId === void 0) return;
|
|
1443
|
+
const prevHandle = inFlight.handles.get(prevGestureId);
|
|
1444
|
+
if (prevHandle?.onEnd) {
|
|
1445
|
+
try {
|
|
1446
|
+
prevHandle.onEnd(uiInvocationCtx(deps), "commit");
|
|
1447
|
+
} catch (e) {
|
|
1448
|
+
console.error(`weasel dispatcher: prior UI handle for "${actionId}" threw on auto-commit`, e);
|
|
1532
1449
|
}
|
|
1533
|
-
inFlightHandles.delete(prevGestureId);
|
|
1534
|
-
inFlightOwners.delete(prevGestureId);
|
|
1535
|
-
uiOngoingByAction.delete(actionId);
|
|
1536
|
-
}
|
|
1537
|
-
const gestureId = `ui-${actionId}-${++uiOngoingSeq}`;
|
|
1538
|
-
let handle;
|
|
1539
|
-
try {
|
|
1540
|
-
handle = action.invoker.start(buildUiInvocationCtx(deps, params), { params });
|
|
1541
|
-
} catch (e) {
|
|
1542
|
-
console.error(`weasel dispatcher: action "${actionId}" threw on start`, e);
|
|
1543
|
-
return null;
|
|
1544
|
-
}
|
|
1545
|
-
if (isEmptyOngoingHandle(handle)) {
|
|
1546
|
-
return null;
|
|
1547
1450
|
}
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1451
|
+
inFlight.take(prevGestureId);
|
|
1452
|
+
byAction.delete(actionId);
|
|
1453
|
+
}
|
|
1454
|
+
function control(actionId, gestureId, handle, deps) {
|
|
1552
1455
|
let ended = false;
|
|
1553
|
-
const live = () => !ended &&
|
|
1456
|
+
const live = () => !ended && inFlight.handles.get(gestureId) === handle;
|
|
1554
1457
|
return {
|
|
1555
1458
|
gestureId,
|
|
1556
1459
|
get active() {
|
|
@@ -1560,7 +1463,7 @@ function createDispatcher(opts) {
|
|
|
1560
1463
|
if (!live()) return;
|
|
1561
1464
|
if (!handle.onMove) return;
|
|
1562
1465
|
try {
|
|
1563
|
-
handle.onMove(
|
|
1466
|
+
handle.onMove(uiInvocationCtx(deps, nextParams));
|
|
1564
1467
|
} catch (e) {
|
|
1565
1468
|
console.error(`weasel dispatcher: action "${actionId}" threw on onMove`, e);
|
|
1566
1469
|
}
|
|
@@ -1574,43 +1477,302 @@ function createDispatcher(opts) {
|
|
|
1574
1477
|
ended = true;
|
|
1575
1478
|
if (handle.onEnd) {
|
|
1576
1479
|
try {
|
|
1577
|
-
handle.onEnd(
|
|
1480
|
+
handle.onEnd(uiInvocationCtx(deps), reason);
|
|
1578
1481
|
} catch (e) {
|
|
1579
1482
|
console.error(`weasel dispatcher: action "${actionId}" threw on onEnd`, e);
|
|
1580
1483
|
}
|
|
1581
1484
|
}
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
inFlightActions.delete(gestureId);
|
|
1585
|
-
if (uiOngoingByAction.get(actionId) === gestureId) {
|
|
1586
|
-
uiOngoingByAction.delete(actionId);
|
|
1587
|
-
}
|
|
1485
|
+
inFlight.take(gestureId);
|
|
1486
|
+
if (byAction.get(actionId) === gestureId) byAction.delete(actionId);
|
|
1588
1487
|
notify();
|
|
1589
1488
|
}
|
|
1590
1489
|
};
|
|
1591
1490
|
}
|
|
1491
|
+
return {
|
|
1492
|
+
begin(actionId, deps, params) {
|
|
1493
|
+
if (!getAction) {
|
|
1494
|
+
console.warn("weasel dispatcher: beginUiOngoing called but no getAction was provided to createDispatcher");
|
|
1495
|
+
return null;
|
|
1496
|
+
}
|
|
1497
|
+
const action = getAction(actionId);
|
|
1498
|
+
if (!action || !action.invoker || action.invoker.timing !== "ongoing") {
|
|
1499
|
+
return null;
|
|
1500
|
+
}
|
|
1501
|
+
commitPrior(actionId, deps);
|
|
1502
|
+
const gestureId = `ui-${actionId}-${++seq}`;
|
|
1503
|
+
let handle;
|
|
1504
|
+
try {
|
|
1505
|
+
handle = action.invoker.start(uiInvocationCtx(deps, params), { params });
|
|
1506
|
+
} catch (e) {
|
|
1507
|
+
console.error(`weasel dispatcher: action "${actionId}" threw on start`, e);
|
|
1508
|
+
return null;
|
|
1509
|
+
}
|
|
1510
|
+
if (isEmptyOngoingHandle(handle)) return null;
|
|
1511
|
+
inFlight.open(gestureId, handle, null);
|
|
1512
|
+
byAction.set(actionId, gestureId);
|
|
1513
|
+
notify();
|
|
1514
|
+
return control(actionId, gestureId, handle, deps);
|
|
1515
|
+
},
|
|
1516
|
+
clear: () => {
|
|
1517
|
+
byAction.clear();
|
|
1518
|
+
}
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// src/interactions/dispatcher/walk.ts
|
|
1523
|
+
function assembleScopedBindings(ctx) {
|
|
1524
|
+
const allowedCapabilities = ctx.getRuleCtx?.()?.allowedCapabilities;
|
|
1525
|
+
const { ordered, state } = assembleEntries(ctx.entriesById.values(), {
|
|
1526
|
+
focusedId: ctx.activeToolId,
|
|
1527
|
+
hotkeyStack: ctx.hotkeyStack,
|
|
1528
|
+
...allowedCapabilities ? { allowedCapabilities } : {}
|
|
1529
|
+
});
|
|
1530
|
+
const view = ctx.viewId ?? null;
|
|
1531
|
+
const result = scopeBindings(ordered, state).filter((sb) => liveInView(sb.binding, view));
|
|
1532
|
+
for (const action of ctx.actions.list()) {
|
|
1533
|
+
const targetScope = action.scope === "hotkey" ? "hotkey" : "ambient";
|
|
1534
|
+
for (const binding of actionBindings(action)) {
|
|
1535
|
+
if (!liveInView(binding, view)) continue;
|
|
1536
|
+
result.push({ binding, scope: targetScope, ownerId: null });
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
return result;
|
|
1540
|
+
}
|
|
1541
|
+
function buildActionMap(registry) {
|
|
1542
|
+
const map = /* @__PURE__ */ new Map();
|
|
1543
|
+
for (const action of registry.list()) {
|
|
1544
|
+
map.set(action.id, action);
|
|
1545
|
+
}
|
|
1546
|
+
return map;
|
|
1547
|
+
}
|
|
1548
|
+
function rank(event, ctx, engaged, collectBarred) {
|
|
1549
|
+
const view = ctx.viewId ?? null;
|
|
1550
|
+
const outcome = matchSortedWithBarred(
|
|
1551
|
+
event,
|
|
1552
|
+
assembleScopedBindings(ctx),
|
|
1553
|
+
ctx.isMac,
|
|
1554
|
+
engaged,
|
|
1555
|
+
{ collectBarred }
|
|
1556
|
+
);
|
|
1557
|
+
const sorted = preferViewScoped(outcome.matches, view);
|
|
1558
|
+
const actionMap = buildActionMap(ctx.actions);
|
|
1559
|
+
const ruleCtx = ruleCtxOf(ctx);
|
|
1560
|
+
const held = /* @__PURE__ */ new Map();
|
|
1561
|
+
const holds = (a) => {
|
|
1562
|
+
if (ruleCtx === void 0) return true;
|
|
1563
|
+
let h = held.get(a);
|
|
1564
|
+
if (h === void 0) held.set(a, h = isEligible(a, ruleCtx));
|
|
1565
|
+
return h;
|
|
1566
|
+
};
|
|
1567
|
+
const ordered = ruleCtx ? preferContextual(sorted, (id) => actionMap.get(id), holds, view) : sorted;
|
|
1568
|
+
return {
|
|
1569
|
+
ordered,
|
|
1570
|
+
barred: outcome.barred,
|
|
1571
|
+
...outcome.claimOwner !== void 0 ? { claimOwner: outcome.claimOwner } : {},
|
|
1572
|
+
actionMap,
|
|
1573
|
+
ruleCtx,
|
|
1574
|
+
eligible: (m) => {
|
|
1575
|
+
const a = actionMap.get(m.binding.actionId);
|
|
1576
|
+
return a === void 0 || holds(a);
|
|
1577
|
+
}
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
function walk(r, event, ctx, attempt, evaluateShadowed) {
|
|
1581
|
+
const steps = [];
|
|
1582
|
+
const tried = /* @__PURE__ */ new Set();
|
|
1583
|
+
let stop = "exhausted";
|
|
1584
|
+
let winner = null;
|
|
1585
|
+
for (const match of r.ordered) {
|
|
1586
|
+
if (!r.eligible(match)) continue;
|
|
1587
|
+
const push = (step) => {
|
|
1588
|
+
steps.push({ match, step });
|
|
1589
|
+
};
|
|
1590
|
+
const actionId = match.binding.actionId;
|
|
1591
|
+
if (tried.has(actionId)) {
|
|
1592
|
+
push({ kind: "duplicate" });
|
|
1593
|
+
continue;
|
|
1594
|
+
}
|
|
1595
|
+
if (stop !== "exhausted" && !evaluateShadowed) {
|
|
1596
|
+
push({ kind: "not-asked" });
|
|
1597
|
+
continue;
|
|
1598
|
+
}
|
|
1599
|
+
tried.add(actionId);
|
|
1600
|
+
const action = r.actionMap.get(actionId);
|
|
1601
|
+
if (!action) {
|
|
1602
|
+
if (attempt) {
|
|
1603
|
+
console.warn(
|
|
1604
|
+
`weasel dispatcher: binding resolved actionId "${actionId}" which has no registered action. Skipping. (misconfiguration)`
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
push({ kind: "no-such-action" });
|
|
1608
|
+
continue;
|
|
1609
|
+
}
|
|
1610
|
+
const deps = buildDepsFromRequires(action, ctx.depRegistry);
|
|
1611
|
+
const enabled = action.enabled ? action.enabled(deps, worldPointOf(event), carriedBy(event)) : true;
|
|
1612
|
+
if (enabled !== true) {
|
|
1613
|
+
push({ kind: "declined", reason: String(enabled) });
|
|
1614
|
+
continue;
|
|
1615
|
+
}
|
|
1616
|
+
if (stop !== "exhausted") {
|
|
1617
|
+
push({ kind: "outranked" });
|
|
1618
|
+
continue;
|
|
1619
|
+
}
|
|
1620
|
+
if (!attempt) {
|
|
1621
|
+
push({ kind: "would-fire" });
|
|
1622
|
+
stop = "fired";
|
|
1623
|
+
winner = { match, action };
|
|
1624
|
+
continue;
|
|
1625
|
+
}
|
|
1626
|
+
const result = attempt(match, action, deps);
|
|
1627
|
+
if (result === "empty-handle") {
|
|
1628
|
+
push({ kind: "empty-handle" });
|
|
1629
|
+
continue;
|
|
1630
|
+
}
|
|
1631
|
+
push({ kind: result === "fired" ? "fired" : "misbound" });
|
|
1632
|
+
stop = result;
|
|
1633
|
+
winner = { match, action };
|
|
1634
|
+
}
|
|
1635
|
+
return { steps, stop, winner };
|
|
1636
|
+
}
|
|
1637
|
+
function verdictsOf(r, walked, evaluateShadowed) {
|
|
1638
|
+
const stepOf = new Map(walked.steps.map((s) => [s.match, s.step]));
|
|
1639
|
+
const verdictByAction = /* @__PURE__ */ new Map();
|
|
1640
|
+
let fired = false;
|
|
1641
|
+
const out = [];
|
|
1642
|
+
for (const match of r.ordered) {
|
|
1643
|
+
const actionId = match.binding.actionId;
|
|
1644
|
+
const action = r.actionMap.get(actionId);
|
|
1645
|
+
if (!action) continue;
|
|
1646
|
+
let verdict = verdictByAction.get(actionId);
|
|
1647
|
+
if (verdict === void 0) {
|
|
1648
|
+
if (!r.eligible(match)) {
|
|
1649
|
+
verdict = fired && !evaluateShadowed ? { kind: "shadowed" } : { kind: "ineligible", reason: describeEligible(action.eligible) };
|
|
1650
|
+
} else {
|
|
1651
|
+
const step = stepOf.get(match);
|
|
1652
|
+
if (step?.kind === "would-fire") {
|
|
1653
|
+
verdict = { kind: "would-fire" };
|
|
1654
|
+
fired = true;
|
|
1655
|
+
} else if (step?.kind === "declined") {
|
|
1656
|
+
verdict = { kind: "disabled", reason: step.reason };
|
|
1657
|
+
} else {
|
|
1658
|
+
verdict = { kind: "shadowed" };
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
verdictByAction.set(actionId, verdict);
|
|
1662
|
+
} else if (verdict.kind === "would-fire") {
|
|
1663
|
+
verdict = { kind: "shadowed" };
|
|
1664
|
+
}
|
|
1665
|
+
out.push({
|
|
1666
|
+
actionId,
|
|
1667
|
+
action,
|
|
1668
|
+
binding: match.binding,
|
|
1669
|
+
scope: match.scope,
|
|
1670
|
+
ownerId: match.ownerId,
|
|
1671
|
+
specificity: specificity(match.binding.spec),
|
|
1672
|
+
verdict
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1675
|
+
return out;
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
// src/interactions/dispatcher/predict.ts
|
|
1679
|
+
function resolveAll(event, ctx, engaged, opts) {
|
|
1680
|
+
const evaluateShadowed = opts?.evaluateShadowed ?? false;
|
|
1681
|
+
const r = rank(event, ctx, engaged, false);
|
|
1682
|
+
if (r.ordered.length === 0) return [];
|
|
1683
|
+
return verdictsOf(r, walk(r, event, ctx, null, evaluateShadowed), evaluateShadowed);
|
|
1684
|
+
}
|
|
1685
|
+
function resolveOnly(event, ctx, engaged) {
|
|
1686
|
+
const r = rank(event, ctx, engaged, false);
|
|
1687
|
+
const { winner } = walk(r, event, ctx, null, false);
|
|
1688
|
+
if (!winner) return null;
|
|
1689
|
+
return {
|
|
1690
|
+
actionId: winner.action.id,
|
|
1691
|
+
action: winner.action,
|
|
1692
|
+
scope: winner.match.scope,
|
|
1693
|
+
ownerId: winner.match.ownerId
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
function explain(event, ctx, engaged, opts) {
|
|
1697
|
+
const r = rank(event, ctx, engaged, true);
|
|
1698
|
+
const walked = walk(r, event, ctx, null, opts?.evaluateShadowed ?? false);
|
|
1699
|
+
return buildDispatchRecord(r, walked, event, ctx, true, walked.winner ? "handled" : "unhandled");
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
// src/interactions/dispatcher/dispatcher.ts
|
|
1703
|
+
function createDispatcher(opts) {
|
|
1704
|
+
const inFlight = createInFlightGestures();
|
|
1705
|
+
const facts = createGestureFacts();
|
|
1706
|
+
const changed = createEmitter();
|
|
1707
|
+
let version = 0;
|
|
1708
|
+
function notify() {
|
|
1709
|
+
version++;
|
|
1710
|
+
changed.emit();
|
|
1711
|
+
}
|
|
1712
|
+
const uiOngoing = createUiOngoing(inFlight, notify, opts?.getAction);
|
|
1713
|
+
let routedView;
|
|
1714
|
+
const run = { inFlight, facts, viewId: () => routedView };
|
|
1715
|
+
const invoking = (event) => (match, action, deps) => invokeAction(run, event, match, action, deps);
|
|
1716
|
+
function handleInput(event, ctx) {
|
|
1717
|
+
routedView = ctx.viewId;
|
|
1718
|
+
const pumped = pumpGesture(run, event);
|
|
1719
|
+
if (pumped !== void 0) return pumped;
|
|
1720
|
+
if (event.kind === "hover") return hover(event, ctx);
|
|
1721
|
+
const r = rank(event, ctx, inFlight.engagedOwners(), DEV2);
|
|
1722
|
+
const walked = walk(r, event, ctx, invoking(event), false);
|
|
1723
|
+
const outcome = walked.stop === "fired" ? "handled" : "unhandled";
|
|
1724
|
+
if (DEV2) recordTrace(buildDispatchRecord(r, walked, event, ctx, false, outcome));
|
|
1725
|
+
return outcome;
|
|
1726
|
+
}
|
|
1727
|
+
function hover(event, ctx) {
|
|
1728
|
+
let fired = false;
|
|
1729
|
+
const invoke = invoking(event);
|
|
1730
|
+
walk(rank(event, ctx, inFlight.engagedOwners(), false), event, ctx, (match, action, deps) => {
|
|
1731
|
+
if (invoke(match, action, deps) === "fired") fired = true;
|
|
1732
|
+
return "empty-handle";
|
|
1733
|
+
}, false);
|
|
1734
|
+
return fired ? "handled" : "unhandled";
|
|
1735
|
+
}
|
|
1736
|
+
function cancelAll(reason) {
|
|
1737
|
+
uiOngoing.clear();
|
|
1738
|
+
endAllGestures(run, reason);
|
|
1739
|
+
}
|
|
1740
|
+
const handleInputWithNotify = (event, ctx) => {
|
|
1741
|
+
const before = inFlight.handles.size;
|
|
1742
|
+
try {
|
|
1743
|
+
return handleInput(event, ctx);
|
|
1744
|
+
} finally {
|
|
1745
|
+
if (before > 0 || inFlight.handles.size > 0) notify();
|
|
1746
|
+
}
|
|
1747
|
+
};
|
|
1748
|
+
const cancelAllWithNotify = (reason) => {
|
|
1749
|
+
const before = inFlight.handles.size;
|
|
1750
|
+
try {
|
|
1751
|
+
cancelAll(reason);
|
|
1752
|
+
} finally {
|
|
1753
|
+
if (before > 0) notify();
|
|
1754
|
+
}
|
|
1755
|
+
};
|
|
1592
1756
|
return {
|
|
1593
1757
|
handleInput: handleInputWithNotify,
|
|
1594
|
-
resolveOnly,
|
|
1595
|
-
resolveAll,
|
|
1596
|
-
explain,
|
|
1758
|
+
resolveOnly: (event, ctx) => resolveOnly(event, ctx, inFlight.engagedOwners()),
|
|
1759
|
+
resolveAll: (event, ctx, resolveOpts) => resolveAll(event, ctx, inFlight.engagedOwners(), resolveOpts),
|
|
1760
|
+
explain: (event, ctx, explainOpts) => explain(event, ctx, inFlight.engagedOwners(), explainOpts),
|
|
1597
1761
|
cancelAll: cancelAllWithNotify,
|
|
1598
|
-
inFlight,
|
|
1599
|
-
inFlightCursor,
|
|
1600
|
-
getInFlightHandles,
|
|
1601
|
-
subscribe,
|
|
1602
|
-
getVersion,
|
|
1603
|
-
getActiveAction,
|
|
1604
|
-
beginUiOngoing
|
|
1762
|
+
inFlight: () => inFlight.handles,
|
|
1763
|
+
inFlightCursor: inFlight.cursor,
|
|
1764
|
+
getInFlightHandles: () => inFlight.handles.values(),
|
|
1765
|
+
subscribe: changed.subscribe,
|
|
1766
|
+
getVersion: () => version,
|
|
1767
|
+
getActiveAction: inFlight.latest,
|
|
1768
|
+
beginUiOngoing: uiOngoing.begin
|
|
1605
1769
|
};
|
|
1606
1770
|
}
|
|
1607
|
-
|
|
1608
|
-
// src/interactions/dispatcher/longPressState.ts
|
|
1609
|
-
var LONG_PRESS_MS = 500;
|
|
1771
|
+
var LONG_PRESS_HAPTIC_MS = 15;
|
|
1610
1772
|
var nowMs = () => typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
1611
1773
|
function createLongPressStore() {
|
|
1612
1774
|
let current = null;
|
|
1613
|
-
const
|
|
1775
|
+
const changed = createEmitter();
|
|
1614
1776
|
return {
|
|
1615
1777
|
get: () => current,
|
|
1616
1778
|
progress: (now = nowMs()) => {
|
|
@@ -1619,16 +1781,11 @@ function createLongPressStore() {
|
|
|
1619
1781
|
const t = (now - current.startedAt) / current.duration;
|
|
1620
1782
|
return t <= 0 ? 0 : t >= 1 ? 1 : t;
|
|
1621
1783
|
},
|
|
1622
|
-
subscribe:
|
|
1623
|
-
listeners.add(fn);
|
|
1624
|
-
return () => {
|
|
1625
|
-
listeners.delete(fn);
|
|
1626
|
-
};
|
|
1627
|
-
},
|
|
1784
|
+
subscribe: changed.subscribe,
|
|
1628
1785
|
set: (next) => {
|
|
1629
1786
|
if (next === current) return;
|
|
1630
1787
|
current = next;
|
|
1631
|
-
|
|
1788
|
+
changed.emit();
|
|
1632
1789
|
}
|
|
1633
1790
|
};
|
|
1634
1791
|
}
|
|
@@ -1716,47 +1873,29 @@ function openPointerSession(origin, down, cb, opts = {}) {
|
|
|
1716
1873
|
function isTool(entry) {
|
|
1717
1874
|
return !!(entry.eligibility?.focus || entry.eligibility?.offhand);
|
|
1718
1875
|
}
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
function
|
|
1723
|
-
|
|
1724
|
-
const
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
const ruleOf = (entry) => rules?.get(entry.ownerId) ?? ALWAYS;
|
|
1738
|
-
const tie = (e, f) => viewsTie(e.views, f.views) && rulesCanHoldTogether(ruleOf(e), ruleOf(f), modes);
|
|
1739
|
-
const conflicts = /* @__PURE__ */ new Map();
|
|
1740
|
-
for (const { base, members } of groups.values()) {
|
|
1741
|
-
const bucket = members.filter((e) => members.some((f) => f !== e && tie(e, f)));
|
|
1742
|
-
if (bucket.length < 2) continue;
|
|
1743
|
-
const first = bucket[0];
|
|
1744
|
-
const key = `${base}|${bucket.map((e) => `${objectId(e.spec)}:${canonicalModifiers(e.modifiers)}`).join(";")}`;
|
|
1745
|
-
if (conflicts.has(key)) continue;
|
|
1746
|
-
conflicts.set(key, {
|
|
1747
|
-
phase: first.phase,
|
|
1748
|
-
gesture: first.gesture,
|
|
1749
|
-
arg: first.arg,
|
|
1750
|
-
target: first.target,
|
|
1751
|
-
modifiers: first.modifiers,
|
|
1752
|
-
ownerIds: bucket.map((e) => e.ownerId)
|
|
1753
|
-
});
|
|
1754
|
-
}
|
|
1755
|
-
return [...conflicts].map(([key, conflict]) => ({ key, conflict }));
|
|
1876
|
+
|
|
1877
|
+
// src/tools/routing/reflection/conflictKeys.ts
|
|
1878
|
+
var PLATFORMS = ["mac", "other"];
|
|
1879
|
+
function resolveMod(mods, platform) {
|
|
1880
|
+
if (mods.mod === void 0) return mods;
|
|
1881
|
+
const { mod, meta: _meta, ctrl: _ctrl, ...rest } = mods;
|
|
1882
|
+
return { ...rest, [platform === "mac" ? "meta" : "ctrl"]: mod };
|
|
1883
|
+
}
|
|
1884
|
+
function sameOutcome(entries) {
|
|
1885
|
+
const first = entries[0];
|
|
1886
|
+
return entries.every((e) => e.ownerId === first.ownerId && e.actionId === first.actionId && sameParams(e.params, first.params));
|
|
1887
|
+
}
|
|
1888
|
+
function sameParams(a, b) {
|
|
1889
|
+
if (a === b) return true;
|
|
1890
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
1891
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
1892
|
+
const keys = Object.keys(a);
|
|
1893
|
+
return keys.length === Object.keys(b).length && keys.every((k) => sameParams(a[k], b[k]));
|
|
1756
1894
|
}
|
|
1757
|
-
function modifierTieKeys(entry) {
|
|
1758
|
-
const
|
|
1759
|
-
const
|
|
1895
|
+
function modifierTieKeys(entry, platform) {
|
|
1896
|
+
const mods = resolveMod(entry.modifiers, platform);
|
|
1897
|
+
const optional = VALID_MOD_NAMES.filter((k) => mods[k] === "optional");
|
|
1898
|
+
const required = VALID_MOD_NAMES.filter((k) => mods[k] === "required");
|
|
1760
1899
|
const keys = [];
|
|
1761
1900
|
for (let bits = 0; bits < 1 << optional.length; bits++) {
|
|
1762
1901
|
const held = [...required, ...optional.filter((_, i) => (bits & 1 << i) !== 0)].sort();
|
|
@@ -1790,6 +1929,57 @@ function objectId(obj) {
|
|
|
1790
1929
|
}
|
|
1791
1930
|
return id;
|
|
1792
1931
|
}
|
|
1932
|
+
|
|
1933
|
+
// src/tools/routing/reflection/conflicts.ts
|
|
1934
|
+
function findConflicts(entries) {
|
|
1935
|
+
return findConflictsKeyed(entries).map((k) => k.conflict);
|
|
1936
|
+
}
|
|
1937
|
+
function findConflictsKeyed(entries, rules, modes = KIT_MODES) {
|
|
1938
|
+
const rows = buildRouteRegistry(entries);
|
|
1939
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1940
|
+
for (const entry of rows) {
|
|
1941
|
+
for (const arg of argKeysOf(entry)) {
|
|
1942
|
+
const base = `${entry.phase}|${entry.gesture}|${arg}|${targetKey(entry)}`;
|
|
1943
|
+
for (const platform of PLATFORMS) {
|
|
1944
|
+
for (const state of modifierTieKeys(entry, platform)) {
|
|
1945
|
+
const key = `${platform}|${base}|${state}`;
|
|
1946
|
+
const bucket = groups.get(key);
|
|
1947
|
+
if (bucket) {
|
|
1948
|
+
if (!bucket.members.includes(entry)) bucket.members.push(entry);
|
|
1949
|
+
} else groups.set(key, { base, platform, members: [entry] });
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
const ruleOf = (entry) => rules?.get(entry.ownerId) ?? ALWAYS;
|
|
1955
|
+
const tie = (e, f) => viewsTie(e.views, f.views) && rulesCanHoldTogether(ruleOf(e), ruleOf(f), modes);
|
|
1956
|
+
const conflicts = /* @__PURE__ */ new Map();
|
|
1957
|
+
for (const { base, platform, members } of groups.values()) {
|
|
1958
|
+
const ties = (e, i, f, j) => i !== j && !(i < j ? e : f).yields && tie(e, f);
|
|
1959
|
+
const bucket = members.filter((e, i) => members.some((f, j) => ties(e, i, f, j)));
|
|
1960
|
+
if (bucket.length < 2) continue;
|
|
1961
|
+
const first = bucket[0];
|
|
1962
|
+
const key = `${base}|${bucket.map((e) => `${objectId(e.spec)}:${canonicalModifiers(e.modifiers)}`).join(";")}`;
|
|
1963
|
+
const found = conflicts.get(key);
|
|
1964
|
+
if (found) {
|
|
1965
|
+
found.platforms.add(platform);
|
|
1966
|
+
continue;
|
|
1967
|
+
}
|
|
1968
|
+
conflicts.set(key, {
|
|
1969
|
+
conflict: {
|
|
1970
|
+
phase: first.phase,
|
|
1971
|
+
gesture: first.gesture,
|
|
1972
|
+
arg: first.arg,
|
|
1973
|
+
target: first.target,
|
|
1974
|
+
modifiers: first.modifiers,
|
|
1975
|
+
ownerIds: bucket.map((e) => e.ownerId)
|
|
1976
|
+
},
|
|
1977
|
+
bucket,
|
|
1978
|
+
platforms: /* @__PURE__ */ new Set([platform])
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1981
|
+
return [...conflicts].filter(([, c]) => c.platforms.size === PLATFORMS.length || !sameOutcome(c.bucket)).map(([key, { conflict }]) => ({ key, conflict }));
|
|
1982
|
+
}
|
|
1793
1983
|
function rulesCanHoldTogether(a, b, modes) {
|
|
1794
1984
|
if (rulesExclusive(a, b)) return false;
|
|
1795
1985
|
return modes.some((m) => ruleCanHoldIn(a, m) && ruleCanHoldIn(b, m));
|
|
@@ -1862,38 +2052,6 @@ function reportRouteConflicts(scopes, warn = (m) => console.warn(m)) {
|
|
|
1862
2052
|
}
|
|
1863
2053
|
return conflicts;
|
|
1864
2054
|
}
|
|
1865
|
-
|
|
1866
|
-
// src/interactions/keyHelpers.ts
|
|
1867
|
-
function isEditableTarget(target) {
|
|
1868
|
-
if (!target || !(target instanceof Element)) return false;
|
|
1869
|
-
const tag = target.tagName;
|
|
1870
|
-
if (tag === "INPUT" || tag === "TEXTAREA") return true;
|
|
1871
|
-
if (target.isContentEditable) return true;
|
|
1872
|
-
const ce = target.getAttribute("contenteditable");
|
|
1873
|
-
return ce === "true" || ce === "";
|
|
1874
|
-
}
|
|
1875
|
-
var ACTIVATED_BY_KEY = [
|
|
1876
|
-
"button",
|
|
1877
|
-
"a[href]",
|
|
1878
|
-
"summary",
|
|
1879
|
-
"select",
|
|
1880
|
-
'input[type="button"]',
|
|
1881
|
-
'input[type="submit"]',
|
|
1882
|
-
'input[type="reset"]',
|
|
1883
|
-
'input[type="checkbox"]',
|
|
1884
|
-
'input[type="radio"]',
|
|
1885
|
-
'[role="button"]',
|
|
1886
|
-
'[role="link"]',
|
|
1887
|
-
'[role="checkbox"]',
|
|
1888
|
-
'[role="switch"]',
|
|
1889
|
-
'[role="menuitem"]',
|
|
1890
|
-
'[role="tab"]',
|
|
1891
|
-
'[role="option"]'
|
|
1892
|
-
].join(", ");
|
|
1893
|
-
function activatesFocusedControl(e) {
|
|
1894
|
-
if (e.key !== " " && e.key !== "Enter") return false;
|
|
1895
|
-
return e.target instanceof Element && e.target.matches(ACTIVATED_BY_KEY);
|
|
1896
|
-
}
|
|
1897
2055
|
function keyMatches(eventKey, spec) {
|
|
1898
2056
|
const want = typeof spec === "string" ? [spec] : spec;
|
|
1899
2057
|
const ek = eventKey.toLowerCase();
|
|
@@ -1921,8 +2079,6 @@ function historyKey(e) {
|
|
|
1921
2079
|
return "redo";
|
|
1922
2080
|
return null;
|
|
1923
2081
|
}
|
|
1924
|
-
|
|
1925
|
-
// src/ingestion/ingestItems.ts
|
|
1926
2082
|
var INGEST_STRING_MIMES = /* @__PURE__ */ new Set([
|
|
1927
2083
|
"text/plain",
|
|
1928
2084
|
"text/html",
|
|
@@ -1967,6 +2123,16 @@ function itemsFromDataTransfer(dt) {
|
|
|
1967
2123
|
}
|
|
1968
2124
|
return Promise.all(reads).then(() => [...files, ...strings]);
|
|
1969
2125
|
}
|
|
2126
|
+
function itemTypesFromDataTransfer(dt) {
|
|
2127
|
+
const mimes = [];
|
|
2128
|
+
for (const item of Array.from(dt.items ?? [])) {
|
|
2129
|
+
const mime = item.type ? normalizeMime(item.type) : "";
|
|
2130
|
+
if (item.kind === "file") mimes.push(mime && mime !== "application/x-moz-file" ? mime : UNKNOWN_MIME);
|
|
2131
|
+
else if (INGEST_STRING_MIMES.has(mime)) mimes.push(mime);
|
|
2132
|
+
}
|
|
2133
|
+
if (mimes.length === 0 && Array.from(dt.types ?? []).includes("Files")) mimes.push(UNKNOWN_MIME);
|
|
2134
|
+
return mimes.map((mime) => ({ mime }));
|
|
2135
|
+
}
|
|
1970
2136
|
function itemsFromClipboardData(cd) {
|
|
1971
2137
|
const out = [];
|
|
1972
2138
|
for (const file of Array.from(cd.files ?? [])) {
|
|
@@ -2025,6 +2191,6 @@ function dwarn(namespace, ...args) {
|
|
|
2025
2191
|
console.warn(`[${namespace}]`, ...args);
|
|
2026
2192
|
}
|
|
2027
2193
|
|
|
2028
|
-
export { ALWAYS, COARSE_TARGET_SCALE, DEFAULT_ALLOWED_CAPABILITIES, DEFAULT_DEVICE_PROFILE, DepNode, INGEST_STRING_MIMES,
|
|
2029
|
-
//# sourceMappingURL=chunk-
|
|
2030
|
-
//# sourceMappingURL=chunk-
|
|
2194
|
+
export { ALWAYS, ActionDisabledReason, COARSE_TARGET_SCALE, DEFAULT_ALLOWED_CAPABILITIES, DEFAULT_DEVICE_PROFILE, DEV2 as DEV, DepNode, INGEST_STRING_MIMES, LONG_PRESS_HAPTIC_MS, NEVER, PREDICATE_TARGET, ScopeNode, TOOL_OFFHAND_ID, actionBindings, assembleEntries, buildDepsFromRequires, buildRouteRegistry, buildRuleCtx, buildToolOffhandBindings, clientToCanvas, clientToCanvasRect, createDepRegistry, createDispatcher, createLongPressStore, depNodeOf, describeRule, dlog, dwarn, evaluate, evaluateActive, evaluateEnabled, findConflicts, heldKeySpec, historyKey, isDebugEnabled, isDev, isTool, itemTypesFromDataTransfer, itemsFromClipboardData, itemsFromDataTransfer, itemsFromFiles, liveScope, makeToolOffhandAction, matchBest, matchSorted, matchSortedWithBarred, matchesKeyBinding, multiTouchGestureId, offhandKeyFor, openPointerSession, pointerGestureId, publishLiveDispatch, pushOwner, recordModeSwitch, reportRouteConflicts, resolveDeviceProfile, resolveParams, routesForSpec, ruleCanHoldIn, rulesExclusive, scopeBindings, targetConsultsAffordance, tuneAction, validateActionDefaultBinding, validateActionId };
|
|
2195
|
+
//# sourceMappingURL=chunk-VCEIQ64H.js.map
|
|
2196
|
+
//# sourceMappingURL=chunk-VCEIQ64H.js.map
|