@weasel-js/gestures 1.10.0 → 1.11.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/README.md +106 -4
- package/dist/index.d.ts +253 -36
- package/dist/index.js +290 -82
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +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
|
+
{ name: "hover", hasTarget: true, arg: { name: "motion", values: ["move", "leave"], default: "move" } },
|
|
6
6
|
{ name: "dblTap", hasTarget: true },
|
|
7
7
|
{ name: "drag", hasTarget: true },
|
|
8
8
|
{ name: "wheel", hasTarget: true, arg: { name: "direction", values: ["up", "down", "*"], default: "*" } },
|
|
@@ -18,6 +18,7 @@ var GESTURE_DESCRIPTORS = [
|
|
|
18
18
|
// matches any payload, which is what an omitted arg slot means for a
|
|
19
19
|
// `'free'` arg.
|
|
20
20
|
{ name: "drop", hasTarget: false, arg: { name: "types", values: "free" } },
|
|
21
|
+
{ name: "carry", hasTarget: false, arg: { name: "types", values: "free" } },
|
|
21
22
|
{ name: "paste", hasTarget: false, arg: { name: "types", values: "free" } }
|
|
22
23
|
];
|
|
23
24
|
var BY_NAME = new Map(
|
|
@@ -286,47 +287,7 @@ function matchIngestTypes(items, types) {
|
|
|
286
287
|
return items.some((it) => it.mime === UNKNOWN_MIME || types.some((g) => mimeMatchesGlob(it.mime, g)));
|
|
287
288
|
}
|
|
288
289
|
|
|
289
|
-
// src/ui/
|
|
290
|
-
function resolveSpecValue(value) {
|
|
291
|
-
if (value === true) return "required";
|
|
292
|
-
if (value === "optional") return "optional";
|
|
293
|
-
return "forbidden";
|
|
294
|
-
}
|
|
295
|
-
function matchModifiers(e, mods2, isMac) {
|
|
296
|
-
let alt = "forbidden";
|
|
297
|
-
let ctrl = "forbidden";
|
|
298
|
-
let meta = "forbidden";
|
|
299
|
-
let shift = "forbidden";
|
|
300
|
-
if (mods2) {
|
|
301
|
-
alt = resolveSpecValue(mods2.alt);
|
|
302
|
-
shift = resolveSpecValue(mods2.shift);
|
|
303
|
-
if (mods2.mod !== void 0) {
|
|
304
|
-
const modReq = resolveSpecValue(mods2.mod);
|
|
305
|
-
if (isMac) meta = modReq;
|
|
306
|
-
else ctrl = modReq;
|
|
307
|
-
} else {
|
|
308
|
-
meta = resolveSpecValue(mods2.meta);
|
|
309
|
-
ctrl = resolveSpecValue(mods2.ctrl);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
return checkKey(alt, e.altKey) && checkKey(ctrl, e.ctrlKey) && checkKey(meta, e.metaKey) && checkKey(shift, e.shiftKey);
|
|
313
|
-
}
|
|
314
|
-
function checkKey(req, held) {
|
|
315
|
-
if (req === "required") return held;
|
|
316
|
-
if (req === "forbidden") return !held;
|
|
317
|
-
return true;
|
|
318
|
-
}
|
|
319
|
-
function matchKey(eventKey, specKey) {
|
|
320
|
-
const lower = eventKey.toLowerCase();
|
|
321
|
-
if (Array.isArray(specKey)) {
|
|
322
|
-
return specKey.some((k) => k.toLowerCase() === lower);
|
|
323
|
-
}
|
|
324
|
-
return specKey.toLowerCase() === lower;
|
|
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
|
-
}
|
|
290
|
+
// src/ui/matchTarget.ts
|
|
330
291
|
function affordanceKindOf(target) {
|
|
331
292
|
if (typeof target !== "object" || target === null) return void 0;
|
|
332
293
|
const k = target.kind;
|
|
@@ -379,6 +340,48 @@ function matchTarget(target, specTarget, bodyTarget, bodyKind) {
|
|
|
379
340
|
}
|
|
380
341
|
}
|
|
381
342
|
}
|
|
343
|
+
|
|
344
|
+
// src/ui/match.ts
|
|
345
|
+
function resolveSpecValue(value) {
|
|
346
|
+
if (value === true) return "required";
|
|
347
|
+
if (value === "optional") return "optional";
|
|
348
|
+
return "forbidden";
|
|
349
|
+
}
|
|
350
|
+
function matchModifiers(e, mods2, isMac) {
|
|
351
|
+
let alt = "forbidden";
|
|
352
|
+
let ctrl = "forbidden";
|
|
353
|
+
let meta = "forbidden";
|
|
354
|
+
let shift = "forbidden";
|
|
355
|
+
if (mods2) {
|
|
356
|
+
alt = resolveSpecValue(mods2.alt);
|
|
357
|
+
shift = resolveSpecValue(mods2.shift);
|
|
358
|
+
if (mods2.mod !== void 0) {
|
|
359
|
+
const modReq = resolveSpecValue(mods2.mod);
|
|
360
|
+
if (isMac) meta = modReq;
|
|
361
|
+
else ctrl = modReq;
|
|
362
|
+
} else {
|
|
363
|
+
meta = resolveSpecValue(mods2.meta);
|
|
364
|
+
ctrl = resolveSpecValue(mods2.ctrl);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return checkKey(alt, e.altKey) && checkKey(ctrl, e.ctrlKey) && checkKey(meta, e.metaKey) && checkKey(shift, e.shiftKey);
|
|
368
|
+
}
|
|
369
|
+
function checkKey(req, held) {
|
|
370
|
+
if (req === "required") return held;
|
|
371
|
+
if (req === "forbidden") return !held;
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
function matchKey(eventKey, specKey) {
|
|
375
|
+
const lower = eventKey.toLowerCase();
|
|
376
|
+
if (Array.isArray(specKey)) {
|
|
377
|
+
return specKey.some((k) => k.toLowerCase() === lower);
|
|
378
|
+
}
|
|
379
|
+
return specKey.toLowerCase() === lower;
|
|
380
|
+
}
|
|
381
|
+
function matchKeyOrCode(e, spec) {
|
|
382
|
+
if (spec.code !== void 0 && e.code !== void 0 && [spec.code].flat().includes(e.code)) return true;
|
|
383
|
+
return matchKey(e.key, spec.key);
|
|
384
|
+
}
|
|
382
385
|
function normalizePhase(spec) {
|
|
383
386
|
if (typeof spec === "string") {
|
|
384
387
|
return [{ channel: "&", phase: spec }];
|
|
@@ -406,6 +409,16 @@ function matchPhaseAtom(a, ctx) {
|
|
|
406
409
|
const isEngaged = ctx.engagedChannels.has(id);
|
|
407
410
|
return a.phase === "engaged" ? isEngaged : !isEngaged;
|
|
408
411
|
}
|
|
412
|
+
function matchPress(e, spec, isMac) {
|
|
413
|
+
if (e.kind !== "pointerdown") return false;
|
|
414
|
+
if (spec.kind === "drag") {
|
|
415
|
+
if (e.stage === "press") return false;
|
|
416
|
+
} else if (e.stage !== "press") {
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
if (!matchModifiers(e, spec.mods, isMac)) return false;
|
|
420
|
+
return matchTarget(e.affordance, spec.target, e.bodyTarget, e.bodyKind);
|
|
421
|
+
}
|
|
409
422
|
function matchSpec(e, spec, isMac, phaseCtx) {
|
|
410
423
|
if (spec.phase !== void 0) {
|
|
411
424
|
const ctx = phaseCtx ?? { selfChannel: null, engagedChannels: EMPTY_ENGAGED };
|
|
@@ -452,24 +465,22 @@ function matchSpec(e, spec, isMac, phaseCtx) {
|
|
|
452
465
|
// Each happens at a point, so its target is what the event classified
|
|
453
466
|
// there: for a long-press, the affordance its press landed on.
|
|
454
467
|
case "contextMenu":
|
|
455
|
-
case "longPress":
|
|
456
|
-
|
|
457
|
-
if (e.kind !== "contextmenu" && e.kind !== "longpress" && e.kind !== "hover") return false;
|
|
468
|
+
case "longPress": {
|
|
469
|
+
if (e.kind !== "contextmenu" && e.kind !== "longpress") return false;
|
|
458
470
|
if (e.kind !== spec.kind.toLowerCase()) return false;
|
|
459
471
|
if (!matchModifiers(e, spec.mods, isMac)) return false;
|
|
460
472
|
return matchTarget(e.affordance, spec.target, e.bodyTarget, e.bodyKind);
|
|
461
473
|
}
|
|
462
|
-
case "
|
|
463
|
-
if (e.kind !== "
|
|
464
|
-
if (e.stage === "press") return false;
|
|
465
|
-
if (!matchModifiers(e, spec.mods, isMac)) return false;
|
|
466
|
-
return matchTarget(e.affordance, spec.target, e.bodyTarget, e.bodyKind);
|
|
467
|
-
}
|
|
468
|
-
case "pointerDown": {
|
|
469
|
-
if (e.kind !== "pointerdown" || e.stage !== "press") return false;
|
|
474
|
+
case "hover": {
|
|
475
|
+
if (e.kind !== "hover") return false;
|
|
470
476
|
if (!matchModifiers(e, spec.mods, isMac)) return false;
|
|
471
|
-
|
|
477
|
+
const on = !e.off && matchTarget(e.affordance, spec.target, e.bodyTarget, e.bodyKind);
|
|
478
|
+
if (spec.motion !== "leave") return on;
|
|
479
|
+
return !on && e.from !== void 0 && matchTarget(e.from.affordance, spec.target, e.from.bodyTarget, e.from.bodyKind);
|
|
472
480
|
}
|
|
481
|
+
case "drag":
|
|
482
|
+
case "pointerDown":
|
|
483
|
+
return matchPress(e, spec, isMac);
|
|
473
484
|
case "multiTouch": {
|
|
474
485
|
if (e.kind !== "multitouch") return false;
|
|
475
486
|
if (e.fingers !== spec.fingers) return false;
|
|
@@ -480,21 +491,25 @@ function matchSpec(e, spec, isMac, phaseCtx) {
|
|
|
480
491
|
if (e.fingers !== spec.fingers) return false;
|
|
481
492
|
return matchModifiers(e, spec.mods, isMac);
|
|
482
493
|
}
|
|
483
|
-
case "drop":
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
return
|
|
487
|
-
}
|
|
488
|
-
case "paste": {
|
|
489
|
-
if (e.kind !== "paste") return false;
|
|
490
|
-
if (!matchIngestTypes(e.items, spec.types)) return false;
|
|
491
|
-
return matchModifiers(e, spec.mods, isMac);
|
|
492
|
-
}
|
|
494
|
+
case "drop":
|
|
495
|
+
case "carry":
|
|
496
|
+
case "paste":
|
|
497
|
+
return matchArrival(e, spec, isMac);
|
|
493
498
|
default: {
|
|
494
499
|
return false;
|
|
495
500
|
}
|
|
496
501
|
}
|
|
497
502
|
}
|
|
503
|
+
function matchArrival(e, spec, isMac) {
|
|
504
|
+
if (e.kind !== spec.kind) return false;
|
|
505
|
+
if (e.kind === "carry") {
|
|
506
|
+
if (e.stage !== "enter" && e.stage !== "drop") return false;
|
|
507
|
+
if (!matchIngestTypes([{ mime: e.type }], spec.types)) return false;
|
|
508
|
+
} else if (e.kind === "drop" || e.kind === "paste") {
|
|
509
|
+
if (!matchIngestTypes(e.items, spec.types)) return false;
|
|
510
|
+
}
|
|
511
|
+
return matchModifiers(e, spec.mods, isMac);
|
|
512
|
+
}
|
|
498
513
|
var EMPTY_ENGAGED = /* @__PURE__ */ new Set();
|
|
499
514
|
|
|
500
515
|
// src/grammar/specKinds.ts
|
|
@@ -513,6 +528,7 @@ var SPEC_KIND_TO_GESTURE = {
|
|
|
513
528
|
multiTouch: "multiTouch",
|
|
514
529
|
multiTouchTap: "multiTouchTap",
|
|
515
530
|
drop: "drop",
|
|
531
|
+
carry: "carry",
|
|
516
532
|
paste: "paste"
|
|
517
533
|
};
|
|
518
534
|
var GESTURE_TO_SPEC_KIND = new Map(
|
|
@@ -557,9 +573,10 @@ function routeToSpec(parsed) {
|
|
|
557
573
|
switch (kind) {
|
|
558
574
|
case void 0:
|
|
559
575
|
return fail(`${parsed.gesture} has no GestureSpec kind`);
|
|
576
|
+
case "hover":
|
|
577
|
+
return withMods({ kind, ...arg === "leave" ? { motion: "leave" } : {}, ...t });
|
|
560
578
|
case "click":
|
|
561
579
|
case "pointerDown":
|
|
562
|
-
case "hover":
|
|
563
580
|
case "doubleClick":
|
|
564
581
|
case "drag":
|
|
565
582
|
case "contextMenu":
|
|
@@ -582,6 +599,7 @@ function routeToSpec(parsed) {
|
|
|
582
599
|
if (arg === void 0) return fail(`a ${kind} spec needs concrete fingers`);
|
|
583
600
|
return withMods({ kind, fingers: Number(arg) });
|
|
584
601
|
case "drop":
|
|
602
|
+
case "carry":
|
|
585
603
|
case "paste":
|
|
586
604
|
return withMods({ kind, ...arg ? { types: arg.split("|") } : {} });
|
|
587
605
|
}
|
|
@@ -626,6 +644,10 @@ function partitionModifiers(mods2) {
|
|
|
626
644
|
}
|
|
627
645
|
return { required, optional };
|
|
628
646
|
}
|
|
647
|
+
function leftClause(target) {
|
|
648
|
+
if (target === void 0 || target === "*") return "off the surface";
|
|
649
|
+
return target === "empty" ? "off empty canvas" : `off a ${target}`;
|
|
650
|
+
}
|
|
629
651
|
function phaseAtomParts({ channel, phase }) {
|
|
630
652
|
const subject = channel === "&" ? "the tool" : channel === "*" ? "any tool" : `the ${channel} tool`;
|
|
631
653
|
if (phase === "initial") return [`${subject} is `, term("idle")];
|
|
@@ -688,13 +710,15 @@ function actionClause(parsed, required) {
|
|
|
688
710
|
case "pointerDown":
|
|
689
711
|
return userDoes(required, `presses${target}`);
|
|
690
712
|
case "hover":
|
|
691
|
-
return userDoes(required, `moves the pointer${target}`);
|
|
713
|
+
return userDoes(required, arg === "leave" ? `moves the pointer ${leftClause(parsed.target)}` : `moves the pointer${target}`);
|
|
692
714
|
case "dblTap":
|
|
693
715
|
return userDoes(required, `double-taps${target}`);
|
|
694
716
|
case "drag":
|
|
695
717
|
return userDoes(required, `drags${target}`);
|
|
696
718
|
case "drop":
|
|
697
719
|
return userDoes(required, `drops ${arg ?? "any"} content onto the canvas`);
|
|
720
|
+
case "carry":
|
|
721
|
+
return userDoes(required, `carries ${arg ?? "anything"} onto the canvas`);
|
|
698
722
|
case "paste":
|
|
699
723
|
return userDoes(required, `pastes ${arg ?? "any"} content`);
|
|
700
724
|
}
|
|
@@ -769,6 +793,115 @@ function isApplePlatform() {
|
|
|
769
793
|
return /mac|iphone|ipad|ipod/i.test(raw);
|
|
770
794
|
}
|
|
771
795
|
|
|
796
|
+
// src/ui/keySpecsFromMods.ts
|
|
797
|
+
var MOD_BY_PLATFORM = {
|
|
798
|
+
mod: {
|
|
799
|
+
macos: { auto: "\u2318", symbol: "\u2318", text: "Cmd" },
|
|
800
|
+
windows: { auto: "Ctrl", symbol: "\u2388", text: "Ctrl" },
|
|
801
|
+
linux: { auto: "Ctrl", symbol: "\u2388", text: "Ctrl" }
|
|
802
|
+
},
|
|
803
|
+
shift: {
|
|
804
|
+
macos: { auto: "\u21E7", symbol: "\u21E7", text: "Shift" },
|
|
805
|
+
windows: { auto: "\u21E7", symbol: "\u21E7", text: "Shift" },
|
|
806
|
+
linux: { auto: "\u21E7", symbol: "\u21E7", text: "Shift" }
|
|
807
|
+
},
|
|
808
|
+
// Off macOS the symbols are ISO/IEC 9995-7's: ⎈ for Control, ⎇ for Alt.
|
|
809
|
+
alt: {
|
|
810
|
+
macos: { auto: "\u2325", symbol: "\u2325", text: "Option" },
|
|
811
|
+
windows: { auto: "Alt", symbol: "\u2387", text: "Alt" },
|
|
812
|
+
linux: { auto: "Alt", symbol: "\u2387", text: "Alt" }
|
|
813
|
+
},
|
|
814
|
+
ctrl: {
|
|
815
|
+
macos: { auto: "\u2303", symbol: "\u2303", text: "Control" },
|
|
816
|
+
windows: { auto: "Ctrl", symbol: "\u2388", text: "Ctrl" },
|
|
817
|
+
linux: { auto: "Ctrl", symbol: "\u2388", text: "Ctrl" }
|
|
818
|
+
},
|
|
819
|
+
// Windows/Linux Meta has a recognized symbol on the physical key (⊞ Win
|
|
820
|
+
// logo / Super logo), so `auto` shows it. Use `text` for Win / Super.
|
|
821
|
+
meta: {
|
|
822
|
+
macos: { auto: "\u2318", symbol: "\u2318", text: "Cmd" },
|
|
823
|
+
windows: { auto: "\u229E", symbol: "\u229E", text: "Win" },
|
|
824
|
+
linux: { auto: "\u229E", symbol: "\u229E", text: "Super" }
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
var MODIFIER_LABELS = new Set(
|
|
828
|
+
Object.values(MOD_BY_PLATFORM).flatMap(
|
|
829
|
+
(byPlatform) => Object.values(byPlatform).flatMap((byLegend) => Object.values(byLegend))
|
|
830
|
+
)
|
|
831
|
+
);
|
|
832
|
+
function detectPlatform() {
|
|
833
|
+
if (typeof navigator === "undefined" || !navigator) return "macos";
|
|
834
|
+
if (isApplePlatform()) return "macos";
|
|
835
|
+
const nav = navigator;
|
|
836
|
+
const raw = nav.userAgentData?.platform || nav.platform || nav.userAgent || "";
|
|
837
|
+
return /\bwin/i.test(raw) ? "windows" : "linux";
|
|
838
|
+
}
|
|
839
|
+
function keySpecsFromMods(mods2, opts = {}) {
|
|
840
|
+
const platform = opts.platform ?? detectPlatform();
|
|
841
|
+
const legend = opts.legend ?? "auto";
|
|
842
|
+
return mods2.map((m) => {
|
|
843
|
+
const label = opts.labels?.[m.name] ?? MOD_BY_PLATFORM[m.name][platform][legend];
|
|
844
|
+
return m.optional ? { label, optional: true } : { label };
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
var NAMED_KEY = {
|
|
848
|
+
Escape: { macos: { auto: "\u238B", symbol: "\u238B", text: "Esc" }, windows: { auto: "Esc", symbol: "\u238B", text: "Esc" }, linux: { auto: "Esc", symbol: "\u238B", text: "Esc" } },
|
|
849
|
+
Enter: { macos: { auto: "\u21B5", symbol: "\u21B5", text: "Return" }, windows: { auto: "Enter", symbol: "\u21B5", text: "Enter" }, linux: { auto: "Enter", symbol: "\u21B5", text: "Enter" } },
|
|
850
|
+
Return: { macos: { auto: "\u21B5", symbol: "\u21B5", text: "Return" }, windows: { auto: "Enter", symbol: "\u21B5", text: "Enter" }, linux: { auto: "Enter", symbol: "\u21B5", text: "Enter" } },
|
|
851
|
+
Tab: { macos: { auto: "\u21E5", symbol: "\u21E5", text: "Tab" }, windows: { auto: "Tab", symbol: "\u21E5", text: "Tab" }, linux: { auto: "Tab", symbol: "\u21E5", text: "Tab" } },
|
|
852
|
+
Space: { macos: { auto: "\u2423", symbol: "\u2423", text: "Space" }, windows: { auto: "Space", symbol: "\u2423", text: "Space" }, linux: { auto: "Space", symbol: "\u2423", text: "Space" } },
|
|
853
|
+
" ": { macos: { auto: "\u2423", symbol: "\u2423", text: "Space" }, windows: { auto: "Space", symbol: "\u2423", text: "Space" }, linux: { auto: "Space", symbol: "\u2423", text: "Space" } },
|
|
854
|
+
Backspace: { macos: { auto: "\u232B", symbol: "\u232B", text: "Backspace" }, windows: { auto: "Backspace", symbol: "\u27F5", text: "Backspace" }, linux: { auto: "Backspace", symbol: "\u27F5", text: "Backspace" } },
|
|
855
|
+
Delete: { macos: { auto: "\u2326", symbol: "\u2326", text: "Delete" }, windows: { auto: "Delete", symbol: "\u2326", text: "Delete" }, linux: { auto: "Delete", symbol: "\u2326", text: "Delete" } },
|
|
856
|
+
// Context-menu key (the right-Ctrl-area key on Windows / Linux keyboards
|
|
857
|
+
// with a ▤ icon). Mac has no equivalent; falls back to text 'Menu'.
|
|
858
|
+
ContextMenu: { macos: { auto: "Menu", symbol: "Menu", text: "Menu" }, windows: { auto: "\u25A4", symbol: "\u25A4", text: "Menu" }, linux: { auto: "\u25A4", symbol: "\u25A4", text: "Menu" } },
|
|
859
|
+
// Symbol mode only: Apple's arrows for Home, End, and the page keys, and
|
|
860
|
+
// ISO/IEC 9995-7's symbols for the rest.
|
|
861
|
+
Home: { macos: { auto: "Home", symbol: "\u2196", text: "Home" }, windows: { auto: "Home", symbol: "\u2196", text: "Home" }, linux: { auto: "Home", symbol: "\u2196", text: "Home" } },
|
|
862
|
+
End: { macos: { auto: "End", symbol: "\u2198", text: "End" }, windows: { auto: "End", symbol: "\u2198", text: "End" }, linux: { auto: "End", symbol: "\u2198", text: "End" } },
|
|
863
|
+
PageUp: { macos: { auto: "PgUp", symbol: "\u21DE", text: "PgUp" }, windows: { auto: "PgUp", symbol: "\u21DE", text: "PgUp" }, linux: { auto: "PgUp", symbol: "\u21DE", text: "PgUp" } },
|
|
864
|
+
PageDown: { macos: { auto: "PgDn", symbol: "\u21DF", text: "PgDn" }, windows: { auto: "PgDn", symbol: "\u21DF", text: "PgDn" }, linux: { auto: "PgDn", symbol: "\u21DF", text: "PgDn" } },
|
|
865
|
+
PrintScreen: { macos: { auto: "PrtSc", symbol: "\u2399", text: "PrtSc" }, windows: { auto: "PrtSc", symbol: "\u2399", text: "PrtSc" }, linux: { auto: "PrtSc", symbol: "\u2399", text: "PrtSc" } },
|
|
866
|
+
Insert: { macos: { auto: "Ins", symbol: "\u2380", text: "Insert" }, windows: { auto: "Ins", symbol: "\u2380", text: "Insert" }, linux: { auto: "Ins", symbol: "\u2380", text: "Insert" } },
|
|
867
|
+
ScrollLock: { macos: { auto: "ScrLk", symbol: "\u21F3", text: "ScrLk" }, windows: { auto: "ScrLk", symbol: "\u21F3", text: "ScrLk" }, linux: { auto: "ScrLk", symbol: "\u21F3", text: "ScrLk" } },
|
|
868
|
+
Pause: { macos: { auto: "Pause", symbol: "\u2389", text: "Pause" }, windows: { auto: "Pause", symbol: "\u2389", text: "Pause" }, linux: { auto: "Pause", symbol: "\u2389", text: "Pause" } },
|
|
869
|
+
ArrowUp: { macos: { auto: "\u2191", symbol: "\u2191", text: "\u2191" }, windows: { auto: "\u2191", symbol: "\u2191", text: "\u2191" }, linux: { auto: "\u2191", symbol: "\u2191", text: "\u2191" } },
|
|
870
|
+
ArrowDown: { macos: { auto: "\u2193", symbol: "\u2193", text: "\u2193" }, windows: { auto: "\u2193", symbol: "\u2193", text: "\u2193" }, linux: { auto: "\u2193", symbol: "\u2193", text: "\u2193" } },
|
|
871
|
+
ArrowLeft: { macos: { auto: "\u2190", symbol: "\u2190", text: "\u2190" }, windows: { auto: "\u2190", symbol: "\u2190", text: "\u2190" }, linux: { auto: "\u2190", symbol: "\u2190", text: "\u2190" } },
|
|
872
|
+
ArrowRight: { macos: { auto: "\u2192", symbol: "\u2192", text: "\u2192" }, windows: { auto: "\u2192", symbol: "\u2192", text: "\u2192" }, linux: { auto: "\u2192", symbol: "\u2192", text: "\u2192" } }
|
|
873
|
+
};
|
|
874
|
+
function keySpecFromKey(raw, opts = {}) {
|
|
875
|
+
const platform = opts.platform ?? detectPlatform();
|
|
876
|
+
const legend = opts.legend ?? "auto";
|
|
877
|
+
const entry = NAMED_KEY[raw];
|
|
878
|
+
const override = opts.labels?.[raw] ?? (raw.length === 1 ? opts.labels?.[raw.toUpperCase()] : void 0);
|
|
879
|
+
const label = override ?? (entry ? entry[platform][legend] : raw.length === 1 ? raw.toUpperCase() : raw);
|
|
880
|
+
return opts.optional ? { label, optional: true } : { label };
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// src/ui/keySpecsFromShortcut.ts
|
|
884
|
+
function keySpecsFromShortcut(shortcut, opts = {}) {
|
|
885
|
+
if (!shortcut) return void 0;
|
|
886
|
+
const mods2 = [];
|
|
887
|
+
if (shortcut.mod) mods2.push({ name: "mod" });
|
|
888
|
+
if (shortcut.shift) mods2.push(shortcut.shift === "optional" ? { name: "shift", optional: true } : { name: "shift" });
|
|
889
|
+
if (shortcut.alt) mods2.push({ name: "alt" });
|
|
890
|
+
const key = typeof shortcut.key === "string" ? shortcut.key : shortcut.key[0];
|
|
891
|
+
const keys = keySpecsFromMods(mods2, opts);
|
|
892
|
+
return key === void 0 ? keys : [...keys, keySpecFromKey(key, opts)];
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// src/ui/formatShortcut.ts
|
|
896
|
+
var labels = (b, opts) => b ? keySpecsFromShortcut({ ...b, shift: b.shift === true }, opts)?.map((k) => k.label) : void 0;
|
|
897
|
+
function formatShortcutParts(b, opts = {}) {
|
|
898
|
+
return labels(b, opts);
|
|
899
|
+
}
|
|
900
|
+
function formatShortcut(b, opts = {}) {
|
|
901
|
+
const platform = opts.platform ?? detectPlatform();
|
|
902
|
+
return platform === "macos" ? labels(b, { platform })?.join("") : labels(b, { platform, legend: "text" })?.join("+");
|
|
903
|
+
}
|
|
904
|
+
|
|
772
905
|
// src/ui/fixedSeat.ts
|
|
773
906
|
var HOST_SELECTOR = "[data-wzl-portal-host],[data-wzl-theme],[data-wzl-mode]";
|
|
774
907
|
function nearestPortalHost(from) {
|
|
@@ -834,7 +967,7 @@ function specificity(spec) {
|
|
|
834
967
|
const mods2 = "mods" in spec ? spec.mods : void 0;
|
|
835
968
|
const m = modsCount(mods2);
|
|
836
969
|
const p = phaseRank("phase" in spec ? spec.phase : void 0);
|
|
837
|
-
const typed = (spec.kind === "drop" || spec.kind === "paste") && spec.types !== void 0 && spec.types.length > 0 ? 2 : 1;
|
|
970
|
+
const typed = (spec.kind === "drop" || spec.kind === "carry" || spec.kind === "paste") && spec.types !== void 0 && spec.types.length > 0 ? 2 : 1;
|
|
838
971
|
return [t, m, p, typed];
|
|
839
972
|
}
|
|
840
973
|
|
|
@@ -868,12 +1001,16 @@ function clickEvents(down, up) {
|
|
|
868
1001
|
x: up.worldX,
|
|
869
1002
|
y: up.worldY,
|
|
870
1003
|
clientX: up.clientX,
|
|
871
|
-
clientY: up.clientY
|
|
872
|
-
...pressedOn(down)
|
|
1004
|
+
clientY: up.clientY
|
|
873
1005
|
};
|
|
1006
|
+
const named = down.doubleClickBody;
|
|
874
1007
|
return {
|
|
875
|
-
click: { kind: "click", ...shared, pressX: down.worldX, pressY: down.worldY },
|
|
876
|
-
doubleclick: {
|
|
1008
|
+
click: { kind: "click", ...shared, ...pressedOn(down), pressX: down.worldX, pressY: down.worldY },
|
|
1009
|
+
doubleclick: {
|
|
1010
|
+
kind: "doubleclick",
|
|
1011
|
+
...shared,
|
|
1012
|
+
...pressedOn(named ? { affordance: down.affordance, ...named } : down)
|
|
1013
|
+
}
|
|
877
1014
|
};
|
|
878
1015
|
}
|
|
879
1016
|
function longPressEvents(down) {
|
|
@@ -893,14 +1030,14 @@ function longPressEvents(down) {
|
|
|
893
1030
|
}
|
|
894
1031
|
|
|
895
1032
|
// src/recognizer/click.ts
|
|
896
|
-
function composeClick(down, up, clicks, now, dispatch, observer) {
|
|
1033
|
+
function composeClick(down, up, clicks, now, dispatch, observer, limits = { maxMs: DOUBLE_CLICK_MAX_MS, maxPx: DOUBLE_CLICK_MAX_PX }) {
|
|
897
1034
|
const evs = clickEvents(down, up);
|
|
898
1035
|
const world = { x: up.worldX, y: up.worldY };
|
|
899
1036
|
dispatch(evs.click);
|
|
900
1037
|
observer.click?.(world);
|
|
901
1038
|
const t = now();
|
|
902
1039
|
const prev = clicks.last;
|
|
903
|
-
const follows = prev !== null && t - prev.t <
|
|
1040
|
+
const follows = prev !== null && t - prev.t < limits.maxMs && Math.hypot(up.clientX - prev.clientX, up.clientY - prev.clientY) < limits.maxPx;
|
|
904
1041
|
if (!follows) {
|
|
905
1042
|
clicks.last = { t, clientX: up.clientX, clientY: up.clientY };
|
|
906
1043
|
return {};
|
|
@@ -954,7 +1091,7 @@ function multiTouchGeometry(positions) {
|
|
|
954
1091
|
angle: Math.atan2(dy, dx) * 180 / Math.PI
|
|
955
1092
|
};
|
|
956
1093
|
}
|
|
957
|
-
function createMultiTouchEpisode() {
|
|
1094
|
+
function createMultiTouchEpisode(tapPx = () => TAP_THRESHOLD_PX) {
|
|
958
1095
|
let episode = null;
|
|
959
1096
|
return {
|
|
960
1097
|
join(positions, centroid, now) {
|
|
@@ -975,14 +1112,14 @@ function createMultiTouchEpisode() {
|
|
|
975
1112
|
},
|
|
976
1113
|
moved(pointerId, to) {
|
|
977
1114
|
const from = episode?.joinedAt.get(pointerId);
|
|
978
|
-
if (episode && from && Math.hypot(to.x - from.x, to.y - from.y) >
|
|
1115
|
+
if (episode && from && Math.hypot(to.x - from.x, to.y - from.y) > tapPx()) episode.moved = true;
|
|
979
1116
|
},
|
|
980
1117
|
finish(centroid, now, maxHoldMs) {
|
|
981
1118
|
const e = episode;
|
|
982
1119
|
episode = null;
|
|
983
1120
|
if (!e) return null;
|
|
984
1121
|
const brief = now - e.startedAt < maxHoldMs;
|
|
985
|
-
const still = Math.hypot(centroid.x - e.centroid.x, centroid.y - e.centroid.y) <=
|
|
1122
|
+
const still = Math.hypot(centroid.x - e.centroid.x, centroid.y - e.centroid.y) <= tapPx();
|
|
986
1123
|
return brief && !e.moved && still ? e.fingers : null;
|
|
987
1124
|
},
|
|
988
1125
|
abandon() {
|
|
@@ -1003,7 +1140,8 @@ function pressFactsOf(s) {
|
|
|
1003
1140
|
worldX: s.x,
|
|
1004
1141
|
worldY: s.y,
|
|
1005
1142
|
...modifiersOf(s),
|
|
1006
|
-
...pressedOn(s)
|
|
1143
|
+
...pressedOn(s),
|
|
1144
|
+
...s.doubleClickBody !== void 0 ? { doubleClickBody: s.doubleClickBody } : {}
|
|
1007
1145
|
};
|
|
1008
1146
|
}
|
|
1009
1147
|
var at = (s, world) => ({
|
|
@@ -1042,16 +1180,27 @@ function fireLongPress(rec, sink, observer) {
|
|
|
1042
1180
|
rec.buffered = null;
|
|
1043
1181
|
if (rec.holdType) observer.longPress?.(rec.holdType);
|
|
1044
1182
|
}
|
|
1183
|
+
function thresholdsOf(opts) {
|
|
1184
|
+
return {
|
|
1185
|
+
longPressMs: () => opts.longPressMs?.() ?? LONG_PRESS_MS,
|
|
1186
|
+
tapPx: () => opts.tapPx?.() ?? TAP_THRESHOLD_PX,
|
|
1187
|
+
dragPx: () => opts.dragPx?.() ?? DRAG_THRESHOLD_PX,
|
|
1188
|
+
doubleClick: () => ({
|
|
1189
|
+
maxMs: opts.doubleClickMs?.() ?? DOUBLE_CLICK_MAX_MS,
|
|
1190
|
+
maxPx: opts.doubleClickPx?.() ?? DOUBLE_CLICK_MAX_PX
|
|
1191
|
+
})
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1045
1194
|
function createPointerRecognizer(opts) {
|
|
1046
1195
|
const { sink, clock, observer = {}, toSurface = (p) => p } = opts;
|
|
1047
1196
|
const world = (s) => opts.toWorld?.(s.clientX, s.clientY) ?? { x: s.clientX, y: s.clientY };
|
|
1048
1197
|
const clicks = opts.clicks ?? { last: null };
|
|
1049
1198
|
const held = /* @__PURE__ */ new Map();
|
|
1050
|
-
const
|
|
1199
|
+
const { longPressMs, tapPx, dragPx, doubleClick } = thresholdsOf(opts);
|
|
1200
|
+
const touches = createMultiTouchEpisode(tapPx);
|
|
1051
1201
|
const hold = createLongPressHold(clock, (pending) => {
|
|
1052
1202
|
observer.holdChanged?.(pending);
|
|
1053
1203
|
});
|
|
1054
|
-
const longPressMs = () => opts.longPressMs?.() ?? LONG_PRESS_MS;
|
|
1055
1204
|
const geometry = (extra = []) => heldGeometry(held, toSurface, extra);
|
|
1056
1205
|
const claimForMultiTouch = (mods2) => {
|
|
1057
1206
|
hold.cancel();
|
|
@@ -1082,7 +1231,7 @@ function createPointerRecognizer(opts) {
|
|
|
1082
1231
|
const to = { x: s.clientX, y: s.clientY };
|
|
1083
1232
|
if (rec) rec.pos = to;
|
|
1084
1233
|
touches.moved(s.pointerId, to);
|
|
1085
|
-
if (rec?.buffered && pastDragThreshold(rec.down, s)) {
|
|
1234
|
+
if (rec?.buffered && pastDragThreshold(rec.down, s, dragPx())) {
|
|
1086
1235
|
const press = rec.buffered;
|
|
1087
1236
|
rec.buffered = null;
|
|
1088
1237
|
hold.cancel(s.pointerId);
|
|
@@ -1118,7 +1267,7 @@ function createPointerRecognizer(opts) {
|
|
|
1118
1267
|
worldY: at2.y,
|
|
1119
1268
|
...modifiersOf(s)
|
|
1120
1269
|
};
|
|
1121
|
-
return composeClick(rec.down, up, clicks, () => clock.now(), (ev) => sink.dispatch(ev), observer);
|
|
1270
|
+
return composeClick(rec.down, up, clicks, () => clock.now(), (ev) => sink.dispatch(ev), observer, doubleClick());
|
|
1122
1271
|
},
|
|
1123
1272
|
cancel(pointerId) {
|
|
1124
1273
|
const rec = held.get(pointerId);
|
|
@@ -1172,6 +1321,65 @@ function isInControlWithin(target, host) {
|
|
|
1172
1321
|
return control !== null && control !== host && host.contains(control);
|
|
1173
1322
|
}
|
|
1174
1323
|
|
|
1324
|
+
// src/ui/heldPointers.ts
|
|
1325
|
+
var trackers = /* @__PURE__ */ new WeakMap();
|
|
1326
|
+
function attach(element) {
|
|
1327
|
+
const doc = element.ownerDocument;
|
|
1328
|
+
const win = doc.defaultView;
|
|
1329
|
+
const held = /* @__PURE__ */ new Map();
|
|
1330
|
+
const down = (e) => {
|
|
1331
|
+
const p = e;
|
|
1332
|
+
held.set(p.pointerId, !!p.buttons);
|
|
1333
|
+
};
|
|
1334
|
+
const up = (e) => {
|
|
1335
|
+
held.delete(e.pointerId);
|
|
1336
|
+
};
|
|
1337
|
+
const move = (e) => {
|
|
1338
|
+
const p = e;
|
|
1339
|
+
if (held.get(p.pointerId) && p.buttons === 0) held.delete(p.pointerId);
|
|
1340
|
+
};
|
|
1341
|
+
const blur = () => {
|
|
1342
|
+
held.clear();
|
|
1343
|
+
};
|
|
1344
|
+
element.addEventListener("pointerdown", down, true);
|
|
1345
|
+
doc.addEventListener("pointerup", up, true);
|
|
1346
|
+
doc.addEventListener("pointercancel", up, true);
|
|
1347
|
+
doc.addEventListener("pointermove", move, true);
|
|
1348
|
+
win?.addEventListener("blur", blur);
|
|
1349
|
+
return {
|
|
1350
|
+
readers: 0,
|
|
1351
|
+
held,
|
|
1352
|
+
detach() {
|
|
1353
|
+
element.removeEventListener("pointerdown", down, true);
|
|
1354
|
+
doc.removeEventListener("pointerup", up, true);
|
|
1355
|
+
doc.removeEventListener("pointercancel", up, true);
|
|
1356
|
+
doc.removeEventListener("pointermove", move, true);
|
|
1357
|
+
win?.removeEventListener("blur", blur);
|
|
1358
|
+
}
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
function heldPointers(element) {
|
|
1362
|
+
let tracker = trackers.get(element);
|
|
1363
|
+
if (!tracker) {
|
|
1364
|
+
tracker = attach(element);
|
|
1365
|
+
trackers.set(element, tracker);
|
|
1366
|
+
}
|
|
1367
|
+
const shared = tracker;
|
|
1368
|
+
shared.readers += 1;
|
|
1369
|
+
let released = false;
|
|
1370
|
+
return {
|
|
1371
|
+
count: () => shared.held.size,
|
|
1372
|
+
release() {
|
|
1373
|
+
if (released) return;
|
|
1374
|
+
released = true;
|
|
1375
|
+
shared.readers -= 1;
|
|
1376
|
+
if (shared.readers > 0) return;
|
|
1377
|
+
shared.detach();
|
|
1378
|
+
if (trackers.get(element) === shared) trackers.delete(element);
|
|
1379
|
+
}
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1175
1383
|
// src/ui/keyOwner.ts
|
|
1176
1384
|
function isEditableTarget(target) {
|
|
1177
1385
|
if (!target || !(target instanceof Element)) return false;
|
|
@@ -1213,6 +1421,6 @@ function focusedControlOwnsKey(e) {
|
|
|
1213
1421
|
return activatesFocusedControl(e) || selectOwnsKey(e);
|
|
1214
1422
|
}
|
|
1215
1423
|
|
|
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 };
|
|
1424
|
+
export { ACTIVE_SIGILS, CONTROL_SELECTOR, DOUBLE_CLICK_MAX_MS, DOUBLE_CLICK_MAX_PX, DRAG_THRESHOLD_PX, GESTURE_DESCRIPTORS, LONG_PRESS_MS, MODIFIER_LABELS, 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, detectPlatform, fixedSeat, focusedControlOwnsKey, formatKeyRoute, formatPhaseAtom, formatRoute, formatShortcut, formatShortcutParts, getGestureDescriptor, heldPointers, hitPredicate, isApplePlatform, isEditableTarget, isInControlWithin, isKnownGestureName, keyRouteToSpec, keySpecFromKey, keySpecsFromMods, keySpecsFromShortcut, longPressEvents, matchIngestTypes, matchKey, matchModifiers, matchPhase, matchSpec, matchTarget, mimeMatchesGlob, modifierComboToParsed, mods, multiTouchGeometry, nearestPortalHost, parseKeyRoute, parseRoute, parseTargetSpec, pastDragThreshold, routeGestureForSpecKind, routeToSpec, specKindForRouteGesture, specificity, stylusOf };
|
|
1217
1425
|
//# sourceMappingURL=index.js.map
|
|
1218
1426
|
//# sourceMappingURL=index.js.map
|