@weasel-js/routing 1.6.1 → 1.7.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/README.md +3 -1
- package/dist/{chunk-VEC3ACV5.js → chunk-ZGKJWZ4S.js} +774 -380
- package/dist/chunk-ZGKJWZ4S.js.map +1 -0
- package/dist/{index-CKmbNpwl.d.ts → index-BV53gfyB.d.ts} +649 -257
- package/dist/index.d.ts +2 -2
- package/dist/index.js +84 -8
- package/dist/index.js.map +1 -1
- package/dist/internal--mYnGecm.d.ts +275 -0
- package/dist/internal.d.ts +7 -0
- package/dist/internal.js +3 -0
- package/dist/internal.js.map +1 -0
- package/dist/react.d.ts +213 -344
- package/dist/react.js +456 -406
- package/dist/react.js.map +1 -1
- package/package.json +10 -5
- package/dist/chunk-VEC3ACV5.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",
|
|
@@ -469,11 +774,14 @@ function liveInView(binding, view) {
|
|
|
469
774
|
const views = binding.opts?.views;
|
|
470
775
|
return views === void 0 || views.includes(view);
|
|
471
776
|
}
|
|
777
|
+
function namesView(binding, view) {
|
|
778
|
+
return binding.opts?.views?.includes(view) ?? false;
|
|
779
|
+
}
|
|
472
780
|
function preferViewScoped(matches, view) {
|
|
473
781
|
const named = [];
|
|
474
782
|
const rest = [];
|
|
475
783
|
for (const m of matches) {
|
|
476
|
-
(m.binding
|
|
784
|
+
(namesView(m.binding, view) ? named : rest).push(m);
|
|
477
785
|
}
|
|
478
786
|
return named.length === 0 ? [...matches] : [...named, ...rest];
|
|
479
787
|
}
|
|
@@ -491,12 +799,39 @@ function isEligible(action, ruleCtx) {
|
|
|
491
799
|
function describeEligible(eligible) {
|
|
492
800
|
return describeRule(eligibleToRule(eligible));
|
|
493
801
|
}
|
|
494
|
-
function
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
802
|
+
function preferContextual(matches, actionLookup, holds, view) {
|
|
803
|
+
const out = [];
|
|
804
|
+
let run = [];
|
|
805
|
+
const flush = () => {
|
|
806
|
+
const gated = run.filter((m) => {
|
|
807
|
+
const action = actionLookup(m.binding.actionId);
|
|
808
|
+
return action?.eligible !== void 0 && holds(action);
|
|
809
|
+
});
|
|
810
|
+
out.push(...gated, ...run.filter((m) => !gated.includes(m)));
|
|
811
|
+
run = [];
|
|
812
|
+
};
|
|
813
|
+
for (const m of matches) {
|
|
814
|
+
const head = run[0];
|
|
815
|
+
if (head && (head.scope !== m.scope || namesView(head.binding, view) !== namesView(m.binding, view) || !sameSpecificity(head.binding.spec, m.binding.spec))) flush();
|
|
816
|
+
run.push(m);
|
|
817
|
+
}
|
|
818
|
+
flush();
|
|
819
|
+
return out;
|
|
820
|
+
}
|
|
821
|
+
var SPECIFICITY_PARTS = ["target", "mods", "phase", "exact"];
|
|
822
|
+
function placedBy(prev, cur, view, contextual) {
|
|
823
|
+
if (namesView(prev.binding, view) !== namesView(cur.binding, view)) return { step: "view" };
|
|
824
|
+
if (prev.scope !== cur.scope) return { step: "tier" };
|
|
825
|
+
const sp = specificity(prev.binding.spec);
|
|
826
|
+
const sc = specificity(cur.binding.spec);
|
|
827
|
+
const i = sp.findIndex((v, k) => v !== sc[k]);
|
|
828
|
+
if (i >= 0) return { step: "specificity", part: SPECIFICITY_PARTS[i] };
|
|
829
|
+
return contextual ? { step: "context" } : { step: "order" };
|
|
830
|
+
}
|
|
831
|
+
function sameSpecificity(a, b) {
|
|
832
|
+
const sa = specificity(a);
|
|
833
|
+
const sb = specificity(b);
|
|
834
|
+
return sa.every((v, i) => v === sb[i]);
|
|
500
835
|
}
|
|
501
836
|
var EMPTY_ENGAGED2 = /* @__PURE__ */ new Set();
|
|
502
837
|
function modifiersOf(e) {
|
|
@@ -628,11 +963,11 @@ function createDispatcher(opts) {
|
|
|
628
963
|
const ordered = [];
|
|
629
964
|
for (let i = ctx.hotkeyStack.length - 1; i >= 0; i--) {
|
|
630
965
|
const id = ctx.hotkeyStack[i];
|
|
631
|
-
const
|
|
632
|
-
if (
|
|
966
|
+
const entry = ctx.entriesById.get(id);
|
|
967
|
+
if (entry && !ordered.includes(entry)) ordered.push(entry);
|
|
633
968
|
}
|
|
634
|
-
for (const
|
|
635
|
-
if (!ordered.includes(
|
|
969
|
+
for (const entry of ctx.entriesById.values()) {
|
|
970
|
+
if (!ordered.includes(entry)) ordered.push(entry);
|
|
636
971
|
}
|
|
637
972
|
const capsCtx = ctx.getRuleCtx?.();
|
|
638
973
|
const allows = capsCtx ? (tags) => tags.every((tag) => capsCtx.allowedCapabilities.has(tag)) : void 0;
|
|
@@ -646,7 +981,7 @@ function createDispatcher(opts) {
|
|
|
646
981
|
const targetScope = action.scope === "hotkey" ? "hotkey" : "ambient";
|
|
647
982
|
for (const binding of actionBindings(action)) {
|
|
648
983
|
if (!liveInView(binding, view)) continue;
|
|
649
|
-
result.push({ binding, scope: targetScope,
|
|
984
|
+
result.push({ binding, scope: targetScope, ownerId: null });
|
|
650
985
|
}
|
|
651
986
|
}
|
|
652
987
|
return result;
|
|
@@ -753,209 +1088,302 @@ function createDispatcher(opts) {
|
|
|
753
1088
|
pinchStartSpreads.delete(id);
|
|
754
1089
|
}
|
|
755
1090
|
}
|
|
756
|
-
const
|
|
757
|
-
const
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
1091
|
+
const r = rank(event, ctx, DEV2);
|
|
1092
|
+
const walked = walk(r, event, ctx, (match, action, deps) => invoke(event, match, action, deps), false);
|
|
1093
|
+
const outcome = walked.stop === "fired" ? "handled" : "unhandled";
|
|
1094
|
+
if (DEV2) recordTrace(buildRecord(r, walked, event, ctx, false, outcome));
|
|
1095
|
+
return outcome;
|
|
1096
|
+
}
|
|
1097
|
+
function invoke(event, match, action, deps) {
|
|
1098
|
+
if (action.invoker?.timing === "immediate") {
|
|
1099
|
+
try {
|
|
1100
|
+
const resolved = resolveParams(match.binding.opts?.params);
|
|
1101
|
+
let params;
|
|
1102
|
+
if (event.kind === "wheel") {
|
|
1103
|
+
params = {
|
|
1104
|
+
deltaX: event.deltaX,
|
|
1105
|
+
deltaY: event.deltaY,
|
|
1106
|
+
clientX: event.clientX,
|
|
1107
|
+
clientY: event.clientY,
|
|
1108
|
+
affordance: event.affordance,
|
|
1109
|
+
...resolved
|
|
1110
|
+
};
|
|
1111
|
+
} else if (event.kind === "pinch") {
|
|
1112
|
+
params = {
|
|
1113
|
+
scale: event.scale,
|
|
1114
|
+
rotation: event.rotation,
|
|
1115
|
+
clientX: event.clientX,
|
|
1116
|
+
clientY: event.clientY,
|
|
1117
|
+
affordance: event.affordance,
|
|
1118
|
+
...resolved
|
|
1119
|
+
};
|
|
1120
|
+
} else if (event.kind === "click" || event.kind === "doubleclick") {
|
|
1121
|
+
params = {
|
|
1122
|
+
worldX: event.x,
|
|
1123
|
+
worldY: event.y,
|
|
1124
|
+
affordance: event.affordance,
|
|
1125
|
+
// Press point as well as release point — an action that places
|
|
1126
|
+
// geometry at the click wants the former. See `ClickEvent`.
|
|
1127
|
+
...event.kind === "click" ? { pressX: event.pressX, pressY: event.pressY } : {},
|
|
1128
|
+
mods: modifiersOf(event),
|
|
1129
|
+
...resolved
|
|
1130
|
+
};
|
|
1131
|
+
} else if (event.kind === "contextmenu" || event.kind === "longpress") {
|
|
1132
|
+
params = {
|
|
1133
|
+
worldX: event.x,
|
|
1134
|
+
worldY: event.y,
|
|
1135
|
+
affordance: event.affordance,
|
|
1136
|
+
bodyTarget: event.bodyTarget,
|
|
1137
|
+
mods: modifiersOf(event),
|
|
1138
|
+
...resolved
|
|
1139
|
+
};
|
|
1140
|
+
} else if (event.kind === "pointerdown") {
|
|
1141
|
+
params = {
|
|
1142
|
+
worldX: event.x,
|
|
1143
|
+
worldY: event.y,
|
|
1144
|
+
affordance: event.affordance,
|
|
1145
|
+
bodyTarget: event.bodyTarget,
|
|
1146
|
+
mods: modifiersOf(event),
|
|
1147
|
+
...resolved
|
|
1148
|
+
};
|
|
1149
|
+
} else if (event.kind === "drop" || event.kind === "paste") {
|
|
1150
|
+
params = {
|
|
1151
|
+
items: event.items,
|
|
1152
|
+
via: event.kind,
|
|
1153
|
+
...event.kind === "drop" && event.x !== void 0 ? { worldX: event.x, worldY: event.y } : {},
|
|
1154
|
+
...resolved
|
|
1155
|
+
};
|
|
1156
|
+
} else {
|
|
1157
|
+
params = resolved;
|
|
1158
|
+
}
|
|
1159
|
+
action.invoker.run(deps, params);
|
|
1160
|
+
} catch (err) {
|
|
1161
|
+
console.error(`weasel dispatcher: action "${action.id}" invoker threw`, err);
|
|
1162
|
+
}
|
|
1163
|
+
return "fired";
|
|
785
1164
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
const action = actionMap.get(match.binding.actionId);
|
|
791
|
-
if (!action) {
|
|
792
|
-
traceCandidates.push({ actionId: match.binding.actionId, scope: match.scope, enabledResult: "no-such-action" });
|
|
793
|
-
console.warn(
|
|
794
|
-
`weasel dispatcher: binding resolved actionId "${match.binding.actionId}" which has no registered action. Skipping. (misconfiguration)`
|
|
1165
|
+
if (action.invoker?.timing === "ongoing") {
|
|
1166
|
+
if (event.kind === "pointerdown" && event.stage === "press") {
|
|
1167
|
+
console.error(
|
|
1168
|
+
`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.`
|
|
795
1169
|
);
|
|
796
|
-
|
|
1170
|
+
return "misbound";
|
|
797
1171
|
}
|
|
798
|
-
const
|
|
799
|
-
if (
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
1172
|
+
const gestureId = gestureIdFor(event);
|
|
1173
|
+
if (event.kind === "pointerdown") {
|
|
1174
|
+
dragOrigins.set(gestureId, {
|
|
1175
|
+
x: event.x ?? 0,
|
|
1176
|
+
y: event.y ?? 0,
|
|
1177
|
+
...event.clientX !== void 0 ? { clientX: event.clientX } : {},
|
|
1178
|
+
...event.clientY !== void 0 ? { clientY: event.clientY } : {}
|
|
1179
|
+
});
|
|
1180
|
+
dragPoints.set(gestureId, [{
|
|
1181
|
+
x: event.x ?? 0,
|
|
1182
|
+
y: event.y ?? 0,
|
|
1183
|
+
...event.pressure !== void 0 ? { pressure: event.pressure } : {},
|
|
1184
|
+
...event.tiltX !== void 0 ? { tiltX: event.tiltX } : {},
|
|
1185
|
+
...event.tiltY !== void 0 ? { tiltY: event.tiltY } : {}
|
|
1186
|
+
}]);
|
|
805
1187
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
try {
|
|
809
|
-
const resolved = resolveParams(match.binding.opts?.params);
|
|
810
|
-
let params;
|
|
811
|
-
if (event.kind === "wheel") {
|
|
812
|
-
params = {
|
|
813
|
-
deltaX: event.deltaX,
|
|
814
|
-
deltaY: event.deltaY,
|
|
815
|
-
clientX: event.clientX,
|
|
816
|
-
clientY: event.clientY,
|
|
817
|
-
affordance: event.affordance,
|
|
818
|
-
...resolved
|
|
819
|
-
};
|
|
820
|
-
} else if (event.kind === "pinch") {
|
|
821
|
-
params = {
|
|
822
|
-
scale: event.scale,
|
|
823
|
-
rotation: event.rotation,
|
|
824
|
-
clientX: event.clientX,
|
|
825
|
-
clientY: event.clientY,
|
|
826
|
-
affordance: event.affordance,
|
|
827
|
-
...resolved
|
|
828
|
-
};
|
|
829
|
-
} else if (event.kind === "click" || event.kind === "doubleclick") {
|
|
830
|
-
params = {
|
|
831
|
-
worldX: event.x,
|
|
832
|
-
worldY: event.y,
|
|
833
|
-
affordance: event.affordance,
|
|
834
|
-
// Press point as well as release point — an action that places
|
|
835
|
-
// geometry at the click wants the former. See `ClickEvent`.
|
|
836
|
-
...event.kind === "click" ? { pressX: event.pressX, pressY: event.pressY } : {},
|
|
837
|
-
mods: modifiersOf(event),
|
|
838
|
-
...resolved
|
|
839
|
-
};
|
|
840
|
-
} else if (event.kind === "contextmenu" || event.kind === "longpress") {
|
|
841
|
-
params = {
|
|
842
|
-
worldX: event.x,
|
|
843
|
-
worldY: event.y,
|
|
844
|
-
affordance: event.affordance,
|
|
845
|
-
bodyTarget: event.bodyTarget,
|
|
846
|
-
mods: modifiersOf(event),
|
|
847
|
-
...resolved
|
|
848
|
-
};
|
|
849
|
-
} else if (event.kind === "pointerdown") {
|
|
850
|
-
params = {
|
|
851
|
-
worldX: event.x,
|
|
852
|
-
worldY: event.y,
|
|
853
|
-
affordance: event.affordance,
|
|
854
|
-
bodyTarget: event.bodyTarget,
|
|
855
|
-
mods: modifiersOf(event),
|
|
856
|
-
...resolved
|
|
857
|
-
};
|
|
858
|
-
} else if (event.kind === "drop" || event.kind === "paste") {
|
|
859
|
-
params = {
|
|
860
|
-
items: event.items,
|
|
861
|
-
via: event.kind,
|
|
862
|
-
...event.kind === "drop" && event.x !== void 0 ? { worldX: event.x, worldY: event.y } : {},
|
|
863
|
-
...resolved
|
|
864
|
-
};
|
|
865
|
-
} else {
|
|
866
|
-
params = resolved;
|
|
867
|
-
}
|
|
868
|
-
action.invoker.run(deps, params);
|
|
869
|
-
} catch (err) {
|
|
870
|
-
console.error(`weasel dispatcher: action "${action.id}" invoker threw`, err);
|
|
871
|
-
}
|
|
872
|
-
finishTrace(action.id, "handled");
|
|
873
|
-
return "handled";
|
|
1188
|
+
if (event.kind === "multitouch" && event.spread !== void 0) {
|
|
1189
|
+
pinchStartSpreads.set(gestureId, event.spread);
|
|
874
1190
|
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
`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.`
|
|
879
|
-
);
|
|
880
|
-
finishTrace(action.id, "unhandled");
|
|
881
|
-
return "unhandled";
|
|
882
|
-
}
|
|
883
|
-
const gestureId = gestureIdFor(event);
|
|
1191
|
+
const invCtx = buildInvocationCtx(event, deps, gestureId);
|
|
1192
|
+
const handle = action.invoker.start(invCtx, match.binding.opts);
|
|
1193
|
+
if (isEmptyOngoingHandle(handle)) {
|
|
884
1194
|
if (event.kind === "pointerdown") {
|
|
885
|
-
dragOrigins.
|
|
886
|
-
|
|
887
|
-
y: event.y ?? 0,
|
|
888
|
-
...event.clientX !== void 0 ? { clientX: event.clientX } : {},
|
|
889
|
-
...event.clientY !== void 0 ? { clientY: event.clientY } : {}
|
|
890
|
-
});
|
|
891
|
-
dragPoints.set(gestureId, [{
|
|
892
|
-
x: event.x ?? 0,
|
|
893
|
-
y: event.y ?? 0,
|
|
894
|
-
...event.pressure !== void 0 ? { pressure: event.pressure } : {},
|
|
895
|
-
...event.tiltX !== void 0 ? { tiltX: event.tiltX } : {},
|
|
896
|
-
...event.tiltY !== void 0 ? { tiltY: event.tiltY } : {}
|
|
897
|
-
}]);
|
|
1195
|
+
dragOrigins.delete(gestureId);
|
|
1196
|
+
dragPoints.delete(gestureId);
|
|
898
1197
|
}
|
|
899
|
-
if (event.kind === "multitouch"
|
|
900
|
-
pinchStartSpreads.
|
|
901
|
-
}
|
|
902
|
-
const invCtx = buildInvocationCtx(event, deps, gestureId);
|
|
903
|
-
const handle = action.invoker.start(invCtx, match.binding.opts);
|
|
904
|
-
if (isEmptyOngoingHandle(handle)) {
|
|
905
|
-
if (event.kind === "pointerdown") {
|
|
906
|
-
dragOrigins.delete(gestureId);
|
|
907
|
-
dragPoints.delete(gestureId);
|
|
908
|
-
}
|
|
909
|
-
if (event.kind === "multitouch") {
|
|
910
|
-
pinchStartSpreads.delete(gestureId);
|
|
911
|
-
}
|
|
912
|
-
traceCandidates.push({ actionId: action.id, scope: match.scope, enabledResult: "empty-handle" });
|
|
913
|
-
continue;
|
|
1198
|
+
if (event.kind === "multitouch") {
|
|
1199
|
+
pinchStartSpreads.delete(gestureId);
|
|
914
1200
|
}
|
|
915
|
-
|
|
916
|
-
inFlightOwners.set(gestureId, match.ownerToolId);
|
|
917
|
-
inFlightActions.set(gestureId, action);
|
|
918
|
-
finishTrace(action.id, "handled");
|
|
919
|
-
return "handled";
|
|
1201
|
+
return "empty-handle";
|
|
920
1202
|
}
|
|
921
|
-
|
|
922
|
-
|
|
1203
|
+
inFlightHandles.set(gestureId, handle);
|
|
1204
|
+
inFlightOwners.set(gestureId, match.ownerId);
|
|
1205
|
+
inFlightActions.set(gestureId, action);
|
|
1206
|
+
return "fired";
|
|
923
1207
|
}
|
|
924
|
-
|
|
925
|
-
return "unhandled";
|
|
1208
|
+
return "fired";
|
|
926
1209
|
}
|
|
927
|
-
function
|
|
928
|
-
const
|
|
929
|
-
const
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
ctx.
|
|
1210
|
+
function rank(event, ctx, collectBarred) {
|
|
1211
|
+
const view = ctx.viewId ?? null;
|
|
1212
|
+
const outcome = matchSortedWithBarred(
|
|
1213
|
+
event,
|
|
1214
|
+
assembleScopedBindings(ctx),
|
|
1215
|
+
ctx.isMac,
|
|
1216
|
+
snapshotEngagedChannels(),
|
|
1217
|
+
{ collectBarred }
|
|
933
1218
|
);
|
|
934
|
-
|
|
1219
|
+
const sorted = preferViewScoped(outcome.matches, view);
|
|
935
1220
|
const actionMap = buildActionMap(ctx.actions);
|
|
936
1221
|
const ruleCtx = ruleCtxOf(ctx);
|
|
1222
|
+
const held = /* @__PURE__ */ new Map();
|
|
1223
|
+
const holds = (a) => {
|
|
1224
|
+
if (ruleCtx === void 0) return true;
|
|
1225
|
+
let h = held.get(a);
|
|
1226
|
+
if (h === void 0) held.set(a, h = isEligible(a, ruleCtx));
|
|
1227
|
+
return h;
|
|
1228
|
+
};
|
|
1229
|
+
const ordered = ruleCtx ? preferContextual(sorted, (id) => actionMap.get(id), holds, view) : sorted;
|
|
1230
|
+
return {
|
|
1231
|
+
ordered,
|
|
1232
|
+
barred: outcome.barred,
|
|
1233
|
+
...outcome.claimOwner !== void 0 ? { claimOwner: outcome.claimOwner } : {},
|
|
1234
|
+
actionMap,
|
|
1235
|
+
ruleCtx,
|
|
1236
|
+
eligible: (m) => {
|
|
1237
|
+
const a = actionMap.get(m.binding.actionId);
|
|
1238
|
+
return a === void 0 || holds(a);
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
function walk(r, event, ctx, attempt, evaluateShadowed) {
|
|
1243
|
+
const steps = [];
|
|
1244
|
+
const tried = /* @__PURE__ */ new Set();
|
|
1245
|
+
let stop = "exhausted";
|
|
1246
|
+
let winner = null;
|
|
1247
|
+
for (const match of r.ordered) {
|
|
1248
|
+
if (!r.eligible(match)) continue;
|
|
1249
|
+
const push = (step) => {
|
|
1250
|
+
steps.push({ match, step });
|
|
1251
|
+
};
|
|
1252
|
+
const actionId = match.binding.actionId;
|
|
1253
|
+
if (tried.has(actionId)) {
|
|
1254
|
+
push({ kind: "duplicate" });
|
|
1255
|
+
continue;
|
|
1256
|
+
}
|
|
1257
|
+
if (stop !== "exhausted" && !evaluateShadowed) {
|
|
1258
|
+
push({ kind: "not-asked" });
|
|
1259
|
+
continue;
|
|
1260
|
+
}
|
|
1261
|
+
tried.add(actionId);
|
|
1262
|
+
const action = r.actionMap.get(actionId);
|
|
1263
|
+
if (!action) {
|
|
1264
|
+
if (attempt) {
|
|
1265
|
+
console.warn(
|
|
1266
|
+
`weasel dispatcher: binding resolved actionId "${actionId}" which has no registered action. Skipping. (misconfiguration)`
|
|
1267
|
+
);
|
|
1268
|
+
}
|
|
1269
|
+
push({ kind: "no-such-action" });
|
|
1270
|
+
continue;
|
|
1271
|
+
}
|
|
1272
|
+
const deps = buildDepsFromRequires(action, ctx.depRegistry);
|
|
1273
|
+
const enabled = action.enabled ? action.enabled(deps, worldPointOf(event)) : true;
|
|
1274
|
+
if (enabled !== true) {
|
|
1275
|
+
push({ kind: "declined", reason: String(enabled) });
|
|
1276
|
+
continue;
|
|
1277
|
+
}
|
|
1278
|
+
if (stop !== "exhausted") {
|
|
1279
|
+
push({ kind: "outranked" });
|
|
1280
|
+
continue;
|
|
1281
|
+
}
|
|
1282
|
+
if (!attempt) {
|
|
1283
|
+
push({ kind: "would-fire" });
|
|
1284
|
+
stop = "fired";
|
|
1285
|
+
winner = { match, action };
|
|
1286
|
+
continue;
|
|
1287
|
+
}
|
|
1288
|
+
const result = attempt(match, action, deps);
|
|
1289
|
+
if (result === "empty-handle") {
|
|
1290
|
+
push({ kind: "empty-handle" });
|
|
1291
|
+
continue;
|
|
1292
|
+
}
|
|
1293
|
+
push({ kind: result === "fired" ? "fired" : "misbound" });
|
|
1294
|
+
stop = result;
|
|
1295
|
+
winner = { match, action };
|
|
1296
|
+
}
|
|
1297
|
+
return { steps, stop, winner };
|
|
1298
|
+
}
|
|
1299
|
+
function buildRecord(r, walked, event, ctx, predicted, outcome) {
|
|
1300
|
+
const view = ctx.viewId ?? null;
|
|
1301
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
1302
|
+
const candidateOf = (m) => {
|
|
1303
|
+
let c = candidates.get(m);
|
|
1304
|
+
if (!c) {
|
|
1305
|
+
const eligible = r.actionMap.get(m.binding.actionId)?.eligible;
|
|
1306
|
+
c = {
|
|
1307
|
+
actionId: m.binding.actionId,
|
|
1308
|
+
routes: routesForSpec(m.binding.spec),
|
|
1309
|
+
scope: m.scope,
|
|
1310
|
+
ownerId: m.ownerId,
|
|
1311
|
+
namesView: namesView(m.binding, view),
|
|
1312
|
+
specificity: specificity(m.binding.spec),
|
|
1313
|
+
...eligible !== void 0 ? { eligible: describeEligible(eligible) } : {}
|
|
1314
|
+
};
|
|
1315
|
+
candidates.set(m, c);
|
|
1316
|
+
}
|
|
1317
|
+
return c;
|
|
1318
|
+
};
|
|
1319
|
+
const dropped = [
|
|
1320
|
+
...r.barred.map((m) => ({
|
|
1321
|
+
candidate: candidateOf(m),
|
|
1322
|
+
filter: "claim",
|
|
1323
|
+
...r.claimOwner !== void 0 ? { owner: r.claimOwner } : {}
|
|
1324
|
+
}))
|
|
1325
|
+
];
|
|
1326
|
+
for (const m of r.ordered) {
|
|
1327
|
+
if (r.eligible(m)) continue;
|
|
1328
|
+
const c = candidateOf(m);
|
|
1329
|
+
dropped.push({ candidate: c, filter: "ineligible", rule: c.eligible ?? "" });
|
|
1330
|
+
}
|
|
1331
|
+
const gated = (m) => r.actionMap.get(m.binding.actionId)?.eligible !== void 0;
|
|
1332
|
+
const ranked = walked.steps.map(({ match, step }, i) => {
|
|
1333
|
+
const prev = walked.steps[i - 1]?.match;
|
|
1334
|
+
return {
|
|
1335
|
+
candidate: candidateOf(match),
|
|
1336
|
+
placedBy: prev ? placedBy(prev, match, view, r.ruleCtx !== void 0 && gated(prev) && !gated(match)) : { step: "first" },
|
|
1337
|
+
walk: step
|
|
1338
|
+
};
|
|
1339
|
+
});
|
|
1340
|
+
const eventKey = event.kind === "key" || event.kind === "key-held" ? event.key : void 0;
|
|
1341
|
+
const world = worldPointOf(event);
|
|
1342
|
+
return {
|
|
1343
|
+
kind: "dispatch",
|
|
1344
|
+
ts: Date.now(),
|
|
1345
|
+
input: {
|
|
1346
|
+
eventKind: event.kind,
|
|
1347
|
+
...eventKey !== void 0 ? { key: eventKey } : {},
|
|
1348
|
+
modifiers: modifiersOf(event),
|
|
1349
|
+
viewId: view,
|
|
1350
|
+
...world !== void 0 ? { world } : {},
|
|
1351
|
+
...r.ruleCtx !== void 0 ? { mode: r.ruleCtx.mode } : {}
|
|
1352
|
+
},
|
|
1353
|
+
matched: [...r.ordered, ...r.barred].map(candidateOf),
|
|
1354
|
+
dropped,
|
|
1355
|
+
ranked,
|
|
1356
|
+
predicted,
|
|
1357
|
+
fired: walked.winner?.action.id ?? null,
|
|
1358
|
+
outcome
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
function resolveAll(event, ctx, resolveOpts) {
|
|
1362
|
+
const evaluateShadowed = resolveOpts?.evaluateShadowed ?? false;
|
|
1363
|
+
const r = rank(event, ctx, false);
|
|
1364
|
+
if (r.ordered.length === 0) return [];
|
|
1365
|
+
const walked = walk(r, event, ctx, null, evaluateShadowed);
|
|
1366
|
+
const stepOf = new Map(walked.steps.map((s) => [s.match, s.step]));
|
|
937
1367
|
const verdictByAction = /* @__PURE__ */ new Map();
|
|
938
1368
|
let fired = false;
|
|
939
1369
|
const out = [];
|
|
940
|
-
for (const match of
|
|
1370
|
+
for (const match of r.ordered) {
|
|
941
1371
|
const actionId = match.binding.actionId;
|
|
942
|
-
const action = actionMap.get(actionId);
|
|
1372
|
+
const action = r.actionMap.get(actionId);
|
|
943
1373
|
if (!action) continue;
|
|
944
1374
|
let verdict = verdictByAction.get(actionId);
|
|
945
1375
|
if (verdict === void 0) {
|
|
946
|
-
if (
|
|
947
|
-
verdict = { kind: "shadowed" };
|
|
948
|
-
} else if (ruleCtx && action.eligible && !isEligible(action, ruleCtx)) {
|
|
949
|
-
verdict = { kind: "ineligible", reason: describeEligible(action.eligible) };
|
|
1376
|
+
if (!r.eligible(match)) {
|
|
1377
|
+
verdict = fired && !evaluateShadowed ? { kind: "shadowed" } : { kind: "ineligible", reason: describeEligible(action.eligible) };
|
|
950
1378
|
} else {
|
|
951
|
-
const
|
|
952
|
-
if (
|
|
953
|
-
verdict = { kind: "disabled", reason: String(disabled) };
|
|
954
|
-
} else if (fired) {
|
|
955
|
-
verdict = { kind: "shadowed" };
|
|
956
|
-
} else {
|
|
1379
|
+
const step = stepOf.get(match);
|
|
1380
|
+
if (step?.kind === "would-fire") {
|
|
957
1381
|
verdict = { kind: "would-fire" };
|
|
958
1382
|
fired = true;
|
|
1383
|
+
} else if (step?.kind === "declined") {
|
|
1384
|
+
verdict = { kind: "disabled", reason: step.reason };
|
|
1385
|
+
} else {
|
|
1386
|
+
verdict = { kind: "shadowed" };
|
|
959
1387
|
}
|
|
960
1388
|
}
|
|
961
1389
|
verdictByAction.set(actionId, verdict);
|
|
@@ -967,7 +1395,7 @@ function createDispatcher(opts) {
|
|
|
967
1395
|
action,
|
|
968
1396
|
binding: match.binding,
|
|
969
1397
|
scope: match.scope,
|
|
970
|
-
|
|
1398
|
+
ownerId: match.ownerId,
|
|
971
1399
|
specificity: specificity(match.binding.spec),
|
|
972
1400
|
verdict
|
|
973
1401
|
});
|
|
@@ -975,15 +1403,21 @@ function createDispatcher(opts) {
|
|
|
975
1403
|
return out;
|
|
976
1404
|
}
|
|
977
1405
|
function resolveOnly(event, ctx) {
|
|
978
|
-
const
|
|
1406
|
+
const r = rank(event, ctx, false);
|
|
1407
|
+
const { winner } = walk(r, event, ctx, null, false);
|
|
979
1408
|
if (!winner) return null;
|
|
980
1409
|
return {
|
|
981
|
-
actionId: winner.
|
|
1410
|
+
actionId: winner.action.id,
|
|
982
1411
|
action: winner.action,
|
|
983
|
-
scope: winner.scope,
|
|
984
|
-
|
|
1412
|
+
scope: winner.match.scope,
|
|
1413
|
+
ownerId: winner.match.ownerId
|
|
985
1414
|
};
|
|
986
1415
|
}
|
|
1416
|
+
function explain(event, ctx, explainOpts) {
|
|
1417
|
+
const r = rank(event, ctx, true);
|
|
1418
|
+
const walked = walk(r, event, ctx, null, explainOpts?.evaluateShadowed ?? false);
|
|
1419
|
+
return buildRecord(r, walked, event, ctx, true, walked.winner ? "handled" : "unhandled");
|
|
1420
|
+
}
|
|
987
1421
|
function cancelAll(reason) {
|
|
988
1422
|
const stubCtx = {
|
|
989
1423
|
world: { x: 0, y: 0 },
|
|
@@ -1123,6 +1557,7 @@ function createDispatcher(opts) {
|
|
|
1123
1557
|
handleInput: handleInputWithNotify,
|
|
1124
1558
|
resolveOnly,
|
|
1125
1559
|
resolveAll,
|
|
1560
|
+
explain,
|
|
1126
1561
|
cancelAll: cancelAllWithNotify,
|
|
1127
1562
|
inFlight,
|
|
1128
1563
|
inFlightCursor,
|
|
@@ -1134,6 +1569,34 @@ function createDispatcher(opts) {
|
|
|
1134
1569
|
};
|
|
1135
1570
|
}
|
|
1136
1571
|
|
|
1572
|
+
// src/interactions/dispatcher/longPressState.ts
|
|
1573
|
+
var LONG_PRESS_MS = 500;
|
|
1574
|
+
var nowMs = () => typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
1575
|
+
function createLongPressStore() {
|
|
1576
|
+
let current = null;
|
|
1577
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1578
|
+
return {
|
|
1579
|
+
get: () => current,
|
|
1580
|
+
progress: (now = nowMs()) => {
|
|
1581
|
+
if (!current) return 0;
|
|
1582
|
+
if (!(current.duration > 0)) return 1;
|
|
1583
|
+
const t = (now - current.startedAt) / current.duration;
|
|
1584
|
+
return t <= 0 ? 0 : t >= 1 ? 1 : t;
|
|
1585
|
+
},
|
|
1586
|
+
subscribe: (fn) => {
|
|
1587
|
+
listeners.add(fn);
|
|
1588
|
+
return () => {
|
|
1589
|
+
listeners.delete(fn);
|
|
1590
|
+
};
|
|
1591
|
+
},
|
|
1592
|
+
set: (next) => {
|
|
1593
|
+
if (next === current) return;
|
|
1594
|
+
current = next;
|
|
1595
|
+
for (const fn of [...listeners]) fn();
|
|
1596
|
+
}
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1137
1600
|
// src/interactions/pointerSession/pointerSession.ts
|
|
1138
1601
|
var LOST_CAPTURE_EVENT = "lostpointercapture";
|
|
1139
1602
|
function reportsButtons(down) {
|
|
@@ -1213,117 +1676,17 @@ function openPointerSession(origin, down, cb, opts = {}) {
|
|
|
1213
1676
|
};
|
|
1214
1677
|
}
|
|
1215
1678
|
|
|
1216
|
-
// src/tools/
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
key: "keyDown",
|
|
1220
|
-
"key-held": "keyHeld",
|
|
1221
|
-
wheel: "wheel",
|
|
1222
|
-
pinch: "pinch",
|
|
1223
|
-
click: "click",
|
|
1224
|
-
doubleClick: "dblTap",
|
|
1225
|
-
contextMenu: "contextMenu",
|
|
1226
|
-
longPress: "longPress",
|
|
1227
|
-
drag: "drag",
|
|
1228
|
-
pointerDown: "pointerDown",
|
|
1229
|
-
multiTouch: void 0,
|
|
1230
|
-
multiTouchTap: "multiTouchTap",
|
|
1231
|
-
drop: "drop",
|
|
1232
|
-
paste: "paste"
|
|
1233
|
-
};
|
|
1234
|
-
function buildRouteRegistry(tools) {
|
|
1235
|
-
const out = [];
|
|
1236
|
-
for (const tool of tools) {
|
|
1237
|
-
for (const binding of tool.bindings ?? []) {
|
|
1238
|
-
const entry = entryFor(tool.id, binding.spec, binding.actionId);
|
|
1239
|
-
if (entry) out.push(entry);
|
|
1240
|
-
}
|
|
1241
|
-
}
|
|
1242
|
-
return out;
|
|
1243
|
-
}
|
|
1244
|
-
function routeGestureForSpecKind(kind) {
|
|
1245
|
-
const gesture = SPEC_KIND_TO_GESTURE[kind];
|
|
1246
|
-
return gesture && isKnownGestureName(gesture) ? gesture : void 0;
|
|
1247
|
-
}
|
|
1248
|
-
function routesForSpec(spec) {
|
|
1249
|
-
const gesture = routeGestureForSpecKind(spec.kind);
|
|
1250
|
-
if (!gesture) return [];
|
|
1251
|
-
const descriptor = getGestureDescriptor(gesture);
|
|
1252
|
-
const modifiers = parseModSpec("mods" in spec ? spec.mods : void 0);
|
|
1253
|
-
const phases = phaseAtomsForSpec("phase" in spec ? spec.phase : void 0);
|
|
1254
|
-
const target = descriptor.hasTarget ? routeTargetForSpec("target" in spec ? spec.target : void 0) ?? "*" : void 0;
|
|
1255
|
-
const args = descriptor.arg ? routeArgsForSpec(spec, descriptor.arg.default) : [void 0];
|
|
1256
|
-
return args.map((arg) => formatRoute({ phases, gesture, arg, target, modifiers }));
|
|
1257
|
-
}
|
|
1258
|
-
function entryFor(toolId, spec, actionId) {
|
|
1259
|
-
const gesture = SPEC_KIND_TO_GESTURE[spec.kind];
|
|
1260
|
-
if (!gesture || !isKnownGestureName(gesture)) return null;
|
|
1261
|
-
const descriptor = getGestureDescriptor(gesture);
|
|
1262
|
-
return {
|
|
1263
|
-
toolId,
|
|
1264
|
-
actionId,
|
|
1265
|
-
gesture,
|
|
1266
|
-
phase: phaseOf("phase" in spec ? spec.phase : void 0),
|
|
1267
|
-
modifiers: parseModSpec("mods" in spec ? spec.mods : void 0),
|
|
1268
|
-
arg: descriptor.arg ? argOf(spec, descriptor.arg.default) : void 0,
|
|
1269
|
-
...descriptor.arg ? argAlternativesOf(spec, descriptor.arg.default) : {},
|
|
1270
|
-
target: descriptor.hasTarget ? routeTargetForSpec("target" in spec ? spec.target : void 0) : void 0,
|
|
1271
|
-
spec
|
|
1272
|
-
};
|
|
1273
|
-
}
|
|
1274
|
-
function phaseOf(phase) {
|
|
1275
|
-
if (phase === void 0 || phase === "*") return "any";
|
|
1276
|
-
if (phase === "initial" || phase === "engaged") return phase;
|
|
1277
|
-
const distinct = new Set(phase.map((atom) => atom.phase));
|
|
1278
|
-
if (distinct.size !== 1) return "any";
|
|
1279
|
-
const only = [...distinct][0];
|
|
1280
|
-
return only === "initial" || only === "engaged" ? only : "any";
|
|
1281
|
-
}
|
|
1282
|
-
function parseModSpec(mods) {
|
|
1283
|
-
if (!mods) return {};
|
|
1284
|
-
const out = {};
|
|
1285
|
-
for (const [name, req] of Object.entries(mods)) {
|
|
1286
|
-
if (req === true) out[name] = "required";
|
|
1287
|
-
else if (req === "optional") out[name] = "optional";
|
|
1288
|
-
}
|
|
1289
|
-
return out;
|
|
1290
|
-
}
|
|
1291
|
-
function phaseAtomsForSpec(phase) {
|
|
1292
|
-
if (phase === void 0) return [{ channel: "&", phase: "*" }];
|
|
1293
|
-
if (phase === "initial" || phase === "engaged" || phase === "*") {
|
|
1294
|
-
return [{ channel: "&", phase }];
|
|
1295
|
-
}
|
|
1296
|
-
return phase;
|
|
1297
|
-
}
|
|
1298
|
-
function routeTargetForSpec(target) {
|
|
1299
|
-
if (target === void 0) return void 0;
|
|
1300
|
-
return typeof target === "string" ? target : PREDICATE_TARGET;
|
|
1301
|
-
}
|
|
1302
|
-
function routeArgsForSpec(spec, fallback) {
|
|
1303
|
-
if ("key" in spec) return Array.isArray(spec.key) ? spec.key : [spec.key];
|
|
1304
|
-
if ("fingers" in spec) return [String(spec.fingers)];
|
|
1305
|
-
if ("direction" in spec) return [spec.direction ?? fallback];
|
|
1306
|
-
if ("types" in spec) return [spec.types?.length ? spec.types.join("|") : fallback];
|
|
1307
|
-
return [fallback];
|
|
1308
|
-
}
|
|
1309
|
-
function argOf(spec, fallback) {
|
|
1310
|
-
const args = routeArgsForSpec(spec, fallback);
|
|
1311
|
-
return args.length > 1 ? args.join("|") : args[0];
|
|
1312
|
-
}
|
|
1313
|
-
function argAlternativesOf(spec, fallback) {
|
|
1314
|
-
const types = "types" in spec ? spec.types : void 0;
|
|
1315
|
-
const alts = types?.length ? types : routeArgsForSpec(spec, fallback).filter((a) => a !== void 0);
|
|
1316
|
-
return alts.length > 1 ? { argAlternatives: alts } : {};
|
|
1679
|
+
// src/tools/types.ts
|
|
1680
|
+
function isTool(entry) {
|
|
1681
|
+
return !!(entry.eligibility?.focus || entry.eligibility?.offhand);
|
|
1317
1682
|
}
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
function findConflicts(tools) {
|
|
1321
|
-
return findConflictsKeyed(tools).map((k) => k.conflict);
|
|
1683
|
+
function findConflicts(entries) {
|
|
1684
|
+
return findConflictsKeyed(entries).map((k) => k.conflict);
|
|
1322
1685
|
}
|
|
1323
|
-
function findConflictsKeyed(
|
|
1324
|
-
const
|
|
1686
|
+
function findConflictsKeyed(entries, rules, modes = KIT_MODES) {
|
|
1687
|
+
const rows = buildRouteRegistry(entries);
|
|
1325
1688
|
const groups = /* @__PURE__ */ new Map();
|
|
1326
|
-
for (const entry of
|
|
1689
|
+
for (const entry of rows) {
|
|
1327
1690
|
for (const arg of argKeysOf(entry)) {
|
|
1328
1691
|
const key = `${entry.phase}|${entry.gesture}|${arg}|${targetKey(entry)}|${canonicalModifiers(entry.modifiers)}`;
|
|
1329
1692
|
const bucket = groups.get(key);
|
|
@@ -1332,8 +1695,11 @@ function findConflictsKeyed(tools) {
|
|
|
1332
1695
|
} else groups.set(key, [entry]);
|
|
1333
1696
|
}
|
|
1334
1697
|
}
|
|
1698
|
+
const ruleOf = (entry) => rules?.get(entry.ownerId) ?? ALWAYS;
|
|
1699
|
+
const tie = (e, f) => viewsTie(e.views, f.views) && rulesCanHoldTogether(ruleOf(e), ruleOf(f), modes);
|
|
1335
1700
|
const conflicts = [];
|
|
1336
|
-
for (const [key,
|
|
1701
|
+
for (const [key, all] of groups) {
|
|
1702
|
+
const bucket = all.filter((e) => all.some((f) => f !== e && tie(e, f)));
|
|
1337
1703
|
if (bucket.length < 2) continue;
|
|
1338
1704
|
const first = bucket[0];
|
|
1339
1705
|
conflicts.push({
|
|
@@ -1344,12 +1710,16 @@ function findConflictsKeyed(tools) {
|
|
|
1344
1710
|
arg: first.arg,
|
|
1345
1711
|
target: first.target,
|
|
1346
1712
|
modifiers: first.modifiers,
|
|
1347
|
-
|
|
1713
|
+
ownerIds: bucket.map((e) => e.ownerId)
|
|
1348
1714
|
}
|
|
1349
1715
|
});
|
|
1350
1716
|
}
|
|
1351
1717
|
return conflicts;
|
|
1352
1718
|
}
|
|
1719
|
+
function viewsTie(a, b) {
|
|
1720
|
+
if (a === void 0 || b === void 0) return a === b;
|
|
1721
|
+
return a.some((v) => b.includes(v));
|
|
1722
|
+
}
|
|
1353
1723
|
function argKeysOf(entry) {
|
|
1354
1724
|
const alts = entry.argAlternatives;
|
|
1355
1725
|
if (alts && alts.length > 0) return [...new Set(alts.map((a) => a.toLowerCase()))];
|
|
@@ -1372,43 +1742,67 @@ function predicateId(pred) {
|
|
|
1372
1742
|
}
|
|
1373
1743
|
return id;
|
|
1374
1744
|
}
|
|
1375
|
-
function
|
|
1745
|
+
function rulesCanHoldTogether(a, b, modes) {
|
|
1746
|
+
if (rulesExclusive(a, b)) return false;
|
|
1747
|
+
return modes.some((m) => ruleCanHoldIn(a, m) && ruleCanHoldIn(b, m));
|
|
1748
|
+
}
|
|
1749
|
+
var KIT_MODES = DEFAULT_MODES.map(activeModeOf);
|
|
1750
|
+
function actionAsEntry(action) {
|
|
1376
1751
|
return { id: action.id, eligibility: {}, bindings: actionBindings(action) };
|
|
1377
1752
|
}
|
|
1753
|
+
function eligibleRule(action) {
|
|
1754
|
+
const eligible = action.eligible;
|
|
1755
|
+
if (eligible === void 0) return void 0;
|
|
1756
|
+
return typeof eligible === "function" ? eligible.rule : eligible;
|
|
1757
|
+
}
|
|
1378
1758
|
function findScopedConflicts(scopes) {
|
|
1379
1759
|
const registry = Array.isArray(scopes.registry) ? scopes.registry : Object.values(scopes.registry);
|
|
1380
1760
|
const actions = scopes.actions ?? [];
|
|
1381
|
-
const
|
|
1382
|
-
const
|
|
1383
|
-
|
|
1761
|
+
const rules = /* @__PURE__ */ new Map();
|
|
1762
|
+
for (const a of actions) {
|
|
1763
|
+
const rule = eligibleRule(a);
|
|
1764
|
+
if (rule) rules.set(a.id, rule);
|
|
1765
|
+
}
|
|
1766
|
+
const hotkeyActions = actions.filter((a) => a.scope === "hotkey").map(actionAsEntry);
|
|
1767
|
+
const gatedActions = [];
|
|
1768
|
+
const ungated = [];
|
|
1769
|
+
for (const a of actions) {
|
|
1770
|
+
if (a.scope === "hotkey") continue;
|
|
1771
|
+
(a.eligible !== void 0 ? gatedActions : ungated).push(actionAsEntry(a));
|
|
1772
|
+
}
|
|
1773
|
+
const modes = scopes.modes ?? KIT_MODES;
|
|
1774
|
+
const ambient = [...scopes.ambient ?? [], ...ungated];
|
|
1384
1775
|
const out = [];
|
|
1385
1776
|
const seen = /* @__PURE__ */ new Set();
|
|
1386
1777
|
const add = (keyed) => {
|
|
1387
1778
|
for (const { key, conflict } of keyed) {
|
|
1388
|
-
const dedupeKey = `${key}|${conflict.
|
|
1779
|
+
const dedupeKey = `${key}|${conflict.ownerIds.join(",")}`;
|
|
1389
1780
|
if (seen.has(dedupeKey)) continue;
|
|
1390
1781
|
seen.add(dedupeKey);
|
|
1391
1782
|
out.push(conflict);
|
|
1392
1783
|
}
|
|
1393
1784
|
};
|
|
1394
|
-
for (const tool of [...registry, ...ambient, ...hotkeyActions])
|
|
1395
|
-
|
|
1785
|
+
for (const tool of [...registry, ...ambient, ...gatedActions, ...hotkeyActions]) {
|
|
1786
|
+
add(findConflictsKeyed([tool], rules, modes));
|
|
1787
|
+
}
|
|
1788
|
+
if (ambient.length > 1) add(findConflictsKeyed(ambient, rules, modes));
|
|
1789
|
+
if (gatedActions.length > 1) add(findConflictsKeyed(gatedActions, rules, modes));
|
|
1396
1790
|
const hotkey = [
|
|
1397
1791
|
...registry.filter((t) => t.def?.hotkey !== void 0),
|
|
1398
1792
|
...hotkeyActions
|
|
1399
1793
|
];
|
|
1400
|
-
if (hotkey.length > 1) add(findConflictsKeyed(hotkey));
|
|
1794
|
+
if (hotkey.length > 1) add(findConflictsKeyed(hotkey, rules, modes));
|
|
1401
1795
|
return out;
|
|
1402
1796
|
}
|
|
1403
1797
|
function formatConflict(conflict) {
|
|
1404
1798
|
const route = formatRoute({
|
|
1405
|
-
phases: [{ channel: "
|
|
1799
|
+
phases: [conflict.phase === "any" ? { channel: "*", phase: "*" } : { channel: "&", phase: conflict.phase }],
|
|
1406
1800
|
gesture: conflict.gesture,
|
|
1407
1801
|
arg: conflict.arg,
|
|
1408
1802
|
target: conflict.target,
|
|
1409
1803
|
modifiers: conflict.modifiers
|
|
1410
1804
|
});
|
|
1411
|
-
return `${route} \u2014 declared by ${conflict.
|
|
1805
|
+
return `${route} \u2014 declared by ${conflict.ownerIds.join(", ")}`;
|
|
1412
1806
|
}
|
|
1413
1807
|
function reportRouteConflicts(scopes, warn = (m) => console.warn(m)) {
|
|
1414
1808
|
const conflicts = findScopedConflicts(scopes);
|
|
@@ -1550,6 +1944,6 @@ function dlog(namespace, ...args) {
|
|
|
1550
1944
|
console.debug(`[${namespace}]`, ...args);
|
|
1551
1945
|
}
|
|
1552
1946
|
|
|
1553
|
-
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,
|
|
1554
|
-
//# sourceMappingURL=chunk-
|
|
1555
|
-
//# sourceMappingURL=chunk-
|
|
1947
|
+
export { ALWAYS, COARSE_TARGET_SCALE, DEFAULT_ALLOWED_CAPABILITIES, DEFAULT_DEVICE_PROFILE, DepNode, INGEST_STRING_MIMES, LONG_PRESS_MS, NEVER, PREDICATE_TARGET, ScopeNode, TOOL_OFFHAND_ID, actionBindings, buildDepsFromRequires, buildRouteRegistry, buildRuleCtx, buildToolOffhandBindings, clientToCanvas, clientToCanvasRect, createDepRegistry, createDispatcher, createLongPressStore, depNodeOf, describeRule, dlog, evaluate, findConflicts, isDev, isEditableTarget, isTool, itemsFromClipboardData, itemsFromDataTransfer, itemsFromFiles, liveScope, makeToolOffhandAction, matchBest, matchSorted, matchSortedWithBarred, matchesKeyBinding, offhandKeyFor, openPointerSession, pointerGestureId, publishLiveDispatch, pushOwner, recordModeSwitch, reportRouteConflicts, resolveDeviceProfile, resolveParams, routesForSpec, ruleCanHoldIn, rulesExclusive, scopeBindings, targetConsultsAffordance, validateActionDefaultBinding, validateActionId };
|
|
1948
|
+
//# sourceMappingURL=chunk-ZGKJWZ4S.js.map
|
|
1949
|
+
//# sourceMappingURL=chunk-ZGKJWZ4S.js.map
|