@densedash/core 0.1.0 → 0.2.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 +59 -0
- package/dist/bare.d.ts +3 -3
- package/dist/bare.js +2 -2
- package/dist/collections/types.d.ts +0 -46
- package/dist/commands/index.d.ts +3 -3
- package/dist/commands/index.js +152 -39
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/keys.js +4 -3
- package/dist/commands/keys.js.map +1 -1
- package/dist/commands/scopes.js +30 -3
- package/dist/commands/scopes.js.map +1 -1
- package/dist/commands/trading-preset.d.ts +5 -1
- package/dist/commands/trading-preset.js +22 -43
- package/dist/commands/trading-preset.js.map +1 -1
- package/dist/commands/types.d.ts +32 -5
- package/dist/defaults/keyboard.js +55 -10
- package/dist/defaults/keyboard.js.map +1 -1
- package/dist/index.d.ts +7 -10
- package/dist/index.js +3 -7
- package/dist/index.js.map +1 -1
- package/dist/kernel.d.ts +4 -4
- package/dist/kernel.js.map +1 -1
- package/dist/navigation/commands.js +200 -0
- package/dist/navigation/commands.js.map +1 -0
- package/dist/navigation/index.d.ts +31 -0
- package/dist/navigation/index.js +148 -0
- package/dist/navigation/index.js.map +1 -0
- package/dist/{collections → navigation}/move.js +1 -1
- package/dist/navigation/move.js.map +1 -0
- package/dist/{tree → navigation}/props.js +25 -9
- package/dist/navigation/props.js.map +1 -0
- package/dist/{collections → navigation}/reconcile.js +1 -1
- package/dist/navigation/reconcile.js.map +1 -0
- package/dist/navigation/selection.js +21 -0
- package/dist/navigation/selection.js.map +1 -0
- package/dist/navigation/state.d.ts +3 -0
- package/dist/navigation/state.js +265 -0
- package/dist/navigation/state.js.map +1 -0
- package/dist/navigation/types.d.ts +86 -0
- package/dist/ports/keyboard.d.ts +3 -2
- package/dist/ports/keyboard.js +9 -0
- package/dist/ports/keyboard.js.map +1 -0
- package/dist/tree/types.d.ts +7 -36
- package/package.json +4 -3
- package/dist/collections/flatten.d.ts +0 -8
- package/dist/collections/flatten.js +0 -59
- package/dist/collections/flatten.js.map +0 -1
- package/dist/collections/move.d.ts +0 -7
- package/dist/collections/move.js.map +0 -1
- package/dist/collections/reconcile.d.ts +0 -9
- package/dist/collections/reconcile.js.map +0 -1
- package/dist/collections/selection.d.ts +0 -12
- package/dist/collections/selection.js +0 -39
- package/dist/collections/selection.js.map +0 -1
- package/dist/tree/commands.js +0 -119
- package/dist/tree/commands.js.map +0 -1
- package/dist/tree/index.d.ts +0 -6
- package/dist/tree/index.js +0 -71
- package/dist/tree/index.js.map +0 -1
- package/dist/tree/props.js.map +0 -1
- package/dist/tree/state.js +0 -126
- package/dist/tree/state.js.map +0 -1
package/dist/commands/scopes.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isKeyboardEditable } from "../ports/keyboard.js";
|
|
1
2
|
// src/commands/scopes.ts
|
|
2
3
|
/** Scope-family ordering matches the port contract; no key matching happens here. */
|
|
3
4
|
function activeScopes(scopes) {
|
|
@@ -34,13 +35,39 @@ function activeScopes(scopes) {
|
|
|
34
35
|
const global = ordered.findIndex((scope) => scope.name === "global");
|
|
35
36
|
if (global >= 0) ordered.push(...ordered.splice(global, 1));
|
|
36
37
|
const exclusive = ordered.find((scope) => scope.options.only);
|
|
37
|
-
|
|
38
|
+
const active = exclusive ? ordered.filter((scope) => scope === exclusive || isDescendant(scope, exclusive)) : ordered;
|
|
39
|
+
return [...active.filter((scope) => scope.options.keys), ...active.filter((scope) => !scope.options.keys)];
|
|
38
40
|
}
|
|
39
41
|
/** Element targets participate only while focus is inside them; document/window targets are ambient. */
|
|
40
42
|
function scopeHasFocus(scope) {
|
|
41
43
|
const target = scope.options.target;
|
|
42
|
-
return !target || !("ownerDocument" in target) || target.ownerDocument === null || target.contains(target.ownerDocument?.activeElement ?? null);
|
|
44
|
+
return scope.options.keys !== void 0 || !target || !("ownerDocument" in target) || target.ownerDocument === null || target.contains(target.ownerDocument?.activeElement ?? null);
|
|
43
45
|
}
|
|
44
|
-
|
|
46
|
+
/** Editable-only command bindings are disabled while a text control has focus. */
|
|
47
|
+
function scopeAcceptsKeys(scope) {
|
|
48
|
+
if (!scopeHasFocus(scope)) return false;
|
|
49
|
+
if (!scope.options.keys && (scope.options.ignoreInputs ?? "editable") !== "editable") return true;
|
|
50
|
+
const target = scope.options.target;
|
|
51
|
+
const document = target && "document" in target ? target.document : target && "defaultView" in target ? target : target?.ownerDocument ?? globalThis.document;
|
|
52
|
+
return !isKeyboardEditable(document?.activeElement);
|
|
53
|
+
}
|
|
54
|
+
/** Element listeners see bubbling keys before ambient listeners, regardless of scope age. */
|
|
55
|
+
function keymapScopes(scopes) {
|
|
56
|
+
return activeScopes(scopes).filter(scopeAcceptsKeys).sort((a, b) => {
|
|
57
|
+
if (Boolean(a.options.keys) !== Boolean(b.options.keys)) return a.options.keys ? -1 : 1;
|
|
58
|
+
if (a.options.keys && b.options.keys) return 0;
|
|
59
|
+
const aTarget = a.options.target;
|
|
60
|
+
const bTarget = b.options.target;
|
|
61
|
+
const aElement = aTarget && "ownerDocument" in aTarget && aTarget.ownerDocument ? aTarget : void 0;
|
|
62
|
+
const bElement = bTarget && "ownerDocument" in bTarget && bTarget.ownerDocument ? bTarget : void 0;
|
|
63
|
+
if (aElement && bElement) {
|
|
64
|
+
if (aElement === bElement) return 0;
|
|
65
|
+
if (aElement.contains(bElement)) return 1;
|
|
66
|
+
if (bElement.contains(aElement)) return -1;
|
|
67
|
+
}
|
|
68
|
+
return Number(Boolean(bElement)) - Number(Boolean(aElement));
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
export { activeScopes, keymapScopes, scopeAcceptsKeys, scopeHasFocus };
|
|
45
72
|
|
|
46
73
|
//# sourceMappingURL=scopes.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scopes.js","names":[],"sources":["../../src/commands/scopes.ts"],"sourcesContent":["import type { KeyboardBinding } from \"../ports/keyboard.js\";\nimport type { Command, CommandScopeOptions } from \"./types.js\";\n\nexport interface Scope {\n readonly name: string;\n readonly commands: readonly Command[];\n readonly options: CommandScopeOptions;\n readonly release: () => void;\n readonly unregister: () => void;\n readonly bindings: KeyboardBinding[];\n readonly warned: Set<string>;\n}\n\n/** Scope-family ordering matches the port contract; no key matching happens here. */\nexport function activeScopes(scopes: ReadonlyMap<string, Scope>): Scope[] {\n function isDescendant(scope: Scope, ancestor: Scope): boolean {\n let parent = scope.options.parent ?? (scope.name === \"global\" ? undefined : \"global\");\n const visited = new Set<string>();\n while (parent !== undefined && !visited.has(parent)) {\n if (parent === ancestor.name) return true;\n visited.add(parent);\n parent = scopes.get(parent)?.options.parent;\n }\n return false;\n }\n\n const registered = Array.from(scopes.values());\n // Keep a scope family together even when a child mounts before its parent.\n const ranked = registered.map((scope, order) => {\n const earliest = registered.findIndex(\n (candidate) => candidate === scope || isDescendant(candidate, scope),\n );\n let depth = scope.name === \"global\" ? 0 : 1;\n let parent = scope.options.parent;\n const visited = new Set<string>();\n while (parent !== undefined && parent !== \"global\" && !visited.has(parent)) {\n visited.add(parent);\n depth++;\n parent = scopes.get(parent)?.options.parent;\n }\n return { scope, order, earliest, depth };\n });\n ranked.sort((a, b) => b.earliest - a.earliest || b.depth - a.depth || b.order - a.order);\n const ordered = ranked.map(({ scope }) => scope);\n const global = ordered.findIndex((scope) => scope.name === \"global\");\n if (global >= 0) ordered.push(...ordered.splice(global, 1));\n const exclusive = ordered.find((scope) => scope.options.only);\n
|
|
1
|
+
{"version":3,"file":"scopes.js","names":[],"sources":["../../src/commands/scopes.ts"],"sourcesContent":["import type { KeyboardBinding } from \"../ports/keyboard.js\";\nimport { isKeyboardEditable } from \"../ports/keyboard.js\";\nimport type { createKeys } from \"./keys.js\";\nimport type { Command, CommandScopeOptions } from \"./types.js\";\n\nexport interface Scope {\n readonly name: string;\n readonly keys: ReturnType<typeof createKeys>;\n commands: readonly Command[];\n readonly options: CommandScopeOptions;\n readonly release: () => void;\n readonly unregister: () => void;\n readonly bindings: {\n readonly key: string;\n readonly commandIndex: number;\n readonly binding: KeyboardBinding;\n enabled: boolean;\n }[];\n readonly warned: Set<string>;\n}\n\n/** Scope-family ordering matches the port contract; no key matching happens here. */\nexport function activeScopes(scopes: ReadonlyMap<string, Scope>): Scope[] {\n function isDescendant(scope: Scope, ancestor: Scope): boolean {\n let parent = scope.options.parent ?? (scope.name === \"global\" ? undefined : \"global\");\n const visited = new Set<string>();\n while (parent !== undefined && !visited.has(parent)) {\n if (parent === ancestor.name) return true;\n visited.add(parent);\n parent = scopes.get(parent)?.options.parent;\n }\n return false;\n }\n\n const registered = Array.from(scopes.values());\n // Keep a scope family together even when a child mounts before its parent.\n const ranked = registered.map((scope, order) => {\n const earliest = registered.findIndex(\n (candidate) => candidate === scope || isDescendant(candidate, scope),\n );\n let depth = scope.name === \"global\" ? 0 : 1;\n let parent = scope.options.parent;\n const visited = new Set<string>();\n while (parent !== undefined && parent !== \"global\" && !visited.has(parent)) {\n visited.add(parent);\n depth++;\n parent = scopes.get(parent)?.options.parent;\n }\n return { scope, order, earliest, depth };\n });\n ranked.sort((a, b) => b.earliest - a.earliest || b.depth - a.depth || b.order - a.order);\n const ordered = ranked.map(({ scope }) => scope);\n const global = ordered.findIndex((scope) => scope.name === \"global\");\n if (global >= 0) ordered.push(...ordered.splice(global, 1));\n const exclusive = ordered.find((scope) => scope.options.only);\n const active = exclusive\n ? ordered.filter((scope) => scope === exclusive || isDescendant(scope, exclusive))\n : ordered;\n return [\n ...active.filter((scope) => scope.options.keys),\n ...active.filter((scope) => !scope.options.keys),\n ];\n}\n\n/** Element targets participate only while focus is inside them; document/window targets are ambient. */\nexport function scopeHasFocus(scope: Scope): boolean {\n const target = scope.options.target;\n return (\n scope.options.keys !== undefined ||\n !target ||\n !(\"ownerDocument\" in target) ||\n target.ownerDocument === null ||\n target.contains(target.ownerDocument?.activeElement ?? null)\n );\n}\n\n/** Editable-only command bindings are disabled while a text control has focus. */\nexport function scopeAcceptsKeys(scope: Scope): boolean {\n if (!scopeHasFocus(scope)) return false;\n if (!scope.options.keys && (scope.options.ignoreInputs ?? \"editable\") !== \"editable\") return true;\n const target = scope.options.target;\n const document =\n target && \"document\" in target\n ? target.document\n : target && \"defaultView\" in target\n ? target\n : (target?.ownerDocument ?? globalThis.document);\n return !isKeyboardEditable(document?.activeElement);\n}\n\n/** Element listeners see bubbling keys before ambient listeners, regardless of scope age. */\nexport function keymapScopes(scopes: ReadonlyMap<string, Scope>): Scope[] {\n return activeScopes(scopes)\n .filter(scopeAcceptsKeys)\n .sort((a, b) => {\n if (Boolean(a.options.keys) !== Boolean(b.options.keys)) return a.options.keys ? -1 : 1;\n if (a.options.keys && b.options.keys) return 0;\n const aTarget = a.options.target;\n const bTarget = b.options.target;\n const aElement =\n aTarget && \"ownerDocument\" in aTarget && aTarget.ownerDocument ? aTarget : undefined;\n const bElement =\n bTarget && \"ownerDocument\" in bTarget && bTarget.ownerDocument ? bTarget : undefined;\n if (aElement && bElement) {\n if (aElement === bElement) return 0;\n if (aElement.contains(bElement)) return 1;\n if (bElement.contains(aElement)) return -1;\n }\n return Number(Boolean(bElement)) - Number(Boolean(aElement));\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
/** Keys only: navigation handlers and confirmation prompts belong to the app. */
|
|
2
|
-
export declare const tradingPreset: Readonly<Record<"tree.next" | "tree.prev" | "tree.first" | "tree.last" | "tree.page-up" | "tree.page-down" | "tree.parent" | "tree.expand" | "tree.collapse" | "tree.toggle" | "tree.select" | "tree.extend-selection" | "tree.open" | "table.sort" | "table.next-column" | "tabs.select.1" | "tabs.select.2" | "tabs.select.3" | "tabs.select.4" | "tabs.select.5" | "tabs.select.6" | "tabs.select.7" | "tabs.select.8" | "tabs.select.9" | "next" | "
|
|
2
|
+
export declare const tradingPreset: Readonly<Record<"tree.next" | "tree.prev" | "tree.first" | "tree.last" | "tree.page-up" | "tree.page-down" | "tree.parent" | "tree.expand" | "tree.collapse" | "tree.toggle" | "tree.select" | "tree.extend-selection" | "tree.open" | "table.sort" | "table.next-column" | "tabs.select.1" | "tabs.select.2" | "tabs.select.3" | "tabs.select.4" | "tabs.select.5" | "tabs.select.6" | "tabs.select.7" | "tabs.select.8" | "tabs.select.9" | "tabs.next" | "tabs.prev" | "tabs.first" | "tabs.last" | "tabs.down" | "tabs.up" | "next" | "previous" | "open" | "approve" | "confirm" | "cancel" | "p-home", readonly import("@tanstack/hotkeys").Hotkey[]>>;
|
|
3
|
+
/** A Tree/List's default page-wide layer: only the four unshifted navigation keys. */
|
|
4
|
+
export declare const treePreset: Readonly<Record<"tree.next" | "tree.prev" | "tree.expand" | "tree.collapse", readonly import("@tanstack/hotkeys").Hotkey[]>>;
|
|
5
|
+
/** A second Tree/List's complete layer: only the four shifted navigation keys. */
|
|
6
|
+
export declare const shiftedTreePreset: Readonly<Record<"tree.next" | "tree.prev" | "tree.expand" | "tree.collapse", readonly import("@tanstack/hotkeys").Hotkey[]>>;
|
|
3
7
|
//# sourceMappingURL=trading-preset.d.ts.map
|
|
@@ -36,55 +36,34 @@ const tradingPreset = definePreset({
|
|
|
36
36
|
"tabs.select.7": ["7"],
|
|
37
37
|
"tabs.select.8": ["8"],
|
|
38
38
|
"tabs.select.9": ["9"],
|
|
39
|
+
"tabs.next": ["ArrowRight"],
|
|
40
|
+
"tabs.prev": ["ArrowLeft"],
|
|
41
|
+
"tabs.first": ["Home"],
|
|
42
|
+
"tabs.last": ["End"],
|
|
43
|
+
"tabs.down": ["ArrowDown"],
|
|
44
|
+
"tabs.up": ["ArrowUp"],
|
|
39
45
|
next: ["J", "ArrowDown"],
|
|
40
46
|
previous: ["K", "ArrowUp"],
|
|
41
|
-
first: ["Control+U"],
|
|
42
|
-
last: ["Control+D"],
|
|
43
47
|
open: ["Enter"],
|
|
44
|
-
collapse: ["H", "ArrowLeft"],
|
|
45
|
-
expand: ["L", "ArrowRight"],
|
|
46
48
|
approve: ["Shift+Y"],
|
|
47
|
-
reject: ["Shift+X"],
|
|
48
|
-
back: ["["],
|
|
49
|
-
forward: ["]"],
|
|
50
49
|
confirm: ["Y"],
|
|
51
50
|
cancel: ["N", "Escape"],
|
|
52
|
-
"p-
|
|
53
|
-
"p-home": [["G", "H"]],
|
|
54
|
-
"p-hyperliquid-account": [["G", "A"]],
|
|
55
|
-
"p-portfolio-studies": [[
|
|
56
|
-
"G",
|
|
57
|
-
"P",
|
|
58
|
-
"S"
|
|
59
|
-
]],
|
|
60
|
-
"p-portfolio-paper": [[
|
|
61
|
-
"G",
|
|
62
|
-
"P",
|
|
63
|
-
"P"
|
|
64
|
-
]],
|
|
65
|
-
"p-portfolio": [[
|
|
66
|
-
"G",
|
|
67
|
-
"P",
|
|
68
|
-
"B"
|
|
69
|
-
]],
|
|
70
|
-
"p-market": [["G", "M"]],
|
|
71
|
-
"p-ideas": [["G", "R"]],
|
|
72
|
-
"p-ops-activity": [[
|
|
73
|
-
"G",
|
|
74
|
-
"O",
|
|
75
|
-
"A"
|
|
76
|
-
]],
|
|
77
|
-
"p-ops-notifications": [[
|
|
78
|
-
"G",
|
|
79
|
-
"O",
|
|
80
|
-
"N"
|
|
81
|
-
]],
|
|
82
|
-
"p-ops-backfills": [[
|
|
83
|
-
"G",
|
|
84
|
-
"O",
|
|
85
|
-
"B"
|
|
86
|
-
]]
|
|
51
|
+
"p-home": [["G", "H"]]
|
|
87
52
|
});
|
|
88
|
-
|
|
53
|
+
/** A Tree/List's default page-wide layer: only the four unshifted navigation keys. */
|
|
54
|
+
const treePreset = definePreset({
|
|
55
|
+
"tree.next": ["J"],
|
|
56
|
+
"tree.prev": ["K"],
|
|
57
|
+
"tree.expand": ["L"],
|
|
58
|
+
"tree.collapse": ["H"]
|
|
59
|
+
});
|
|
60
|
+
/** A second Tree/List's complete layer: only the four shifted navigation keys. */
|
|
61
|
+
const shiftedTreePreset = definePreset({
|
|
62
|
+
"tree.next": ["Shift+J"],
|
|
63
|
+
"tree.prev": ["Shift+K"],
|
|
64
|
+
"tree.expand": ["Shift+L"],
|
|
65
|
+
"tree.collapse": ["Shift+H"]
|
|
66
|
+
});
|
|
67
|
+
export { shiftedTreePreset, tradingPreset, treePreset };
|
|
89
68
|
|
|
90
69
|
//# sourceMappingURL=trading-preset.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trading-preset.js","names":[],"sources":["../../src/commands/trading-preset.ts"],"sourcesContent":["import type { KeyboardHotkey } from \"../ports/keyboard.js\";\n\nfunction definePreset<const Id extends string>(\n preset: Readonly<Record<Id, readonly KeyboardHotkey[]>>,\n): Readonly<Record<Id, readonly KeyboardHotkey[]>> {\n return preset;\n}\n\n/** Keys only: navigation handlers and confirmation prompts belong to the app. */\nexport const tradingPreset = definePreset({\n \"tree.next\": [\"J\", \"ArrowDown\", \"Shift+ArrowDown\"],\n \"tree.prev\": [\"K\", \"ArrowUp\", \"Shift+ArrowUp\"],\n \"tree.first\": [\"Home\", \"Control+U\"],\n \"tree.last\": [\"End\", \"Control+D\"],\n \"tree.page-up\": [\"PageUp\"],\n \"tree.page-down\": [\"PageDown\"],\n \"tree.parent\": [\"Alt+ArrowUp\"],\n \"tree.expand\": [\"L\", \"ArrowRight\"],\n \"tree.collapse\": [\"H\", \"ArrowLeft\"],\n \"tree.toggle\": [],\n \"tree.select\": [\"Space\"],\n \"tree.extend-selection\": [\"Shift+Space\"],\n \"tree.open\": [\"Enter\"],\n \"table.sort\": [\"S\"],\n \"table.next-column\": [\"Shift+S\"],\n \"tabs.select.1\": [\"1\"],\n \"tabs.select.2\": [\"2\"],\n \"tabs.select.3\": [\"3\"],\n \"tabs.select.4\": [\"4\"],\n \"tabs.select.5\": [\"5\"],\n \"tabs.select.6\": [\"6\"],\n \"tabs.select.7\": [\"7\"],\n \"tabs.select.8\": [\"8\"],\n \"tabs.select.9\": [\"9\"],\n
|
|
1
|
+
{"version":3,"file":"trading-preset.js","names":[],"sources":["../../src/commands/trading-preset.ts"],"sourcesContent":["import type { KeyboardHotkey } from \"../ports/keyboard.js\";\n\nfunction definePreset<const Id extends string>(\n preset: Readonly<Record<Id, readonly KeyboardHotkey[]>>,\n): Readonly<Record<Id, readonly KeyboardHotkey[]>> {\n return preset;\n}\n\n/** Keys only: navigation handlers and confirmation prompts belong to the app. */\nexport const tradingPreset = definePreset({\n \"tree.next\": [\"J\", \"ArrowDown\", \"Shift+ArrowDown\"],\n \"tree.prev\": [\"K\", \"ArrowUp\", \"Shift+ArrowUp\"],\n \"tree.first\": [\"Home\", \"Control+U\"],\n \"tree.last\": [\"End\", \"Control+D\"],\n \"tree.page-up\": [\"PageUp\"],\n \"tree.page-down\": [\"PageDown\"],\n \"tree.parent\": [\"Alt+ArrowUp\"],\n \"tree.expand\": [\"L\", \"ArrowRight\"],\n \"tree.collapse\": [\"H\", \"ArrowLeft\"],\n // Unbound actions stay out of Legend until an app supplies an override.\n \"tree.toggle\": [],\n \"tree.select\": [\"Space\"],\n \"tree.extend-selection\": [\"Shift+Space\"],\n \"tree.open\": [\"Enter\"],\n \"table.sort\": [\"S\"],\n \"table.next-column\": [\"Shift+S\"],\n \"tabs.select.1\": [\"1\"],\n \"tabs.select.2\": [\"2\"],\n \"tabs.select.3\": [\"3\"],\n \"tabs.select.4\": [\"4\"],\n \"tabs.select.5\": [\"5\"],\n \"tabs.select.6\": [\"6\"],\n \"tabs.select.7\": [\"7\"],\n \"tabs.select.8\": [\"8\"],\n \"tabs.select.9\": [\"9\"],\n // Bound by the focused tab list only, so ArrowRight and ArrowLeft stay free in panels.\n \"tabs.next\": [\"ArrowRight\"],\n \"tabs.prev\": [\"ArrowLeft\"],\n \"tabs.first\": [\"Home\"],\n \"tabs.last\": [\"End\"],\n // A vertical tab list's next and previous tab.\n \"tabs.down\": [\"ArrowDown\"],\n \"tabs.up\": [\"ArrowUp\"],\n next: [\"J\", \"ArrowDown\"],\n previous: [\"K\", \"ArrowUp\"],\n open: [\"Enter\"],\n approve: [\"Shift+Y\"],\n confirm: [\"Y\"],\n cancel: [\"N\", \"Escape\"],\n \"p-home\": [[\"G\", \"H\"]],\n});\n\n/** A Tree/List's default page-wide layer: only the four unshifted navigation keys. */\nexport const treePreset = definePreset({\n \"tree.next\": [\"J\"],\n \"tree.prev\": [\"K\"],\n \"tree.expand\": [\"L\"],\n \"tree.collapse\": [\"H\"],\n});\n\n/** A second Tree/List's complete layer: only the four shifted navigation keys. */\nexport const shiftedTreePreset = definePreset({\n \"tree.next\": [\"Shift+J\"],\n \"tree.prev\": [\"Shift+K\"],\n \"tree.expand\": [\"Shift+L\"],\n \"tree.collapse\": [\"Shift+H\"],\n});\n"],"mappings":";AAEA,SAAS,aACP,QACiD;CACjD,OAAO;AACT;;AAGA,MAAa,gBAAgB,aAAa;CACxC,aAAa;EAAC;EAAK;EAAa;CAAiB;CACjD,aAAa;EAAC;EAAK;EAAW;CAAe;CAC7C,cAAc,CAAC,QAAQ,WAAW;CAClC,aAAa,CAAC,OAAO,WAAW;CAChC,gBAAgB,CAAC,QAAQ;CACzB,kBAAkB,CAAC,UAAU;CAC7B,eAAe,CAAC,aAAa;CAC7B,eAAe,CAAC,KAAK,YAAY;CACjC,iBAAiB,CAAC,KAAK,WAAW;CAElC,eAAe,CAAC;CAChB,eAAe,CAAC,OAAO;CACvB,yBAAyB,CAAC,aAAa;CACvC,aAAa,CAAC,OAAO;CACrB,cAAc,CAAC,GAAG;CAClB,qBAAqB,CAAC,SAAS;CAC/B,iBAAiB,CAAC,GAAG;CACrB,iBAAiB,CAAC,GAAG;CACrB,iBAAiB,CAAC,GAAG;CACrB,iBAAiB,CAAC,GAAG;CACrB,iBAAiB,CAAC,GAAG;CACrB,iBAAiB,CAAC,GAAG;CACrB,iBAAiB,CAAC,GAAG;CACrB,iBAAiB,CAAC,GAAG;CACrB,iBAAiB,CAAC,GAAG;CAErB,aAAa,CAAC,YAAY;CAC1B,aAAa,CAAC,WAAW;CACzB,cAAc,CAAC,MAAM;CACrB,aAAa,CAAC,KAAK;CAEnB,aAAa,CAAC,WAAW;CACzB,WAAW,CAAC,SAAS;CACrB,MAAM,CAAC,KAAK,WAAW;CACvB,UAAU,CAAC,KAAK,SAAS;CACzB,MAAM,CAAC,OAAO;CACd,SAAS,CAAC,SAAS;CACnB,SAAS,CAAC,GAAG;CACb,QAAQ,CAAC,KAAK,QAAQ;CACtB,UAAU,CAAC,CAAC,KAAK,GAAG,CAAC;AACvB,CAAC;;AAGD,MAAa,aAAa,aAAa;CACrC,aAAa,CAAC,GAAG;CACjB,aAAa,CAAC,GAAG;CACjB,eAAe,CAAC,GAAG;CACnB,iBAAiB,CAAC,GAAG;AACvB,CAAC;;AAGD,MAAa,oBAAoB,aAAa;CAC5C,aAAa,CAAC,SAAS;CACvB,aAAa,CAAC,SAAS;CACvB,eAAe,CAAC,SAAS;CACzB,iBAAiB,CAAC,SAAS;AAC7B,CAAC"}
|
package/dist/commands/types.d.ts
CHANGED
|
@@ -4,13 +4,13 @@ import { tradingPreset } from "./trading-preset.js";
|
|
|
4
4
|
/** Augment through either public entry with `commands: { open: unknown; ... }`. */
|
|
5
5
|
export interface Register {}
|
|
6
6
|
/** Commands that densedash parts register themselves, so apps can rebind them by id. */
|
|
7
|
-
type PartCommandId =
|
|
7
|
+
type PartCommandId = keyof typeof tradingPreset & (`tree.${string}` | `tabs.${string}` | `table.${string}`);
|
|
8
8
|
export type CommandId = PartCommandId | (Register extends {
|
|
9
9
|
commands: infer Commands;
|
|
10
|
-
} ?
|
|
10
|
+
} ? keyof Commands & string : string);
|
|
11
11
|
export type KeyOverrideId = Register extends {
|
|
12
12
|
commands: infer Commands;
|
|
13
|
-
} ?
|
|
13
|
+
} ? (keyof Commands & string) | PartCommandId | "confirm" | "cancel" : keyof typeof tradingPreset;
|
|
14
14
|
export interface CommandContext {
|
|
15
15
|
readonly event?: KeyboardEvent;
|
|
16
16
|
}
|
|
@@ -18,6 +18,10 @@ export interface Command {
|
|
|
18
18
|
readonly id: CommandId;
|
|
19
19
|
readonly label: string;
|
|
20
20
|
readonly group?: string;
|
|
21
|
+
/** Short legend label for this group; defaults to the group name. */
|
|
22
|
+
readonly groupLabel?: string;
|
|
23
|
+
/** Secondary hints stay out of default legends, but remain bound and in the keymap. */
|
|
24
|
+
readonly legend?: "secondary";
|
|
21
25
|
readonly enabled?: () => boolean;
|
|
22
26
|
readonly confirm?: {
|
|
23
27
|
readonly prompt: string;
|
|
@@ -34,8 +38,19 @@ export interface KeysOptions {
|
|
|
34
38
|
readonly overrides?: KeyOverrides;
|
|
35
39
|
readonly onChange?: (overrides: ValidatedKeyOverrides) => void;
|
|
36
40
|
}
|
|
41
|
+
/** Per-instance keys. Omit to retain focus-scoped bindings. */
|
|
42
|
+
export interface KeyLayer {
|
|
43
|
+
readonly preset?: Preset;
|
|
44
|
+
readonly overrides?: KeyOverrides;
|
|
45
|
+
/** Instance label shown in legends and collision errors. */
|
|
46
|
+
readonly label: string;
|
|
47
|
+
}
|
|
37
48
|
export interface CommandScopeOptions extends Omit<KeyboardBindingOptions, "scope" | "enabled"> {
|
|
49
|
+
/** Defaults to "editable": declared commands beat widget typeahead, but leave text fields alone. */
|
|
50
|
+
readonly ignoreInputs?: boolean | "auto" | "editable";
|
|
38
51
|
readonly parent?: string;
|
|
52
|
+
/** Bind this instance throughout the document, ahead of focus-scoped keys. */
|
|
53
|
+
readonly keys?: KeyLayer;
|
|
39
54
|
/** Rebind just the supplied commands in an exclusive scope. */
|
|
40
55
|
readonly only?: boolean;
|
|
41
56
|
}
|
|
@@ -43,8 +58,14 @@ export interface KeymapEntry {
|
|
|
43
58
|
readonly command: string;
|
|
44
59
|
readonly label: string;
|
|
45
60
|
readonly group?: string;
|
|
61
|
+
readonly groupLabel?: string;
|
|
62
|
+
readonly legend?: "secondary";
|
|
46
63
|
readonly scope: string;
|
|
64
|
+
/** Instance label for page-wide key layers. */
|
|
65
|
+
readonly layer?: string;
|
|
47
66
|
readonly keys: readonly string[];
|
|
67
|
+
/** Evaluated when the keymap is published; update the scope to refresh app state. */
|
|
68
|
+
readonly enabled: boolean;
|
|
48
69
|
}
|
|
49
70
|
export interface PendingConfirm {
|
|
50
71
|
readonly command: string;
|
|
@@ -55,6 +76,12 @@ export interface PendingConfirm {
|
|
|
55
76
|
export interface Commands {
|
|
56
77
|
/** A scope name has one owner. Unregister before reusing it. */
|
|
57
78
|
register(scope: string, commands: readonly Command[], options?: CommandScopeOptions): () => void;
|
|
79
|
+
/**
|
|
80
|
+
* Replace definitions without rebinding or clearing pending confirmation/sequence state.
|
|
81
|
+
* The ordered ids must match the registration; unregister to change them or scope options.
|
|
82
|
+
* Pending confirmation uses the latest definition and refreshes its label/prompt.
|
|
83
|
+
*/
|
|
84
|
+
update(scope: string, commands: readonly Command[]): void;
|
|
58
85
|
/** Optional scope selects a specific owner; otherwise use descendant/latest precedence. */
|
|
59
86
|
invoke(id: CommandId, context?: CommandContext & {
|
|
60
87
|
readonly scope?: string;
|
|
@@ -63,9 +90,9 @@ export interface Commands {
|
|
|
63
90
|
readonly confirm: Pick<Store<PendingConfirm | null>, "get" | "subscribe">;
|
|
64
91
|
/**
|
|
65
92
|
* The keys bound to a command id (its override, else its preset keys), whether or not a
|
|
66
|
-
* scope has focus. `keymap` republishes after `setOverrides`, so subscribe to it to follow.
|
|
93
|
+
* scope has focus. Pass a registration scope to read its instance layer. `keymap` republishes after `setOverrides`, so subscribe to it to follow.
|
|
67
94
|
*/
|
|
68
|
-
keys(id: CommandId): readonly KeyboardHotkey[];
|
|
95
|
+
keys(id: CommandId, scope?: string): readonly KeyboardHotkey[];
|
|
69
96
|
accept(): void;
|
|
70
97
|
cancel(): void;
|
|
71
98
|
/** Replaces all overrides, rebinds live scopes, then notifies onChange with validated keys. */
|
|
@@ -1,5 +1,20 @@
|
|
|
1
|
+
import { isKeyboardEditable } from "../ports/keyboard.js";
|
|
1
2
|
import { ALL_KEYS, HotkeyManager, detectPlatform, formatForDisplay, normalizeHotkey, parseHotkey, toHotkeyRegistrationView, validateHotkey } from "@tanstack/hotkeys";
|
|
2
3
|
// src/defaults/keyboard.ts
|
|
4
|
+
/**
|
|
5
|
+
* Hotkeys' display parts, joined as compact keycap labels: glyphs with no separator on macOS
|
|
6
|
+
* (`⇧J`, `⌘K`) and text joined by `+` elsewhere (`Shift+J`, `Ctrl+K`). A letter pressed on its
|
|
7
|
+
* own reads in lower case (`j`, and `g r` for a sequence), as it is typed.
|
|
8
|
+
*/
|
|
9
|
+
function formatChord(chord, platform) {
|
|
10
|
+
const parts = formatForDisplay(chord, {
|
|
11
|
+
platform,
|
|
12
|
+
parts: true
|
|
13
|
+
});
|
|
14
|
+
const [only] = parts;
|
|
15
|
+
if (parts.length === 1 && only !== void 0 && /^[A-Z]$/.test(only)) return only.toLowerCase();
|
|
16
|
+
return parts.join(platform === "mac" ? "" : "+");
|
|
17
|
+
}
|
|
3
18
|
function createKeyboardPort() {
|
|
4
19
|
const manager = Reflect.construct(HotkeyManager, []);
|
|
5
20
|
const register = manager.register;
|
|
@@ -76,7 +91,7 @@ function createKeyboardPort() {
|
|
|
76
91
|
};
|
|
77
92
|
}
|
|
78
93
|
},
|
|
79
|
-
format: (hotkey) =>
|
|
94
|
+
format: (hotkey) => typeof hotkey === "string" ? formatChord(hotkey, platform) : hotkey.map((step) => formatChord(step, platform)).join(" "),
|
|
80
95
|
activateScope(scope, options) {
|
|
81
96
|
requireLive();
|
|
82
97
|
return own(manager.activateScope(scope, options));
|
|
@@ -85,10 +100,41 @@ function createKeyboardPort() {
|
|
|
85
100
|
requireLive();
|
|
86
101
|
let currentOptions = options;
|
|
87
102
|
let currentHandler = handler;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
103
|
+
const targetDocument = () => {
|
|
104
|
+
const target = currentOptions.target;
|
|
105
|
+
if (target && "document" in target) return target.document;
|
|
106
|
+
if (target && "defaultView" in target) return target;
|
|
107
|
+
return target?.ownerDocument ?? (typeof document === "undefined" ? void 0 : document);
|
|
108
|
+
};
|
|
109
|
+
const resolvedOptions = () => {
|
|
110
|
+
const { ignoreInputs, ...bindingOptions } = currentOptions;
|
|
111
|
+
return {
|
|
112
|
+
...bindingOptions,
|
|
113
|
+
...ignoreInputs === void 0 ? {} : { ignoreInputs: ignoreInputs === "editable" ? false : ignoreInputs },
|
|
114
|
+
enabled: currentOptions.enabled !== false && (ignoreInputs !== "editable" || !isKeyboardEditable(targetDocument()?.activeElement)),
|
|
115
|
+
conflictBehavior: "allow"
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
let handle = register.call(manager, hotkey, handler, resolvedOptions());
|
|
119
|
+
let releaseFocus = () => {};
|
|
120
|
+
function watchFocus() {
|
|
121
|
+
releaseFocus();
|
|
122
|
+
if (currentOptions.ignoreInputs !== "editable") return;
|
|
123
|
+
const doc = targetDocument();
|
|
124
|
+
if (!doc) return;
|
|
125
|
+
const update = (event) => {
|
|
126
|
+
const next = event.type === "focusout" ? event.relatedTarget : doc.activeElement;
|
|
127
|
+
const element = next instanceof (doc.defaultView?.Element ?? Element) ? next : null;
|
|
128
|
+
handle.setOptions({ enabled: currentOptions.enabled !== false && !isKeyboardEditable(element) });
|
|
129
|
+
};
|
|
130
|
+
doc.addEventListener("focusin", update, true);
|
|
131
|
+
doc.addEventListener("focusout", update, true);
|
|
132
|
+
releaseFocus = () => {
|
|
133
|
+
doc.removeEventListener("focusin", update, true);
|
|
134
|
+
doc.removeEventListener("focusout", update, true);
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
watchFocus();
|
|
92
138
|
let active = true;
|
|
93
139
|
return {
|
|
94
140
|
update({ handler: nextHandler, ...nextOptions }) {
|
|
@@ -101,17 +147,16 @@ function createKeyboardPort() {
|
|
|
101
147
|
currentHandler = nextHandler ?? currentHandler;
|
|
102
148
|
if (currentOptions.target !== previousTarget) {
|
|
103
149
|
handle.unregister();
|
|
104
|
-
handle = register.call(manager, hotkey, currentHandler,
|
|
105
|
-
...currentOptions,
|
|
106
|
-
conflictBehavior: "allow"
|
|
107
|
-
});
|
|
150
|
+
handle = register.call(manager, hotkey, currentHandler, resolvedOptions());
|
|
108
151
|
} else {
|
|
109
152
|
handle.callback = currentHandler;
|
|
110
|
-
handle.setOptions(
|
|
153
|
+
handle.setOptions(resolvedOptions());
|
|
111
154
|
}
|
|
155
|
+
watchFocus();
|
|
112
156
|
},
|
|
113
157
|
dispose: own(() => {
|
|
114
158
|
active = false;
|
|
159
|
+
releaseFocus();
|
|
115
160
|
handle.unregister();
|
|
116
161
|
})
|
|
117
162
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keyboard.js","names":[],"sources":["../../src/defaults/keyboard.ts"],"sourcesContent":["import {\n ALL_KEYS,\n HotkeyManager,\n detectPlatform,\n formatForDisplay,\n normalizeHotkey,\n parseHotkey,\n toHotkeyRegistrationView,\n validateHotkey,\n} from \"@tanstack/hotkeys\";\nimport type { HotkeyRegistrationHandle } from \"@tanstack/hotkeys\";\nimport type {\n KeyboardBindingOptions,\n KeyboardHandler,\n KeyboardHotkey,\n KeyboardHotkeyChord,\n KeyboardPort,\n PendingKeyboardSequence,\n} from \"../ports/keyboard.js\";\n\nexport function createKeyboardPort(): KeyboardPort {\n // 0.24.0 exposes only a singleton factory and marks its zero-argument constructor\n // private in types. The shipped constructor is independent of that singleton.\n // Keep this pinned interop here: each kernel must own its scopes and listeners.\n const manager = Reflect.construct(HotkeyManager, []) as HotkeyManager;\n // The port exposes shared options and an event-only callback. Erase upstream's\n // per-chord conditional inference here rather than distributing its key union.\n const register = manager.register as unknown as (\n hotkey: KeyboardHotkey,\n handler: KeyboardHandler,\n options: KeyboardBindingOptions & { conflictBehavior: \"allow\" },\n ) => HotkeyRegistrationHandle<KeyboardBindingOptions>;\n const platform = detectPlatform();\n const releases = new Set<() => void>();\n let disposed = false;\n let registrations = manager.registrations.state;\n let pending: readonly PendingKeyboardSequence[] = [];\n\n function readPending(): readonly PendingKeyboardSequence[] {\n if (registrations === manager.registrations.state) return pending;\n registrations = manager.registrations.state;\n const sequences: PendingKeyboardSequence[] = [];\n for (const registration of registrations.values()) {\n const view = toHotkeyRegistrationView(registration);\n if (view.kind === \"sequence\" && view.matchedStepCount > 0) {\n sequences.push({\n id: view.id,\n hotkey: view.hotkey,\n matchedStepCount: view.matchedStepCount,\n scope: view.options.scope ?? \"global\",\n target: view.target,\n });\n }\n }\n pending = sequences;\n return pending;\n }\n\n function requireLive(): void {\n if (disposed) throw new Error(\"Keyboard port has been disposed.\");\n }\n\n function own(cleanup: () => void): () => void {\n const release = () => {\n if (!releases.delete(release)) return;\n cleanup();\n };\n releases.add(release);\n return release;\n }\n\n return {\n platform,\n parse(input) {\n if (\n typeof input !== \"string\" &&\n !(\n Array.isArray(input) &&\n Array.from(input).every((step: unknown) => typeof step === \"string\")\n )\n ) {\n return { ok: false, reason: \"Expected a hotkey string or an array of hotkey strings.\" };\n }\n try {\n const steps: readonly string[] = typeof input === \"string\" ? [input] : input;\n const normalized: KeyboardHotkeyChord[] = [];\n const knownKeys: ReadonlySet<string> = ALL_KEYS;\n for (const step of steps) {\n const validation = validateHotkey(step);\n if (!validation.valid) return { ok: false, reason: validation.errors.join(\"; \") };\n const parsed = parseHotkey(step, platform);\n // Upstream validates key-name shape, so even \"Bogus\" passes. Use its\n // canonical logical-key set before trusting its normalized return type.\n if (parsed.key !== undefined && !knownKeys.has(parsed.key)) {\n return { ok: false, reason: `Unknown key: '${parsed.key}'.` };\n }\n normalized.push(normalizeHotkey(step, platform));\n }\n const first = normalized[0];\n if (first === undefined) return { ok: false, reason: \"Sequence cannot be empty.\" };\n return { ok: true, hotkey: normalized.length === 1 ? first : normalized };\n } catch (cause) {\n return { ok: false, reason: cause instanceof Error ? cause.message : \"Invalid hotkey.\" };\n }\n },\n format: (hotkey) => formatForDisplay(hotkey, { platform }),\n activateScope(scope, options) {\n requireLive();\n return own(manager.activateScope(scope, options));\n },\n bind(hotkey, handler, options = {}) {\n requireLive();\n let currentOptions: KeyboardBindingOptions = options;\n let currentHandler = handler;\n let handle = register.call(manager, hotkey, handler, {\n ...options,\n conflictBehavior: \"allow\",\n });\n let active = true;\n const dispose = own(() => {\n active = false;\n handle.unregister();\n });\n return {\n update({ handler: nextHandler, ...nextOptions }) {\n if (!active) return;\n const previousTarget = currentOptions.target;\n currentOptions = { ...currentOptions, ...nextOptions };\n currentHandler = nextHandler ?? currentHandler;\n // Hotkeys setOptions does not move a registration to a different target.\n if (currentOptions.target !== previousTarget) {\n handle.unregister();\n handle = register.call(manager, hotkey, currentHandler, {\n ...currentOptions,\n conflictBehavior: \"allow\",\n });\n } else {\n handle.callback = currentHandler;\n handle.setOptions(nextOptions);\n }\n },\n dispose,\n };\n },\n pending: {\n get: readPending,\n subscribe(listener) {\n if (disposed) return () => {};\n const subscription = manager.registrations.subscribe(() => listener(readPending()));\n return own(subscription.unsubscribe);\n },\n },\n dispose() {\n if (disposed) return;\n disposed = true;\n for (const release of releases) release();\n manager.destroy();\n readPending();\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"keyboard.js","names":[],"sources":["../../src/defaults/keyboard.ts"],"sourcesContent":["import {\n ALL_KEYS,\n HotkeyManager,\n detectPlatform,\n formatForDisplay,\n normalizeHotkey,\n parseHotkey,\n toHotkeyRegistrationView,\n validateHotkey,\n} from \"@tanstack/hotkeys\";\nimport type { HotkeyOptions, HotkeyRegistrationHandle } from \"@tanstack/hotkeys\";\nimport type {\n KeyboardBindingOptions,\n KeyboardHandler,\n KeyboardHotkey,\n KeyboardHotkeyChord,\n KeyboardPlatform,\n KeyboardPort,\n PendingKeyboardSequence,\n} from \"../ports/keyboard.js\";\nimport { isKeyboardEditable } from \"../ports/keyboard.js\";\n\n/**\n * Hotkeys' display parts, joined as compact keycap labels: glyphs with no separator on macOS\n * (`⇧J`, `⌘K`) and text joined by `+` elsewhere (`Shift+J`, `Ctrl+K`). A letter pressed on its\n * own reads in lower case (`j`, and `g r` for a sequence), as it is typed.\n */\nfunction formatChord(chord: KeyboardHotkeyChord, platform: KeyboardPlatform): string {\n const parts = formatForDisplay(chord, { platform, parts: true });\n const [only] = parts;\n if (parts.length === 1 && only !== undefined && /^[A-Z]$/.test(only)) return only.toLowerCase();\n return parts.join(platform === \"mac\" ? \"\" : \"+\");\n}\n\nexport function createKeyboardPort(): KeyboardPort {\n // 0.24.0 exposes only a singleton factory and marks its zero-argument constructor\n // private in types. The shipped constructor is independent of that singleton.\n // Keep this pinned interop here: each kernel must own its scopes and listeners.\n const manager = Reflect.construct(HotkeyManager, []) as HotkeyManager;\n // The port exposes shared options and an event-only callback. Erase upstream's\n // per-chord conditional inference here rather than distributing its key union.\n const register = manager.register as unknown as (\n hotkey: KeyboardHotkey,\n handler: KeyboardHandler,\n options: HotkeyOptions & { conflictBehavior: \"allow\" },\n ) => HotkeyRegistrationHandle<HotkeyOptions>;\n const platform = detectPlatform();\n const releases = new Set<() => void>();\n let disposed = false;\n let registrations = manager.registrations.state;\n let pending: readonly PendingKeyboardSequence[] = [];\n\n function readPending(): readonly PendingKeyboardSequence[] {\n if (registrations === manager.registrations.state) return pending;\n registrations = manager.registrations.state;\n const sequences: PendingKeyboardSequence[] = [];\n for (const registration of registrations.values()) {\n const view = toHotkeyRegistrationView(registration);\n if (view.kind === \"sequence\" && view.matchedStepCount > 0) {\n sequences.push({\n id: view.id,\n hotkey: view.hotkey,\n matchedStepCount: view.matchedStepCount,\n scope: view.options.scope ?? \"global\",\n target: view.target,\n });\n }\n }\n pending = sequences;\n return pending;\n }\n\n function requireLive(): void {\n if (disposed) throw new Error(\"Keyboard port has been disposed.\");\n }\n\n function own(cleanup: () => void): () => void {\n const release = () => {\n if (!releases.delete(release)) return;\n cleanup();\n };\n releases.add(release);\n return release;\n }\n\n return {\n platform,\n parse(input) {\n if (\n typeof input !== \"string\" &&\n !(\n Array.isArray(input) &&\n Array.from(input).every((step: unknown) => typeof step === \"string\")\n )\n ) {\n return { ok: false, reason: \"Expected a hotkey string or an array of hotkey strings.\" };\n }\n try {\n const steps: readonly string[] = typeof input === \"string\" ? [input] : input;\n const normalized: KeyboardHotkeyChord[] = [];\n const knownKeys: ReadonlySet<string> = ALL_KEYS;\n for (const step of steps) {\n const validation = validateHotkey(step);\n if (!validation.valid) return { ok: false, reason: validation.errors.join(\"; \") };\n const parsed = parseHotkey(step, platform);\n // Upstream validates key-name shape, so even \"Bogus\" passes. Use its\n // canonical logical-key set before trusting its normalized return type.\n if (parsed.key !== undefined && !knownKeys.has(parsed.key)) {\n return { ok: false, reason: `Unknown key: '${parsed.key}'.` };\n }\n normalized.push(normalizeHotkey(step, platform));\n }\n const first = normalized[0];\n if (first === undefined) return { ok: false, reason: \"Sequence cannot be empty.\" };\n return { ok: true, hotkey: normalized.length === 1 ? first : normalized };\n } catch (cause) {\n return { ok: false, reason: cause instanceof Error ? cause.message : \"Invalid hotkey.\" };\n }\n },\n format: (hotkey) =>\n typeof hotkey === \"string\"\n ? formatChord(hotkey, platform)\n : hotkey.map((step) => formatChord(step, platform)).join(\" \"),\n activateScope(scope, options) {\n requireLive();\n return own(manager.activateScope(scope, options));\n },\n bind(hotkey, handler, options = {}) {\n requireLive();\n let currentOptions: KeyboardBindingOptions = options;\n let currentHandler = handler;\n const targetDocument = () => {\n const target = currentOptions.target;\n if (target && \"document\" in target) return target.document;\n // Document also has ownerDocument, but its value is null.\n if (target && \"defaultView\" in target) return target;\n return target?.ownerDocument ?? (typeof document === \"undefined\" ? undefined : document);\n };\n const resolvedOptions = (): HotkeyOptions & { conflictBehavior: \"allow\" } => {\n const { ignoreInputs, ...bindingOptions } = currentOptions;\n return {\n ...bindingOptions,\n ...(ignoreInputs === undefined\n ? {}\n : { ignoreInputs: ignoreInputs === \"editable\" ? false : ignoreInputs }),\n enabled:\n currentOptions.enabled !== false &&\n (ignoreInputs !== \"editable\" || !isKeyboardEditable(targetDocument()?.activeElement)),\n conflictBehavior: \"allow\",\n };\n };\n let handle = register.call(manager, hotkey, handler, resolvedOptions());\n let releaseFocus = () => {};\n function watchFocus(): void {\n releaseFocus();\n if (currentOptions.ignoreInputs !== \"editable\") return;\n const doc = targetDocument();\n if (!doc) return;\n const update = (event: FocusEvent) => {\n const next = event.type === \"focusout\" ? event.relatedTarget : doc.activeElement;\n const element = next instanceof (doc.defaultView?.Element ?? Element) ? next : null;\n handle.setOptions({\n enabled: currentOptions.enabled !== false && !isKeyboardEditable(element),\n });\n };\n doc.addEventListener(\"focusin\", update, true);\n doc.addEventListener(\"focusout\", update, true);\n releaseFocus = () => {\n doc.removeEventListener(\"focusin\", update, true);\n doc.removeEventListener(\"focusout\", update, true);\n };\n }\n watchFocus();\n let active = true;\n const dispose = own(() => {\n active = false;\n releaseFocus();\n handle.unregister();\n });\n return {\n update({ handler: nextHandler, ...nextOptions }) {\n if (!active) return;\n const previousTarget = currentOptions.target;\n currentOptions = { ...currentOptions, ...nextOptions };\n currentHandler = nextHandler ?? currentHandler;\n // Hotkeys setOptions does not move a registration to a different target.\n if (currentOptions.target !== previousTarget) {\n handle.unregister();\n handle = register.call(manager, hotkey, currentHandler, resolvedOptions());\n } else {\n handle.callback = currentHandler;\n handle.setOptions(resolvedOptions());\n }\n watchFocus();\n },\n dispose,\n };\n },\n pending: {\n get: readPending,\n subscribe(listener) {\n if (disposed) return () => {};\n const subscription = manager.registrations.subscribe(() => listener(readPending()));\n return own(subscription.unsubscribe);\n },\n },\n dispose() {\n if (disposed) return;\n disposed = true;\n for (const release of releases) release();\n manager.destroy();\n readPending();\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Store, StorePort } from "./ports/store.js";
|
|
2
2
|
import { KeyboardBinding, KeyboardBindingOptions, KeyboardHandler, KeyboardHotkey, KeyboardHotkeyChord, KeyboardParseResult, KeyboardPendingStore, KeyboardPlatform, KeyboardPort, KeyboardScopeOptions, KeyboardTarget, PendingKeyboardSequence } from "./ports/keyboard.js";
|
|
3
|
-
import { tradingPreset } from "./commands/trading-preset.js";
|
|
4
|
-
import { Command, CommandContext, CommandId, CommandScopeOptions, Commands, KeyOverrideId, KeyOverrides, KeymapEntry, KeysOptions, PendingConfirm, Preset, Register, ValidatedKeyOverrides } from "./commands/types.js";
|
|
3
|
+
import { shiftedTreePreset, tradingPreset, treePreset } from "./commands/trading-preset.js";
|
|
4
|
+
import { Command, CommandContext, CommandId, CommandScopeOptions, Commands, KeyLayer, KeyOverrideId, KeyOverrides, KeymapEntry, KeysOptions, PendingConfirm, Preset, Register, ValidatedKeyOverrides } from "./commands/types.js";
|
|
5
5
|
import { ClockPort } from "./ports/clock.js";
|
|
6
6
|
import { ReporterPort } from "./ports/reporter.js";
|
|
7
7
|
import { BasePorts, CheckedKernelOptions, Kernel, KernelOptions, Plugin, PortProviders, Ports, ResolvedPorts } from "./kernel.js";
|
|
@@ -9,15 +9,12 @@ import "./bare.js";
|
|
|
9
9
|
import { Task } from "./task.js";
|
|
10
10
|
import { ChangeDetails, Controllable } from "./controllable.js";
|
|
11
11
|
import { createCommands } from "./commands/index.js";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { clearSelection, selectAll, selectRange, toggleSelection } from "./collections/selection.js";
|
|
17
|
-
import { Tree, TreeAction, TreeChangeReason, TreeCommands, TreeFocusStrategy, TreeMode, TreeOptions, TreePartProps, TreeRootProps, TreeRowProps, TreeSnapshot, TreeToggleProps } from "./tree/types.js";
|
|
18
|
-
import { createTree } from "./tree/index.js";
|
|
12
|
+
import { TreeAction, TreeChangeReason, TreeCommands, TreeFocusStrategy, TreeLabels, TreeMode, TreePartProps, TreeRootProps, TreeRowProps, TreeToggleProps } from "./tree/types.js";
|
|
13
|
+
import { createNavigableTable, navigationFeature, navigationFeatures } from "./navigation/index.js";
|
|
14
|
+
import { NavigableTable, NavigationCommands, NavigationFeatures, NavigationOptions, NavigationRow, NavigationSnapshot, NavigationState, NavigationTable } from "./navigation/types.js";
|
|
15
|
+
import { ExpandedState, RowData, RowSelectionState } from "@tanstack/table-core";
|
|
19
16
|
export declare const version = "0.0.0";
|
|
20
17
|
/** Creates an app-scoped kernel; explicit ports override plugins, which override defaults. */
|
|
21
18
|
export declare function createKernel<const Options extends KernelOptions = KernelOptions>(options?: Options & CheckedKernelOptions<Options>): Kernel<ResolvedPorts<Options>>;
|
|
22
|
-
export { type BasePorts, type ChangeDetails, type ClockPort, type Command, type CommandContext, type CommandId, type CommandScopeOptions, type Commands, type Controllable, type Kernel, type KernelOptions, type
|
|
19
|
+
export { type BasePorts, type ChangeDetails, type ClockPort, type Command, type CommandContext, type CommandId, type CommandScopeOptions, type Commands, type Controllable, type ExpandedState, type Kernel, type KernelOptions, type KeyLayer, type KeyOverrideId, type KeyOverrides, type KeyboardBinding, type KeyboardBindingOptions, type KeyboardHandler, type KeyboardHotkey, type KeyboardHotkeyChord, type KeyboardParseResult, type KeyboardPendingStore, type KeyboardPlatform, type KeyboardPort, type KeyboardScopeOptions, type KeyboardTarget, type KeymapEntry, type KeysOptions, type NavigableTable, type NavigationCommands, type NavigationFeatures, type NavigationOptions, type NavigationRow, type NavigationSnapshot, type NavigationState, type NavigationTable, type PendingConfirm, type PendingKeyboardSequence, type Plugin, type PortProviders, type Ports, type Preset, type Register, type ReporterPort, type ResolvedPorts, type RowData, type RowSelectionState, type Store, type StorePort, type Task, type TreeAction, type TreeChangeReason, type TreeCommands, type TreeFocusStrategy, type TreeLabels, type TreeMode, type TreePartProps, type TreeRootProps, type TreeRowProps, type TreeToggleProps, type ValidatedKeyOverrides, createCommands, createNavigableTable, navigationFeature, navigationFeatures, shiftedTreePreset, tradingPreset, treePreset };
|
|
23
20
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { defaultProviders } from "./defaults/index.js";
|
|
2
|
-
import { tradingPreset } from "./commands/trading-preset.js";
|
|
2
|
+
import { shiftedTreePreset, tradingPreset, treePreset } from "./commands/trading-preset.js";
|
|
3
3
|
import { resolveKernel } from "./kernel.js";
|
|
4
4
|
import { createCommands } from "./commands/index.js";
|
|
5
|
-
import {
|
|
6
|
-
import { move } from "./collections/move.js";
|
|
7
|
-
import { reconcile } from "./collections/reconcile.js";
|
|
8
|
-
import { clearSelection, selectAll, selectRange, toggleSelection } from "./collections/selection.js";
|
|
9
|
-
import { createTree } from "./tree/index.js";
|
|
5
|
+
import { createNavigableTable, navigationFeature, navigationFeatures } from "./navigation/index.js";
|
|
10
6
|
// src/index.ts
|
|
11
7
|
const version = "0.0.0";
|
|
12
8
|
/** Creates an app-scoped kernel; explicit ports override plugins, which override defaults. */
|
|
@@ -16,6 +12,6 @@ function createKernel(options) {
|
|
|
16
12
|
defaults: defaultProviders
|
|
17
13
|
});
|
|
18
14
|
}
|
|
19
|
-
export {
|
|
15
|
+
export { createCommands, createKernel, createNavigableTable, navigationFeature, navigationFeatures, shiftedTreePreset, tradingPreset, treePreset, version };
|
|
20
16
|
|
|
21
17
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { defaultProviders } from \"./defaults/index.js\";\nimport { resolveKernel } from \"./kernel.js\";\nimport type { CheckedKernelOptions, Kernel, KernelOptions, ResolvedPorts } from \"./kernel.js\";\n\nexport type {\n BasePorts,\n Kernel,\n KernelOptions,\n Plugin,\n PortProviders,\n Ports,\n ResolvedPorts,\n ClockPort,\n KeyboardPort,\n KeyboardHotkey,\n KeyboardHotkeyChord,\n KeyboardPlatform,\n KeyboardTarget,\n KeyboardHandler,\n KeyboardParseResult,\n KeyboardScopeOptions,\n KeyboardBindingOptions,\n KeyboardBinding,\n PendingKeyboardSequence,\n KeyboardPendingStore,\n ReporterPort,\n Store,\n StorePort,\n Task,\n ChangeDetails,\n Controllable,\n} from \"./bare.js\";\n\nexport * from \"./commands/index.js\";\n\nexport const version = \"0.0.0\";\nexport
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { defaultProviders } from \"./defaults/index.js\";\nimport { resolveKernel } from \"./kernel.js\";\nimport type { CheckedKernelOptions, Kernel, KernelOptions, ResolvedPorts } from \"./kernel.js\";\n\nexport type {\n BasePorts,\n Kernel,\n KernelOptions,\n Plugin,\n PortProviders,\n Ports,\n ResolvedPorts,\n ClockPort,\n KeyboardPort,\n KeyboardHotkey,\n KeyboardHotkeyChord,\n KeyboardPlatform,\n KeyboardTarget,\n KeyboardHandler,\n KeyboardParseResult,\n KeyboardScopeOptions,\n KeyboardBindingOptions,\n KeyboardBinding,\n PendingKeyboardSequence,\n KeyboardPendingStore,\n ReporterPort,\n Store,\n StorePort,\n Task,\n ChangeDetails,\n Controllable,\n} from \"./bare.js\";\n\nexport * from \"./commands/index.js\";\n\nexport const version = \"0.0.0\";\nexport type {\n TreeAction,\n TreeChangeReason,\n TreeCommands,\n TreeFocusStrategy,\n TreeLabels,\n TreeMode,\n TreePartProps,\n TreeRootProps,\n TreeRowProps,\n TreeToggleProps,\n} from \"./tree/types.js\";\nexport { createNavigableTable, navigationFeature, navigationFeatures } from \"./navigation/index.js\";\nexport type {\n NavigableTable,\n NavigationCommands,\n NavigationFeatures,\n NavigationOptions,\n NavigationRow,\n NavigationSnapshot,\n NavigationState,\n NavigationTable,\n} from \"./navigation/types.js\";\nexport type { ExpandedState, RowData, RowSelectionState } from \"@tanstack/table-core\";\n\n/** Creates an app-scoped kernel; explicit ports override plugins, which override defaults. */\nexport function createKernel<const Options extends KernelOptions = KernelOptions>(\n options?: Options & CheckedKernelOptions<Options>,\n): Kernel<ResolvedPorts<Options>> {\n // The resolver applies the same precedence encoded by ResolvedPorts.\n return resolveKernel({ options: options ?? {}, defaults: defaultProviders }) as Kernel<\n ResolvedPorts<Options>\n >;\n}\n"],"mappings":";;;;;;AAmCA,MAAa,UAAU;;AA2BvB,SAAgB,aACd,SACgC;CAEhC,OAAO,cAAc;EAAE,SAAS,WAAW,CAAC;EAAG,UAAU;CAAiB,CAAC;AAG7E"}
|
package/dist/kernel.d.ts
CHANGED
|
@@ -34,24 +34,24 @@ export interface KernelOptions {
|
|
|
34
34
|
ports?: Partial<Ports>;
|
|
35
35
|
keys?: KeysOptions;
|
|
36
36
|
}
|
|
37
|
-
/** Reject extra override ids
|
|
37
|
+
/** Reject extra override ids; valid keys retain the inferred options without another intersection. */
|
|
38
38
|
export type CheckedKernelOptions<Options extends KernelOptions> = Options extends {
|
|
39
39
|
keys: {
|
|
40
40
|
overrides: infer Overrides;
|
|
41
41
|
};
|
|
42
|
-
} ? Options & {
|
|
42
|
+
} ? keyof Overrides extends KeyOverrideId ? Options : Options & {
|
|
43
43
|
keys: {
|
|
44
44
|
overrides: Record<Exclude<keyof Overrides, KeyOverrideId>, never>;
|
|
45
45
|
};
|
|
46
46
|
} : Options;
|
|
47
47
|
type PluginPort<Installed, Key extends keyof Ports> = Installed extends {
|
|
48
|
-
provides:
|
|
48
|
+
provides: { [Port in Key]: (...args: never[]) => infer Value extends Ports[Key]; };
|
|
49
49
|
} ? Value : never;
|
|
50
50
|
type InstalledPort<Installed, Key extends keyof Ports> = Installed extends readonly Plugin[] ? number extends Installed["length"] ? Ports[Key] : [PluginPort<Installed[number], Key>] extends [never] ? Ports[Key] : PluginPort<Installed[number], Key> : Ports[Key];
|
|
51
51
|
type ConfiguredPort<Options extends KernelOptions, Key extends keyof Ports> = "plugins" extends keyof Options ? InstalledPort<Options["plugins"], Key> : Ports[Key];
|
|
52
52
|
/** Shallow inference preserves required overrides and accounts for optional ones. */
|
|
53
53
|
export type ResolvedPorts<Options extends KernelOptions> = { readonly [Key in keyof Ports]: Options extends {
|
|
54
|
-
ports:
|
|
54
|
+
ports: { [Port in Key]: infer Value extends Ports[Key]; };
|
|
55
55
|
} ? Value : Key extends keyof NonNullable<Options["ports"]> ? NonNullable<NonNullable<Options["ports"]>[Key]> | ConfiguredPort<Options, Key> : ConfiguredPort<Options, Key>; };
|
|
56
56
|
export {};
|
|
57
57
|
//# sourceMappingURL=kernel.d.ts.map
|
package/dist/kernel.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kernel.js","names":[],"sources":["../src/kernel.ts"],"sourcesContent":["import { createCommands } from \"./commands/index.js\";\nimport type { Commands, KeyOverrideId, KeysOptions } from \"./commands/types.js\";\nimport type { ClockPort } from \"./ports/clock.js\";\nimport type { KeyboardPort } from \"./ports/keyboard.js\";\nimport type { ReporterPort } from \"./ports/reporter.js\";\nimport type { StorePort } from \"./ports/store.js\";\n\nexport interface BasePorts {\n readonly clock: ClockPort;\n readonly reporter: ReporterPort;\n}\n\nexport interface Ports extends BasePorts {\n readonly store: StorePort;\n readonly keyboard: KeyboardPort;\n}\n\n/** Base factories have no dependencies; leaf factories receive only resolved base ports. */\nexport interface PortProviders {\n clock?: () => ClockPort;\n reporter?: () => ReporterPort;\n store?: (base: BasePorts) => StorePort;\n keyboard?: (base: BasePorts) => KeyboardPort;\n}\n\nexport interface Plugin {\n readonly name: string;\n readonly provides?: PortProviders;\n setup?(kernel: Kernel): void | (() => void);\n}\n\nexport interface Kernel<P = Ports> {\n readonly ports: P;\n readonly commands: Commands;\n /** Runs setup cleanups in reverse order, then disposes commands and the keyboard port, once. */\n dispose(): void;\n}\n\nexport interface KernelOptions {\n plugins?: readonly Plugin[];\n ports?: Partial<Ports>;\n keys?: KeysOptions;\n}\n\n/** Reject extra override ids
|
|
1
|
+
{"version":3,"file":"kernel.js","names":[],"sources":["../src/kernel.ts"],"sourcesContent":["import { createCommands } from \"./commands/index.js\";\nimport type { Commands, KeyOverrideId, KeysOptions } from \"./commands/types.js\";\nimport type { ClockPort } from \"./ports/clock.js\";\nimport type { KeyboardPort } from \"./ports/keyboard.js\";\nimport type { ReporterPort } from \"./ports/reporter.js\";\nimport type { StorePort } from \"./ports/store.js\";\n\nexport interface BasePorts {\n readonly clock: ClockPort;\n readonly reporter: ReporterPort;\n}\n\nexport interface Ports extends BasePorts {\n readonly store: StorePort;\n readonly keyboard: KeyboardPort;\n}\n\n/** Base factories have no dependencies; leaf factories receive only resolved base ports. */\nexport interface PortProviders {\n clock?: () => ClockPort;\n reporter?: () => ReporterPort;\n store?: (base: BasePorts) => StorePort;\n keyboard?: (base: BasePorts) => KeyboardPort;\n}\n\nexport interface Plugin {\n readonly name: string;\n readonly provides?: PortProviders;\n setup?(kernel: Kernel): void | (() => void);\n}\n\nexport interface Kernel<P = Ports> {\n readonly ports: P;\n readonly commands: Commands;\n /** Runs setup cleanups in reverse order, then disposes commands and the keyboard port, once. */\n dispose(): void;\n}\n\nexport interface KernelOptions {\n plugins?: readonly Plugin[];\n ports?: Partial<Ports>;\n keys?: KeysOptions;\n}\n\n/** Reject extra override ids; valid keys retain the inferred options without another intersection. */\nexport type CheckedKernelOptions<Options extends KernelOptions> = Options extends {\n keys: { overrides: infer Overrides };\n}\n ? keyof Overrides extends KeyOverrideId\n ? Options\n : Options & { keys: { overrides: Record<Exclude<keyof Overrides, KeyOverrideId>, never> } }\n : Options;\n\ntype PluginPort<Installed, Key extends keyof Ports> = Installed extends {\n provides: { [Port in Key]: (...args: never[]) => infer Value extends Ports[Key] };\n}\n ? Value\n : never;\n\n// A runtime-sized array might be empty. Distribute tuple alternatives so an\n// optional plugin cannot promise a port extension that is absent at runtime.\ntype InstalledPort<Installed, Key extends keyof Ports> = Installed extends readonly Plugin[]\n ? number extends Installed[\"length\"]\n ? Ports[Key]\n : [PluginPort<Installed[number], Key>] extends [never]\n ? Ports[Key]\n : PluginPort<Installed[number], Key>\n : Ports[Key];\n\ntype ConfiguredPort<\n Options extends KernelOptions,\n Key extends keyof Ports,\n> = \"plugins\" extends keyof Options ? InstalledPort<Options[\"plugins\"], Key> : Ports[Key];\n\n/** Shallow inference preserves required overrides and accounts for optional ones. */\nexport type ResolvedPorts<Options extends KernelOptions> = {\n readonly [Key in keyof Ports]: Options extends {\n ports: { [Port in Key]: infer Value extends Ports[Key] };\n }\n ? Value\n : Key extends keyof NonNullable<Options[\"ports\"]>\n ? NonNullable<NonNullable<Options[\"ports\"]>[Key]> | ConfiguredPort<Options, Key>\n : ConfiguredPort<Options, Key>;\n};\n\nconst portNames = [\"clock\", \"reporter\", \"store\", \"keyboard\"] as const;\n\nfunction requirePort<T>(name: keyof Ports, value: T | undefined): T {\n if (value === undefined) {\n throw new Error(`Missing port \"${name}\". Supply it through a plugin or explicit ports.`);\n }\n return value;\n}\n\n/** Internal composition root shared by main and bare; imports no implementations. */\nexport function resolveKernel({\n options = {},\n defaults = {},\n}: {\n options?: KernelOptions;\n defaults?: PortProviders;\n} = {}): Kernel {\n const plugins = options.plugins ?? [];\n const overrides: Partial<Ports> = options.ports ?? {};\n const providers: PortProviders = {};\n const owners: Partial<Record<keyof Ports, string>> = {};\n\n // Validate all conflicts and missing ports before invoking any provider.\n for (const plugin of plugins) {\n for (const name of portNames) {\n if (plugin.provides?.[name] === undefined) continue;\n const owner = owners[name];\n if (owner !== undefined) {\n throw new Error(\n `Port \"${name}\" is provided by both plugins \"${owner}\" and \"${plugin.name}\".`,\n );\n }\n owners[name] = plugin.name;\n }\n Object.assign(providers, plugin.provides);\n }\n for (const name of portNames) {\n requirePort(name, overrides[name] ?? providers[name] ?? defaults[name]);\n }\n\n const base: BasePorts = {\n clock: requirePort(\"clock\", overrides.clock ?? (providers.clock ?? defaults.clock)?.()),\n reporter: requirePort(\n \"reporter\",\n overrides.reporter ?? (providers.reporter ?? defaults.reporter)?.(),\n ),\n };\n const ports: Ports = {\n ...base,\n store: requirePort(\"store\", overrides.store ?? (providers.store ?? defaults.store)?.(base)),\n keyboard: requirePort(\n \"keyboard\",\n overrides.keyboard ?? (providers.keyboard ?? defaults.keyboard)?.(base),\n ),\n };\n const cleanups: (() => void)[] = [() => ports.keyboard.dispose()];\n let commands: Commands;\n try {\n commands = createCommands({\n ports,\n ...(options.keys === undefined ? {} : { keys: options.keys }),\n });\n } catch (cause) {\n try {\n ports.keyboard.dispose();\n } catch (cleanupCause) {\n throw new AggregateError([cause, cleanupCause], \"Command setup and keyboard cleanup failed.\");\n }\n throw cause;\n }\n cleanups.push(() => commands.dispose());\n let disposed = false;\n const kernel: Kernel = {\n ports,\n commands,\n dispose() {\n if (disposed) return;\n disposed = true;\n const errors: unknown[] = [];\n for (const cleanup of cleanups.reverse()) {\n try {\n cleanup();\n } catch (cause) {\n errors.push(cause);\n }\n }\n cleanups.length = 0;\n if (errors.length > 0) {\n throw new AggregateError(errors, \"Kernel cleanup failed.\");\n }\n },\n };\n\n try {\n for (const plugin of plugins) {\n if (disposed) break;\n const cleanup = plugin.setup?.(kernel);\n if (cleanup) {\n // setup can itself dispose the kernel before returning its cleanup.\n if (disposed) cleanup();\n else cleanups.push(cleanup);\n }\n }\n } catch (cause) {\n try {\n kernel.dispose();\n } catch (cleanupCause) {\n throw new AggregateError([cause, cleanupCause], \"Kernel setup and cleanup failed.\");\n }\n throw cause;\n }\n\n return kernel;\n}\n"],"mappings":";;AAqFA,MAAM,YAAY;CAAC;CAAS;CAAY;CAAS;AAAU;AAE3D,SAAS,YAAe,MAAmB,OAAyB;CAClE,IAAI,UAAU,KAAA,GACZ,MAAM,IAAI,MAAM,iBAAiB,KAAK,iDAAiD;CAEzF,OAAO;AACT;;AAGA,SAAgB,cAAc,EAC5B,UAAU,CAAC,GACX,WAAW,CAAC,MAIV,CAAC,GAAW;CACd,MAAM,UAAU,QAAQ,WAAW,CAAC;CACpC,MAAM,YAA4B,QAAQ,SAAS,CAAC;CACpD,MAAM,YAA2B,CAAC;CAClC,MAAM,SAA+C,CAAC;CAGtD,KAAK,MAAM,UAAU,SAAS;EAC5B,KAAK,MAAM,QAAQ,WAAW;GAC5B,IAAI,OAAO,WAAW,UAAU,KAAA,GAAW;GAC3C,MAAM,QAAQ,OAAO;GACrB,IAAI,UAAU,KAAA,GACZ,MAAM,IAAI,MACR,SAAS,KAAK,iCAAiC,MAAM,SAAS,OAAO,KAAK,GAC5E;GAEF,OAAO,QAAQ,OAAO;EACxB;EACA,OAAO,OAAO,WAAW,OAAO,QAAQ;CAC1C;CACA,KAAK,MAAM,QAAQ,WACjB,YAAY,MAAM,UAAU,SAAS,UAAU,SAAS,SAAS,KAAK;CAGxE,MAAM,OAAkB;EACtB,OAAO,YAAY,SAAS,UAAU,UAAU,UAAU,SAAS,SAAS,MAAA,GAAS,CAAC;EACtF,UAAU,YACR,YACA,UAAU,aAAa,UAAU,YAAY,SAAS,SAAA,GAAY,CACpE;CACF;CACA,MAAM,QAAe;EACnB,GAAG;EACH,OAAO,YAAY,SAAS,UAAU,UAAU,UAAU,SAAS,SAAS,MAAA,GAAS,IAAI,CAAC;EAC1F,UAAU,YACR,YACA,UAAU,aAAa,UAAU,YAAY,SAAS,SAAA,GAAY,IAAI,CACxE;CACF;CACA,MAAM,WAA2B,OAAO,MAAM,SAAS,QAAQ,CAAC;CAChE,IAAI;CACJ,IAAI;EACF,WAAW,eAAe;GACxB;GACA,GAAI,QAAQ,SAAS,KAAA,IAAY,CAAC,IAAI,EAAE,MAAM,QAAQ,KAAK;EAC7D,CAAC;CACH,SAAS,OAAO;EACd,IAAI;GACF,MAAM,SAAS,QAAQ;EACzB,SAAS,cAAc;GACrB,MAAM,IAAI,eAAe,CAAC,OAAO,YAAY,GAAG,4CAA4C;EAC9F;EACA,MAAM;CACR;CACA,SAAS,WAAW,SAAS,QAAQ,CAAC;CACtC,IAAI,WAAW;CACf,MAAM,SAAiB;EACrB;EACA;EACA,UAAU;GACR,IAAI,UAAU;GACd,WAAW;GACX,MAAM,SAAoB,CAAC;GAC3B,KAAK,MAAM,WAAW,SAAS,QAAQ,GACrC,IAAI;IACF,QAAQ;GACV,SAAS,OAAO;IACd,OAAO,KAAK,KAAK;GACnB;GAEF,SAAS,SAAS;GAClB,IAAI,OAAO,SAAS,GAClB,MAAM,IAAI,eAAe,QAAQ,wBAAwB;EAE7D;CACF;CAEA,IAAI;EACF,KAAK,MAAM,UAAU,SAAS;GAC5B,IAAI,UAAU;GACd,MAAM,UAAU,OAAO,QAAQ,MAAM;GACrC,IAAI,SAAS;IAEX,IAAI,UAAU,QAAQ;SACjB,SAAS,KAAK,OAAO;GAC5B;EACF;CACF,SAAS,OAAO;EACd,IAAI;GACF,OAAO,QAAQ;EACjB,SAAS,cAAc;GACrB,MAAM,IAAI,eAAe,CAAC,OAAO,YAAY,GAAG,kCAAkC;EACpF;EACA,MAAM;CACR;CAEA,OAAO;AACT"}
|