@weasel-js/gestures 1.11.1 → 1.12.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 CHANGED
@@ -1164,15 +1164,31 @@ type ShortcutInput = MenuShortcut;
1164
1164
  interface FormatShortcutOptions {
1165
1165
  /** Defaults to the detected platform. */
1166
1166
  platform?: Platform;
1167
+ /** {@link formatShortcutParts} only; {@link formatShortcut} picks its own. Defaults to `'auto'`. */
1168
+ legend?: LegendStyle;
1167
1169
  }
1168
- /** Format a shortcut as an array of display chips, one per key: the
1169
- * modifiers, then the key, each as the platform's keyboard prints it, so
1170
- * `mod` is `⌘` on macOS and `Ctrl` elsewhere. UIs that want a boxed per-key
1171
- * look render each chip in its own element. `shift: 'optional'` is treated
1172
- * as falsy. */
1173
- declare function formatShortcutParts(b: ShortcutInput | undefined, opts?: FormatShortcutOptions): readonly string[] | undefined;
1170
+ /** One keymap on the way to a key: its label, and the chips of the leader that opens it. */
1171
+ interface ShortcutLeaderStep {
1172
+ label: string;
1173
+ keys: readonly KeyLegend[];
1174
+ }
1175
+ /** A shortcut as display chips: {@link formatShortcutParts}'s result. */
1176
+ interface ShortcutParts {
1177
+ /** The keymaps leading to the key, outermost first; empty for a key outside any keymap. */
1178
+ leader: readonly ShortcutLeaderStep[];
1179
+ /** The key's own chips: the modifiers, then the key. */
1180
+ keys: readonly KeyLegend[];
1181
+ }
1182
+ /** What {@link formatShortcut} writes after each leader step's label: `Loupe ▸ 2`. */
1183
+ declare const SHORTCUT_LEADER_SEPARATOR = " \u25B8 ";
1184
+ /** Format a shortcut as display chips, one per key: the modifiers, then the
1185
+ * key, each as the platform's keyboard prints it, so `mod` is `⌘` on macOS
1186
+ * and `Ctrl` elsewhere, after the keymaps that lead to it (`Loupe ▸ 2`).
1187
+ * UIs that want a boxed per-key look render each chip in its own element.
1188
+ * `shift: 'optional'` gives an optional chip. */
1189
+ declare function formatShortcutParts(b: ShortcutInput | undefined, opts?: FormatShortcutOptions): ShortcutParts | undefined;
1174
1190
  /** Format a shortcut as a single display string. Order: mod, shift, alt,
1175
- * key. On macOS the glyphs run together (`⌘⇧Z`); elsewhere the keys are
1191
+ * key, after each keymap that leads to it (`Loupe ▸ ⌘2`). On macOS the glyphs run together (`⌘⇧Z`); elsewhere the keys are
1176
1192
  * spelled out and joined with `+` (`Ctrl+Shift+Z`). Returns `undefined` for
1177
1193
  * `undefined` input so callers can `??` a fallback. When `key` is an array,
1178
1194
  * the first element is used. `shift: 'optional'` is treated as falsy. */
