@weasel-js/routing 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-D2TZLDP5.js → chunk-Q3Y3DPKH.js} +770 -370
- package/dist/chunk-Q3Y3DPKH.js.map +1 -0
- package/dist/{index-B_KvoXww.d.ts → index-Bmq4lSHY.d.ts} +601 -247
- package/dist/index.d.ts +2 -3
- package/dist/index.js +94 -8
- package/dist/index.js.map +1 -1
- package/dist/react.d.ts +220 -87
- package/dist/react.js +362 -302
- package/dist/react.js.map +1 -1
- package/package.json +5 -5
- package/dist/chunk-D2TZLDP5.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { RESERVED_ID_PREFIXES, RESERVED_ID_NAMES, parseTargetSpec, matchSpec,
|
|
2
|
-
export { GESTURE_DESCRIPTORS, RESERVED_ID_NAMES, RESERVED_ID_PREFIXES, ROUTE_FIELD_DEFINITIONS, ROUTE_TERMS, canonicalModifiers, collapseShiftPairs, describeRoute, describeRouteParts, formatPhaseAtom, formatRoute, getGestureDescriptor, isKnownGestureName, matchKey, matchModifiers, matchPhase, matchSpec, matchTarget, parseRoute } from '@weasel-js/gestures';
|
|
3
|
-
import { NORMAL,
|
|
1
|
+
import { RESERVED_ID_PREFIXES, RESERVED_ID_NAMES, parseTargetSpec, matchSpec, routeGestureForSpecKind, getGestureDescriptor, formatRoute, canonicalModifiers, specificity } from '@weasel-js/gestures';
|
|
2
|
+
export { GESTURE_DESCRIPTORS, RESERVED_ID_NAMES, RESERVED_ID_PREFIXES, ROUTE_FIELD_DEFINITIONS, ROUTE_TERMS, canonicalModifiers, collapseShiftPairs, describeRoute, describeRouteParts, formatPhaseAtom, formatRoute, getGestureDescriptor, isKnownGestureName, matchKey, matchModifiers, matchPhase, matchSpec, matchTarget, parseRoute, routeGestureForSpecKind, specKindForRouteGesture, specificity } from '@weasel-js/gestures';
|
|
3
|
+
import { activeModeOf, NORMAL, DEFAULT_MODES } from '@weasel-js/modes';
|
|
4
4
|
import { resolveCursor } from '@weasel-js/cursor';
|
|
5
5
|
|
|
6
6
|
// src/interactions/actions/binding.ts
|
|
@@ -44,6 +44,150 @@ function buildDepsFromRequires(action, depRegistry) {
|
|
|
44
44
|
return deps;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
// src/interactions/actions/scopeNode.ts
|
|
48
|
+
var ScopeNode = class {
|
|
49
|
+
/** `descends`: whether reads made here answer from the active child down —
|
|
50
|
+
* true for a root or a yoke, which chrome reads through; false for a scope,
|
|
51
|
+
* which answers for itself even with a scope nested inside it. */
|
|
52
|
+
constructor(parent, descends) {
|
|
53
|
+
this.parent = parent;
|
|
54
|
+
this.descends = descends;
|
|
55
|
+
this.tree = parent ? parent.tree : { version: 0, listeners: /* @__PURE__ */ new Set() };
|
|
56
|
+
}
|
|
57
|
+
parent;
|
|
58
|
+
descends;
|
|
59
|
+
tree;
|
|
60
|
+
children = [];
|
|
61
|
+
activeChild = null;
|
|
62
|
+
/** Bump the tree's version and tell every listener in it. */
|
|
63
|
+
changed() {
|
|
64
|
+
this.tree.version++;
|
|
65
|
+
for (const listener of this.tree.listeners) {
|
|
66
|
+
try {
|
|
67
|
+
listener();
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.error("weasel input scope: subscriber threw", err);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** Join the parent's children, newest last; returns the leave. */
|
|
74
|
+
mount() {
|
|
75
|
+
const parent = this.parent;
|
|
76
|
+
if (!parent) return () => {
|
|
77
|
+
};
|
|
78
|
+
parent.children.push(this);
|
|
79
|
+
this.changed();
|
|
80
|
+
let left = false;
|
|
81
|
+
return () => {
|
|
82
|
+
if (left) return;
|
|
83
|
+
left = true;
|
|
84
|
+
const i = parent.children.lastIndexOf(this);
|
|
85
|
+
if (i !== -1) parent.children.splice(i, 1);
|
|
86
|
+
if (parent.activeChild === this) parent.activeChild = null;
|
|
87
|
+
this.changed();
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** Make this node the one its ancestors resolve through. */
|
|
91
|
+
activate() {
|
|
92
|
+
let moved = false;
|
|
93
|
+
for (let n = this; n.parent; n = n.parent) {
|
|
94
|
+
if (n.parent.activeChild !== n) {
|
|
95
|
+
n.parent.activeChild = n;
|
|
96
|
+
moved = true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (moved) this.changed();
|
|
100
|
+
}
|
|
101
|
+
/** The node a lookup made here starts from: down the active children, or the
|
|
102
|
+
* newest where nothing has been activated yet. A yoke with no members yet
|
|
103
|
+
* is passed over — there is no canvas behind it to answer. */
|
|
104
|
+
leaf() {
|
|
105
|
+
let n = this;
|
|
106
|
+
for (; ; ) {
|
|
107
|
+
const next = n.activeChild ?? n.newestMember();
|
|
108
|
+
if (!next) return n;
|
|
109
|
+
n = next;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
newestMember() {
|
|
113
|
+
for (let i = this.children.length - 1; i >= 0; i--) {
|
|
114
|
+
const c = this.children[i];
|
|
115
|
+
if (!(c.descends && c.children.length === 0)) return c;
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
/** Where a read made through this node starts. */
|
|
120
|
+
start() {
|
|
121
|
+
return this.descends ? this.leaf() : this;
|
|
122
|
+
}
|
|
123
|
+
/** Whether this node is on the path a read from the root takes. */
|
|
124
|
+
isActive() {
|
|
125
|
+
let root = this;
|
|
126
|
+
while (root.parent) root = root.parent;
|
|
127
|
+
for (const n of root.leaf().chain()) if (n === this) return true;
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
/** This node, then each ancestor up to the root. */
|
|
131
|
+
*chain() {
|
|
132
|
+
for (let n = this; n; n = n.parent) yield n;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
function pushOwner(stack, value) {
|
|
136
|
+
if (value === null) return () => {
|
|
137
|
+
};
|
|
138
|
+
stack.push(value);
|
|
139
|
+
let released = false;
|
|
140
|
+
return () => {
|
|
141
|
+
if (released) return;
|
|
142
|
+
released = true;
|
|
143
|
+
const i = stack.lastIndexOf(value);
|
|
144
|
+
if (i !== -1) stack.splice(i, 1);
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/interactions/actions/depNode.ts
|
|
149
|
+
var DepNode = class extends ScopeNode {
|
|
150
|
+
sources = /* @__PURE__ */ new Map();
|
|
151
|
+
registry = {
|
|
152
|
+
register: (name, source) => {
|
|
153
|
+
const key = name;
|
|
154
|
+
const entry = source;
|
|
155
|
+
const stack = this.sources.get(key);
|
|
156
|
+
if (stack) stack.push(entry);
|
|
157
|
+
else this.sources.set(key, [entry]);
|
|
158
|
+
let released = false;
|
|
159
|
+
return () => {
|
|
160
|
+
if (released) return;
|
|
161
|
+
released = true;
|
|
162
|
+
const cur = this.sources.get(key);
|
|
163
|
+
if (!cur) return;
|
|
164
|
+
const i = cur.lastIndexOf(entry);
|
|
165
|
+
if (i === -1) return;
|
|
166
|
+
cur.splice(i, 1);
|
|
167
|
+
if (cur.length === 0) this.sources.delete(key);
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
get: (name) => {
|
|
171
|
+
for (const n of this.start().chain()) {
|
|
172
|
+
const source = n.sources.get(name)?.at(-1);
|
|
173
|
+
if (source) return source();
|
|
174
|
+
}
|
|
175
|
+
return void 0;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
constructor(parent, descends) {
|
|
179
|
+
super(parent, descends);
|
|
180
|
+
NODES.set(this.registry, this);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
var NODES = /* @__PURE__ */ new WeakMap();
|
|
184
|
+
function depNodeOf(registry) {
|
|
185
|
+
return registry ? NODES.get(registry) ?? null : null;
|
|
186
|
+
}
|
|
187
|
+
function createDepRegistry() {
|
|
188
|
+
return new DepNode(null, true).registry;
|
|
189
|
+
}
|
|
190
|
+
|
|
47
191
|
// src/interactions/actions/registry.ts
|
|
48
192
|
function validateActionId(id) {
|
|
49
193
|
if (id.length === 0) {
|
|
@@ -99,6 +243,8 @@ function offhandKeyFor(trigger) {
|
|
|
99
243
|
return "Meta";
|
|
100
244
|
case "shift":
|
|
101
245
|
return "Shift";
|
|
246
|
+
default:
|
|
247
|
+
return trigger;
|
|
102
248
|
}
|
|
103
249
|
}
|
|
104
250
|
function buildToolOffhandBindings(specs) {
|
|
@@ -148,7 +294,7 @@ function scopeBindings(entries, state) {
|
|
|
148
294
|
const scope = liveScope(entry.id, entry.eligibility ?? {}, state);
|
|
149
295
|
if (scope === null) continue;
|
|
150
296
|
for (const binding of entry.bindings ?? []) {
|
|
151
|
-
out.push({ binding, scope,
|
|
297
|
+
out.push({ binding, scope, ownerId: entry.id });
|
|
152
298
|
}
|
|
153
299
|
}
|
|
154
300
|
return out;
|
|
@@ -161,34 +307,6 @@ function isDev() {
|
|
|
161
307
|
|
|
162
308
|
// src/interactions/dispatcher/matcher.ts
|
|
163
309
|
var SCOPE_PRIORITY = ["hotkey", "active", "ambient"];
|
|
164
|
-
function modsCount(mods) {
|
|
165
|
-
if (!mods) return 0;
|
|
166
|
-
let n = 0;
|
|
167
|
-
if (mods.alt === true) n++;
|
|
168
|
-
if (mods.ctrl === true) n++;
|
|
169
|
-
if (mods.meta === true) n++;
|
|
170
|
-
if (mods.mod === true) n++;
|
|
171
|
-
if (mods.shift === true) n++;
|
|
172
|
-
return n;
|
|
173
|
-
}
|
|
174
|
-
function targetRank(target) {
|
|
175
|
-
if (target === void 0) return 0;
|
|
176
|
-
const form = parseTargetSpec(target);
|
|
177
|
-
if (form === null) return 1;
|
|
178
|
-
switch (form.form) {
|
|
179
|
-
case "kind":
|
|
180
|
-
return form.requireSelected ? 3 : 2;
|
|
181
|
-
case "affordance":
|
|
182
|
-
return 2;
|
|
183
|
-
case "body":
|
|
184
|
-
return 1;
|
|
185
|
-
case "predicate":
|
|
186
|
-
return 1;
|
|
187
|
-
default: {
|
|
188
|
-
return 1;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
310
|
function targetConsultsAffordance(specTarget) {
|
|
193
311
|
if (specTarget === void 0) return false;
|
|
194
312
|
const form = parseTargetSpec(specTarget);
|
|
@@ -249,25 +367,6 @@ function reportDeadClaim(owner, warn) {
|
|
|
249
367
|
`[weasel] exclusive claim by "${key}" matched no binding: no \`affordance:\` or \`kindOf\` target resolved against it, so the press was dropped.`
|
|
250
368
|
);
|
|
251
369
|
}
|
|
252
|
-
function phaseRank(spec) {
|
|
253
|
-
if (spec === void 0) return 0;
|
|
254
|
-
const atoms = typeof spec === "string" ? [{ channel: "&", phase: spec }] : spec;
|
|
255
|
-
if (atoms.length === 0) return 0;
|
|
256
|
-
let min = 2;
|
|
257
|
-
for (const a of atoms) {
|
|
258
|
-
const rank = (a.channel === "*" ? 0 : 1) + (a.phase === "*" ? 0 : 1);
|
|
259
|
-
if (rank < min) min = rank;
|
|
260
|
-
}
|
|
261
|
-
return min;
|
|
262
|
-
}
|
|
263
|
-
function specificity(spec) {
|
|
264
|
-
const t = targetRank(specTargetOf(spec));
|
|
265
|
-
const mods = "mods" in spec ? spec.mods : void 0;
|
|
266
|
-
const m = modsCount(mods);
|
|
267
|
-
const p = phaseRank("phase" in spec ? spec.phase : void 0);
|
|
268
|
-
const typed = (spec.kind === "drop" || spec.kind === "paste") && spec.types !== void 0 && spec.types.length > 0 ? 2 : 1;
|
|
269
|
-
return [t, m, p, typed];
|
|
270
|
-
}
|
|
271
370
|
function compareSpecificity(a, b) {
|
|
272
371
|
const sa = specificity(a);
|
|
273
372
|
const sb = specificity(b);
|
|
@@ -281,28 +380,122 @@ function matchBest(e, bindings, isMac, engagedChannels) {
|
|
|
281
380
|
return sorted.length > 0 ? sorted[0] : null;
|
|
282
381
|
}
|
|
283
382
|
function matchSorted(e, bindings, isMac, engagedChannels, warn = (m) => console.warn(m)) {
|
|
284
|
-
|
|
383
|
+
return matchSortedWithBarred(e, bindings, isMac, engagedChannels, { warn }).matches;
|
|
384
|
+
}
|
|
385
|
+
function matchSortedWithBarred(e, bindings, isMac, engagedChannels, opts = {}) {
|
|
386
|
+
const warn = opts.warn ?? ((m) => console.warn(m));
|
|
285
387
|
const engaged = engagedChannels ?? EMPTY_ENGAGED;
|
|
286
388
|
const exclusive = isExclusiveClaim(e);
|
|
287
|
-
const
|
|
389
|
+
const admits = (sb) => !exclusive || targetConsultsAffordance(specTargetOf(sb.binding.spec));
|
|
390
|
+
const out = [];
|
|
391
|
+
const barred = [];
|
|
288
392
|
for (const scope of SCOPE_PRIORITY) {
|
|
289
393
|
const scopeMatches = [];
|
|
290
|
-
|
|
394
|
+
const scopeBarred = [];
|
|
395
|
+
for (const sb of bindings) {
|
|
291
396
|
if (sb.scope !== scope) continue;
|
|
292
|
-
const
|
|
397
|
+
const admitted = admits(sb);
|
|
398
|
+
if (!admitted && !opts.collectBarred) continue;
|
|
399
|
+
const phaseCtx = { selfChannel: sb.ownerId, engagedChannels: engaged };
|
|
293
400
|
if (matchSpec(e, sb.binding.spec, isMac, phaseCtx)) {
|
|
294
|
-
scopeMatches.push({ binding: sb.binding, scope,
|
|
401
|
+
(admitted ? scopeMatches : scopeBarred).push({ binding: sb.binding, scope, ownerId: sb.ownerId });
|
|
295
402
|
}
|
|
296
403
|
}
|
|
297
404
|
scopeMatches.sort((a, b) => compareSpecificity(a.binding.spec, b.binding.spec));
|
|
405
|
+
scopeBarred.sort((a, b) => compareSpecificity(a.binding.spec, b.binding.spec));
|
|
298
406
|
out.push(...scopeMatches);
|
|
407
|
+
barred.push(...scopeBarred);
|
|
299
408
|
}
|
|
300
409
|
if (exclusive && out.length === 0 && bindings.length > 0) {
|
|
301
410
|
reportDeadClaim(claimOf(e)?.owner, warn);
|
|
302
411
|
}
|
|
303
|
-
|
|
412
|
+
const owner = exclusive ? claimOf(e)?.owner : void 0;
|
|
413
|
+
return { matches: out, barred, ...owner !== void 0 ? { claimOwner: owner } : {} };
|
|
304
414
|
}
|
|
305
415
|
var EMPTY_ENGAGED = /* @__PURE__ */ new Set();
|
|
416
|
+
var PREDICATE_TARGET = "predicate";
|
|
417
|
+
function buildRouteRegistry(entries) {
|
|
418
|
+
const out = [];
|
|
419
|
+
for (const owner of entries) {
|
|
420
|
+
for (const binding of owner.bindings ?? []) {
|
|
421
|
+
const row = entryFor(owner.id, binding.spec, binding.actionId);
|
|
422
|
+
if (!row) continue;
|
|
423
|
+
const views = binding.opts?.views;
|
|
424
|
+
out.push(views === void 0 ? row : { ...row, views });
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return out;
|
|
428
|
+
}
|
|
429
|
+
function routesForSpec(spec) {
|
|
430
|
+
const gesture = routeGestureForSpecKind(spec.kind);
|
|
431
|
+
if (!gesture) return [];
|
|
432
|
+
const descriptor = getGestureDescriptor(gesture);
|
|
433
|
+
const modifiers = parseModSpec("mods" in spec ? spec.mods : void 0);
|
|
434
|
+
const phases = phaseAtomsForSpec("phase" in spec ? spec.phase : void 0);
|
|
435
|
+
const target = descriptor.hasTarget ? routeTargetForSpec("target" in spec ? spec.target : void 0) ?? "*" : void 0;
|
|
436
|
+
const args = descriptor.arg ? routeArgsForSpec(spec, descriptor.arg.default) : [void 0];
|
|
437
|
+
return args.map((arg) => formatRoute({ phases, gesture, arg, target, modifiers }));
|
|
438
|
+
}
|
|
439
|
+
function entryFor(ownerId, spec, actionId) {
|
|
440
|
+
const gesture = routeGestureForSpecKind(spec.kind);
|
|
441
|
+
if (!gesture) return null;
|
|
442
|
+
const descriptor = getGestureDescriptor(gesture);
|
|
443
|
+
return {
|
|
444
|
+
ownerId,
|
|
445
|
+
actionId,
|
|
446
|
+
gesture,
|
|
447
|
+
phase: phaseOf("phase" in spec ? spec.phase : void 0),
|
|
448
|
+
modifiers: parseModSpec("mods" in spec ? spec.mods : void 0),
|
|
449
|
+
arg: descriptor.arg ? argOf(spec, descriptor.arg.default) : void 0,
|
|
450
|
+
...descriptor.arg ? argAlternativesOf(spec, descriptor.arg.default) : {},
|
|
451
|
+
target: descriptor.hasTarget ? routeTargetForSpec("target" in spec ? spec.target : void 0) : void 0,
|
|
452
|
+
spec
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
function phaseOf(phase) {
|
|
456
|
+
if (phase === void 0 || phase === "*") return "any";
|
|
457
|
+
if (phase === "initial" || phase === "engaged") return phase;
|
|
458
|
+
const distinct = new Set(phase.map((atom) => atom.phase));
|
|
459
|
+
if (distinct.size !== 1) return "any";
|
|
460
|
+
const only = [...distinct][0];
|
|
461
|
+
return only === "initial" || only === "engaged" ? only : "any";
|
|
462
|
+
}
|
|
463
|
+
function parseModSpec(mods) {
|
|
464
|
+
if (!mods) return {};
|
|
465
|
+
const out = {};
|
|
466
|
+
for (const [name, req] of Object.entries(mods)) {
|
|
467
|
+
if (req === true) out[name] = "required";
|
|
468
|
+
else if (req === "optional") out[name] = "optional";
|
|
469
|
+
}
|
|
470
|
+
return out;
|
|
471
|
+
}
|
|
472
|
+
function phaseAtomsForSpec(phase) {
|
|
473
|
+
if (phase === void 0) return [{ channel: "*", phase: "*" }];
|
|
474
|
+
if (phase === "initial" || phase === "engaged" || phase === "*") {
|
|
475
|
+
return [{ channel: "&", phase }];
|
|
476
|
+
}
|
|
477
|
+
return phase;
|
|
478
|
+
}
|
|
479
|
+
function routeTargetForSpec(target) {
|
|
480
|
+
if (target === void 0) return void 0;
|
|
481
|
+
return typeof target === "string" ? target : PREDICATE_TARGET;
|
|
482
|
+
}
|
|
483
|
+
function routeArgsForSpec(spec, fallback) {
|
|
484
|
+
if ("key" in spec) return Array.isArray(spec.key) ? spec.key : [spec.key];
|
|
485
|
+
if ("fingers" in spec) return [String(spec.fingers)];
|
|
486
|
+
if ("direction" in spec) return [spec.direction ?? fallback];
|
|
487
|
+
if ("types" in spec) return [spec.types?.length ? spec.types.join("|") : fallback];
|
|
488
|
+
return [fallback];
|
|
489
|
+
}
|
|
490
|
+
function argOf(spec, fallback) {
|
|
491
|
+
const args = routeArgsForSpec(spec, fallback);
|
|
492
|
+
return args.length > 1 ? args.join("|") : args[0];
|
|
493
|
+
}
|
|
494
|
+
function argAlternativesOf(spec, fallback) {
|
|
495
|
+
const types = "types" in spec ? spec.types : void 0;
|
|
496
|
+
const alts = types?.length ? types : routeArgsForSpec(spec, fallback).filter((a) => a !== void 0);
|
|
497
|
+
return alts.length > 1 ? { argAlternatives: alts } : {};
|
|
498
|
+
}
|
|
306
499
|
|
|
307
500
|
// src/eligibility/deviceProfile.ts
|
|
308
501
|
var COARSE_TARGET_SCALE = 1.75;
|
|
@@ -418,10 +611,118 @@ function evaluate(rule, ctx) {
|
|
|
418
611
|
if (isWhenRule(rule)) return rule.when(ctx);
|
|
419
612
|
return evaluateSelector(rule, ctx);
|
|
420
613
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
614
|
+
|
|
615
|
+
// src/eligibility/exclusive.ts
|
|
616
|
+
function rulesExclusive(a, b) {
|
|
617
|
+
if (isAllRule(a)) return a.all.some((r) => rulesExclusive(r, b));
|
|
618
|
+
if (isAllRule(b)) return b.all.some((r) => rulesExclusive(a, r));
|
|
619
|
+
if (isAnyRule(a)) return a.any.every((r) => rulesExclusive(r, b));
|
|
620
|
+
if (isAnyRule(b)) return b.any.every((r) => rulesExclusive(a, r));
|
|
621
|
+
if (isNotRule(a)) return rulesEqual(a.not, b);
|
|
622
|
+
if (isNotRule(b)) return rulesEqual(b.not, a);
|
|
623
|
+
if (isWhenRule(a) || isWhenRule(b)) return false;
|
|
624
|
+
return selectorsExclusive(a, b);
|
|
625
|
+
}
|
|
626
|
+
function ruleCanHoldIn(rule, mode) {
|
|
627
|
+
return decideIn(rule, mode) !== false;
|
|
628
|
+
}
|
|
629
|
+
function decideIn(rule, mode) {
|
|
630
|
+
if (isAllRule(rule)) return all3(rule.all.map((r) => decideIn(r, mode)));
|
|
631
|
+
if (isAnyRule(rule)) {
|
|
632
|
+
const parts = rule.any.map((r) => decideIn(r, mode));
|
|
633
|
+
if (parts.includes(true)) return true;
|
|
634
|
+
return parts.every((p) => p === false) ? false : void 0;
|
|
635
|
+
}
|
|
636
|
+
if (isNotRule(rule)) {
|
|
637
|
+
const inner = decideIn(rule.not, mode);
|
|
638
|
+
return inner === void 0 ? void 0 : !inner;
|
|
639
|
+
}
|
|
640
|
+
if (isWhenRule(rule)) return void 0;
|
|
641
|
+
const ctx = { mode: mode.id, allowedCapabilities: mode.allowedCapabilities };
|
|
642
|
+
const settled = [
|
|
643
|
+
rule.mode === void 0 ? true : checkMode(rule.mode, ctx),
|
|
644
|
+
rule.capability === void 0 ? true : checkCapability(rule.capability, ctx)
|
|
645
|
+
];
|
|
646
|
+
const { mode: _m, capability: _c, ...rest } = rule;
|
|
647
|
+
return all3([...settled, Object.keys(rest).length > 0 ? void 0 : true]);
|
|
648
|
+
}
|
|
649
|
+
function all3(parts) {
|
|
650
|
+
if (parts.includes(false)) return false;
|
|
651
|
+
return parts.every((p) => p === true) ? true : void 0;
|
|
652
|
+
}
|
|
653
|
+
var BOOLEAN_KEYS = [
|
|
654
|
+
"gesturing",
|
|
655
|
+
"focused",
|
|
656
|
+
"hovering",
|
|
657
|
+
"hoveringSelected",
|
|
658
|
+
"editingAnchors",
|
|
659
|
+
"resizable",
|
|
660
|
+
"coarsePointer",
|
|
661
|
+
"canHover"
|
|
662
|
+
];
|
|
663
|
+
function selectorsExclusive(a, b) {
|
|
664
|
+
for (const key of BOOLEAN_KEYS) {
|
|
665
|
+
if (a[key] !== void 0 && b[key] !== void 0 && a[key] !== b[key]) return true;
|
|
666
|
+
}
|
|
667
|
+
if (a.actionIs !== void 0 && b.actionIs !== void 0 && a.actionIs !== b.actionIs) return true;
|
|
668
|
+
if (a.actionIs !== void 0 && b.gesturing === false) return true;
|
|
669
|
+
if (b.actionIs !== void 0 && a.gesturing === false) return true;
|
|
670
|
+
if (a.mode !== void 0 && b.mode !== void 0 && modesExclusive(a.mode, b.mode)) return true;
|
|
671
|
+
if (a.selection && b.selection && selectionsExclusive(a.selection, b.selection)) return true;
|
|
672
|
+
if (a.capability !== void 0 && b.capability !== void 0 && capabilitiesExclusive(a.capability, b.capability)) return true;
|
|
673
|
+
return false;
|
|
674
|
+
}
|
|
675
|
+
function modeSet(m) {
|
|
676
|
+
if (typeof m === "string") return { in: /* @__PURE__ */ new Set([m]) };
|
|
677
|
+
if ("not" in m) return { notIn: /* @__PURE__ */ new Set([m.not]) };
|
|
678
|
+
return { in: new Set(m.in) };
|
|
679
|
+
}
|
|
680
|
+
function modesExclusive(a, b) {
|
|
681
|
+
const x = modeSet(a);
|
|
682
|
+
const y = modeSet(b);
|
|
683
|
+
if ("in" in x && "in" in y) return ![...x.in].some((m) => y.in.has(m));
|
|
684
|
+
if ("in" in x && "notIn" in y) return [...x.in].every((m) => y.notIn.has(m));
|
|
685
|
+
if ("notIn" in x && "in" in y) return [...y.in].every((m) => x.notIn.has(m));
|
|
686
|
+
return false;
|
|
687
|
+
}
|
|
688
|
+
function selectionRange(s) {
|
|
689
|
+
let lo = 0;
|
|
690
|
+
let hi = Infinity;
|
|
691
|
+
if (s.empty === true) hi = 0;
|
|
692
|
+
if (s.empty === false) lo = 1;
|
|
693
|
+
if (s.is !== void 0) {
|
|
694
|
+
lo = Math.max(lo, s.is);
|
|
695
|
+
hi = Math.min(hi, s.is);
|
|
696
|
+
}
|
|
697
|
+
if (s.atLeast !== void 0) lo = Math.max(lo, s.atLeast);
|
|
698
|
+
return [lo, hi];
|
|
699
|
+
}
|
|
700
|
+
function selectionsExclusive(a, b) {
|
|
701
|
+
const [alo, ahi] = selectionRange(a);
|
|
702
|
+
const [blo, bhi] = selectionRange(b);
|
|
703
|
+
return Math.max(alo, blo) > Math.min(ahi, bhi);
|
|
704
|
+
}
|
|
705
|
+
function capabilityTerms(c) {
|
|
706
|
+
if (typeof c === "string") return { requires: [c] };
|
|
707
|
+
if (Array.isArray(c)) return { requires: c };
|
|
708
|
+
if ("not" in c) return { requires: [], forbids: c.not };
|
|
709
|
+
return { requires: [] };
|
|
710
|
+
}
|
|
711
|
+
function capabilitiesExclusive(a, b) {
|
|
712
|
+
const x = capabilityTerms(a);
|
|
713
|
+
const y = capabilityTerms(b);
|
|
714
|
+
return x.forbids !== void 0 && y.requires.includes(x.forbids) || y.forbids !== void 0 && x.requires.includes(y.forbids);
|
|
715
|
+
}
|
|
716
|
+
function rulesEqual(a, b) {
|
|
717
|
+
if (a === b) return true;
|
|
718
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
719
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
720
|
+
const ka = Object.keys(a);
|
|
721
|
+
const kb = Object.keys(b);
|
|
722
|
+
if (ka.length !== kb.length) return false;
|
|
723
|
+
return ka.every((k) => rulesEqual(a[k], b[k]));
|
|
724
|
+
}
|
|
725
|
+
var DEFAULT_ALLOWED_CAPABILITIES = activeModeOf(NORMAL).allowedCapabilities;
|
|
425
726
|
function buildRuleCtx(args) {
|
|
426
727
|
return {
|
|
427
728
|
focused: args.focused,
|
|
@@ -455,6 +756,10 @@ function recordTrace(entry) {
|
|
|
455
756
|
traceLog.push(entry);
|
|
456
757
|
if (traceLog.length > TRACE_LIMIT) traceLog.shift();
|
|
457
758
|
}
|
|
759
|
+
function publishLiveDispatch(explain) {
|
|
760
|
+
if (!DEV2 || typeof window === "undefined") return;
|
|
761
|
+
window.__weaselDispatchLive__ = explain;
|
|
762
|
+
}
|
|
458
763
|
function recordModeSwitch(mode, from, to, detail) {
|
|
459
764
|
recordTrace({
|
|
460
765
|
kind: "mode",
|
|
@@ -465,6 +770,25 @@ function recordModeSwitch(mode, from, to, detail) {
|
|
|
465
770
|
...detail !== void 0 ? { detail } : {}
|
|
466
771
|
});
|
|
467
772
|
}
|
|
773
|
+
function liveInView(binding, view) {
|
|
774
|
+
const views = binding.opts?.views;
|
|
775
|
+
return views === void 0 || views.includes(view);
|
|
776
|
+
}
|
|
777
|
+
function namesView(binding, view) {
|
|
778
|
+
return binding.opts?.views?.includes(view) ?? false;
|
|
779
|
+
}
|
|
780
|
+
function preferViewScoped(matches, view) {
|
|
781
|
+
const named = [];
|
|
782
|
+
const rest = [];
|
|
783
|
+
for (const m of matches) {
|
|
784
|
+
(namesView(m.binding, view) ? named : rest).push(m);
|
|
785
|
+
}
|
|
786
|
+
return named.length === 0 ? [...matches] : [...named, ...rest];
|
|
787
|
+
}
|
|
788
|
+
function ruleCtxOf(ctx) {
|
|
789
|
+
const r = ctx.getRuleCtx?.();
|
|
790
|
+
return r ? { ...r, viewId: ctx.viewId ?? null } : void 0;
|
|
791
|
+
}
|
|
468
792
|
function eligibleToRule(eligible) {
|
|
469
793
|
return typeof eligible === "function" ? eligible.rule : eligible;
|
|
470
794
|
}
|
|
@@ -482,6 +806,40 @@ function filterEligible(matches, actionLookup, ruleCtx) {
|
|
|
482
806
|
return isEligible(action, ruleCtx);
|
|
483
807
|
});
|
|
484
808
|
}
|
|
809
|
+
function preferContextual(matches, actionLookup, holds, view) {
|
|
810
|
+
const out = [];
|
|
811
|
+
let run = [];
|
|
812
|
+
const flush = () => {
|
|
813
|
+
const gated = run.filter((m) => {
|
|
814
|
+
const action = actionLookup(m.binding.actionId);
|
|
815
|
+
return action?.eligible !== void 0 && holds(action);
|
|
816
|
+
});
|
|
817
|
+
out.push(...gated, ...run.filter((m) => !gated.includes(m)));
|
|
818
|
+
run = [];
|
|
819
|
+
};
|
|
820
|
+
for (const m of matches) {
|
|
821
|
+
const head = run[0];
|
|
822
|
+
if (head && (head.scope !== m.scope || namesView(head.binding, view) !== namesView(m.binding, view) || !sameSpecificity(head.binding.spec, m.binding.spec))) flush();
|
|
823
|
+
run.push(m);
|
|
824
|
+
}
|
|
825
|
+
flush();
|
|
826
|
+
return out;
|
|
827
|
+
}
|
|
828
|
+
var SPECIFICITY_PARTS = ["target", "mods", "phase", "exact"];
|
|
829
|
+
function placedBy(prev, cur, view, contextual) {
|
|
830
|
+
if (namesView(prev.binding, view) !== namesView(cur.binding, view)) return { step: "view" };
|
|
831
|
+
if (prev.scope !== cur.scope) return { step: "tier" };
|
|
832
|
+
const sp = specificity(prev.binding.spec);
|
|
833
|
+
const sc = specificity(cur.binding.spec);
|
|
834
|
+
const i = sp.findIndex((v, k) => v !== sc[k]);
|
|
835
|
+
if (i >= 0) return { step: "specificity", part: SPECIFICITY_PARTS[i] };
|
|
836
|
+
return contextual ? { step: "context" } : { step: "order" };
|
|
837
|
+
}
|
|
838
|
+
function sameSpecificity(a, b) {
|
|
839
|
+
const sa = specificity(a);
|
|
840
|
+
const sb = specificity(b);
|
|
841
|
+
return sa.every((v, i) => v === sb[i]);
|
|
842
|
+
}
|
|
485
843
|
var EMPTY_ENGAGED2 = /* @__PURE__ */ new Set();
|
|
486
844
|
function modifiersOf(e) {
|
|
487
845
|
return { alt: e.altKey, ctrl: e.ctrlKey, meta: e.metaKey, shift: e.shiftKey };
|
|
@@ -516,6 +874,7 @@ function createDispatcher(opts) {
|
|
|
516
874
|
function isEmptyOngoingHandle(h) {
|
|
517
875
|
return !h.onMove && !h.onEnd && !h.overlay && !h.previewIds && !h.previewPose;
|
|
518
876
|
}
|
|
877
|
+
let routedView;
|
|
519
878
|
function buildUiInvocationCtx(deps, params) {
|
|
520
879
|
return {
|
|
521
880
|
world: { x: 0, y: 0 },
|
|
@@ -534,7 +893,8 @@ function createDispatcher(opts) {
|
|
|
534
893
|
const base = {
|
|
535
894
|
world: { x: 0, y: 0 },
|
|
536
895
|
modifiers,
|
|
537
|
-
deps
|
|
896
|
+
deps,
|
|
897
|
+
...routedView !== void 0 ? { viewId: routedView } : {}
|
|
538
898
|
};
|
|
539
899
|
const clientPoint = (ev) => {
|
|
540
900
|
const cx = ev.clientX;
|
|
@@ -610,23 +970,25 @@ function createDispatcher(opts) {
|
|
|
610
970
|
const ordered = [];
|
|
611
971
|
for (let i = ctx.hotkeyStack.length - 1; i >= 0; i--) {
|
|
612
972
|
const id = ctx.hotkeyStack[i];
|
|
613
|
-
const
|
|
614
|
-
if (
|
|
973
|
+
const entry = ctx.entriesById.get(id);
|
|
974
|
+
if (entry && !ordered.includes(entry)) ordered.push(entry);
|
|
615
975
|
}
|
|
616
|
-
for (const
|
|
617
|
-
if (!ordered.includes(
|
|
976
|
+
for (const entry of ctx.entriesById.values()) {
|
|
977
|
+
if (!ordered.includes(entry)) ordered.push(entry);
|
|
618
978
|
}
|
|
619
979
|
const capsCtx = ctx.getRuleCtx?.();
|
|
620
980
|
const allows = capsCtx ? (tags) => tags.every((tag) => capsCtx.allowedCapabilities.has(tag)) : void 0;
|
|
981
|
+
const view = ctx.viewId ?? null;
|
|
621
982
|
const result = scopeBindings(ordered, {
|
|
622
983
|
focusedId: ctx.activeToolId,
|
|
623
984
|
engagedIds: new Set(ctx.hotkeyStack),
|
|
624
985
|
...allows ? { allows } : {}
|
|
625
|
-
});
|
|
986
|
+
}).filter((sb) => liveInView(sb.binding, view));
|
|
626
987
|
for (const action of ctx.actions.list()) {
|
|
627
988
|
const targetScope = action.scope === "hotkey" ? "hotkey" : "ambient";
|
|
628
989
|
for (const binding of actionBindings(action)) {
|
|
629
|
-
|
|
990
|
+
if (!liveInView(binding, view)) continue;
|
|
991
|
+
result.push({ binding, scope: targetScope, ownerId: null });
|
|
630
992
|
}
|
|
631
993
|
}
|
|
632
994
|
return result;
|
|
@@ -647,6 +1009,7 @@ function createDispatcher(opts) {
|
|
|
647
1009
|
return map;
|
|
648
1010
|
}
|
|
649
1011
|
function handleInput(event, ctx) {
|
|
1012
|
+
routedView = ctx.viewId;
|
|
650
1013
|
if (event.kind === "key-held" && event.phase === "up") {
|
|
651
1014
|
const gestureId = gestureIdFor(event);
|
|
652
1015
|
const handle = inFlightHandles.get(gestureId);
|
|
@@ -732,203 +1095,302 @@ function createDispatcher(opts) {
|
|
|
732
1095
|
pinchStartSpreads.delete(id);
|
|
733
1096
|
}
|
|
734
1097
|
}
|
|
735
|
-
const
|
|
736
|
-
const
|
|
737
|
-
const
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
1098
|
+
const r = rank(event, ctx, DEV2);
|
|
1099
|
+
const walked = walk(r, event, ctx, (match, action, deps) => invoke(event, match, action, deps), false);
|
|
1100
|
+
const outcome = walked.stop === "fired" ? "handled" : "unhandled";
|
|
1101
|
+
if (DEV2) recordTrace(buildRecord(r, walked, event, ctx, false, outcome));
|
|
1102
|
+
return outcome;
|
|
1103
|
+
}
|
|
1104
|
+
function invoke(event, match, action, deps) {
|
|
1105
|
+
if (action.invoker?.timing === "immediate") {
|
|
1106
|
+
try {
|
|
1107
|
+
const resolved = resolveParams(match.binding.opts?.params);
|
|
1108
|
+
let params;
|
|
1109
|
+
if (event.kind === "wheel") {
|
|
1110
|
+
params = {
|
|
1111
|
+
deltaX: event.deltaX,
|
|
1112
|
+
deltaY: event.deltaY,
|
|
1113
|
+
clientX: event.clientX,
|
|
1114
|
+
clientY: event.clientY,
|
|
1115
|
+
affordance: event.affordance,
|
|
1116
|
+
...resolved
|
|
1117
|
+
};
|
|
1118
|
+
} else if (event.kind === "pinch") {
|
|
1119
|
+
params = {
|
|
1120
|
+
scale: event.scale,
|
|
1121
|
+
rotation: event.rotation,
|
|
1122
|
+
clientX: event.clientX,
|
|
1123
|
+
clientY: event.clientY,
|
|
1124
|
+
affordance: event.affordance,
|
|
1125
|
+
...resolved
|
|
1126
|
+
};
|
|
1127
|
+
} else if (event.kind === "click" || event.kind === "doubleclick") {
|
|
1128
|
+
params = {
|
|
1129
|
+
worldX: event.x,
|
|
1130
|
+
worldY: event.y,
|
|
1131
|
+
affordance: event.affordance,
|
|
1132
|
+
// Press point as well as release point — an action that places
|
|
1133
|
+
// geometry at the click wants the former. See `ClickEvent`.
|
|
1134
|
+
...event.kind === "click" ? { pressX: event.pressX, pressY: event.pressY } : {},
|
|
1135
|
+
mods: modifiersOf(event),
|
|
1136
|
+
...resolved
|
|
1137
|
+
};
|
|
1138
|
+
} else if (event.kind === "contextmenu" || event.kind === "longpress") {
|
|
1139
|
+
params = {
|
|
1140
|
+
worldX: event.x,
|
|
1141
|
+
worldY: event.y,
|
|
1142
|
+
affordance: event.affordance,
|
|
1143
|
+
bodyTarget: event.bodyTarget,
|
|
1144
|
+
mods: modifiersOf(event),
|
|
1145
|
+
...resolved
|
|
1146
|
+
};
|
|
1147
|
+
} else if (event.kind === "pointerdown") {
|
|
1148
|
+
params = {
|
|
1149
|
+
worldX: event.x,
|
|
1150
|
+
worldY: event.y,
|
|
1151
|
+
affordance: event.affordance,
|
|
1152
|
+
bodyTarget: event.bodyTarget,
|
|
1153
|
+
mods: modifiersOf(event),
|
|
1154
|
+
...resolved
|
|
1155
|
+
};
|
|
1156
|
+
} else if (event.kind === "drop" || event.kind === "paste") {
|
|
1157
|
+
params = {
|
|
1158
|
+
items: event.items,
|
|
1159
|
+
via: event.kind,
|
|
1160
|
+
...event.kind === "drop" && event.x !== void 0 ? { worldX: event.x, worldY: event.y } : {},
|
|
1161
|
+
...resolved
|
|
1162
|
+
};
|
|
1163
|
+
} else {
|
|
1164
|
+
params = resolved;
|
|
1165
|
+
}
|
|
1166
|
+
action.invoker.run(deps, params);
|
|
1167
|
+
} catch (err) {
|
|
1168
|
+
console.error(`weasel dispatcher: action "${action.id}" invoker threw`, err);
|
|
1169
|
+
}
|
|
1170
|
+
return "fired";
|
|
761
1171
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
const action = actionMap.get(match.binding.actionId);
|
|
767
|
-
if (!action) {
|
|
768
|
-
traceCandidates.push({ actionId: match.binding.actionId, scope: match.scope, enabledResult: "no-such-action" });
|
|
769
|
-
console.warn(
|
|
770
|
-
`weasel dispatcher: binding resolved actionId "${match.binding.actionId}" which has no registered action. Skipping. (misconfiguration)`
|
|
1172
|
+
if (action.invoker?.timing === "ongoing") {
|
|
1173
|
+
if (event.kind === "pointerdown" && event.stage === "press") {
|
|
1174
|
+
console.error(
|
|
1175
|
+
`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.`
|
|
771
1176
|
);
|
|
772
|
-
|
|
1177
|
+
return "misbound";
|
|
773
1178
|
}
|
|
774
|
-
const
|
|
775
|
-
if (
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
1179
|
+
const gestureId = gestureIdFor(event);
|
|
1180
|
+
if (event.kind === "pointerdown") {
|
|
1181
|
+
dragOrigins.set(gestureId, {
|
|
1182
|
+
x: event.x ?? 0,
|
|
1183
|
+
y: event.y ?? 0,
|
|
1184
|
+
...event.clientX !== void 0 ? { clientX: event.clientX } : {},
|
|
1185
|
+
...event.clientY !== void 0 ? { clientY: event.clientY } : {}
|
|
1186
|
+
});
|
|
1187
|
+
dragPoints.set(gestureId, [{
|
|
1188
|
+
x: event.x ?? 0,
|
|
1189
|
+
y: event.y ?? 0,
|
|
1190
|
+
...event.pressure !== void 0 ? { pressure: event.pressure } : {},
|
|
1191
|
+
...event.tiltX !== void 0 ? { tiltX: event.tiltX } : {},
|
|
1192
|
+
...event.tiltY !== void 0 ? { tiltY: event.tiltY } : {}
|
|
1193
|
+
}]);
|
|
781
1194
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
try {
|
|
785
|
-
const resolved = resolveParams(match.binding.opts?.params);
|
|
786
|
-
let params;
|
|
787
|
-
if (event.kind === "wheel") {
|
|
788
|
-
params = {
|
|
789
|
-
deltaX: event.deltaX,
|
|
790
|
-
deltaY: event.deltaY,
|
|
791
|
-
clientX: event.clientX,
|
|
792
|
-
clientY: event.clientY,
|
|
793
|
-
affordance: event.affordance,
|
|
794
|
-
...resolved
|
|
795
|
-
};
|
|
796
|
-
} else if (event.kind === "pinch") {
|
|
797
|
-
params = {
|
|
798
|
-
scale: event.scale,
|
|
799
|
-
rotation: event.rotation,
|
|
800
|
-
clientX: event.clientX,
|
|
801
|
-
clientY: event.clientY,
|
|
802
|
-
affordance: event.affordance,
|
|
803
|
-
...resolved
|
|
804
|
-
};
|
|
805
|
-
} else if (event.kind === "click" || event.kind === "doubleclick") {
|
|
806
|
-
params = {
|
|
807
|
-
worldX: event.x,
|
|
808
|
-
worldY: event.y,
|
|
809
|
-
affordance: event.affordance,
|
|
810
|
-
// Press point as well as release point — an action that places
|
|
811
|
-
// geometry at the click wants the former. See `ClickEvent`.
|
|
812
|
-
...event.kind === "click" ? { pressX: event.pressX, pressY: event.pressY } : {},
|
|
813
|
-
mods: modifiersOf(event),
|
|
814
|
-
...resolved
|
|
815
|
-
};
|
|
816
|
-
} else if (event.kind === "contextmenu" || event.kind === "longpress") {
|
|
817
|
-
params = {
|
|
818
|
-
worldX: event.x,
|
|
819
|
-
worldY: event.y,
|
|
820
|
-
affordance: event.affordance,
|
|
821
|
-
bodyTarget: event.bodyTarget,
|
|
822
|
-
mods: modifiersOf(event),
|
|
823
|
-
...resolved
|
|
824
|
-
};
|
|
825
|
-
} else if (event.kind === "pointerdown") {
|
|
826
|
-
params = {
|
|
827
|
-
worldX: event.x,
|
|
828
|
-
worldY: event.y,
|
|
829
|
-
affordance: event.affordance,
|
|
830
|
-
bodyTarget: event.bodyTarget,
|
|
831
|
-
mods: modifiersOf(event),
|
|
832
|
-
...resolved
|
|
833
|
-
};
|
|
834
|
-
} else if (event.kind === "drop" || event.kind === "paste") {
|
|
835
|
-
params = {
|
|
836
|
-
items: event.items,
|
|
837
|
-
via: event.kind,
|
|
838
|
-
...event.kind === "drop" && event.x !== void 0 ? { worldX: event.x, worldY: event.y } : {},
|
|
839
|
-
...resolved
|
|
840
|
-
};
|
|
841
|
-
} else {
|
|
842
|
-
params = resolved;
|
|
843
|
-
}
|
|
844
|
-
action.invoker.run(deps, params);
|
|
845
|
-
} catch (err) {
|
|
846
|
-
console.error(`weasel dispatcher: action "${action.id}" invoker threw`, err);
|
|
847
|
-
}
|
|
848
|
-
finishTrace(action.id, "handled");
|
|
849
|
-
return "handled";
|
|
1195
|
+
if (event.kind === "multitouch" && event.spread !== void 0) {
|
|
1196
|
+
pinchStartSpreads.set(gestureId, event.spread);
|
|
850
1197
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
`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.`
|
|
855
|
-
);
|
|
856
|
-
finishTrace(action.id, "unhandled");
|
|
857
|
-
return "unhandled";
|
|
858
|
-
}
|
|
859
|
-
const gestureId = gestureIdFor(event);
|
|
1198
|
+
const invCtx = buildInvocationCtx(event, deps, gestureId);
|
|
1199
|
+
const handle = action.invoker.start(invCtx, match.binding.opts);
|
|
1200
|
+
if (isEmptyOngoingHandle(handle)) {
|
|
860
1201
|
if (event.kind === "pointerdown") {
|
|
861
|
-
dragOrigins.
|
|
862
|
-
|
|
863
|
-
y: event.y ?? 0,
|
|
864
|
-
...event.clientX !== void 0 ? { clientX: event.clientX } : {},
|
|
865
|
-
...event.clientY !== void 0 ? { clientY: event.clientY } : {}
|
|
866
|
-
});
|
|
867
|
-
dragPoints.set(gestureId, [{
|
|
868
|
-
x: event.x ?? 0,
|
|
869
|
-
y: event.y ?? 0,
|
|
870
|
-
...event.pressure !== void 0 ? { pressure: event.pressure } : {},
|
|
871
|
-
...event.tiltX !== void 0 ? { tiltX: event.tiltX } : {},
|
|
872
|
-
...event.tiltY !== void 0 ? { tiltY: event.tiltY } : {}
|
|
873
|
-
}]);
|
|
1202
|
+
dragOrigins.delete(gestureId);
|
|
1203
|
+
dragPoints.delete(gestureId);
|
|
874
1204
|
}
|
|
875
|
-
if (event.kind === "multitouch"
|
|
876
|
-
pinchStartSpreads.
|
|
1205
|
+
if (event.kind === "multitouch") {
|
|
1206
|
+
pinchStartSpreads.delete(gestureId);
|
|
877
1207
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
1208
|
+
return "empty-handle";
|
|
1209
|
+
}
|
|
1210
|
+
inFlightHandles.set(gestureId, handle);
|
|
1211
|
+
inFlightOwners.set(gestureId, match.ownerId);
|
|
1212
|
+
inFlightActions.set(gestureId, action);
|
|
1213
|
+
return "fired";
|
|
1214
|
+
}
|
|
1215
|
+
return "fired";
|
|
1216
|
+
}
|
|
1217
|
+
function rank(event, ctx, collectBarred) {
|
|
1218
|
+
const view = ctx.viewId ?? null;
|
|
1219
|
+
const outcome = matchSortedWithBarred(
|
|
1220
|
+
event,
|
|
1221
|
+
assembleScopedBindings(ctx),
|
|
1222
|
+
ctx.isMac,
|
|
1223
|
+
snapshotEngagedChannels(),
|
|
1224
|
+
{ collectBarred }
|
|
1225
|
+
);
|
|
1226
|
+
const sorted = preferViewScoped(outcome.matches, view);
|
|
1227
|
+
const actionMap = buildActionMap(ctx.actions);
|
|
1228
|
+
const ruleCtx = ruleCtxOf(ctx);
|
|
1229
|
+
const held = /* @__PURE__ */ new Map();
|
|
1230
|
+
const holds = (a) => {
|
|
1231
|
+
if (ruleCtx === void 0) return true;
|
|
1232
|
+
let h = held.get(a);
|
|
1233
|
+
if (h === void 0) held.set(a, h = isEligible(a, ruleCtx));
|
|
1234
|
+
return h;
|
|
1235
|
+
};
|
|
1236
|
+
const ordered = ruleCtx ? preferContextual(sorted, (id) => actionMap.get(id), holds, view) : sorted;
|
|
1237
|
+
return {
|
|
1238
|
+
ordered,
|
|
1239
|
+
barred: outcome.barred,
|
|
1240
|
+
...outcome.claimOwner !== void 0 ? { claimOwner: outcome.claimOwner } : {},
|
|
1241
|
+
actionMap,
|
|
1242
|
+
ruleCtx,
|
|
1243
|
+
eligible: (m) => {
|
|
1244
|
+
const a = actionMap.get(m.binding.actionId);
|
|
1245
|
+
return a === void 0 || holds(a);
|
|
1246
|
+
}
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
function walk(r, event, ctx, attempt, evaluateShadowed) {
|
|
1250
|
+
const steps = [];
|
|
1251
|
+
const tried = /* @__PURE__ */ new Set();
|
|
1252
|
+
let stop = "exhausted";
|
|
1253
|
+
let winner = null;
|
|
1254
|
+
for (const match of r.ordered) {
|
|
1255
|
+
if (!r.eligible(match)) continue;
|
|
1256
|
+
const push = (step) => {
|
|
1257
|
+
steps.push({ match, step });
|
|
1258
|
+
};
|
|
1259
|
+
const actionId = match.binding.actionId;
|
|
1260
|
+
if (tried.has(actionId)) {
|
|
1261
|
+
push({ kind: "duplicate" });
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
if (stop !== "exhausted" && !evaluateShadowed) {
|
|
1265
|
+
push({ kind: "not-asked" });
|
|
1266
|
+
continue;
|
|
1267
|
+
}
|
|
1268
|
+
tried.add(actionId);
|
|
1269
|
+
const action = r.actionMap.get(actionId);
|
|
1270
|
+
if (!action) {
|
|
1271
|
+
if (attempt) {
|
|
1272
|
+
console.warn(
|
|
1273
|
+
`weasel dispatcher: binding resolved actionId "${actionId}" which has no registered action. Skipping. (misconfiguration)`
|
|
1274
|
+
);
|
|
890
1275
|
}
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
1276
|
+
push({ kind: "no-such-action" });
|
|
1277
|
+
continue;
|
|
1278
|
+
}
|
|
1279
|
+
const deps = buildDepsFromRequires(action, ctx.depRegistry);
|
|
1280
|
+
const enabled = action.enabled ? action.enabled(deps, worldPointOf(event)) : true;
|
|
1281
|
+
if (enabled !== true) {
|
|
1282
|
+
push({ kind: "declined", reason: String(enabled) });
|
|
1283
|
+
continue;
|
|
1284
|
+
}
|
|
1285
|
+
if (stop !== "exhausted") {
|
|
1286
|
+
push({ kind: "outranked" });
|
|
1287
|
+
continue;
|
|
1288
|
+
}
|
|
1289
|
+
if (!attempt) {
|
|
1290
|
+
push({ kind: "would-fire" });
|
|
1291
|
+
stop = "fired";
|
|
1292
|
+
winner = { match, action };
|
|
1293
|
+
continue;
|
|
896
1294
|
}
|
|
897
|
-
|
|
898
|
-
|
|
1295
|
+
const result = attempt(match, action, deps);
|
|
1296
|
+
if (result === "empty-handle") {
|
|
1297
|
+
push({ kind: "empty-handle" });
|
|
1298
|
+
continue;
|
|
1299
|
+
}
|
|
1300
|
+
push({ kind: result === "fired" ? "fired" : "misbound" });
|
|
1301
|
+
stop = result;
|
|
1302
|
+
winner = { match, action };
|
|
899
1303
|
}
|
|
900
|
-
|
|
901
|
-
|
|
1304
|
+
return { steps, stop, winner };
|
|
1305
|
+
}
|
|
1306
|
+
function buildRecord(r, walked, event, ctx, predicted, outcome) {
|
|
1307
|
+
const view = ctx.viewId ?? null;
|
|
1308
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
1309
|
+
const candidateOf = (m) => {
|
|
1310
|
+
let c = candidates.get(m);
|
|
1311
|
+
if (!c) {
|
|
1312
|
+
const eligible = r.actionMap.get(m.binding.actionId)?.eligible;
|
|
1313
|
+
c = {
|
|
1314
|
+
actionId: m.binding.actionId,
|
|
1315
|
+
routes: routesForSpec(m.binding.spec),
|
|
1316
|
+
scope: m.scope,
|
|
1317
|
+
ownerId: m.ownerId,
|
|
1318
|
+
namesView: namesView(m.binding, view),
|
|
1319
|
+
specificity: specificity(m.binding.spec),
|
|
1320
|
+
...eligible !== void 0 ? { eligible: describeEligible(eligible) } : {}
|
|
1321
|
+
};
|
|
1322
|
+
candidates.set(m, c);
|
|
1323
|
+
}
|
|
1324
|
+
return c;
|
|
1325
|
+
};
|
|
1326
|
+
const dropped = [
|
|
1327
|
+
...r.barred.map((m) => ({
|
|
1328
|
+
candidate: candidateOf(m),
|
|
1329
|
+
filter: "claim",
|
|
1330
|
+
...r.claimOwner !== void 0 ? { owner: r.claimOwner } : {}
|
|
1331
|
+
}))
|
|
1332
|
+
];
|
|
1333
|
+
for (const m of r.ordered) {
|
|
1334
|
+
if (r.eligible(m)) continue;
|
|
1335
|
+
const c = candidateOf(m);
|
|
1336
|
+
dropped.push({ candidate: c, filter: "ineligible", rule: c.eligible ?? "" });
|
|
1337
|
+
}
|
|
1338
|
+
const gated = (m) => r.actionMap.get(m.binding.actionId)?.eligible !== void 0;
|
|
1339
|
+
const ranked = walked.steps.map(({ match, step }, i) => {
|
|
1340
|
+
const prev = walked.steps[i - 1]?.match;
|
|
1341
|
+
return {
|
|
1342
|
+
candidate: candidateOf(match),
|
|
1343
|
+
placedBy: prev ? placedBy(prev, match, view, r.ruleCtx !== void 0 && gated(prev) && !gated(match)) : { step: "first" },
|
|
1344
|
+
walk: step
|
|
1345
|
+
};
|
|
1346
|
+
});
|
|
1347
|
+
const eventKey = event.kind === "key" || event.kind === "key-held" ? event.key : void 0;
|
|
1348
|
+
const world = worldPointOf(event);
|
|
1349
|
+
return {
|
|
1350
|
+
kind: "dispatch",
|
|
1351
|
+
ts: Date.now(),
|
|
1352
|
+
input: {
|
|
1353
|
+
eventKind: event.kind,
|
|
1354
|
+
...eventKey !== void 0 ? { key: eventKey } : {},
|
|
1355
|
+
modifiers: modifiersOf(event),
|
|
1356
|
+
viewId: view,
|
|
1357
|
+
...world !== void 0 ? { world } : {},
|
|
1358
|
+
...r.ruleCtx !== void 0 ? { mode: r.ruleCtx.mode } : {}
|
|
1359
|
+
},
|
|
1360
|
+
matched: [...r.ordered, ...r.barred].map(candidateOf),
|
|
1361
|
+
dropped,
|
|
1362
|
+
ranked,
|
|
1363
|
+
predicted,
|
|
1364
|
+
fired: walked.winner?.action.id ?? null,
|
|
1365
|
+
outcome
|
|
1366
|
+
};
|
|
902
1367
|
}
|
|
903
1368
|
function resolveAll(event, ctx, resolveOpts) {
|
|
904
|
-
const
|
|
905
|
-
const
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
const
|
|
909
|
-
const ruleCtx = ctx.getRuleCtx?.();
|
|
1369
|
+
const evaluateShadowed = resolveOpts?.evaluateShadowed ?? false;
|
|
1370
|
+
const r = rank(event, ctx, false);
|
|
1371
|
+
if (r.ordered.length === 0) return [];
|
|
1372
|
+
const walked = walk(r, event, ctx, null, evaluateShadowed);
|
|
1373
|
+
const stepOf = new Map(walked.steps.map((s) => [s.match, s.step]));
|
|
910
1374
|
const verdictByAction = /* @__PURE__ */ new Map();
|
|
911
1375
|
let fired = false;
|
|
912
1376
|
const out = [];
|
|
913
|
-
for (const match of
|
|
1377
|
+
for (const match of r.ordered) {
|
|
914
1378
|
const actionId = match.binding.actionId;
|
|
915
|
-
const action = actionMap.get(actionId);
|
|
1379
|
+
const action = r.actionMap.get(actionId);
|
|
916
1380
|
if (!action) continue;
|
|
917
1381
|
let verdict = verdictByAction.get(actionId);
|
|
918
1382
|
if (verdict === void 0) {
|
|
919
|
-
if (
|
|
920
|
-
verdict = { kind: "shadowed" };
|
|
921
|
-
} else if (ruleCtx && action.eligible && !isEligible(action, ruleCtx)) {
|
|
922
|
-
verdict = { kind: "ineligible", reason: describeEligible(action.eligible) };
|
|
1383
|
+
if (!r.eligible(match)) {
|
|
1384
|
+
verdict = fired && !evaluateShadowed ? { kind: "shadowed" } : { kind: "ineligible", reason: describeEligible(action.eligible) };
|
|
923
1385
|
} else {
|
|
924
|
-
const
|
|
925
|
-
if (
|
|
926
|
-
verdict = { kind: "disabled", reason: String(disabled) };
|
|
927
|
-
} else if (fired) {
|
|
928
|
-
verdict = { kind: "shadowed" };
|
|
929
|
-
} else {
|
|
1386
|
+
const step = stepOf.get(match);
|
|
1387
|
+
if (step?.kind === "would-fire") {
|
|
930
1388
|
verdict = { kind: "would-fire" };
|
|
931
1389
|
fired = true;
|
|
1390
|
+
} else if (step?.kind === "declined") {
|
|
1391
|
+
verdict = { kind: "disabled", reason: step.reason };
|
|
1392
|
+
} else {
|
|
1393
|
+
verdict = { kind: "shadowed" };
|
|
932
1394
|
}
|
|
933
1395
|
}
|
|
934
1396
|
verdictByAction.set(actionId, verdict);
|
|
@@ -940,7 +1402,7 @@ function createDispatcher(opts) {
|
|
|
940
1402
|
action,
|
|
941
1403
|
binding: match.binding,
|
|
942
1404
|
scope: match.scope,
|
|
943
|
-
|
|
1405
|
+
ownerId: match.ownerId,
|
|
944
1406
|
specificity: specificity(match.binding.spec),
|
|
945
1407
|
verdict
|
|
946
1408
|
});
|
|
@@ -948,15 +1410,21 @@ function createDispatcher(opts) {
|
|
|
948
1410
|
return out;
|
|
949
1411
|
}
|
|
950
1412
|
function resolveOnly(event, ctx) {
|
|
951
|
-
const
|
|
1413
|
+
const r = rank(event, ctx, false);
|
|
1414
|
+
const { winner } = walk(r, event, ctx, null, false);
|
|
952
1415
|
if (!winner) return null;
|
|
953
1416
|
return {
|
|
954
|
-
actionId: winner.
|
|
1417
|
+
actionId: winner.action.id,
|
|
955
1418
|
action: winner.action,
|
|
956
|
-
scope: winner.scope,
|
|
957
|
-
|
|
1419
|
+
scope: winner.match.scope,
|
|
1420
|
+
ownerId: winner.match.ownerId
|
|
958
1421
|
};
|
|
959
1422
|
}
|
|
1423
|
+
function explain(event, ctx, explainOpts) {
|
|
1424
|
+
const r = rank(event, ctx, true);
|
|
1425
|
+
const walked = walk(r, event, ctx, null, explainOpts?.evaluateShadowed ?? false);
|
|
1426
|
+
return buildRecord(r, walked, event, ctx, true, walked.winner ? "handled" : "unhandled");
|
|
1427
|
+
}
|
|
960
1428
|
function cancelAll(reason) {
|
|
961
1429
|
const stubCtx = {
|
|
962
1430
|
world: { x: 0, y: 0 },
|
|
@@ -1096,6 +1564,7 @@ function createDispatcher(opts) {
|
|
|
1096
1564
|
handleInput: handleInputWithNotify,
|
|
1097
1565
|
resolveOnly,
|
|
1098
1566
|
resolveAll,
|
|
1567
|
+
explain,
|
|
1099
1568
|
cancelAll: cancelAllWithNotify,
|
|
1100
1569
|
inFlight,
|
|
1101
1570
|
inFlightCursor,
|
|
@@ -1186,117 +1655,17 @@ function openPointerSession(origin, down, cb, opts = {}) {
|
|
|
1186
1655
|
};
|
|
1187
1656
|
}
|
|
1188
1657
|
|
|
1189
|
-
// src/tools/
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
key: "keyDown",
|
|
1193
|
-
"key-held": "keyHeld",
|
|
1194
|
-
wheel: "wheel",
|
|
1195
|
-
pinch: "pinch",
|
|
1196
|
-
click: "click",
|
|
1197
|
-
doubleClick: "dblTap",
|
|
1198
|
-
contextMenu: "contextMenu",
|
|
1199
|
-
longPress: "longPress",
|
|
1200
|
-
drag: "drag",
|
|
1201
|
-
pointerDown: "pointerDown",
|
|
1202
|
-
multiTouch: void 0,
|
|
1203
|
-
multiTouchTap: "multiTouchTap",
|
|
1204
|
-
drop: "drop",
|
|
1205
|
-
paste: "paste"
|
|
1206
|
-
};
|
|
1207
|
-
function buildRouteRegistry(tools) {
|
|
1208
|
-
const out = [];
|
|
1209
|
-
for (const tool of tools) {
|
|
1210
|
-
for (const binding of tool.bindings ?? []) {
|
|
1211
|
-
const entry = entryFor(tool.id, binding.spec, binding.actionId);
|
|
1212
|
-
if (entry) out.push(entry);
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
return out;
|
|
1216
|
-
}
|
|
1217
|
-
function routeGestureForSpecKind(kind) {
|
|
1218
|
-
const gesture = SPEC_KIND_TO_GESTURE[kind];
|
|
1219
|
-
return gesture && isKnownGestureName(gesture) ? gesture : void 0;
|
|
1220
|
-
}
|
|
1221
|
-
function routesForSpec(spec) {
|
|
1222
|
-
const gesture = routeGestureForSpecKind(spec.kind);
|
|
1223
|
-
if (!gesture) return [];
|
|
1224
|
-
const descriptor = getGestureDescriptor(gesture);
|
|
1225
|
-
const modifiers = parseModSpec("mods" in spec ? spec.mods : void 0);
|
|
1226
|
-
const phases = phaseAtomsForSpec("phase" in spec ? spec.phase : void 0);
|
|
1227
|
-
const target = descriptor.hasTarget ? routeTargetForSpec("target" in spec ? spec.target : void 0) ?? "*" : void 0;
|
|
1228
|
-
const args = descriptor.arg ? routeArgsForSpec(spec, descriptor.arg.default) : [void 0];
|
|
1229
|
-
return args.map((arg) => formatRoute({ phases, gesture, arg, target, modifiers }));
|
|
1230
|
-
}
|
|
1231
|
-
function entryFor(toolId, spec, actionId) {
|
|
1232
|
-
const gesture = SPEC_KIND_TO_GESTURE[spec.kind];
|
|
1233
|
-
if (!gesture || !isKnownGestureName(gesture)) return null;
|
|
1234
|
-
const descriptor = getGestureDescriptor(gesture);
|
|
1235
|
-
return {
|
|
1236
|
-
toolId,
|
|
1237
|
-
actionId,
|
|
1238
|
-
gesture,
|
|
1239
|
-
phase: phaseOf("phase" in spec ? spec.phase : void 0),
|
|
1240
|
-
modifiers: parseModSpec("mods" in spec ? spec.mods : void 0),
|
|
1241
|
-
arg: descriptor.arg ? argOf(spec, descriptor.arg.default) : void 0,
|
|
1242
|
-
...descriptor.arg ? argAlternativesOf(spec, descriptor.arg.default) : {},
|
|
1243
|
-
target: descriptor.hasTarget ? routeTargetForSpec("target" in spec ? spec.target : void 0) : void 0,
|
|
1244
|
-
spec
|
|
1245
|
-
};
|
|
1246
|
-
}
|
|
1247
|
-
function phaseOf(phase) {
|
|
1248
|
-
if (phase === void 0 || phase === "*") return "any";
|
|
1249
|
-
if (phase === "initial" || phase === "engaged") return phase;
|
|
1250
|
-
const distinct = new Set(phase.map((atom) => atom.phase));
|
|
1251
|
-
if (distinct.size !== 1) return "any";
|
|
1252
|
-
const only = [...distinct][0];
|
|
1253
|
-
return only === "initial" || only === "engaged" ? only : "any";
|
|
1254
|
-
}
|
|
1255
|
-
function parseModSpec(mods) {
|
|
1256
|
-
if (!mods) return {};
|
|
1257
|
-
const out = {};
|
|
1258
|
-
for (const [name, req] of Object.entries(mods)) {
|
|
1259
|
-
if (req === true) out[name] = "required";
|
|
1260
|
-
else if (req === "optional") out[name] = "optional";
|
|
1261
|
-
}
|
|
1262
|
-
return out;
|
|
1263
|
-
}
|
|
1264
|
-
function phaseAtomsForSpec(phase) {
|
|
1265
|
-
if (phase === void 0) return [{ channel: "&", phase: "*" }];
|
|
1266
|
-
if (phase === "initial" || phase === "engaged" || phase === "*") {
|
|
1267
|
-
return [{ channel: "&", phase }];
|
|
1268
|
-
}
|
|
1269
|
-
return phase;
|
|
1658
|
+
// src/tools/types.ts
|
|
1659
|
+
function isTool(entry) {
|
|
1660
|
+
return !!(entry.eligibility?.focus || entry.eligibility?.offhand);
|
|
1270
1661
|
}
|
|
1271
|
-
function
|
|
1272
|
-
|
|
1273
|
-
return typeof target === "string" ? target : PREDICATE_TARGET;
|
|
1274
|
-
}
|
|
1275
|
-
function routeArgsForSpec(spec, fallback) {
|
|
1276
|
-
if ("key" in spec) return Array.isArray(spec.key) ? spec.key : [spec.key];
|
|
1277
|
-
if ("fingers" in spec) return [String(spec.fingers)];
|
|
1278
|
-
if ("direction" in spec) return [spec.direction ?? fallback];
|
|
1279
|
-
if ("types" in spec) return [spec.types?.length ? spec.types.join("|") : fallback];
|
|
1280
|
-
return [fallback];
|
|
1662
|
+
function findConflicts(entries) {
|
|
1663
|
+
return findConflictsKeyed(entries).map((k) => k.conflict);
|
|
1281
1664
|
}
|
|
1282
|
-
function
|
|
1283
|
-
const
|
|
1284
|
-
return args.length > 1 ? args.join("|") : args[0];
|
|
1285
|
-
}
|
|
1286
|
-
function argAlternativesOf(spec, fallback) {
|
|
1287
|
-
const types = "types" in spec ? spec.types : void 0;
|
|
1288
|
-
const alts = types?.length ? types : routeArgsForSpec(spec, fallback).filter((a) => a !== void 0);
|
|
1289
|
-
return alts.length > 1 ? { argAlternatives: alts } : {};
|
|
1290
|
-
}
|
|
1291
|
-
|
|
1292
|
-
// src/tools/routing/reflection/conflicts.ts
|
|
1293
|
-
function findConflicts(tools) {
|
|
1294
|
-
return findConflictsKeyed(tools).map((k) => k.conflict);
|
|
1295
|
-
}
|
|
1296
|
-
function findConflictsKeyed(tools) {
|
|
1297
|
-
const entries = buildRouteRegistry(tools);
|
|
1665
|
+
function findConflictsKeyed(entries, rules, modes = KIT_MODES) {
|
|
1666
|
+
const rows = buildRouteRegistry(entries);
|
|
1298
1667
|
const groups = /* @__PURE__ */ new Map();
|
|
1299
|
-
for (const entry of
|
|
1668
|
+
for (const entry of rows) {
|
|
1300
1669
|
for (const arg of argKeysOf(entry)) {
|
|
1301
1670
|
const key = `${entry.phase}|${entry.gesture}|${arg}|${targetKey(entry)}|${canonicalModifiers(entry.modifiers)}`;
|
|
1302
1671
|
const bucket = groups.get(key);
|
|
@@ -1305,8 +1674,11 @@ function findConflictsKeyed(tools) {
|
|
|
1305
1674
|
} else groups.set(key, [entry]);
|
|
1306
1675
|
}
|
|
1307
1676
|
}
|
|
1677
|
+
const ruleOf = (entry) => rules?.get(entry.ownerId) ?? ALWAYS;
|
|
1678
|
+
const tie = (e, f) => viewsTie(e.views, f.views) && rulesCanHoldTogether(ruleOf(e), ruleOf(f), modes);
|
|
1308
1679
|
const conflicts = [];
|
|
1309
|
-
for (const [key,
|
|
1680
|
+
for (const [key, all] of groups) {
|
|
1681
|
+
const bucket = all.filter((e) => all.some((f) => f !== e && tie(e, f)));
|
|
1310
1682
|
if (bucket.length < 2) continue;
|
|
1311
1683
|
const first = bucket[0];
|
|
1312
1684
|
conflicts.push({
|
|
@@ -1317,12 +1689,16 @@ function findConflictsKeyed(tools) {
|
|
|
1317
1689
|
arg: first.arg,
|
|
1318
1690
|
target: first.target,
|
|
1319
1691
|
modifiers: first.modifiers,
|
|
1320
|
-
|
|
1692
|
+
ownerIds: bucket.map((e) => e.ownerId)
|
|
1321
1693
|
}
|
|
1322
1694
|
});
|
|
1323
1695
|
}
|
|
1324
1696
|
return conflicts;
|
|
1325
1697
|
}
|
|
1698
|
+
function viewsTie(a, b) {
|
|
1699
|
+
if (a === void 0 || b === void 0) return a === b;
|
|
1700
|
+
return a.some((v) => b.includes(v));
|
|
1701
|
+
}
|
|
1326
1702
|
function argKeysOf(entry) {
|
|
1327
1703
|
const alts = entry.argAlternatives;
|
|
1328
1704
|
if (alts && alts.length > 0) return [...new Set(alts.map((a) => a.toLowerCase()))];
|
|
@@ -1345,43 +1721,67 @@ function predicateId(pred) {
|
|
|
1345
1721
|
}
|
|
1346
1722
|
return id;
|
|
1347
1723
|
}
|
|
1348
|
-
function
|
|
1724
|
+
function rulesCanHoldTogether(a, b, modes) {
|
|
1725
|
+
if (rulesExclusive(a, b)) return false;
|
|
1726
|
+
return modes.some((m) => ruleCanHoldIn(a, m) && ruleCanHoldIn(b, m));
|
|
1727
|
+
}
|
|
1728
|
+
var KIT_MODES = DEFAULT_MODES.map(activeModeOf);
|
|
1729
|
+
function actionAsEntry(action) {
|
|
1349
1730
|
return { id: action.id, eligibility: {}, bindings: actionBindings(action) };
|
|
1350
1731
|
}
|
|
1732
|
+
function eligibleRule(action) {
|
|
1733
|
+
const eligible = action.eligible;
|
|
1734
|
+
if (eligible === void 0) return void 0;
|
|
1735
|
+
return typeof eligible === "function" ? eligible.rule : eligible;
|
|
1736
|
+
}
|
|
1351
1737
|
function findScopedConflicts(scopes) {
|
|
1352
1738
|
const registry = Array.isArray(scopes.registry) ? scopes.registry : Object.values(scopes.registry);
|
|
1353
1739
|
const actions = scopes.actions ?? [];
|
|
1354
|
-
const
|
|
1355
|
-
const
|
|
1356
|
-
|
|
1740
|
+
const rules = /* @__PURE__ */ new Map();
|
|
1741
|
+
for (const a of actions) {
|
|
1742
|
+
const rule = eligibleRule(a);
|
|
1743
|
+
if (rule) rules.set(a.id, rule);
|
|
1744
|
+
}
|
|
1745
|
+
const hotkeyActions = actions.filter((a) => a.scope === "hotkey").map(actionAsEntry);
|
|
1746
|
+
const gatedActions = [];
|
|
1747
|
+
const ungated = [];
|
|
1748
|
+
for (const a of actions) {
|
|
1749
|
+
if (a.scope === "hotkey") continue;
|
|
1750
|
+
(a.eligible !== void 0 ? gatedActions : ungated).push(actionAsEntry(a));
|
|
1751
|
+
}
|
|
1752
|
+
const modes = scopes.modes ?? KIT_MODES;
|
|
1753
|
+
const ambient = [...scopes.ambient ?? [], ...ungated];
|
|
1357
1754
|
const out = [];
|
|
1358
1755
|
const seen = /* @__PURE__ */ new Set();
|
|
1359
1756
|
const add = (keyed) => {
|
|
1360
1757
|
for (const { key, conflict } of keyed) {
|
|
1361
|
-
const dedupeKey = `${key}|${conflict.
|
|
1758
|
+
const dedupeKey = `${key}|${conflict.ownerIds.join(",")}`;
|
|
1362
1759
|
if (seen.has(dedupeKey)) continue;
|
|
1363
1760
|
seen.add(dedupeKey);
|
|
1364
1761
|
out.push(conflict);
|
|
1365
1762
|
}
|
|
1366
1763
|
};
|
|
1367
|
-
for (const tool of [...registry, ...ambient, ...hotkeyActions])
|
|
1368
|
-
|
|
1764
|
+
for (const tool of [...registry, ...ambient, ...gatedActions, ...hotkeyActions]) {
|
|
1765
|
+
add(findConflictsKeyed([tool], rules, modes));
|
|
1766
|
+
}
|
|
1767
|
+
if (ambient.length > 1) add(findConflictsKeyed(ambient, rules, modes));
|
|
1768
|
+
if (gatedActions.length > 1) add(findConflictsKeyed(gatedActions, rules, modes));
|
|
1369
1769
|
const hotkey = [
|
|
1370
1770
|
...registry.filter((t) => t.def?.hotkey !== void 0),
|
|
1371
1771
|
...hotkeyActions
|
|
1372
1772
|
];
|
|
1373
|
-
if (hotkey.length > 1) add(findConflictsKeyed(hotkey));
|
|
1773
|
+
if (hotkey.length > 1) add(findConflictsKeyed(hotkey, rules, modes));
|
|
1374
1774
|
return out;
|
|
1375
1775
|
}
|
|
1376
1776
|
function formatConflict(conflict) {
|
|
1377
1777
|
const route = formatRoute({
|
|
1378
|
-
phases: [{ channel: "
|
|
1778
|
+
phases: [conflict.phase === "any" ? { channel: "*", phase: "*" } : { channel: "&", phase: conflict.phase }],
|
|
1379
1779
|
gesture: conflict.gesture,
|
|
1380
1780
|
arg: conflict.arg,
|
|
1381
1781
|
target: conflict.target,
|
|
1382
1782
|
modifiers: conflict.modifiers
|
|
1383
1783
|
});
|
|
1384
|
-
return `${route} \u2014 declared by ${conflict.
|
|
1784
|
+
return `${route} \u2014 declared by ${conflict.ownerIds.join(", ")}`;
|
|
1385
1785
|
}
|
|
1386
1786
|
function reportRouteConflicts(scopes, warn = (m) => console.warn(m)) {
|
|
1387
1787
|
const conflicts = findScopedConflicts(scopes);
|
|
@@ -1523,6 +1923,6 @@ function dlog(namespace, ...args) {
|
|
|
1523
1923
|
console.debug(`[${namespace}]`, ...args);
|
|
1524
1924
|
}
|
|
1525
1925
|
|
|
1526
|
-
export { ALWAYS, COARSE_TARGET_SCALE, DEFAULT_ALLOWED_CAPABILITIES, DEFAULT_DEVICE_PROFILE, INGEST_STRING_MIMES, NEVER, PREDICATE_TARGET, TOOL_OFFHAND_ID, actionBindings, buildDepsFromRequires, buildRouteRegistry, buildRuleCtx, buildToolOffhandBindings, clientToCanvas, clientToCanvasRect, createDispatcher, describeRule, dlog, evaluate, filterEligible, findConflicts, isDev, isEditableTarget, itemsFromClipboardData, itemsFromDataTransfer, itemsFromFiles, liveScope, makeToolOffhandAction, matchBest, matchSorted, matchesKeyBinding, offhandKeyFor, openPointerSession, pointerGestureId, recordModeSwitch, reportRouteConflicts, resolveDeviceProfile, resolveParams,
|
|
1527
|
-
//# sourceMappingURL=chunk-
|
|
1528
|
-
//# sourceMappingURL=chunk-
|
|
1926
|
+
export { ALWAYS, COARSE_TARGET_SCALE, DEFAULT_ALLOWED_CAPABILITIES, DEFAULT_DEVICE_PROFILE, DepNode, INGEST_STRING_MIMES, NEVER, PREDICATE_TARGET, ScopeNode, TOOL_OFFHAND_ID, actionBindings, buildDepsFromRequires, buildRouteRegistry, buildRuleCtx, buildToolOffhandBindings, clientToCanvas, clientToCanvasRect, createDepRegistry, createDispatcher, depNodeOf, describeRule, dlog, evaluate, filterEligible, findConflicts, isDev, isEditableTarget, isTool, itemsFromClipboardData, itemsFromDataTransfer, itemsFromFiles, liveScope, makeToolOffhandAction, matchBest, matchSorted, matchSortedWithBarred, matchesKeyBinding, offhandKeyFor, openPointerSession, pointerGestureId, preferContextual, publishLiveDispatch, pushOwner, recordModeSwitch, reportRouteConflicts, resolveDeviceProfile, resolveParams, routesForSpec, ruleCanHoldIn, rulesExclusive, scopeBindings, targetConsultsAffordance, validateActionDefaultBinding, validateActionId };
|
|
1927
|
+
//# sourceMappingURL=chunk-Q3Y3DPKH.js.map
|
|
1928
|
+
//# sourceMappingURL=chunk-Q3Y3DPKH.js.map
|