@weasel-js/gestures 1.9.3 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +437 -221
- package/dist/index.js +438 -29
- 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 },
|
|
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: "*" } },
|
|
@@ -11,6 +12,7 @@ var GESTURE_DESCRIPTORS = [
|
|
|
11
12
|
{ name: "keyHeld", hasTarget: false, arg: { name: "key", values: "free" } },
|
|
12
13
|
{ name: "contextMenu", hasTarget: true },
|
|
13
14
|
{ name: "longPress", hasTarget: true },
|
|
15
|
+
{ name: "multiTouch", hasTarget: false, arg: { name: "fingers", values: ["2", "3", "4"] } },
|
|
14
16
|
{ name: "multiTouchTap", hasTarget: false, arg: { name: "fingers", values: ["2", "3", "4"] } },
|
|
15
17
|
// The arg is the MIME-glob filter. No default: a spec with no `types`
|
|
16
18
|
// matches any payload, which is what an omitted arg slot means for a
|
|
@@ -270,6 +272,20 @@ function keyRouteToSpec(r) {
|
|
|
270
272
|
return spec;
|
|
271
273
|
}
|
|
272
274
|
|
|
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;
|
|
282
|
+
}
|
|
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)));
|
|
287
|
+
}
|
|
288
|
+
|
|
273
289
|
// src/ui/match.ts
|
|
274
290
|
function resolveSpecValue(value) {
|
|
275
291
|
if (value === true) return "required";
|
|
@@ -307,6 +323,10 @@ function matchKey(eventKey, specKey) {
|
|
|
307
323
|
}
|
|
308
324
|
return specKey.toLowerCase() === lower;
|
|
309
325
|
}
|
|
326
|
+
function matchKeyOrCode(e, spec) {
|
|
327
|
+
if (spec.code !== void 0 && e.code !== void 0 && [spec.code].flat().includes(e.code)) return true;
|
|
328
|
+
return matchKey(e.key, spec.key);
|
|
329
|
+
}
|
|
310
330
|
function affordanceKindOf(target) {
|
|
311
331
|
if (typeof target !== "object" || target === null) return void 0;
|
|
312
332
|
const k = target.kind;
|
|
@@ -342,8 +362,7 @@ function matchTarget(target, specTarget, bodyTarget, bodyKind) {
|
|
|
342
362
|
const form = parseTargetSpec(specTarget);
|
|
343
363
|
if (form === null) return false;
|
|
344
364
|
switch (form.form) {
|
|
345
|
-
// Receives the
|
|
346
|
-
// plus the bodyTarget classification.
|
|
365
|
+
// Receives the affordance hit plus the bodyTarget classification.
|
|
347
366
|
case "predicate":
|
|
348
367
|
return form.kindOf(target, bodyTarget);
|
|
349
368
|
// Resolved from the `classifyTarget` result packed into the event's
|
|
@@ -387,17 +406,6 @@ function matchPhaseAtom(a, ctx) {
|
|
|
387
406
|
const isEngaged = ctx.engagedChannels.has(id);
|
|
388
407
|
return a.phase === "engaged" ? isEngaged : !isEngaged;
|
|
389
408
|
}
|
|
390
|
-
function mimeMatchesGlob(mime, glob) {
|
|
391
|
-
const m = mime.toLowerCase();
|
|
392
|
-
const g = glob.toLowerCase();
|
|
393
|
-
if (g === "*" || g === "*/*") return true;
|
|
394
|
-
if (g.endsWith("/*")) return m.startsWith(g.slice(0, -1));
|
|
395
|
-
return m === g;
|
|
396
|
-
}
|
|
397
|
-
function matchIngestTypes(items, types) {
|
|
398
|
-
if (!types || types.length === 0) return true;
|
|
399
|
-
return items.some((it) => types.some((g) => mimeMatchesGlob(it.mime, g)));
|
|
400
|
-
}
|
|
401
409
|
function matchSpec(e, spec, isMac, phaseCtx) {
|
|
402
410
|
if (spec.phase !== void 0) {
|
|
403
411
|
const ctx = phaseCtx ?? { selfChannel: null, engagedChannels: EMPTY_ENGAGED };
|
|
@@ -406,13 +414,13 @@ function matchSpec(e, spec, isMac, phaseCtx) {
|
|
|
406
414
|
switch (spec.kind) {
|
|
407
415
|
case "key": {
|
|
408
416
|
if (e.kind !== "key") return false;
|
|
409
|
-
if (!
|
|
417
|
+
if (!matchKeyOrCode(e, spec)) return false;
|
|
410
418
|
return matchModifiers(e, spec.mods, isMac);
|
|
411
419
|
}
|
|
412
420
|
case "key-held": {
|
|
413
421
|
if (e.kind !== "key-held") return false;
|
|
414
422
|
if (e.phase !== "down") return false;
|
|
415
|
-
if (!
|
|
423
|
+
if (!matchKeyOrCode(e, spec)) return false;
|
|
416
424
|
return matchModifiers(e, spec.mods, isMac);
|
|
417
425
|
}
|
|
418
426
|
case "wheel": {
|
|
@@ -441,13 +449,13 @@ function matchSpec(e, spec, isMac, phaseCtx) {
|
|
|
441
449
|
if (!matchModifiers(e, spec.mods, isMac)) return false;
|
|
442
450
|
return matchTarget(e.affordance, spec.target, e.bodyTarget, e.bodyKind);
|
|
443
451
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
if (e.kind !==
|
|
452
|
+
// Each happens at a point, so its target is what the event classified
|
|
453
|
+
// there: for a long-press, the affordance its press landed on.
|
|
454
|
+
case "contextMenu":
|
|
455
|
+
case "longPress":
|
|
456
|
+
case "hover": {
|
|
457
|
+
if (e.kind !== "contextmenu" && e.kind !== "longpress" && e.kind !== "hover") return false;
|
|
458
|
+
if (e.kind !== spec.kind.toLowerCase()) return false;
|
|
451
459
|
if (!matchModifiers(e, spec.mods, isMac)) return false;
|
|
452
460
|
return matchTarget(e.affordance, spec.target, e.bodyTarget, e.bodyKind);
|
|
453
461
|
}
|
|
@@ -501,7 +509,8 @@ var SPEC_KIND_TO_GESTURE = {
|
|
|
501
509
|
longPress: "longPress",
|
|
502
510
|
drag: "drag",
|
|
503
511
|
pointerDown: "pointerDown",
|
|
504
|
-
|
|
512
|
+
hover: "hover",
|
|
513
|
+
multiTouch: "multiTouch",
|
|
505
514
|
multiTouchTap: "multiTouchTap",
|
|
506
515
|
drop: "drop",
|
|
507
516
|
paste: "paste"
|
|
@@ -550,6 +559,7 @@ function routeToSpec(parsed) {
|
|
|
550
559
|
return fail(`${parsed.gesture} has no GestureSpec kind`);
|
|
551
560
|
case "click":
|
|
552
561
|
case "pointerDown":
|
|
562
|
+
case "hover":
|
|
553
563
|
case "doubleClick":
|
|
554
564
|
case "drag":
|
|
555
565
|
case "contextMenu":
|
|
@@ -567,14 +577,13 @@ function routeToSpec(parsed) {
|
|
|
567
577
|
for (const m of optionalMods) optional[m] = "optional";
|
|
568
578
|
return withMods({ kind, key, ...optionalMods.length > 0 ? { mods: optional } : {} });
|
|
569
579
|
}
|
|
580
|
+
case "multiTouch":
|
|
570
581
|
case "multiTouchTap":
|
|
571
|
-
if (arg === void 0) return fail(
|
|
582
|
+
if (arg === void 0) return fail(`a ${kind} spec needs concrete fingers`);
|
|
572
583
|
return withMods({ kind, fingers: Number(arg) });
|
|
573
584
|
case "drop":
|
|
574
585
|
case "paste":
|
|
575
586
|
return withMods({ kind, ...arg ? { types: arg.split("|") } : {} });
|
|
576
|
-
case "multiTouch":
|
|
577
|
-
return fail("multiTouch has no route gesture");
|
|
578
587
|
}
|
|
579
588
|
}
|
|
580
589
|
|
|
@@ -593,8 +602,8 @@ var ROUTE_TERMS = {
|
|
|
593
602
|
};
|
|
594
603
|
var ROUTE_FIELD_DEFINITIONS = {
|
|
595
604
|
phases: "Which lifecycle stage(s) a channel must be in for this route to fire. `initial` = the channel is idle; `engaged` = the channel is mid-gesture. The channel is the binding's own tool (`&`), any tool (`*`), or a named tool id.",
|
|
596
|
-
gesture: "The class of input event that triggers this route \u2014 click, drag, double-tap, keyDown/keyUp, wheel, pinch, contextMenu, multiTouchTap, drop, or paste.",
|
|
597
|
-
arg: "Sub-class of the gesture. Direction for wheel (up / down / *) and pinch (in / out / *), key name for keyDown / keyUp, finger count for multiTouchTap, MIME glob for drop / paste. Other gestures have no arg slot.",
|
|
605
|
+
gesture: "The class of input event that triggers this route \u2014 click, drag, double-tap, keyDown/keyUp, wheel, pinch, contextMenu, multiTouch, multiTouchTap, drop, or paste.",
|
|
606
|
+
arg: "Sub-class of the gesture. Direction for wheel (up / down / *) and pinch (in / out / *), key name for keyDown / keyUp, finger count for multiTouch / multiTouchTap, MIME glob for drop / paste. Other gestures have no arg slot.",
|
|
598
607
|
target: "Which hit-test result the gesture must land on. `*` matches any target; `empty` matches the empty canvas; otherwise the value names a specific hit-target kind.",
|
|
599
608
|
modifiers: "Modifier keys the user must hold (`+key`) or may optionally hold (`?key`) for this route to match. Unlisted modifiers must not be held."
|
|
600
609
|
};
|
|
@@ -666,6 +675,8 @@ function actionClause(parsed, required) {
|
|
|
666
675
|
const direction = arg === "in" ? " in" : arg === "out" ? " out" : "";
|
|
667
676
|
return userDoes(required, `pinches${direction}${target}`);
|
|
668
677
|
}
|
|
678
|
+
case "multiTouch":
|
|
679
|
+
return userDoes(required, `touches with ${arg ?? "multiple"} fingers`);
|
|
669
680
|
case "multiTouchTap":
|
|
670
681
|
return userDoes(required, `taps with ${arg ?? "multiple"} fingers`);
|
|
671
682
|
case "contextMenu":
|
|
@@ -676,6 +687,8 @@ function actionClause(parsed, required) {
|
|
|
676
687
|
return userDoes(required, `clicks${target}`);
|
|
677
688
|
case "pointerDown":
|
|
678
689
|
return userDoes(required, `presses${target}`);
|
|
690
|
+
case "hover":
|
|
691
|
+
return userDoes(required, `moves the pointer${target}`);
|
|
679
692
|
case "dblTap":
|
|
680
693
|
return userDoes(required, `double-taps${target}`);
|
|
681
694
|
case "drag":
|
|
@@ -725,6 +738,54 @@ function canonicalModifiers(mods2) {
|
|
|
725
738
|
return Object.entries(mods2).sort(([a], [b]) => a.localeCompare(b)).map(([name, req]) => `${name}=${req}`).join("&");
|
|
726
739
|
}
|
|
727
740
|
|
|
741
|
+
// src/ui/stylus.ts
|
|
742
|
+
function stylusOf(src) {
|
|
743
|
+
return {
|
|
744
|
+
...typeof src.pressure === "number" ? { pressure: src.pressure } : {},
|
|
745
|
+
...typeof src.tiltX === "number" ? { tiltX: src.tiltX } : {},
|
|
746
|
+
...typeof src.tiltY === "number" ? { tiltY: src.tiltY } : {}
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// src/ui/spec.ts
|
|
751
|
+
function hitPredicate(fn) {
|
|
752
|
+
return Object.assign(
|
|
753
|
+
(hit, bodyTarget) => fn(hit, bodyTarget),
|
|
754
|
+
{ readsAffordance: true }
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
function bodyPredicate(fn) {
|
|
758
|
+
return Object.assign(
|
|
759
|
+
(_hit, bodyTarget) => fn(bodyTarget),
|
|
760
|
+
{ readsAffordance: false }
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// src/ui/platform.ts
|
|
765
|
+
function isApplePlatform() {
|
|
766
|
+
if (typeof navigator === "undefined" || !navigator) return false;
|
|
767
|
+
const nav = navigator;
|
|
768
|
+
const raw = nav.userAgentData?.platform || nav.platform || nav.userAgent || "";
|
|
769
|
+
return /mac|iphone|ipad|ipod/i.test(raw);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// src/ui/fixedSeat.ts
|
|
773
|
+
var HOST_SELECTOR = "[data-wzl-portal-host],[data-wzl-theme],[data-wzl-mode]";
|
|
774
|
+
function nearestPortalHost(from) {
|
|
775
|
+
return from?.closest(HOST_SELECTOR) ?? null;
|
|
776
|
+
}
|
|
777
|
+
function containsFixed(el) {
|
|
778
|
+
const cs = getComputedStyle(el);
|
|
779
|
+
const set = (value) => value !== void 0 && value !== "" && value !== "none";
|
|
780
|
+
return set(cs.transform) || set(cs.filter) || set(cs.backdropFilter) || set(cs.perspective) || /paint|layout|strict|content/.test(cs.contain ?? "");
|
|
781
|
+
}
|
|
782
|
+
function fixedSeat(from) {
|
|
783
|
+
const host = nearestPortalHost(from) ?? document.body;
|
|
784
|
+
if (host === document.body || !containsFixed(host)) return { host, left: 0, top: 0 };
|
|
785
|
+
const rect = host.getBoundingClientRect();
|
|
786
|
+
return { host, left: rect.left, top: rect.top };
|
|
787
|
+
}
|
|
788
|
+
|
|
728
789
|
// src/ui/specificity.ts
|
|
729
790
|
function modsCount(mods2) {
|
|
730
791
|
if (!mods2) return 0;
|
|
@@ -783,6 +844,313 @@ function pastDragThreshold(from, to, threshold = DRAG_THRESHOLD_PX) {
|
|
|
783
844
|
return Math.hypot(to.clientX - from.clientX, to.clientY - from.clientY) >= threshold;
|
|
784
845
|
}
|
|
785
846
|
|
|
847
|
+
// src/recognizer/thresholds.ts
|
|
848
|
+
var LONG_PRESS_MS = 500;
|
|
849
|
+
var DOUBLE_CLICK_MAX_MS = 600;
|
|
850
|
+
var DOUBLE_CLICK_MAX_PX = 8;
|
|
851
|
+
var TAP_THRESHOLD_PX = 8;
|
|
852
|
+
|
|
853
|
+
// src/recognizer/pressFacts.ts
|
|
854
|
+
function pressedOn(down) {
|
|
855
|
+
return {
|
|
856
|
+
...down.affordance !== void 0 ? { affordance: down.affordance } : {},
|
|
857
|
+
...down.bodyTarget !== void 0 ? { bodyTarget: down.bodyTarget } : {},
|
|
858
|
+
...down.bodyKind !== void 0 ? { bodyKind: down.bodyKind } : {}
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
function clickEvents(down, up) {
|
|
862
|
+
const shared = {
|
|
863
|
+
target: up.target,
|
|
864
|
+
altKey: up.altKey,
|
|
865
|
+
ctrlKey: up.ctrlKey,
|
|
866
|
+
metaKey: up.metaKey,
|
|
867
|
+
shiftKey: up.shiftKey,
|
|
868
|
+
x: up.worldX,
|
|
869
|
+
y: up.worldY,
|
|
870
|
+
clientX: up.clientX,
|
|
871
|
+
clientY: up.clientY,
|
|
872
|
+
...pressedOn(down)
|
|
873
|
+
};
|
|
874
|
+
return {
|
|
875
|
+
click: { kind: "click", ...shared, pressX: down.worldX, pressY: down.worldY },
|
|
876
|
+
doubleclick: { kind: "doubleclick", ...shared }
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
function longPressEvents(down) {
|
|
880
|
+
const shared = {
|
|
881
|
+
target: down.target,
|
|
882
|
+
altKey: down.altKey,
|
|
883
|
+
ctrlKey: down.ctrlKey,
|
|
884
|
+
metaKey: down.metaKey,
|
|
885
|
+
shiftKey: down.shiftKey,
|
|
886
|
+
...pressedOn(down)
|
|
887
|
+
};
|
|
888
|
+
const at2 = { x: down.worldX, y: down.worldY, clientX: down.clientX, clientY: down.clientY };
|
|
889
|
+
return {
|
|
890
|
+
longpress: { kind: "longpress", ...at2, ...shared },
|
|
891
|
+
contextmenu: { kind: "contextmenu", ...at2, ...shared }
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// src/recognizer/click.ts
|
|
896
|
+
function composeClick(down, up, clicks, now, dispatch, observer) {
|
|
897
|
+
const evs = clickEvents(down, up);
|
|
898
|
+
const world = { x: up.worldX, y: up.worldY };
|
|
899
|
+
dispatch(evs.click);
|
|
900
|
+
observer.click?.(world);
|
|
901
|
+
const t = now();
|
|
902
|
+
const prev = clicks.last;
|
|
903
|
+
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;
|
|
904
|
+
if (!follows) {
|
|
905
|
+
clicks.last = { t, clientX: up.clientX, clientY: up.clientY };
|
|
906
|
+
return {};
|
|
907
|
+
}
|
|
908
|
+
observer.doubleClick?.(world);
|
|
909
|
+
const doubleClick = dispatch(evs.doubleclick);
|
|
910
|
+
clicks.last = null;
|
|
911
|
+
return { doubleClick };
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// src/recognizer/longPressHold.ts
|
|
915
|
+
function createLongPressHold(clock, onChange) {
|
|
916
|
+
let pending = null;
|
|
917
|
+
const cancel = (pointerId) => {
|
|
918
|
+
if (!pending || pointerId !== void 0 && pending.pointerId !== pointerId) return;
|
|
919
|
+
clock.clearTimer(pending.timer);
|
|
920
|
+
pending = null;
|
|
921
|
+
onChange(null);
|
|
922
|
+
};
|
|
923
|
+
return {
|
|
924
|
+
arm(press, fire) {
|
|
925
|
+
cancel();
|
|
926
|
+
const timer = clock.setTimer(() => {
|
|
927
|
+
pending = null;
|
|
928
|
+
onChange(null);
|
|
929
|
+
fire();
|
|
930
|
+
}, press.duration);
|
|
931
|
+
pending = { pointerId: press.pointerId, timer };
|
|
932
|
+
onChange(press);
|
|
933
|
+
},
|
|
934
|
+
pendingFor: (pointerId) => pending?.pointerId === pointerId,
|
|
935
|
+
cancel
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// src/recognizer/multiTouch.ts
|
|
940
|
+
function multiTouchGeometry(positions) {
|
|
941
|
+
const pts = [...positions];
|
|
942
|
+
if (pts.length < 2) return { centroid: { x: 0, y: 0 }, spread: 0, angle: 0 };
|
|
943
|
+
let sumX = 0;
|
|
944
|
+
let sumY = 0;
|
|
945
|
+
for (const p of pts) {
|
|
946
|
+
sumX += p.x;
|
|
947
|
+
sumY += p.y;
|
|
948
|
+
}
|
|
949
|
+
const dx = pts[1].x - pts[0].x;
|
|
950
|
+
const dy = pts[1].y - pts[0].y;
|
|
951
|
+
return {
|
|
952
|
+
centroid: { x: sumX / pts.length, y: sumY / pts.length },
|
|
953
|
+
spread: Math.sqrt(dx * dx + dy * dy),
|
|
954
|
+
angle: Math.atan2(dy, dx) * 180 / Math.PI
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
function createMultiTouchEpisode() {
|
|
958
|
+
let episode = null;
|
|
959
|
+
return {
|
|
960
|
+
join(positions, centroid, now) {
|
|
961
|
+
if (!episode) {
|
|
962
|
+
episode = {
|
|
963
|
+
fingers: positions.size,
|
|
964
|
+
centroid: { ...centroid },
|
|
965
|
+
joinedAt: /* @__PURE__ */ new Map(),
|
|
966
|
+
moved: false,
|
|
967
|
+
startedAt: now
|
|
968
|
+
};
|
|
969
|
+
} else {
|
|
970
|
+
episode.fingers = positions.size;
|
|
971
|
+
}
|
|
972
|
+
for (const [id, p] of positions) {
|
|
973
|
+
if (!episode.joinedAt.has(id)) episode.joinedAt.set(id, { ...p });
|
|
974
|
+
}
|
|
975
|
+
},
|
|
976
|
+
moved(pointerId, to) {
|
|
977
|
+
const from = episode?.joinedAt.get(pointerId);
|
|
978
|
+
if (episode && from && Math.hypot(to.x - from.x, to.y - from.y) > TAP_THRESHOLD_PX) episode.moved = true;
|
|
979
|
+
},
|
|
980
|
+
finish(centroid, now, maxHoldMs) {
|
|
981
|
+
const e = episode;
|
|
982
|
+
episode = null;
|
|
983
|
+
if (!e) return null;
|
|
984
|
+
const brief = now - e.startedAt < maxHoldMs;
|
|
985
|
+
const still = Math.hypot(centroid.x - e.centroid.x, centroid.y - e.centroid.y) <= TAP_THRESHOLD_PX;
|
|
986
|
+
return brief && !e.moved && still ? e.fingers : null;
|
|
987
|
+
},
|
|
988
|
+
abandon() {
|
|
989
|
+
episode = null;
|
|
990
|
+
}
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// src/recognizer/samples.ts
|
|
995
|
+
function modifiersOf(e) {
|
|
996
|
+
return { altKey: e.altKey, ctrlKey: e.ctrlKey, metaKey: e.metaKey, shiftKey: e.shiftKey };
|
|
997
|
+
}
|
|
998
|
+
function pressFactsOf(s) {
|
|
999
|
+
return {
|
|
1000
|
+
target: s.target,
|
|
1001
|
+
clientX: s.clientX,
|
|
1002
|
+
clientY: s.clientY,
|
|
1003
|
+
worldX: s.x,
|
|
1004
|
+
worldY: s.y,
|
|
1005
|
+
...modifiersOf(s),
|
|
1006
|
+
...pressedOn(s)
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
var at = (s, world) => ({
|
|
1010
|
+
pointerId: s.pointerId,
|
|
1011
|
+
x: world.x,
|
|
1012
|
+
y: world.y,
|
|
1013
|
+
clientX: s.clientX,
|
|
1014
|
+
clientY: s.clientY,
|
|
1015
|
+
...modifiersOf(s)
|
|
1016
|
+
});
|
|
1017
|
+
function pointerDownEvent(s) {
|
|
1018
|
+
return { kind: "pointerdown", target: s.target, ...at(s, s), ...stylusOf(s), ...pressedOn(s) };
|
|
1019
|
+
}
|
|
1020
|
+
function pointerMoveEvent(s, world) {
|
|
1021
|
+
return { kind: "pointermove", ...at(s, world), ...stylusOf(s) };
|
|
1022
|
+
}
|
|
1023
|
+
function pointerUpEvent(s, world) {
|
|
1024
|
+
return { kind: "pointerup", ...at(s, world) };
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
// src/recognizer/pointerRecognizer.ts
|
|
1028
|
+
function multiTouchEvent(fingers, mods2, more) {
|
|
1029
|
+
return { kind: "multitouch", fingers, ...modifiersOf(mods2), ...more };
|
|
1030
|
+
}
|
|
1031
|
+
var positionsOf = (held) => new Map([...held].map(([id, p]) => [id, p.pos]));
|
|
1032
|
+
function heldGeometry(held, toSurface, extra) {
|
|
1033
|
+
const g = multiTouchGeometry([...positionsOf(held).values(), ...extra]);
|
|
1034
|
+
return { ...g, centroid: toSurface(g.centroid) };
|
|
1035
|
+
}
|
|
1036
|
+
function fireLongPress(rec, sink, observer) {
|
|
1037
|
+
if (!rec) return;
|
|
1038
|
+
const evs = longPressEvents(rec.down);
|
|
1039
|
+
let result = sink.dispatch(evs.longpress);
|
|
1040
|
+
if (result === "unhandled") result = sink.dispatch(evs.contextmenu);
|
|
1041
|
+
if (result === "unhandled") return;
|
|
1042
|
+
rec.buffered = null;
|
|
1043
|
+
if (rec.holdType) observer.longPress?.(rec.holdType);
|
|
1044
|
+
}
|
|
1045
|
+
function createPointerRecognizer(opts) {
|
|
1046
|
+
const { sink, clock, observer = {}, toSurface = (p) => p } = opts;
|
|
1047
|
+
const world = (s) => opts.toWorld?.(s.clientX, s.clientY) ?? { x: s.clientX, y: s.clientY };
|
|
1048
|
+
const clicks = opts.clicks ?? { last: null };
|
|
1049
|
+
const held = /* @__PURE__ */ new Map();
|
|
1050
|
+
const touches = createMultiTouchEpisode();
|
|
1051
|
+
const hold = createLongPressHold(clock, (pending) => {
|
|
1052
|
+
observer.holdChanged?.(pending);
|
|
1053
|
+
});
|
|
1054
|
+
const longPressMs = () => opts.longPressMs?.() ?? LONG_PRESS_MS;
|
|
1055
|
+
const geometry = (extra = []) => heldGeometry(held, toSurface, extra);
|
|
1056
|
+
const claimForMultiTouch = (mods2) => {
|
|
1057
|
+
hold.cancel();
|
|
1058
|
+
for (const p of held.values()) p.buffered = null;
|
|
1059
|
+
const g = geometry();
|
|
1060
|
+
touches.join(positionsOf(held), g.centroid, clock.now());
|
|
1061
|
+
sink.dispatch(multiTouchEvent(held.size, mods2, g));
|
|
1062
|
+
};
|
|
1063
|
+
return {
|
|
1064
|
+
press(s) {
|
|
1065
|
+
const ev = pointerDownEvent(s);
|
|
1066
|
+
const rec = { pos: { x: s.clientX, y: s.clientY }, buffered: ev, down: pressFactsOf(s) };
|
|
1067
|
+
held.set(s.pointerId, rec);
|
|
1068
|
+
if (held.size < 2) sink.dispatch({ ...ev, stage: "press" });
|
|
1069
|
+
if ((s.pointerType === "touch" || s.pointerType === "pen") && held.size === 1) {
|
|
1070
|
+
rec.holdType = s.pointerType;
|
|
1071
|
+
hold.arm(
|
|
1072
|
+
{ pointerId: s.pointerId, pointerType: s.pointerType, press: rec.down, startedAt: clock.now(), duration: longPressMs() },
|
|
1073
|
+
() => {
|
|
1074
|
+
fireLongPress(held.get(s.pointerId), sink, observer);
|
|
1075
|
+
}
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
if (held.size >= 2) claimForMultiTouch(s);
|
|
1079
|
+
},
|
|
1080
|
+
move(s) {
|
|
1081
|
+
const rec = held.get(s.pointerId);
|
|
1082
|
+
const to = { x: s.clientX, y: s.clientY };
|
|
1083
|
+
if (rec) rec.pos = to;
|
|
1084
|
+
touches.moved(s.pointerId, to);
|
|
1085
|
+
if (rec?.buffered && pastDragThreshold(rec.down, s)) {
|
|
1086
|
+
const press = rec.buffered;
|
|
1087
|
+
rec.buffered = null;
|
|
1088
|
+
hold.cancel(s.pointerId);
|
|
1089
|
+
sink.dispatch(press);
|
|
1090
|
+
}
|
|
1091
|
+
if (held.size >= 2 && sink.multiTouchInFlight(held.size)) {
|
|
1092
|
+
sink.dispatch(multiTouchEvent(held.size, s, geometry()));
|
|
1093
|
+
}
|
|
1094
|
+
sink.dispatch(pointerMoveEvent(s, world(s)));
|
|
1095
|
+
},
|
|
1096
|
+
release(s) {
|
|
1097
|
+
hold.cancel(s.pointerId);
|
|
1098
|
+
const before = held.size;
|
|
1099
|
+
const rec = held.get(s.pointerId);
|
|
1100
|
+
held.delete(s.pointerId);
|
|
1101
|
+
if (before >= 2) sink.dispatch(multiTouchEvent(before, s, { end: "commit" }));
|
|
1102
|
+
if (before >= 2 && held.size < 2) {
|
|
1103
|
+
const fingers = touches.finish(
|
|
1104
|
+
geometry([{ x: s.clientX, y: s.clientY }]).centroid,
|
|
1105
|
+
clock.now(),
|
|
1106
|
+
longPressMs()
|
|
1107
|
+
);
|
|
1108
|
+
if (fingers !== null) sink.dispatch({ kind: "multitouchtap", fingers, ...modifiersOf(s) });
|
|
1109
|
+
}
|
|
1110
|
+
sink.dispatch(pointerUpEvent(s, world(s)));
|
|
1111
|
+
if (!rec?.buffered) return {};
|
|
1112
|
+
const at2 = world(s);
|
|
1113
|
+
const up = {
|
|
1114
|
+
target: s.target,
|
|
1115
|
+
clientX: s.clientX,
|
|
1116
|
+
clientY: s.clientY,
|
|
1117
|
+
worldX: at2.x,
|
|
1118
|
+
worldY: at2.y,
|
|
1119
|
+
...modifiersOf(s)
|
|
1120
|
+
};
|
|
1121
|
+
return composeClick(rec.down, up, clicks, () => clock.now(), (ev) => sink.dispatch(ev), observer);
|
|
1122
|
+
},
|
|
1123
|
+
cancel(pointerId) {
|
|
1124
|
+
const rec = held.get(pointerId);
|
|
1125
|
+
if (!rec) return;
|
|
1126
|
+
const before = held.size;
|
|
1127
|
+
held.delete(pointerId);
|
|
1128
|
+
hold.cancel(pointerId);
|
|
1129
|
+
if (before >= 2) sink.dispatch(multiTouchEvent(before, rec.down, { end: "cancel" }));
|
|
1130
|
+
touches.abandon();
|
|
1131
|
+
sink.dispatch({ kind: "pointercancel", pointerId, ...modifiersOf(rec.down) });
|
|
1132
|
+
},
|
|
1133
|
+
takeHold() {
|
|
1134
|
+
let holding = false;
|
|
1135
|
+
for (const [id, rec] of held) {
|
|
1136
|
+
if (!rec.holdType) continue;
|
|
1137
|
+
holding = true;
|
|
1138
|
+
if (!hold.pendingFor(id)) continue;
|
|
1139
|
+
hold.cancel(id);
|
|
1140
|
+
fireLongPress(rec, sink, observer);
|
|
1141
|
+
}
|
|
1142
|
+
return holding;
|
|
1143
|
+
},
|
|
1144
|
+
heldCount: () => held.size,
|
|
1145
|
+
positionOf: (pointerId) => held.get(pointerId)?.pos,
|
|
1146
|
+
reset() {
|
|
1147
|
+
hold.cancel();
|
|
1148
|
+
held.clear();
|
|
1149
|
+
touches.abandon();
|
|
1150
|
+
}
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
|
|
786
1154
|
// src/ui/controlTarget.ts
|
|
787
1155
|
var CONTROL_SELECTOR = [
|
|
788
1156
|
"button",
|
|
@@ -804,6 +1172,47 @@ function isInControlWithin(target, host) {
|
|
|
804
1172
|
return control !== null && control !== host && host.contains(control);
|
|
805
1173
|
}
|
|
806
1174
|
|
|
807
|
-
|
|
1175
|
+
// src/ui/keyOwner.ts
|
|
1176
|
+
function isEditableTarget(target) {
|
|
1177
|
+
if (!target || !(target instanceof Element)) return false;
|
|
1178
|
+
const tag = target.tagName;
|
|
1179
|
+
if (tag === "INPUT" || tag === "TEXTAREA") return true;
|
|
1180
|
+
if (target.isContentEditable) return true;
|
|
1181
|
+
const ce = target.getAttribute("contenteditable");
|
|
1182
|
+
return ce === "true" || ce === "";
|
|
1183
|
+
}
|
|
1184
|
+
var ACTIVATED_BY_KEY = [
|
|
1185
|
+
"button",
|
|
1186
|
+
"a[href]",
|
|
1187
|
+
"summary",
|
|
1188
|
+
"select",
|
|
1189
|
+
'input[type="button"]',
|
|
1190
|
+
'input[type="submit"]',
|
|
1191
|
+
'input[type="reset"]',
|
|
1192
|
+
'input[type="checkbox"]',
|
|
1193
|
+
'input[type="radio"]',
|
|
1194
|
+
'[role="button"]',
|
|
1195
|
+
'[role="link"]',
|
|
1196
|
+
'[role="checkbox"]',
|
|
1197
|
+
'[role="switch"]',
|
|
1198
|
+
'[role="menuitem"]',
|
|
1199
|
+
'[role="tab"]',
|
|
1200
|
+
'[role="option"]'
|
|
1201
|
+
].join(", ");
|
|
1202
|
+
function activatesFocusedControl(e) {
|
|
1203
|
+
if (e.key !== " " && e.key !== "Enter") return false;
|
|
1204
|
+
return e.target instanceof Element && e.target.matches(ACTIVATED_BY_KEY);
|
|
1205
|
+
}
|
|
1206
|
+
var SELECT_NAVIGATION = /* @__PURE__ */ new Set(["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Home", "End", "PageUp", "PageDown"]);
|
|
1207
|
+
function selectOwnsKey(e) {
|
|
1208
|
+
if (!(e.target instanceof Element) || e.target.tagName !== "SELECT") return false;
|
|
1209
|
+
if (e.metaKey || e.ctrlKey || e.altKey) return false;
|
|
1210
|
+
return SELECT_NAVIGATION.has(e.key) || e.key.length === 1;
|
|
1211
|
+
}
|
|
1212
|
+
function focusedControlOwnsKey(e) {
|
|
1213
|
+
return activatesFocusedControl(e) || selectOwnsKey(e);
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
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, 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 };
|
|
808
1217
|
//# sourceMappingURL=index.js.map
|
|
809
1218
|
//# sourceMappingURL=index.js.map
|