@@ -1181,7 +1197,7 @@ declare function formatShortcut(b: ShortcutInput | undefined, opts?: FormatShort
1181
1197
  /**
1182
1198
  * A shortcut as key legends, one per key, as `@weasel-js/ui`'s `KeySequence` draws them: mod, shift, alt, then the
1183
1199
  * key, each in the platform's own legend (`⌘` on macOS, `Ctrl` elsewhere).
1184
- * `formatShortcutParts` is these labels as strings. `shift: 'optional'`
1200
+ * Only the key itself: `formatShortcutParts` adds the keymaps that lead to it. `shift: 'optional'`
1185
1201
  * becomes an optional key; a key list
1186
1202
  * renders its first key. Returns `undefined` for no shortcut.
1187
1203
  *
@@ -1199,6 +1215,24 @@ declare function keySpecsFromShortcut(shortcut: ShortcutInput | undefined, opts?
1199
1215
  */
1200
1216
  declare function keySpecFromCode(code: string, opts?: KeySpecsFromModsOptions): KeyLegend;
1201
1217
 
1218
+ /** A keystroke in the `Mod+Shift+K` form. Modifiers are case-insensitive:
1219
+ * `Meta`/`Cmd`/`Command`, `Ctrl`/`Control`, `Alt`/`Option`, `Shift`, and
1220
+ * `Mod` for Cmd on Apple platforms and Ctrl elsewhere. */
1221
+ declare function key(shortcut: string): KeySpec;
1222
+ /** The same keystroke held down, as a `key-held` spec. */
1223
+ declare function held(shortcut: string): KeyHeldSpec;
1224
+ /** A binding override that removes the binding. */
1225
+ declare function unbound(): null;
1226
+ /** What a binding override is written as: a route string, a spec, or `null` to unbind. */
1227
+ type BindingOverrideInput = string | GestureSpec | null;
1228
+ /** Read a binding override into the spec it names, or `null`. */
1229
+ declare function bindingSpec(input: BindingOverrideInput): GestureSpec | null;
1230
+ /** A key spec as its route string — what `bindingSpec` reads back to an
1231
+ * equal spec — or `null` for a key the route grammar cannot spell (`?`,
1232
+ * `)`, `*`). A spec is plain JSON, so store that one as it is. A key list is
1233
+ * written as its first key. */
1234
+ declare function keySpecRoute(spec: KeySpec | KeyHeldSpec): string | null;
1235
+
1202
1236
  /**
1203
1237
  * The nearest element `from` sits inside that can host a portalled overlay,
1204
1238
  * or `null` when there is none and the body is the only answer.
@@ -1523,4 +1557,4 @@ declare function activatesFocusedControl(e: KeyboardEvent): boolean;
1523
1557
  */
1524
1558
  declare function focusedControlOwnsKey(e: KeyboardEvent): boolean;
1525
1559
 
1526
- export { ACTIVE_SIGILS, type BodyClassification, type BodyKind, type BodyTarget, CONTROL_SELECTOR, type CarryEvent, type CarrySpec, type ChannelRef, type ClaimableGesture, type ClickEvent, type ClickMemory, type ClickObserver, type ClickSpec, type ContextMenuEvent, type ContextMenuSpec, DOUBLE_CLICK_MAX_MS, DOUBLE_CLICK_MAX_PX, DRAG_THRESHOLD_PX, type DescribeRouteOptions, type DoubleClickBody, type DoubleClickEvent, type DoubleClickSpec, type DragSpec, type DropEvent, type DropSpec, type EventModifiers, type FixedSeat, type FormatShortcutOptions, GESTURE_DESCRIPTORS, type GestureArgSpec, type GestureDescriptor, type GestureName, type GestureSpec, type HeldPointers, type HeldPress, type HoverEvent, type HoverSpec, type HoverWhere, type IngestItem, type InputEvent, type KeyEvent, type KeyHeldEvent, type KeyHeldSpec, type KeyLabels, type KeyLegend, type KeySpec, type KeySpecsFromModsOptions, LONG_PRESS_MS, type LegendStyle, type LogicalMod, type LogicalModSpec, type LongPressEvent, type LongPressSpec, MODIFIER_LABELS, MOD_NAME_SET, type ModRequirement, type ModSpec, type ModifierCombo, type ModifierKey, type ModifiersEvent, type MultiTouchEvent, type MultiTouchGeometry, type MultiTouchSpec, type MultiTouchTapEvent, type MultiTouchTapSpec, type OptionalMod, type ParsedKeyRoute, type ParsedModifiers, type ParsedRoute, type PasteEvent, type PasteSpec, type PhaseAtom, type PhaseContext, type PhaseSpec, type PinchEvent, type PinchSpec, type Platform, type Point, type PointerCancelEvent, type PointerDownEvent, type PointerDownSpec, type PointerIdentity, type PointerMoveEvent, type PointerRecognizer, type PointerRecognizerOptions, type PointerSample, type PointerSampleStylus, type PointerUpEvent, type PressFacts, type PressSample, RESERVED_ID_NAMES, RESERVED_ID_PREFIXES, RESERVED_SIGILS, ROUTE_FIELD_DEFINITIONS, ROUTE_TERMS, type RecognizerClock, type RecognizerObserver, type RecognizerSink, type ReleaseFacts, type ReleaseOutcome, type RouteDescriptionPart, type RouteFieldName, type RoutePhase, type RouteTermLabel, type ShortcutInput, TAP_THRESHOLD_PX, type TargetPredicate, type TargetSpec, type TargetSpecForm, UNKNOWN_MIME, VALID_MOD_NAMES, type WheelEvent, type WheelSpec, activatesFocusedControl, bodyPredicate, canonicalModifiers, clickEvents, collapseShiftPairs, createPointerRecognizer, describeRoute, describeRouteParts, detectPlatform, fixedSeat, focusedControlOwnsKey, formatKeyRoute, formatPhaseAtom, formatRoute, formatShortcut, formatShortcutParts, getGestureDescriptor, heldPointers, hitPredicate, isApplePlatform, isEditableTarget, isInControlWithin, isKnownGestureName, keyRouteToSpec, keySpecFromCode, keySpecFromKey, keySpecsFromMods, keySpecsFromShortcut, longPressEvents, matchIngestTypes, matchKey, matchModifiers, matchPhase, matchSpec, matchTarget, mimeMatchesGlob, modifierComboToParsed, mods, multiTouchGeometry, nearestPortalHost, parseKeyRoute, parseRoute, parseTargetSpec, pastDragThreshold, routeGestureForSpecKind, routeToSpec, specKindForRouteGesture, specificity, stylusOf };
1560
+ export { ACTIVE_SIGILS, type BindingOverrideInput, type BodyClassification, type BodyKind, type BodyTarget, CONTROL_SELECTOR, type CarryEvent, type CarrySpec, type ChannelRef, type ClaimableGesture, type ClickEvent, type ClickMemory, type ClickObserver, type ClickSpec, type ContextMenuEvent, type ContextMenuSpec, DOUBLE_CLICK_MAX_MS, DOUBLE_CLICK_MAX_PX, DRAG_THRESHOLD_PX, type DescribeRouteOptions, type DoubleClickBody, type DoubleClickEvent, type DoubleClickSpec, type DragSpec, type DropEvent, type DropSpec, type EventModifiers, type FixedSeat, type FormatShortcutOptions, GESTURE_DESCRIPTORS, type GestureArgSpec, type GestureDescriptor, type GestureName, type GestureSpec, type HeldPointers, type HeldPress, type HoverEvent, type HoverSpec, type HoverWhere, type IngestItem, type InputEvent, type KeyEvent, type KeyHeldEvent, type KeyHeldSpec, type KeyLabels, type KeyLegend, type KeySpec, type KeySpecsFromModsOptions, LONG_PRESS_MS, type LegendStyle, type LogicalMod, type LogicalModSpec, type LongPressEvent, type LongPressSpec, MODIFIER_LABELS, MOD_NAME_SET, type ModRequirement, type ModSpec, type ModifierCombo, type ModifierKey, type ModifiersEvent, type MultiTouchEvent, type MultiTouchGeometry, type MultiTouchSpec, type MultiTouchTapEvent, type MultiTouchTapSpec, type OptionalMod, type ParsedKeyRoute, type ParsedModifiers, type ParsedRoute, type PasteEvent, type PasteSpec, type PhaseAtom, type PhaseContext, type PhaseSpec, type PinchEvent, type PinchSpec, type Platform, type Point, type PointerCancelEvent, type PointerDownEvent, type PointerDownSpec, type PointerIdentity, type PointerMoveEvent, type PointerRecognizer, type PointerRecognizerOptions, type PointerSample, type PointerSampleStylus, type PointerUpEvent, type PressFacts, type PressSample, RESERVED_ID_NAMES, RESERVED_ID_PREFIXES, RESERVED_SIGILS, ROUTE_FIELD_DEFINITIONS, ROUTE_TERMS, type RecognizerClock, type RecognizerObserver, type RecognizerSink, type ReleaseFacts, type ReleaseOutcome, type RouteDescriptionPart, type RouteFieldName, type RoutePhase, type RouteTermLabel, SHORTCUT_LEADER_SEPARATOR, type ShortcutInput, type ShortcutLeaderStep, type ShortcutParts, TAP_THRESHOLD_PX, type TargetPredicate, type TargetSpec, type TargetSpecForm, UNKNOWN_MIME, VALID_MOD_NAMES, type WheelEvent, type WheelSpec, activatesFocusedControl, bindingSpec, bodyPredicate, canonicalModifiers, clickEvents, collapseShiftPairs, createPointerRecognizer, describeRoute, describeRouteParts, detectPlatform, fixedSeat, focusedControlOwnsKey, formatKeyRoute, formatPhaseAtom, formatRoute, formatShortcut, formatShortcutParts, getGestureDescriptor, held, heldPointers, hitPredicate, isApplePlatform, isEditableTarget, isInControlWithin, isKnownGestureName, key, keyRouteToSpec, keySpecFromCode, keySpecFromKey, keySpecRoute, keySpecsFromMods, keySpecsFromShortcut, longPressEvents, matchIngestTypes, matchKey, matchModifiers, matchPhase, matchSpec, matchTarget, mimeMatchesGlob, modifierComboToParsed, mods, multiTouchGeometry, nearestPortalHost, parseKeyRoute, parseRoute, parseTargetSpec, pastDragThreshold, routeGestureForSpecKind, routeToSpec, specKindForRouteGesture, specificity, stylusOf, unbound };
package/dist/index.js CHANGED
@@ -107,7 +107,7 @@ function parseRoute(input) {
107
107
  target = "*";
108
108
  }
109
109
  const modifiers = {};
110
- let held;
110
+ let held2;
111
111
  while (rest.length > 0) {
112
112
  const ch = rest[0];
113
113
  if (RESERVED_SIGILS.has(ch)) {
@@ -123,8 +123,8 @@ function parseRoute(input) {
123
123
  const name = nameMatch[1];
124
124
  if (name === HELD) {
125
125
  if (gestureName !== "hover") throw new Error(`invalid route (${gestureName} takes no "${HELD}"): ${input}`);
126
- if (held !== void 0) throw new Error(`invalid route (duplicate "${HELD}"): ${input}`);
127
- held = sigil === "+" ? "required" : "optional";
126
+ if (held2 !== void 0) throw new Error(`invalid route (duplicate "${HELD}"): ${input}`);
127
+ held2 = sigil === "+" ? "required" : "optional";
128
128
  rest = rest.slice(nameMatch[0].length).trimStart();
129
129
  continue;
130
130
  }
@@ -137,7 +137,7 @@ function parseRoute(input) {
137
137
  modifiers[name] = sigil === "+" ? "required" : "optional";
138
138
  rest = rest.slice(nameMatch[0].length).trimStart();
139
139
  }
140
- return { phases, gesture: gestureName, arg, target, modifiers, ...held ? { held } : {} };
140
+ return { phases, gesture: gestureName, arg, target, modifiers, ...held2 ? { held: held2 } : {} };
141
141
  }
142
142
  var HELD = "held";
143
143
  function parsePhaseList(raw, input) {
@@ -264,15 +264,15 @@ function formatRoute(r) {
264
264
  // src/grammar/keyRouteGrammar.ts
265
265
  var OPTIONAL_SET = new Set(VALID_MOD_NAMES);
266
266
  function parseKeyRoute(input) {
267
- const [key, ...mods2] = input.split("?");
268
- if (!key) throw new Error(`invalid key route (empty key): ${input}`);
267
+ const [key2, ...mods2] = input.split("?");
268
+ if (!key2) throw new Error(`invalid key route (empty key): ${input}`);
269
269
  const seen = /* @__PURE__ */ new Set();
270
270
  for (const m of mods2) {
271
271
  if (!OPTIONAL_SET.has(m)) throw new Error(`unknown optional modifier "${m}" in ${input}`);
272
272
  if (seen.has(m)) throw new Error(`duplicate optional modifier "${m}" in ${input}`);
273
273
  seen.add(m);
274
274
  }
275
- return { key, optionalMods: mods2 };
275
+ return { key: key2, optionalMods: mods2 };
276
276
  }
277
277
  function formatKeyRoute(r) {
278
278
  return r.optionalMods.length === 0 ? r.key : `${r.key}?${r.optionalMods.join("?")}`;
@@ -378,9 +378,9 @@ function matchModifiers(e, mods2, isMac) {
378
378
  }
379
379
  return checkKey(alt, e.altKey) && checkKey(ctrl, e.ctrlKey) && checkKey(meta, e.metaKey) && checkKey(shift, e.shiftKey);
380
380
  }
381
- function checkKey(req, held) {
382
- if (req === "required") return held;
383
- if (req === "forbidden") return !held;
381
+ function checkKey(req, held2) {
382
+ if (req === "required") return held2;
383
+ if (req === "forbidden") return !held2;
384
384
  return true;
385
385
  }
386
386
  function matchKey(eventKey, specKey) {
@@ -609,10 +609,10 @@ function routeToSpec(parsed) {
609
609
  case "key":
610
610
  case "key-held": {
611
611
  if (arg === void 0) return fail("a key spec needs a concrete key");
612
- const { key, optionalMods } = parseKeyRoute(arg);
612
+ const { key: key2, optionalMods } = parseKeyRoute(arg);
613
613
  const optional = {};
614
614
  for (const m of optionalMods) optional[m] = "optional";
615
- return withMods({ kind, key, ...optionalMods.length > 0 ? { mods: optional } : {} });
615
+ return withMods({ kind, key: key2, ...optionalMods.length > 0 ? { mods: optional } : {} });
616
616
  }
617
617
  case "multiTouch":
618
618
  case "multiTouchTap":
@@ -712,8 +712,8 @@ function actionClause(parsed, required) {
712
712
  case "keyUp":
713
713
  case "keyHeld": {
714
714
  const verb = parsed.gesture === "keyDown" ? "presses" : parsed.gesture === "keyUp" ? "releases" : "holds";
715
- const key = arg ?? "any key";
716
- return required.length > 0 ? `the user holds ${joinAnd(required)} and ${verb} ${key}` : `the user ${verb} ${key}`;
715
+ const key2 = arg ?? "any key";
716
+ return required.length > 0 ? `the user holds ${joinAnd(required)} and ${verb} ${key2}` : `the user ${verb} ${key2}`;
717
717
  }
718
718
  case "wheel": {
719
719
  const direction = arg === "up" ? " up" : arg === "down" ? " down" : "";
@@ -736,9 +736,9 @@ function actionClause(parsed, required) {
736
736
  case "pointerDown":
737
737
  return userDoes(required, `presses${target}`);
738
738
  case "hover": {
739
- const held = parsed.held === "required" ? " with a button held" : parsed.held === "optional" ? " whether or not a button is held" : "";
739
+ const held2 = parsed.held === "required" ? " with a button held" : parsed.held === "optional" ? " whether or not a button is held" : "";
740
740
  const where = arg === "leave" ? ` ${leftClause(parsed.target)}` : target;
741
- return userDoes(required, `moves the pointer${where}${held}`);
741
+ return userDoes(required, `moves the pointer${where}${held2}`);
742
742
  }
743
743
  case "dblTap":
744
744
  return userDoes(required, `double-taps${target}`);
@@ -779,10 +779,10 @@ function mergeAdjacentStrings(parts) {
779
779
  }
780
780
 
781
781
  // src/grammar/modifierComboToParsed.ts
782
- function modifierComboToParsed(key) {
783
- if (key === "default") return {};
782
+ function modifierComboToParsed(key2) {
783
+ if (key2 === "default") return {};
784
784
  const out = {};
785
- for (const part of key.split("+")) {
785
+ for (const part of key2.split("+")) {
786
786
  out[part] = "required";
787
787
  }
788
788
  return out;
@@ -916,9 +916,9 @@ function keySpecsFromShortcut(shortcut, opts = {}) {
916
916
  if (shortcut.mod) mods2.push({ name: "mod" });
917
917
  if (shortcut.shift) mods2.push(shortcut.shift === "optional" ? { name: "shift", optional: true } : { name: "shift" });
918
918
  if (shortcut.alt) mods2.push({ name: "alt" });
919
- const key = typeof shortcut.key === "string" ? shortcut.key : shortcut.key[0];
919
+ const key2 = typeof shortcut.key === "string" ? shortcut.key : shortcut.key[0];
920
920
  const keys = keySpecsFromMods(mods2, opts);
921
- return key === void 0 ? keys : [...keys, keySpecFromKey(key, opts)];
921
+ return key2 === void 0 ? keys : [...keys, keySpecFromKey(key2, opts)];
922
922
  }
923
923
 
924
924
  // src/ui/keySpecFromCode.ts
@@ -961,13 +961,79 @@ function keySpecFromCode(code, opts = {}) {
961
961
  }
962
962
 
963
963
  // src/ui/formatShortcut.ts
964
- var labels = (b, opts) => b ? keySpecsFromShortcut({ ...b, shift: b.shift === true }, opts)?.map((k) => k.label) : void 0;
964
+ var SHORTCUT_LEADER_SEPARATOR = " \u25B8 ";
965
965
  function formatShortcutParts(b, opts = {}) {
966
- return labels(b, opts);
966
+ const keys = keySpecsFromShortcut(b, opts);
967
+ if (!b || !keys) return void 0;
968
+ const leader = (b.leader ?? []).map((l) => ({ label: l.label, keys: keySpecsFromShortcut(l.shortcut, opts) ?? [] }));
969
+ return { leader, keys };
967
970
  }
968
971
  function formatShortcut(b, opts = {}) {
969
972
  const platform = opts.platform ?? detectPlatform();
970
- return platform === "macos" ? labels(b, { platform })?.join("") : labels(b, { platform, legend: "text" })?.join("+");
973
+ const mac = platform === "macos";
974
+ const parts = formatShortcutParts(b && { ...b, shift: b.shift === true }, mac ? { platform } : { platform, legend: "text" });
975
+ if (!parts) return void 0;
976
+ const keys = parts.keys.map((k) => k.label).join(mac ? "" : "+");
977
+ return [...parts.leader.map((l) => `${l.label}${SHORTCUT_LEADER_SEPARATOR}`), keys].join("");
978
+ }
979
+
980
+ // src/ui/keySugar.ts
981
+ var MODIFIERS = {
982
+ meta: "meta",
983
+ cmd: "meta",
984
+ command: "meta",
985
+ ctrl: "ctrl",
986
+ control: "ctrl",
987
+ alt: "alt",
988
+ option: "alt",
989
+ shift: "shift",
990
+ mod: "mod"
991
+ };
992
+ var KEY_NAMES = { space: " ", esc: "Escape" };
993
+ function parse(shortcut) {
994
+ const parts = shortcut.endsWith("++") ? [...shortcut.slice(0, -2).split("+"), "+"] : shortcut.split("+");
995
+ const raw = parts.pop();
996
+ if (!raw) throw new Error(`shortcut "${shortcut}" names no key`);
997
+ const mods2 = {};
998
+ for (const part of parts) {
999
+ const mod = MODIFIERS[part.toLowerCase()];
1000
+ if (!mod) throw new Error(`shortcut "${shortcut}": unknown modifier "${part}"`);
1001
+ mods2[mod] = true;
1002
+ }
1003
+ return { key: KEY_NAMES[raw.toLowerCase()] ?? raw, mods: mods2 };
1004
+ }
1005
+ function key(shortcut) {
1006
+ const { key: k, mods: mods2 } = parse(shortcut);
1007
+ return Object.keys(mods2).length > 0 ? { kind: "key", key: k, mods: mods2 } : { kind: "key", key: k };
1008
+ }
1009
+ function held(shortcut) {
1010
+ const { key: k, mods: mods2 } = parse(shortcut);
1011
+ return Object.keys(mods2).length > 0 ? { kind: "key-held", key: k, mods: mods2 } : { kind: "key-held", key: k };
1012
+ }
1013
+ function unbound() {
1014
+ return null;
1015
+ }
1016
+ function bindingSpec(input) {
1017
+ if (typeof input === "string") return routeToSpec(parseRoute(input));
1018
+ return input;
1019
+ }
1020
+ var MOD_ORDER3 = ["mod", "shift", "alt", "ctrl", "meta"];
1021
+ function keySpecRoute(spec) {
1022
+ const k = Array.isArray(spec.key) ? spec.key[0] : spec.key;
1023
+ const optional = MOD_ORDER3.filter((m) => spec.mods?.[m] === "optional");
1024
+ const required = MOD_ORDER3.filter((m) => spec.mods?.[m] === true);
1025
+ const arg = [k, ...optional].join("?");
1026
+ const gesture = spec.kind === "key" ? "keyDown" : "keyHeld";
1027
+ const route = [`[*:*] ${gesture}(${arg})`, ...required.map((m) => `+${m}`)].join(" ");
1028
+ return spellsKey(route, k) ? route : null;
1029
+ }
1030
+ function spellsKey(route, k) {
1031
+ try {
1032
+ const back = bindingSpec(route);
1033
+ return back !== null && (back.kind === "key" || back.kind === "key-held") && back.key === k;
1034
+ } catch {
1035
+ return false;
1036
+ }
971
1037
  }
972
1038
 
973
1039
  // src/ui/fixedSeat.ts
@@ -1234,9 +1300,9 @@ function pointerUpEvent(s, world) {
1234
1300
  function multiTouchEvent(fingers, mods2, more) {
1235
1301
  return { kind: "multitouch", fingers, ...modifiersOf(mods2), ...more };
1236
1302
  }
1237
- var positionsOf = (held) => new Map([...held].map(([id, p]) => [id, p.pos]));
1238
- function heldGeometry(held, toSurface, extra) {
1239
- const g = multiTouchGeometry([...positionsOf(held).values(), ...extra]);
1303
+ var positionsOf = (held2) => new Map([...held2].map(([id, p]) => [id, p.pos]));
1304
+ function heldGeometry(held2, toSurface, extra) {
1305
+ const g = multiTouchGeometry([...positionsOf(held2).values(), ...extra]);
1240
1306
  return { ...g, centroid: toSurface(g.centroid) };
1241
1307
  }
1242
1308
  function fireLongPress(rec, sink, observer) {
@@ -1270,40 +1336,40 @@ function createPointerRecognizer(opts) {
1270
1336
  const { sink, clock, observer = {}, toSurface = (p) => p } = opts;
1271
1337
  const world = (s) => opts.toWorld?.(s.clientX, s.clientY) ?? { x: s.clientX, y: s.clientY };
1272
1338
  const clicks = opts.clicks ?? { last: null };
1273
- const held = /* @__PURE__ */ new Map();
1339
+ const held2 = /* @__PURE__ */ new Map();
1274
1340
  const { longPressMs, tapPx, dragPx, doubleClick } = thresholdsOf(opts);
1275
1341
  const touches = createMultiTouchEpisode(tapPx);
1276
1342
  const hold = createLongPressHold(clock, (pending) => {
1277
1343
  observer.holdChanged?.(pending);
1278
1344
  });
1279
- const geometry = (extra = []) => heldGeometry(held, toSurface, extra);
1345
+ const geometry = (extra = []) => heldGeometry(held2, toSurface, extra);
1280
1346
  const happenedAt = sampleTime(clock);
1281
1347
  const claimForMultiTouch = (s) => {
1282
1348
  hold.cancel();
1283
- for (const p of held.values()) p.buffered = null;
1349
+ for (const p of held2.values()) p.buffered = null;
1284
1350
  const g = geometry();
1285
- touches.join(positionsOf(held), g.centroid, happenedAt(s));
1286
- sink.dispatch(multiTouchEvent(held.size, s, g));
1351
+ touches.join(positionsOf(held2), g.centroid, happenedAt(s));
1352
+ sink.dispatch(multiTouchEvent(held2.size, s, g));
1287
1353
  };
1288
1354
  return {
1289
1355
  press(s) {
1290
1356
  const ev = pointerDownEvent(s);
1291
1357
  const rec = { pos: { x: s.clientX, y: s.clientY }, buffered: ev, down: pressFactsOf(s) };
1292
- held.set(s.pointerId, rec);
1293
- if (held.size < 2) sink.dispatch({ ...ev, stage: "press" });
1294
- if ((s.pointerType === "touch" || s.pointerType === "pen") && held.size === 1) {
1358
+ held2.set(s.pointerId, rec);
1359
+ if (held2.size < 2) sink.dispatch({ ...ev, stage: "press" });
1360
+ if ((s.pointerType === "touch" || s.pointerType === "pen") && held2.size === 1) {
1295
1361
  rec.holdType = s.pointerType;
1296
1362
  hold.arm(
1297
1363
  { pointerId: s.pointerId, pointerType: s.pointerType, press: rec.down, startedAt: clock.now(), duration: longPressMs() },
1298
1364
  () => {
1299
- fireLongPress(held.get(s.pointerId), sink, observer);
1365
+ fireLongPress(held2.get(s.pointerId), sink, observer);
1300
1366
  }
1301
1367
  );
1302
1368
  }
1303
- if (held.size >= 2) claimForMultiTouch(s);
1369
+ if (held2.size >= 2) claimForMultiTouch(s);
1304
1370
  },
1305
1371
  move(s) {
1306
- const rec = held.get(s.pointerId);
1372
+ const rec = held2.get(s.pointerId);
1307
1373
  const to = { x: s.clientX, y: s.clientY };
1308
1374
  if (rec) rec.pos = to;
1309
1375
  touches.moved(s.pointerId, to);
@@ -1313,18 +1379,18 @@ function createPointerRecognizer(opts) {
1313
1379
  hold.cancel(s.pointerId);
1314
1380
  sink.dispatch(press);
1315
1381
  }
1316
- if (held.size >= 2 && sink.multiTouchInFlight(held.size)) {
1317
- sink.dispatch(multiTouchEvent(held.size, s, geometry()));
1382
+ if (held2.size >= 2 && sink.multiTouchInFlight(held2.size)) {
1383
+ sink.dispatch(multiTouchEvent(held2.size, s, geometry()));
1318
1384
  }
1319
1385
  sink.dispatch(pointerMoveEvent(s, world(s)));
1320
1386
  },
1321
1387
  release(s) {
1322
1388
  hold.cancel(s.pointerId);
1323
- const before = held.size;
1324
- const rec = held.get(s.pointerId);
1325
- held.delete(s.pointerId);
1389
+ const before = held2.size;
1390
+ const rec = held2.get(s.pointerId);
1391
+ held2.delete(s.pointerId);
1326
1392
  if (before >= 2) sink.dispatch(multiTouchEvent(before, s, { end: "commit" }));
1327
- if (before >= 2 && held.size < 2) {
1393
+ if (before >= 2 && held2.size < 2) {
1328
1394
  const fingers = touches.finish(
1329
1395
  geometry([{ x: s.clientX, y: s.clientY }]).centroid,
1330
1396
  happenedAt(s),
@@ -1346,10 +1412,10 @@ function createPointerRecognizer(opts) {
1346
1412
  return composeClick(rec.down, up, clicks, () => happenedAt(s), (ev) => sink.dispatch(ev), observer, doubleClick());
1347
1413
  },
1348
1414
  cancel(pointerId) {
1349
- const rec = held.get(pointerId);
1415
+ const rec = held2.get(pointerId);
1350
1416
  if (!rec) return;
1351
- const before = held.size;
1352
- held.delete(pointerId);
1417
+ const before = held2.size;
1418
+ held2.delete(pointerId);
1353
1419
  hold.cancel(pointerId);
1354
1420
  if (before >= 2) sink.dispatch(multiTouchEvent(before, rec.down, { end: "cancel" }));
1355
1421
  touches.abandon();
@@ -1357,7 +1423,7 @@ function createPointerRecognizer(opts) {
1357
1423
  },
1358
1424
  takeHold() {
1359
1425
  let holding = false;
1360
- for (const [id, rec] of held) {
1426
+ for (const [id, rec] of held2) {
1361
1427
  if (!rec.holdType) continue;
1362
1428
  holding = true;
1363
1429
  if (!hold.pendingFor(id)) continue;
@@ -1366,11 +1432,11 @@ function createPointerRecognizer(opts) {
1366
1432
  }
1367
1433
  return holding;
1368
1434
  },
1369
- heldCount: () => held.size,
1370
- positionOf: (pointerId) => held.get(pointerId)?.pos,
1435
+ heldCount: () => held2.size,
1436
+ positionOf: (pointerId) => held2.get(pointerId)?.pos,
1371
1437
  reset() {
1372
1438
  hold.cancel();
1373
- held.clear();
1439
+ held2.clear();
1374
1440
  touches.abandon();
1375
1441
  }
1376
1442
  };
@@ -1402,20 +1468,20 @@ var trackers = /* @__PURE__ */ new WeakMap();
1402
1468
  function attach(element) {
1403
1469
  const doc = element.ownerDocument;
1404
1470
  const win = doc.defaultView;
1405
- const held = /* @__PURE__ */ new Map();
1471
+ const held2 = /* @__PURE__ */ new Map();
1406
1472
  const down = (e) => {
1407
1473
  const p = e;
1408
- held.set(p.pointerId, !!p.buttons);
1474
+ held2.set(p.pointerId, !!p.buttons);
1409
1475
  };
1410
1476
  const up = (e) => {
1411
- held.delete(e.pointerId);
1477
+ held2.delete(e.pointerId);
1412
1478
  };
1413
1479
  const move = (e) => {
1414
1480
  const p = e;
1415
- if (held.get(p.pointerId) && p.buttons === 0) held.delete(p.pointerId);
1481
+ if (held2.get(p.pointerId) && p.buttons === 0) held2.delete(p.pointerId);
1416
1482
  };
1417
1483
  const blur = () => {
1418
- held.clear();
1484
+ held2.clear();
1419
1485
  };
1420
1486
  element.addEventListener("pointerdown", down, true);
1421
1487
  doc.addEventListener("pointerup", up, true);
@@ -1424,7 +1490,7 @@ function attach(element) {
1424
1490
  win?.addEventListener("blur", blur);
1425
1491
  return {
1426
1492
  readers: 0,
1427
- held,
1493
+ held: held2,
1428
1494
  detach() {
1429
1495
  element.removeEventListener("pointerdown", down, true);
1430
1496
  doc.removeEventListener("pointerup", up, true);
@@ -1497,6 +1563,6 @@ function focusedControlOwnsKey(e) {
1497
1563
  return activatesFocusedControl(e) || selectOwnsKey(e);
1498
1564
  }
1499
1565
 
1500
- 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, keySpecFromCode, keySpecFromKey, keySpecsFromMods, keySpecsFromShortcut, longPressEvents, matchIngestTypes, matchKey, matchModifiers, matchPhase, matchSpec, matchTarget, mimeMatchesGlob, modifierComboToParsed, mods, multiTouchGeometry, nearestPortalHost, parseKeyRoute, parseRoute, parseTargetSpec, pastDragThreshold, routeGestureForSpecKind, routeToSpec, specKindForRouteGesture, specificity, stylusOf };
1566
+ 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, SHORTCUT_LEADER_SEPARATOR, TAP_THRESHOLD_PX, UNKNOWN_MIME, VALID_MOD_NAMES, activatesFocusedControl, bindingSpec, bodyPredicate, canonicalModifiers, clickEvents, collapseShiftPairs, createPointerRecognizer, describeRoute, describeRouteParts, detectPlatform, fixedSeat, focusedControlOwnsKey, formatKeyRoute, formatPhaseAtom, formatRoute, formatShortcut, formatShortcutParts, getGestureDescriptor, held, heldPointers, hitPredicate, isApplePlatform, isEditableTarget, isInControlWithin, isKnownGestureName, key, keyRouteToSpec, keySpecFromCode, keySpecFromKey, keySpecRoute, keySpecsFromMods, keySpecsFromShortcut, longPressEvents, matchIngestTypes, matchKey, matchModifiers, matchPhase, matchSpec, matchTarget, mimeMatchesGlob, modifierComboToParsed, mods, multiTouchGeometry, nearestPortalHost, parseKeyRoute, parseRoute, parseTargetSpec, pastDragThreshold, routeGestureForSpecKind, routeToSpec, specKindForRouteGesture, specificity, stylusOf, unbound };
1501
1567
  //# sourceMappingURL=index.js.map
1502
1568
  //# sourceMappingURL=index.js.map