@weasel-js/gestures 1.9.4 → 1.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -4
- package/dist/index.d.ts +434 -219
- package/dist/index.js +521 -65
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
var GESTURE_DESCRIPTORS = [
|
|
3
3
|
{ name: "click", hasTarget: true },
|
|
4
4
|
{ name: "pointerDown", hasTarget: true },
|
|
5
|
+
{ name: "hover", hasTarget: true, arg: { name: "motion", values: ["move", "leave"], default: "move" } },
|
|
5
6
|
{ name: "dblTap", hasTarget: true },
|
|
6
7
|
{ name: "drag", hasTarget: true },
|
|
7
8
|
{ name: "wheel", hasTarget: true, arg: { name: "direction", values: ["up", "down", "*"], default: "*" } },
|
|
@@ -271,47 +272,21 @@ function keyRouteToSpec(r) {
|
|
|
271
272
|
return spec;
|
|
272
273
|
}
|
|
273
274
|
|
|
274
|
-
// src/ui/
|
|
275
|
-
function
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
let alt = "forbidden";
|
|
282
|
-
let ctrl = "forbidden";
|
|
283
|
-
let meta = "forbidden";
|
|
284
|
-
let shift = "forbidden";
|
|
285
|
-
if (mods2) {
|
|
286
|
-
alt = resolveSpecValue(mods2.alt);
|
|
287
|
-
shift = resolveSpecValue(mods2.shift);
|
|
288
|
-
if (mods2.mod !== void 0) {
|
|
289
|
-
const modReq = resolveSpecValue(mods2.mod);
|
|
290
|
-
if (isMac) meta = modReq;
|
|
291
|
-
else ctrl = modReq;
|
|
292
|
-
} else {
|
|
293
|
-
meta = resolveSpecValue(mods2.meta);
|
|
294
|
-
ctrl = resolveSpecValue(mods2.ctrl);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
return checkKey(alt, e.altKey) && checkKey(ctrl, e.ctrlKey) && checkKey(meta, e.metaKey) && checkKey(shift, e.shiftKey);
|
|
298
|
-
}
|
|
299
|
-
function checkKey(req, held) {
|
|
300
|
-
if (req === "required") return held;
|
|
301
|
-
if (req === "forbidden") return !held;
|
|
302
|
-
return true;
|
|
303
|
-
}
|
|
304
|
-
function matchKey(eventKey, specKey) {
|
|
305
|
-
const lower = eventKey.toLowerCase();
|
|
306
|
-
if (Array.isArray(specKey)) {
|
|
307
|
-
return specKey.some((k) => k.toLowerCase() === lower);
|
|
308
|
-
}
|
|
309
|
-
return specKey.toLowerCase() === lower;
|
|
275
|
+
// src/ui/ingestTypes.ts
|
|
276
|
+
function mimeMatchesGlob(mime, glob) {
|
|
277
|
+
const m = mime.toLowerCase();
|
|
278
|
+
const g = glob.toLowerCase();
|
|
279
|
+
if (g === "*" || g === "*/*") return true;
|
|
280
|
+
if (g.endsWith("/*")) return m.startsWith(g.slice(0, -1));
|
|
281
|
+
return m === g;
|
|
310
282
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
283
|
+
var UNKNOWN_MIME = "*/*";
|
|
284
|
+
function matchIngestTypes(items, types) {
|
|
285
|
+
if (!types || types.length === 0) return true;
|
|
286
|
+
return items.some((it) => it.mime === UNKNOWN_MIME || types.some((g) => mimeMatchesGlob(it.mime, g)));
|
|
314
287
|
}
|
|
288
|
+
|
|
289
|
+
// src/ui/matchTarget.ts
|
|
315
290
|
function affordanceKindOf(target) {
|
|
316
291
|
if (typeof target !== "object" || target === null) return void 0;
|
|
317
292
|
const k = target.kind;
|
|
@@ -364,6 +339,48 @@ function matchTarget(target, specTarget, bodyTarget, bodyKind) {
|
|
|
364
339
|
}
|
|
365
340
|
}
|
|
366
341
|
}
|
|
342
|
+
|
|
343
|
+
// src/ui/match.ts
|
|
344
|
+
function resolveSpecValue(value) {
|
|
345
|
+
if (value === true) return "required";
|
|
346
|
+
if (value === "optional") return "optional";
|
|
347
|
+
return "forbidden";
|
|
348
|
+
}
|
|
349
|
+
function matchModifiers(e, mods2, isMac) {
|
|
350
|
+
let alt = "forbidden";
|
|
351
|
+
let ctrl = "forbidden";
|
|
352
|
+
let meta = "forbidden";
|
|
353
|
+
let shift = "forbidden";
|
|
354
|
+
if (mods2) {
|
|
355
|
+
alt = resolveSpecValue(mods2.alt);
|
|
356
|
+
shift = resolveSpecValue(mods2.shift);
|
|
357
|
+
if (mods2.mod !== void 0) {
|
|
358
|
+
const modReq = resolveSpecValue(mods2.mod);
|
|
359
|
+
if (isMac) meta = modReq;
|
|
360
|
+
else ctrl = modReq;
|
|
361
|
+
} else {
|
|
362
|
+
meta = resolveSpecValue(mods2.meta);
|
|
363
|
+
ctrl = resolveSpecValue(mods2.ctrl);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return checkKey(alt, e.altKey) && checkKey(ctrl, e.ctrlKey) && checkKey(meta, e.metaKey) && checkKey(shift, e.shiftKey);
|
|
367
|
+
}
|
|
368
|
+
function checkKey(req, held) {
|
|
369
|
+
if (req === "required") return held;
|
|
370
|
+
if (req === "forbidden") return !held;
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
function matchKey(eventKey, specKey) {
|
|
374
|
+
const lower = eventKey.toLowerCase();
|
|
375
|
+
if (Array.isArray(specKey)) {
|
|
376
|
+
return specKey.some((k) => k.toLowerCase() === lower);
|
|
377
|
+
}
|
|
378
|
+
return specKey.toLowerCase() === lower;
|
|
379
|
+
}
|
|
380
|
+
function matchKeyOrCode(e, spec) {
|
|
381
|
+
if (spec.code !== void 0 && e.code !== void 0 && [spec.code].flat().includes(e.code)) return true;
|
|
382
|
+
return matchKey(e.key, spec.key);
|
|
383
|
+
}
|
|
367
384
|
function normalizePhase(spec) {
|
|
368
385
|
if (typeof spec === "string") {
|
|
369
386
|
return [{ channel: "&", phase: spec }];
|
|
@@ -391,16 +408,15 @@ function matchPhaseAtom(a, ctx) {
|
|
|
391
408
|
const isEngaged = ctx.engagedChannels.has(id);
|
|
392
409
|
return a.phase === "engaged" ? isEngaged : !isEngaged;
|
|
393
410
|
}
|
|
394
|
-
function
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
if (
|
|
399
|
-
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
return items.some((it) => types.some((g) => mimeMatchesGlob(it.mime, g)));
|
|
411
|
+
function matchPress(e, spec, isMac) {
|
|
412
|
+
if (e.kind !== "pointerdown") return false;
|
|
413
|
+
if (spec.kind === "drag") {
|
|
414
|
+
if (e.stage === "press") return false;
|
|
415
|
+
} else if (e.stage !== "press") {
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
if (!matchModifiers(e, spec.mods, isMac)) return false;
|
|
419
|
+
return matchTarget(e.affordance, spec.target, e.bodyTarget, e.bodyKind);
|
|
404
420
|
}
|
|
405
421
|
function matchSpec(e, spec, isMac, phaseCtx) {
|
|
406
422
|
if (spec.phase !== void 0) {
|
|
@@ -445,27 +461,25 @@ function matchSpec(e, spec, isMac, phaseCtx) {
|
|
|
445
461
|
if (!matchModifiers(e, spec.mods, isMac)) return false;
|
|
446
462
|
return matchTarget(e.affordance, spec.target, e.bodyTarget, e.bodyKind);
|
|
447
463
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
return matchTarget(e.affordance, spec.target, e.bodyTarget, e.bodyKind);
|
|
452
|
-
}
|
|
464
|
+
// Each happens at a point, so its target is what the event classified
|
|
465
|
+
// there: for a long-press, the affordance its press landed on.
|
|
466
|
+
case "contextMenu":
|
|
453
467
|
case "longPress": {
|
|
454
|
-
if (e.kind !== "longpress") return false;
|
|
455
|
-
if (
|
|
456
|
-
return matchTarget(e.affordance, spec.target, e.bodyTarget, e.bodyKind);
|
|
457
|
-
}
|
|
458
|
-
case "drag": {
|
|
459
|
-
if (e.kind !== "pointerdown") return false;
|
|
460
|
-
if (e.stage === "press") return false;
|
|
468
|
+
if (e.kind !== "contextmenu" && e.kind !== "longpress") return false;
|
|
469
|
+
if (e.kind !== spec.kind.toLowerCase()) return false;
|
|
461
470
|
if (!matchModifiers(e, spec.mods, isMac)) return false;
|
|
462
471
|
return matchTarget(e.affordance, spec.target, e.bodyTarget, e.bodyKind);
|
|
463
472
|
}
|
|
464
|
-
case "
|
|
465
|
-
if (e.kind !== "
|
|
473
|
+
case "hover": {
|
|
474
|
+
if (e.kind !== "hover") return false;
|
|
466
475
|
if (!matchModifiers(e, spec.mods, isMac)) return false;
|
|
467
|
-
|
|
476
|
+
const on = !e.off && matchTarget(e.affordance, spec.target, e.bodyTarget, e.bodyKind);
|
|
477
|
+
if (spec.motion !== "leave") return on;
|
|
478
|
+
return !on && e.from !== void 0 && matchTarget(e.from.affordance, spec.target, e.from.bodyTarget, e.from.bodyKind);
|
|
468
479
|
}
|
|
480
|
+
case "drag":
|
|
481
|
+
case "pointerDown":
|
|
482
|
+
return matchPress(e, spec, isMac);
|
|
469
483
|
case "multiTouch": {
|
|
470
484
|
if (e.kind !== "multitouch") return false;
|
|
471
485
|
if (e.fingers !== spec.fingers) return false;
|
|
@@ -505,6 +519,7 @@ var SPEC_KIND_TO_GESTURE = {
|
|
|
505
519
|
longPress: "longPress",
|
|
506
520
|
drag: "drag",
|
|
507
521
|
pointerDown: "pointerDown",
|
|
522
|
+
hover: "hover",
|
|
508
523
|
multiTouch: "multiTouch",
|
|
509
524
|
multiTouchTap: "multiTouchTap",
|
|
510
525
|
drop: "drop",
|
|
@@ -552,6 +567,8 @@ function routeToSpec(parsed) {
|
|
|
552
567
|
switch (kind) {
|
|
553
568
|
case void 0:
|
|
554
569
|
return fail(`${parsed.gesture} has no GestureSpec kind`);
|
|
570
|
+
case "hover":
|
|
571
|
+
return withMods({ kind, ...arg === "leave" ? { motion: "leave" } : {}, ...t });
|
|
555
572
|
case "click":
|
|
556
573
|
case "pointerDown":
|
|
557
574
|
case "doubleClick":
|
|
@@ -620,6 +637,10 @@ function partitionModifiers(mods2) {
|
|
|
620
637
|
}
|
|
621
638
|
return { required, optional };
|
|
622
639
|
}
|
|
640
|
+
function leftClause(target) {
|
|
641
|
+
if (target === void 0 || target === "*") return "off the surface";
|
|
642
|
+
return target === "empty" ? "off empty canvas" : `off a ${target}`;
|
|
643
|
+
}
|
|
623
644
|
function phaseAtomParts({ channel, phase }) {
|
|
624
645
|
const subject = channel === "&" ? "the tool" : channel === "*" ? "any tool" : `the ${channel} tool`;
|
|
625
646
|
if (phase === "initial") return [`${subject} is `, term("idle")];
|
|
@@ -681,6 +702,8 @@ function actionClause(parsed, required) {
|
|
|
681
702
|
return userDoes(required, `clicks${target}`);
|
|
682
703
|
case "pointerDown":
|
|
683
704
|
return userDoes(required, `presses${target}`);
|
|
705
|
+
case "hover":
|
|
706
|
+
return userDoes(required, arg === "leave" ? `moves the pointer ${leftClause(parsed.target)}` : `moves the pointer${target}`);
|
|
684
707
|
case "dblTap":
|
|
685
708
|
return userDoes(required, `double-taps${target}`);
|
|
686
709
|
case "drag":
|
|
@@ -730,6 +753,15 @@ function canonicalModifiers(mods2) {
|
|
|
730
753
|
return Object.entries(mods2).sort(([a], [b]) => a.localeCompare(b)).map(([name, req]) => `${name}=${req}`).join("&");
|
|
731
754
|
}
|
|
732
755
|
|
|
756
|
+
// src/ui/stylus.ts
|
|
757
|
+
function stylusOf(src) {
|
|
758
|
+
return {
|
|
759
|
+
...typeof src.pressure === "number" ? { pressure: src.pressure } : {},
|
|
760
|
+
...typeof src.tiltX === "number" ? { tiltX: src.tiltX } : {},
|
|
761
|
+
...typeof src.tiltY === "number" ? { tiltY: src.tiltY } : {}
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
|
|
733
765
|
// src/ui/spec.ts
|
|
734
766
|
function hitPredicate(fn) {
|
|
735
767
|
return Object.assign(
|
|
@@ -752,6 +784,23 @@ function isApplePlatform() {
|
|
|
752
784
|
return /mac|iphone|ipad|ipod/i.test(raw);
|
|
753
785
|
}
|
|
754
786
|
|
|
787
|
+
// src/ui/fixedSeat.ts
|
|
788
|
+
var HOST_SELECTOR = "[data-wzl-portal-host],[data-wzl-theme],[data-wzl-mode]";
|
|
789
|
+
function nearestPortalHost(from) {
|
|
790
|
+
return from?.closest(HOST_SELECTOR) ?? null;
|
|
791
|
+
}
|
|
792
|
+
function containsFixed(el) {
|
|
793
|
+
const cs = getComputedStyle(el);
|
|
794
|
+
const set = (value) => value !== void 0 && value !== "" && value !== "none";
|
|
795
|
+
return set(cs.transform) || set(cs.filter) || set(cs.backdropFilter) || set(cs.perspective) || /paint|layout|strict|content/.test(cs.contain ?? "");
|
|
796
|
+
}
|
|
797
|
+
function fixedSeat(from) {
|
|
798
|
+
const host = nearestPortalHost(from) ?? document.body;
|
|
799
|
+
if (host === document.body || !containsFixed(host)) return { host, left: 0, top: 0 };
|
|
800
|
+
const rect = host.getBoundingClientRect();
|
|
801
|
+
return { host, left: rect.left, top: rect.top };
|
|
802
|
+
}
|
|
803
|
+
|
|
755
804
|
// src/ui/specificity.ts
|
|
756
805
|
function modsCount(mods2) {
|
|
757
806
|
if (!mods2) return 0;
|
|
@@ -810,6 +859,313 @@ function pastDragThreshold(from, to, threshold = DRAG_THRESHOLD_PX) {
|
|
|
810
859
|
return Math.hypot(to.clientX - from.clientX, to.clientY - from.clientY) >= threshold;
|
|
811
860
|
}
|
|
812
861
|
|
|
862
|
+
// src/recognizer/thresholds.ts
|
|
863
|
+
var LONG_PRESS_MS = 500;
|
|
864
|
+
var DOUBLE_CLICK_MAX_MS = 600;
|
|
865
|
+
var DOUBLE_CLICK_MAX_PX = 8;
|
|
866
|
+
var TAP_THRESHOLD_PX = 8;
|
|
867
|
+
|
|
868
|
+
// src/recognizer/pressFacts.ts
|
|
869
|
+
function pressedOn(down) {
|
|
870
|
+
return {
|
|
871
|
+
...down.affordance !== void 0 ? { affordance: down.affordance } : {},
|
|
872
|
+
...down.bodyTarget !== void 0 ? { bodyTarget: down.bodyTarget } : {},
|
|
873
|
+
...down.bodyKind !== void 0 ? { bodyKind: down.bodyKind } : {}
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
function clickEvents(down, up) {
|
|
877
|
+
const shared = {
|
|
878
|
+
target: up.target,
|
|
879
|
+
altKey: up.altKey,
|
|
880
|
+
ctrlKey: up.ctrlKey,
|
|
881
|
+
metaKey: up.metaKey,
|
|
882
|
+
shiftKey: up.shiftKey,
|
|
883
|
+
x: up.worldX,
|
|
884
|
+
y: up.worldY,
|
|
885
|
+
clientX: up.clientX,
|
|
886
|
+
clientY: up.clientY,
|
|
887
|
+
...pressedOn(down)
|
|
888
|
+
};
|
|
889
|
+
return {
|
|
890
|
+
click: { kind: "click", ...shared, pressX: down.worldX, pressY: down.worldY },
|
|
891
|
+
doubleclick: { kind: "doubleclick", ...shared }
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
function longPressEvents(down) {
|
|
895
|
+
const shared = {
|
|
896
|
+
target: down.target,
|
|
897
|
+
altKey: down.altKey,
|
|
898
|
+
ctrlKey: down.ctrlKey,
|
|
899
|
+
metaKey: down.metaKey,
|
|
900
|
+
shiftKey: down.shiftKey,
|
|
901
|
+
...pressedOn(down)
|
|
902
|
+
};
|
|
903
|
+
const at2 = { x: down.worldX, y: down.worldY, clientX: down.clientX, clientY: down.clientY };
|
|
904
|
+
return {
|
|
905
|
+
longpress: { kind: "longpress", ...at2, ...shared },
|
|
906
|
+
contextmenu: { kind: "contextmenu", ...at2, ...shared }
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// src/recognizer/click.ts
|
|
911
|
+
function composeClick(down, up, clicks, now, dispatch, observer) {
|
|
912
|
+
const evs = clickEvents(down, up);
|
|
913
|
+
const world = { x: up.worldX, y: up.worldY };
|
|
914
|
+
dispatch(evs.click);
|
|
915
|
+
observer.click?.(world);
|
|
916
|
+
const t = now();
|
|
917
|
+
const prev = clicks.last;
|
|
918
|
+
const follows = prev !== null && t - prev.t < DOUBLE_CLICK_MAX_MS && Math.hypot(up.clientX - prev.clientX, up.clientY - prev.clientY) < DOUBLE_CLICK_MAX_PX;
|
|
919
|
+
if (!follows) {
|
|
920
|
+
clicks.last = { t, clientX: up.clientX, clientY: up.clientY };
|
|
921
|
+
return {};
|
|
922
|
+
}
|
|
923
|
+
observer.doubleClick?.(world);
|
|
924
|
+
const doubleClick = dispatch(evs.doubleclick);
|
|
925
|
+
clicks.last = null;
|
|
926
|
+
return { doubleClick };
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// src/recognizer/longPressHold.ts
|
|
930
|
+
function createLongPressHold(clock, onChange) {
|
|
931
|
+
let pending = null;
|
|
932
|
+
const cancel = (pointerId) => {
|
|
933
|
+
if (!pending || pointerId !== void 0 && pending.pointerId !== pointerId) return;
|
|
934
|
+
clock.clearTimer(pending.timer);
|
|
935
|
+
pending = null;
|
|
936
|
+
onChange(null);
|
|
937
|
+
};
|
|
938
|
+
return {
|
|
939
|
+
arm(press, fire) {
|
|
940
|
+
cancel();
|
|
941
|
+
const timer = clock.setTimer(() => {
|
|
942
|
+
pending = null;
|
|
943
|
+
onChange(null);
|
|
944
|
+
fire();
|
|
945
|
+
}, press.duration);
|
|
946
|
+
pending = { pointerId: press.pointerId, timer };
|
|
947
|
+
onChange(press);
|
|
948
|
+
},
|
|
949
|
+
pendingFor: (pointerId) => pending?.pointerId === pointerId,
|
|
950
|
+
cancel
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// src/recognizer/multiTouch.ts
|
|
955
|
+
function multiTouchGeometry(positions) {
|
|
956
|
+
const pts = [...positions];
|
|
957
|
+
if (pts.length < 2) return { centroid: { x: 0, y: 0 }, spread: 0, angle: 0 };
|
|
958
|
+
let sumX = 0;
|
|
959
|
+
let sumY = 0;
|
|
960
|
+
for (const p of pts) {
|
|
961
|
+
sumX += p.x;
|
|
962
|
+
sumY += p.y;
|
|
963
|
+
}
|
|
964
|
+
const dx = pts[1].x - pts[0].x;
|
|
965
|
+
const dy = pts[1].y - pts[0].y;
|
|
966
|
+
return {
|
|
967
|
+
centroid: { x: sumX / pts.length, y: sumY / pts.length },
|
|
968
|
+
spread: Math.sqrt(dx * dx + dy * dy),
|
|
969
|
+
angle: Math.atan2(dy, dx) * 180 / Math.PI
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
function createMultiTouchEpisode() {
|
|
973
|
+
let episode = null;
|
|
974
|
+
return {
|
|
975
|
+
join(positions, centroid, now) {
|
|
976
|
+
if (!episode) {
|
|
977
|
+
episode = {
|
|
978
|
+
fingers: positions.size,
|
|
979
|
+
centroid: { ...centroid },
|
|
980
|
+
joinedAt: /* @__PURE__ */ new Map(),
|
|
981
|
+
moved: false,
|
|
982
|
+
startedAt: now
|
|
983
|
+
};
|
|
984
|
+
} else {
|
|
985
|
+
episode.fingers = positions.size;
|
|
986
|
+
}
|
|
987
|
+
for (const [id, p] of positions) {
|
|
988
|
+
if (!episode.joinedAt.has(id)) episode.joinedAt.set(id, { ...p });
|
|
989
|
+
}
|
|
990
|
+
},
|
|
991
|
+
moved(pointerId, to) {
|
|
992
|
+
const from = episode?.joinedAt.get(pointerId);
|
|
993
|
+
if (episode && from && Math.hypot(to.x - from.x, to.y - from.y) > TAP_THRESHOLD_PX) episode.moved = true;
|
|
994
|
+
},
|
|
995
|
+
finish(centroid, now, maxHoldMs) {
|
|
996
|
+
const e = episode;
|
|
997
|
+
episode = null;
|
|
998
|
+
if (!e) return null;
|
|
999
|
+
const brief = now - e.startedAt < maxHoldMs;
|
|
1000
|
+
const still = Math.hypot(centroid.x - e.centroid.x, centroid.y - e.centroid.y) <= TAP_THRESHOLD_PX;
|
|
1001
|
+
return brief && !e.moved && still ? e.fingers : null;
|
|
1002
|
+
},
|
|
1003
|
+
abandon() {
|
|
1004
|
+
episode = null;
|
|
1005
|
+
}
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// src/recognizer/samples.ts
|
|
1010
|
+
function modifiersOf(e) {
|
|
1011
|
+
return { altKey: e.altKey, ctrlKey: e.ctrlKey, metaKey: e.metaKey, shiftKey: e.shiftKey };
|
|
1012
|
+
}
|
|
1013
|
+
function pressFactsOf(s) {
|
|
1014
|
+
return {
|
|
1015
|
+
target: s.target,
|
|
1016
|
+
clientX: s.clientX,
|
|
1017
|
+
clientY: s.clientY,
|
|
1018
|
+
worldX: s.x,
|
|
1019
|
+
worldY: s.y,
|
|
1020
|
+
...modifiersOf(s),
|
|
1021
|
+
...pressedOn(s)
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
var at = (s, world) => ({
|
|
1025
|
+
pointerId: s.pointerId,
|
|
1026
|
+
x: world.x,
|
|
1027
|
+
y: world.y,
|
|
1028
|
+
clientX: s.clientX,
|
|
1029
|
+
clientY: s.clientY,
|
|
1030
|
+
...modifiersOf(s)
|
|
1031
|
+
});
|
|
1032
|
+
function pointerDownEvent(s) {
|
|
1033
|
+
return { kind: "pointerdown", target: s.target, ...at(s, s), ...stylusOf(s), ...pressedOn(s) };
|
|
1034
|
+
}
|
|
1035
|
+
function pointerMoveEvent(s, world) {
|
|
1036
|
+
return { kind: "pointermove", ...at(s, world), ...stylusOf(s) };
|
|
1037
|
+
}
|
|
1038
|
+
function pointerUpEvent(s, world) {
|
|
1039
|
+
return { kind: "pointerup", ...at(s, world) };
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// src/recognizer/pointerRecognizer.ts
|
|
1043
|
+
function multiTouchEvent(fingers, mods2, more) {
|
|
1044
|
+
return { kind: "multitouch", fingers, ...modifiersOf(mods2), ...more };
|
|
1045
|
+
}
|
|
1046
|
+
var positionsOf = (held) => new Map([...held].map(([id, p]) => [id, p.pos]));
|
|
1047
|
+
function heldGeometry(held, toSurface, extra) {
|
|
1048
|
+
const g = multiTouchGeometry([...positionsOf(held).values(), ...extra]);
|
|
1049
|
+
return { ...g, centroid: toSurface(g.centroid) };
|
|
1050
|
+
}
|
|
1051
|
+
function fireLongPress(rec, sink, observer) {
|
|
1052
|
+
if (!rec) return;
|
|
1053
|
+
const evs = longPressEvents(rec.down);
|
|
1054
|
+
let result = sink.dispatch(evs.longpress);
|
|
1055
|
+
if (result === "unhandled") result = sink.dispatch(evs.contextmenu);
|
|
1056
|
+
if (result === "unhandled") return;
|
|
1057
|
+
rec.buffered = null;
|
|
1058
|
+
if (rec.holdType) observer.longPress?.(rec.holdType);
|
|
1059
|
+
}
|
|
1060
|
+
function createPointerRecognizer(opts) {
|
|
1061
|
+
const { sink, clock, observer = {}, toSurface = (p) => p } = opts;
|
|
1062
|
+
const world = (s) => opts.toWorld?.(s.clientX, s.clientY) ?? { x: s.clientX, y: s.clientY };
|
|
1063
|
+
const clicks = opts.clicks ?? { last: null };
|
|
1064
|
+
const held = /* @__PURE__ */ new Map();
|
|
1065
|
+
const touches = createMultiTouchEpisode();
|
|
1066
|
+
const hold = createLongPressHold(clock, (pending) => {
|
|
1067
|
+
observer.holdChanged?.(pending);
|
|
1068
|
+
});
|
|
1069
|
+
const longPressMs = () => opts.longPressMs?.() ?? LONG_PRESS_MS;
|
|
1070
|
+
const geometry = (extra = []) => heldGeometry(held, toSurface, extra);
|
|
1071
|
+
const claimForMultiTouch = (mods2) => {
|
|
1072
|
+
hold.cancel();
|
|
1073
|
+
for (const p of held.values()) p.buffered = null;
|
|
1074
|
+
const g = geometry();
|
|
1075
|
+
touches.join(positionsOf(held), g.centroid, clock.now());
|
|
1076
|
+
sink.dispatch(multiTouchEvent(held.size, mods2, g));
|
|
1077
|
+
};
|
|
1078
|
+
return {
|
|
1079
|
+
press(s) {
|
|
1080
|
+
const ev = pointerDownEvent(s);
|
|
1081
|
+
const rec = { pos: { x: s.clientX, y: s.clientY }, buffered: ev, down: pressFactsOf(s) };
|
|
1082
|
+
held.set(s.pointerId, rec);
|
|
1083
|
+
if (held.size < 2) sink.dispatch({ ...ev, stage: "press" });
|
|
1084
|
+
if ((s.pointerType === "touch" || s.pointerType === "pen") && held.size === 1) {
|
|
1085
|
+
rec.holdType = s.pointerType;
|
|
1086
|
+
hold.arm(
|
|
1087
|
+
{ pointerId: s.pointerId, pointerType: s.pointerType, press: rec.down, startedAt: clock.now(), duration: longPressMs() },
|
|
1088
|
+
() => {
|
|
1089
|
+
fireLongPress(held.get(s.pointerId), sink, observer);
|
|
1090
|
+
}
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
if (held.size >= 2) claimForMultiTouch(s);
|
|
1094
|
+
},
|
|
1095
|
+
move(s) {
|
|
1096
|
+
const rec = held.get(s.pointerId);
|
|
1097
|
+
const to = { x: s.clientX, y: s.clientY };
|
|
1098
|
+
if (rec) rec.pos = to;
|
|
1099
|
+
touches.moved(s.pointerId, to);
|
|
1100
|
+
if (rec?.buffered && pastDragThreshold(rec.down, s)) {
|
|
1101
|
+
const press = rec.buffered;
|
|
1102
|
+
rec.buffered = null;
|
|
1103
|
+
hold.cancel(s.pointerId);
|
|
1104
|
+
sink.dispatch(press);
|
|
1105
|
+
}
|
|
1106
|
+
if (held.size >= 2 && sink.multiTouchInFlight(held.size)) {
|
|
1107
|
+
sink.dispatch(multiTouchEvent(held.size, s, geometry()));
|
|
1108
|
+
}
|
|
1109
|
+
sink.dispatch(pointerMoveEvent(s, world(s)));
|
|
1110
|
+
},
|
|
1111
|
+
release(s) {
|
|
1112
|
+
hold.cancel(s.pointerId);
|
|
1113
|
+
const before = held.size;
|
|
1114
|
+
const rec = held.get(s.pointerId);
|
|
1115
|
+
held.delete(s.pointerId);
|
|
1116
|
+
if (before >= 2) sink.dispatch(multiTouchEvent(before, s, { end: "commit" }));
|
|
1117
|
+
if (before >= 2 && held.size < 2) {
|
|
1118
|
+
const fingers = touches.finish(
|
|
1119
|
+
geometry([{ x: s.clientX, y: s.clientY }]).centroid,
|
|
1120
|
+
clock.now(),
|
|
1121
|
+
longPressMs()
|
|
1122
|
+
);
|
|
1123
|
+
if (fingers !== null) sink.dispatch({ kind: "multitouchtap", fingers, ...modifiersOf(s) });
|
|
1124
|
+
}
|
|
1125
|
+
sink.dispatch(pointerUpEvent(s, world(s)));
|
|
1126
|
+
if (!rec?.buffered) return {};
|
|
1127
|
+
const at2 = world(s);
|
|
1128
|
+
const up = {
|
|
1129
|
+
target: s.target,
|
|
1130
|
+
clientX: s.clientX,
|
|
1131
|
+
clientY: s.clientY,
|
|
1132
|
+
worldX: at2.x,
|
|
1133
|
+
worldY: at2.y,
|
|
1134
|
+
...modifiersOf(s)
|
|
1135
|
+
};
|
|
1136
|
+
return composeClick(rec.down, up, clicks, () => clock.now(), (ev) => sink.dispatch(ev), observer);
|
|
1137
|
+
},
|
|
1138
|
+
cancel(pointerId) {
|
|
1139
|
+
const rec = held.get(pointerId);
|
|
1140
|
+
if (!rec) return;
|
|
1141
|
+
const before = held.size;
|
|
1142
|
+
held.delete(pointerId);
|
|
1143
|
+
hold.cancel(pointerId);
|
|
1144
|
+
if (before >= 2) sink.dispatch(multiTouchEvent(before, rec.down, { end: "cancel" }));
|
|
1145
|
+
touches.abandon();
|
|
1146
|
+
sink.dispatch({ kind: "pointercancel", pointerId, ...modifiersOf(rec.down) });
|
|
1147
|
+
},
|
|
1148
|
+
takeHold() {
|
|
1149
|
+
let holding = false;
|
|
1150
|
+
for (const [id, rec] of held) {
|
|
1151
|
+
if (!rec.holdType) continue;
|
|
1152
|
+
holding = true;
|
|
1153
|
+
if (!hold.pendingFor(id)) continue;
|
|
1154
|
+
hold.cancel(id);
|
|
1155
|
+
fireLongPress(rec, sink, observer);
|
|
1156
|
+
}
|
|
1157
|
+
return holding;
|
|
1158
|
+
},
|
|
1159
|
+
heldCount: () => held.size,
|
|
1160
|
+
positionOf: (pointerId) => held.get(pointerId)?.pos,
|
|
1161
|
+
reset() {
|
|
1162
|
+
hold.cancel();
|
|
1163
|
+
held.clear();
|
|
1164
|
+
touches.abandon();
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
|
|
813
1169
|
// src/ui/controlTarget.ts
|
|
814
1170
|
var CONTROL_SELECTOR = [
|
|
815
1171
|
"button",
|
|
@@ -831,6 +1187,106 @@ function isInControlWithin(target, host) {
|
|
|
831
1187
|
return control !== null && control !== host && host.contains(control);
|
|
832
1188
|
}
|
|
833
1189
|
|
|
834
|
-
|
|
1190
|
+
// src/ui/heldPointers.ts
|
|
1191
|
+
var trackers = /* @__PURE__ */ new WeakMap();
|
|
1192
|
+
function attach(element) {
|
|
1193
|
+
const doc = element.ownerDocument;
|
|
1194
|
+
const win = doc.defaultView;
|
|
1195
|
+
const held = /* @__PURE__ */ new Map();
|
|
1196
|
+
const down = (e) => {
|
|
1197
|
+
const p = e;
|
|
1198
|
+
held.set(p.pointerId, !!p.buttons);
|
|
1199
|
+
};
|
|
1200
|
+
const up = (e) => {
|
|
1201
|
+
held.delete(e.pointerId);
|
|
1202
|
+
};
|
|
1203
|
+
const move = (e) => {
|
|
1204
|
+
const p = e;
|
|
1205
|
+
if (held.get(p.pointerId) && p.buttons === 0) held.delete(p.pointerId);
|
|
1206
|
+
};
|
|
1207
|
+
const blur = () => {
|
|
1208
|
+
held.clear();
|
|
1209
|
+
};
|
|
1210
|
+
element.addEventListener("pointerdown", down, true);
|
|
1211
|
+
doc.addEventListener("pointerup", up, true);
|
|
1212
|
+
doc.addEventListener("pointercancel", up, true);
|
|
1213
|
+
doc.addEventListener("pointermove", move, true);
|
|
1214
|
+
win?.addEventListener("blur", blur);
|
|
1215
|
+
return {
|
|
1216
|
+
readers: 0,
|
|
1217
|
+
held,
|
|
1218
|
+
detach() {
|
|
1219
|
+
element.removeEventListener("pointerdown", down, true);
|
|
1220
|
+
doc.removeEventListener("pointerup", up, true);
|
|
1221
|
+
doc.removeEventListener("pointercancel", up, true);
|
|
1222
|
+
doc.removeEventListener("pointermove", move, true);
|
|
1223
|
+
win?.removeEventListener("blur", blur);
|
|
1224
|
+
}
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
function heldPointers(element) {
|
|
1228
|
+
let tracker = trackers.get(element);
|
|
1229
|
+
if (!tracker) {
|
|
1230
|
+
tracker = attach(element);
|
|
1231
|
+
trackers.set(element, tracker);
|
|
1232
|
+
}
|
|
1233
|
+
const shared = tracker;
|
|
1234
|
+
shared.readers += 1;
|
|
1235
|
+
let released = false;
|
|
1236
|
+
return {
|
|
1237
|
+
count: () => shared.held.size,
|
|
1238
|
+
release() {
|
|
1239
|
+
if (released) return;
|
|
1240
|
+
released = true;
|
|
1241
|
+
shared.readers -= 1;
|
|
1242
|
+
if (shared.readers > 0) return;
|
|
1243
|
+
shared.detach();
|
|
1244
|
+
if (trackers.get(element) === shared) trackers.delete(element);
|
|
1245
|
+
}
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// src/ui/keyOwner.ts
|
|
1250
|
+
function isEditableTarget(target) {
|
|
1251
|
+
if (!target || !(target instanceof Element)) return false;
|
|
1252
|
+
const tag = target.tagName;
|
|
1253
|
+
if (tag === "INPUT" || tag === "TEXTAREA") return true;
|
|
1254
|
+
if (target.isContentEditable) return true;
|
|
1255
|
+
const ce = target.getAttribute("contenteditable");
|
|
1256
|
+
return ce === "true" || ce === "";
|
|
1257
|
+
}
|
|
1258
|
+
var ACTIVATED_BY_KEY = [
|
|
1259
|
+
"button",
|
|
1260
|
+
"a[href]",
|
|
1261
|
+
"summary",
|
|
1262
|
+
"select",
|
|
1263
|
+
'input[type="button"]',
|
|
1264
|
+
'input[type="submit"]',
|
|
1265
|
+
'input[type="reset"]',
|
|
1266
|
+
'input[type="checkbox"]',
|
|
1267
|
+
'input[type="radio"]',
|
|
1268
|
+
'[role="button"]',
|
|
1269
|
+
'[role="link"]',
|
|
1270
|
+
'[role="checkbox"]',
|
|
1271
|
+
'[role="switch"]',
|
|
1272
|
+
'[role="menuitem"]',
|
|
1273
|
+
'[role="tab"]',
|
|
1274
|
+
'[role="option"]'
|
|
1275
|
+
].join(", ");
|
|
1276
|
+
function activatesFocusedControl(e) {
|
|
1277
|
+
if (e.key !== " " && e.key !== "Enter") return false;
|
|
1278
|
+
return e.target instanceof Element && e.target.matches(ACTIVATED_BY_KEY);
|
|
1279
|
+
}
|
|
1280
|
+
var SELECT_NAVIGATION = /* @__PURE__ */ new Set(["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Home", "End", "PageUp", "PageDown"]);
|
|
1281
|
+
function selectOwnsKey(e) {
|
|
1282
|
+
if (!(e.target instanceof Element) || e.target.tagName !== "SELECT") return false;
|
|
1283
|
+
if (e.metaKey || e.ctrlKey || e.altKey) return false;
|
|
1284
|
+
return SELECT_NAVIGATION.has(e.key) || e.key.length === 1;
|
|
1285
|
+
}
|
|
1286
|
+
function focusedControlOwnsKey(e) {
|
|
1287
|
+
return activatesFocusedControl(e) || selectOwnsKey(e);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
export { ACTIVE_SIGILS, CONTROL_SELECTOR, DOUBLE_CLICK_MAX_MS, DOUBLE_CLICK_MAX_PX, DRAG_THRESHOLD_PX, GESTURE_DESCRIPTORS, LONG_PRESS_MS, MOD_NAME_SET, RESERVED_ID_NAMES, RESERVED_ID_PREFIXES, RESERVED_SIGILS, ROUTE_FIELD_DEFINITIONS, ROUTE_TERMS, TAP_THRESHOLD_PX, UNKNOWN_MIME, VALID_MOD_NAMES, activatesFocusedControl, bodyPredicate, canonicalModifiers, clickEvents, collapseShiftPairs, createPointerRecognizer, describeRoute, describeRouteParts, fixedSeat, focusedControlOwnsKey, formatKeyRoute, formatPhaseAtom, formatRoute, getGestureDescriptor, heldPointers, hitPredicate, isApplePlatform, isEditableTarget, isInControlWithin, isKnownGestureName, keyRouteToSpec, longPressEvents, matchIngestTypes, matchKey, matchModifiers, matchPhase, matchSpec, matchTarget, mimeMatchesGlob, modifierComboToParsed, mods, multiTouchGeometry, nearestPortalHost, parseKeyRoute, parseRoute, parseTargetSpec, pastDragThreshold, routeGestureForSpecKind, routeToSpec, specKindForRouteGesture, specificity, stylusOf };
|
|
835
1291
|
//# sourceMappingURL=index.js.map
|
|
836
1292
|
//# sourceMappingURL=index.js.map
|