@densedash/core 0.1.1 → 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 +3 -2
- 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/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Commands
|
|
2
|
+
|
|
3
|
+
Commands define actions; presets and overrides choose their keys. Each registered scope owns
|
|
4
|
+
its keyboard bindings and focus listeners until its unregister function runs.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
const unregister = kernel.commands.register("orders", [
|
|
8
|
+
{ id: "approve", label: "Approve order", run: approveOrder },
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
kernel.commands.update("orders", [
|
|
12
|
+
{ id: "approve", label: "Approve selected order", run: approveSelectedOrder },
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
unregister();
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`update(scope, commands)` replaces definitions in place, including labels, groups, `hidden`,
|
|
19
|
+
confirmation prompts, `enabled` predicates and handlers. It requires the same command ids in
|
|
20
|
+
the same order; changing membership, order or scope options requires unregistering first.
|
|
21
|
+
Updates leave the scope's bindings and sequence progress intact. A pending confirmation keeps
|
|
22
|
+
its original keyboard event, uses the latest handler and enabled predicate, and refreshes its
|
|
23
|
+
label and prompt. Removing `confirm` from the definition leaves an existing prompt pending
|
|
24
|
+
until accepted or canceled. Updating an unknown scope throws; updates after disposal do nothing.
|
|
25
|
+
|
|
26
|
+
`commands.keymap` exposes formatted keys and the evaluated `enabled` flag. Predicate failures
|
|
27
|
+
are reported as command errors and make that entry disabled. Call `update` when app state
|
|
28
|
+
changes to refresh enabled state; the React `useCommands` hook does this after each render.
|
|
29
|
+
Disabled commands release their keys to other scopes and the browser default. Duplicate keys
|
|
30
|
+
within one scope still belong to their first registered command, even when it is disabled.
|
|
31
|
+
Equal snapshots retain their array identity across focus and definition updates. Override
|
|
32
|
+
replacement still notifies subscribers so consumers of `commands.keys(id)` can refresh keys
|
|
33
|
+
for unfocused scopes.
|
|
34
|
+
|
|
35
|
+
`Footer.Legend` and `Keymap.Legend` join commands by `layer`, then `group`, and show their keys
|
|
36
|
+
side by side. `groupLabel` supplies the group's short label; a group without one uses its name.
|
|
37
|
+
Ungrouped commands keep their own labels. Footer shows the first key per command and compresses
|
|
38
|
+
three or more consecutive digits into a range. Keymap does the same for grouped commands;
|
|
39
|
+
ungrouped commands retain all their alternative keys.
|
|
40
|
+
|
|
41
|
+
Built-in primary hints cover moving, opening/closing, sorting, picking a tab and
|
|
42
|
+
stepping between tabs. Activating, jumping, paging, going to a parent, selecting/extending selection and
|
|
43
|
+
toggling expansion are secondary (`legend: "secondary"`). Both legends omit secondary hints
|
|
44
|
+
by default so a focused tree fits a narrow footer. Pass `all` to include them; `Keymap.Legend all`
|
|
45
|
+
also lists alternative keys. Disabled, hidden, shadowed and unbound commands remain omitted.
|
|
46
|
+
`tree.toggle` has no default key; bind it and use `all` to include it.
|
|
47
|
+
|
|
48
|
+
Pass `labels={{ "tree.move": "navigate", "tabs.pick": "choose a view" }}` to either legend to
|
|
49
|
+
override the default labels by group id, as with row label overrides. The command metadata is
|
|
50
|
+
the shared source for both legends; it can also be replaced through `commands.update`.
|
|
51
|
+
An instance's `keys: { label }` takes precedence and retains all its hints. An explicit `groupBy`
|
|
52
|
+
returns both the item's name and its label; it takes precedence over default labels. An explicit
|
|
53
|
+
`filter` or `groupBy` selects from all available entries, including secondary hints. `order`
|
|
54
|
+
uses group ids or layer names, independently of translated labels. `AppShell.Footer` forwards
|
|
55
|
+
these same options to its legend. None of these presentation options change bindings.
|
|
56
|
+
|
|
57
|
+
ChordHint omits disabled sequences.
|
|
58
|
+
The default preset retains component commands, confirmation controls and the generic/navigation
|
|
59
|
+
ids used by examples (`next`, `previous`, `open`, `approve`, `p-home`).
|
package/dist/bare.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BasePorts, CheckedKernelOptions, Kernel, KernelOptions, Plugin, PortProviders, Ports, ResolvedPorts } from "./kernel.js";
|
|
2
2
|
import { createCommands } from "./commands/index.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 { KeyboardBinding, KeyboardBindingOptions, KeyboardHandler, KeyboardHotkey, KeyboardHotkeyChord, KeyboardParseResult, KeyboardPendingStore, KeyboardPlatform, KeyboardPort, KeyboardScopeOptions, KeyboardTarget, PendingKeyboardSequence } from "./ports/keyboard.js";
|
|
7
7
|
import { ReporterPort } from "./ports/reporter.js";
|
|
@@ -10,5 +10,5 @@ import { Task } from "./task.js";
|
|
|
10
10
|
import { ChangeDetails, Controllable } from "./controllable.js";
|
|
11
11
|
/** No defaults: every port must be supplied by plugins or explicit ports. */
|
|
12
12
|
export declare function createKernel<const Options extends KernelOptions = KernelOptions>(options?: Options & CheckedKernelOptions<Options>): Kernel<ResolvedPorts<Options>>;
|
|
13
|
-
export { type BasePorts, type ChangeDetails, type ClockPort, type Command, type CommandContext, type CommandId, type CommandScopeOptions, type Commands, type Controllable, type Kernel, type KernelOptions, 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 PendingConfirm, type PendingKeyboardSequence, type Plugin, type PortProviders, type Ports, type Preset, type Register, type ReporterPort, type ResolvedPorts, type Store, type StorePort, type Task, type ValidatedKeyOverrides, createCommands, tradingPreset };
|
|
13
|
+
export { type BasePorts, type ChangeDetails, type ClockPort, type Command, type CommandContext, type CommandId, type CommandScopeOptions, type Commands, type Controllable, 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 PendingConfirm, type PendingKeyboardSequence, type Plugin, type PortProviders, type Ports, type Preset, type Register, type ReporterPort, type ResolvedPorts, type Store, type StorePort, type Task, type ValidatedKeyOverrides, createCommands, shiftedTreePreset, tradingPreset, treePreset };
|
|
14
14
|
//# sourceMappingURL=bare.d.ts.map
|
package/dist/bare.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { tradingPreset } from "./commands/trading-preset.js";
|
|
1
|
+
import { shiftedTreePreset, tradingPreset, treePreset } from "./commands/trading-preset.js";
|
|
2
2
|
import { resolveKernel } from "./kernel.js";
|
|
3
3
|
import { createCommands } from "./commands/index.js";
|
|
4
4
|
// src/bare.ts
|
|
@@ -6,6 +6,6 @@ import { createCommands } from "./commands/index.js";
|
|
|
6
6
|
function createKernel(options) {
|
|
7
7
|
return resolveKernel({ options: options ?? {} });
|
|
8
8
|
}
|
|
9
|
-
export { createCommands, createKernel, tradingPreset };
|
|
9
|
+
export { createCommands, createKernel, shiftedTreePreset, tradingPreset, treePreset };
|
|
10
10
|
|
|
11
11
|
//# sourceMappingURL=bare.js.map
|
|
@@ -1,48 +1,2 @@
|
|
|
1
1
|
export type Key = string;
|
|
2
|
-
/** Callbacks must be synchronous and stable for the duration of a call. */
|
|
3
|
-
export interface TreeSource<T> {
|
|
4
|
-
roots: readonly T[];
|
|
5
|
-
getKey(item: NoInfer<T>): Key;
|
|
6
|
-
getChildren?(item: NoInfer<T>): readonly NoInfer<T>[] | undefined;
|
|
7
|
-
isDisabled?(item: NoInfer<T>): boolean;
|
|
8
|
-
getTextValue?(item: NoInfer<T>): string;
|
|
9
|
-
}
|
|
10
|
-
export interface TreeRow<T> {
|
|
11
|
-
readonly key: Key;
|
|
12
|
-
readonly item: T;
|
|
13
|
-
readonly index: number;
|
|
14
|
-
readonly depth: number;
|
|
15
|
-
readonly parentKey: Key | null;
|
|
16
|
-
readonly hasChildren: boolean;
|
|
17
|
-
readonly expanded: boolean;
|
|
18
|
-
readonly posInSet: number;
|
|
19
|
-
readonly setSize: number;
|
|
20
|
-
readonly disabled: boolean;
|
|
21
|
-
readonly textValue: string;
|
|
22
|
-
}
|
|
23
|
-
export interface TreeState {
|
|
24
|
-
highlighted: Key | null;
|
|
25
|
-
selected: ReadonlySet<Key>;
|
|
26
|
-
anchor: Key | null;
|
|
27
|
-
expanded: ReadonlySet<Key>;
|
|
28
|
-
}
|
|
29
|
-
export type Move = {
|
|
30
|
-
type: "next" | "prev";
|
|
31
|
-
count?: number;
|
|
32
|
-
} | {
|
|
33
|
-
type: "first" | "last";
|
|
34
|
-
} | {
|
|
35
|
-
type: "parent" | "firstChild";
|
|
36
|
-
} | {
|
|
37
|
-
type: "page";
|
|
38
|
-
direction: 1 | -1;
|
|
39
|
-
size: number;
|
|
40
|
-
} | {
|
|
41
|
-
type: "typeahead";
|
|
42
|
-
text: string;
|
|
43
|
-
};
|
|
44
|
-
export interface MoveOptions {
|
|
45
|
-
/** Wrap next, prev, page and typeahead at the ends. Defaults to false. */
|
|
46
|
-
loop?: boolean;
|
|
47
|
-
}
|
|
48
2
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/commands/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { tradingPreset } from "./trading-preset.js";
|
|
1
|
+
import { shiftedTreePreset, tradingPreset, treePreset } from "./trading-preset.js";
|
|
2
2
|
import { Ports } from "../kernel.js";
|
|
3
|
-
import { Command, CommandContext, CommandId, CommandScopeOptions, Commands, KeyOverrideId, KeyOverrides, KeymapEntry, KeysOptions, PendingConfirm, Preset, Register, ValidatedKeyOverrides } from "./types.js";
|
|
3
|
+
import { Command, CommandContext, CommandId, CommandScopeOptions, Commands, KeyLayer, KeyOverrideId, KeyOverrides, KeymapEntry, KeysOptions, PendingConfirm, Preset, Register, ValidatedKeyOverrides } from "./types.js";
|
|
4
4
|
/** Only port interfaces enter this owner; bare and default kernels share the same layer. */
|
|
5
5
|
export declare function createCommands({ ports: { keyboard, store, reporter }, keys: options }: {
|
|
6
6
|
ports: Pick<Ports, "keyboard" | "store" | "reporter">;
|
|
7
7
|
keys?: KeysOptions;
|
|
8
8
|
}): Commands;
|
|
9
|
-
export { type Command, type CommandContext, type CommandId, type CommandScopeOptions, type Commands, type KeyOverrideId, type KeyOverrides, type KeymapEntry, type KeysOptions, type PendingConfirm, type Preset, type Register, type ValidatedKeyOverrides, tradingPreset };
|
|
9
|
+
export { type Command, type CommandContext, type CommandId, type CommandScopeOptions, type Commands, type KeyLayer, type KeyOverrideId, type KeyOverrides, type KeymapEntry, type KeysOptions, type PendingConfirm, type Preset, type Register, type ValidatedKeyOverrides, shiftedTreePreset, tradingPreset, treePreset };
|
|
10
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/commands/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { tradingPreset } from "./trading-preset.js";
|
|
1
|
+
import { shiftedTreePreset, tradingPreset, treePreset } from "./trading-preset.js";
|
|
2
2
|
import { createKeys, keyIdentity } from "./keys.js";
|
|
3
|
-
import { activeScopes, scopeHasFocus } from "./scopes.js";
|
|
3
|
+
import { activeScopes, keymapScopes, scopeHasFocus } from "./scopes.js";
|
|
4
4
|
// src/commands/index.ts
|
|
5
5
|
let nextConfirmScope = 0;
|
|
6
6
|
/** Only port interfaces enter this owner; bare and default kernels share the same layer. */
|
|
@@ -16,40 +16,95 @@ function createCommands({ ports: { keyboard, store, reporter }, keys: options =
|
|
|
16
16
|
const confirmScope = `densedash:confirm:${nextConfirmScope++}`;
|
|
17
17
|
let pending = null;
|
|
18
18
|
let disposed = false;
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
claimed.add(identity);
|
|
27
|
-
return true;
|
|
28
|
-
});
|
|
29
|
-
if (command.hidden || keys.get(command.id).length > 0 && available.length === 0) continue;
|
|
30
|
-
entries.push({
|
|
31
|
-
command: command.id,
|
|
32
|
-
label: command.label,
|
|
33
|
-
scope: scope.name,
|
|
34
|
-
...command.group === void 0 ? {} : { group: command.group },
|
|
35
|
-
keys: available.map((key) => keyboard.format(key))
|
|
19
|
+
function commandEnabled(command) {
|
|
20
|
+
try {
|
|
21
|
+
return command.enabled?.() !== false;
|
|
22
|
+
} catch (cause) {
|
|
23
|
+
reporter.error(cause, {
|
|
24
|
+
source: "command",
|
|
25
|
+
id: command.id
|
|
36
26
|
});
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function validateLayers() {
|
|
31
|
+
const owners = /* @__PURE__ */ new Map();
|
|
32
|
+
for (const scope of scopes.values()) {
|
|
33
|
+
if (!scope.options.keys) continue;
|
|
34
|
+
for (const command of scope.commands) for (const key of scope.keys.get(command.id)) {
|
|
35
|
+
const identity = keyIdentity(key);
|
|
36
|
+
const owner = owners.get(identity);
|
|
37
|
+
if (owner && owner !== scope) throw new Error(`Key layers "${owner.options.keys?.label}" (${owner.name}) and "${scope.options.keys.label}" (${scope.name}) share ${keyboard.format(key)}.`);
|
|
38
|
+
owners.set(identity, scope);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function publishKeymap(keysChanged = false) {
|
|
43
|
+
const layered = /* @__PURE__ */ new Set();
|
|
44
|
+
for (const scope of activeScopes(scopes)) {
|
|
45
|
+
if (!scope.options.keys) continue;
|
|
46
|
+
for (const owned of scope.bindings) if (commandEnabled(scope.commands[owned.commandIndex])) layered.add(owned.key);
|
|
47
|
+
}
|
|
48
|
+
for (const scope of scopes.values()) for (const owned of scope.bindings) {
|
|
49
|
+
const enabled = commandEnabled(scope.commands[owned.commandIndex]) && (scope.options.keys !== void 0 || !layered.has(owned.key));
|
|
50
|
+
if (enabled === owned.enabled) continue;
|
|
51
|
+
owned.enabled = enabled;
|
|
52
|
+
owned.binding.update({ enabled });
|
|
53
|
+
}
|
|
54
|
+
const entries = [];
|
|
55
|
+
const claimed = /* @__PURE__ */ new Map();
|
|
56
|
+
for (const scope of pending ? [] : keymapScopes(scopes)) {
|
|
57
|
+
const reserved = /* @__PURE__ */ new Set();
|
|
58
|
+
for (const command of scope.commands) {
|
|
59
|
+
const enabled = commandEnabled(command);
|
|
60
|
+
const available = scope.keys.get(command.id).filter((key) => {
|
|
61
|
+
const identity = keyIdentity(key);
|
|
62
|
+
if (reserved.has(identity)) return false;
|
|
63
|
+
reserved.add(identity);
|
|
64
|
+
if (claimed.has(identity)) return false;
|
|
65
|
+
const first = typeof key === "string" ? void 0 : key[0];
|
|
66
|
+
const prefixOwner = first === void 0 ? void 0 : claimed.get(keyIdentity(first));
|
|
67
|
+
const prefixTarget = prefixOwner?.options.keys ? void 0 : prefixOwner?.options.target;
|
|
68
|
+
if (prefixTarget && "ownerDocument" in prefixTarget && prefixTarget.ownerDocument && prefixTarget !== scope.options.target) return false;
|
|
69
|
+
if (enabled) claimed.set(identity, scope);
|
|
70
|
+
return true;
|
|
71
|
+
});
|
|
72
|
+
if (command.hidden || scope.keys.get(command.id).length > 0 && available.length === 0) continue;
|
|
73
|
+
entries.push({
|
|
74
|
+
command: command.id,
|
|
75
|
+
label: command.label,
|
|
76
|
+
scope: scope.name,
|
|
77
|
+
...scope.options.keys ? { layer: scope.options.keys.label } : {},
|
|
78
|
+
...command.group === void 0 ? {} : { group: command.group },
|
|
79
|
+
...command.groupLabel === void 0 ? {} : { groupLabel: command.groupLabel },
|
|
80
|
+
...command.legend === void 0 ? {} : { legend: command.legend },
|
|
81
|
+
keys: available.map((key) => keyboard.format(key)),
|
|
82
|
+
enabled
|
|
83
|
+
});
|
|
84
|
+
}
|
|
37
85
|
}
|
|
38
86
|
if (pending) {
|
|
39
87
|
entries.push({
|
|
40
88
|
command: "confirm",
|
|
41
89
|
label: "Confirm",
|
|
42
90
|
scope: confirmScope,
|
|
43
|
-
keys: keys.get("confirm").map((key) => keyboard.format(key))
|
|
91
|
+
keys: keys.get("confirm").map((key) => keyboard.format(key)),
|
|
92
|
+
enabled: true
|
|
44
93
|
});
|
|
45
94
|
entries.push({
|
|
46
95
|
command: "cancel",
|
|
47
96
|
label: "Cancel",
|
|
48
97
|
scope: confirmScope,
|
|
49
|
-
keys: keys.get("cancel").map((key) => keyboard.format(key))
|
|
98
|
+
keys: keys.get("cancel").map((key) => keyboard.format(key)),
|
|
99
|
+
enabled: true
|
|
50
100
|
});
|
|
51
101
|
}
|
|
52
|
-
keymap.
|
|
102
|
+
const previous = keymap.get();
|
|
103
|
+
const equal = previous.length === entries.length && entries.every((entry, index) => {
|
|
104
|
+
const before = previous[index];
|
|
105
|
+
return before !== void 0 && before.command === entry.command && before.label === entry.label && before.group === entry.group && before.groupLabel === entry.groupLabel && before.legend === entry.legend && before.scope === entry.scope && before.layer === entry.layer && before.enabled === entry.enabled && before.keys.length === entry.keys.length && before.keys.every((key, keyIndex) => key === entry.keys[keyIndex]);
|
|
106
|
+
});
|
|
107
|
+
if (keysChanged || !equal) keymap.set(entries);
|
|
53
108
|
}
|
|
54
109
|
function cancel() {
|
|
55
110
|
if (!pending) return;
|
|
@@ -63,7 +118,7 @@ function createCommands({ ports: { keyboard, store, reporter }, keys: options =
|
|
|
63
118
|
}
|
|
64
119
|
function run(command, context) {
|
|
65
120
|
try {
|
|
66
|
-
if (command
|
|
121
|
+
if (!commandEnabled(command)) return;
|
|
67
122
|
const result = reporter.trace ? reporter.trace(command.id, () => command.run(context)) : command.run(context);
|
|
68
123
|
if (result !== void 0) Promise.resolve(result).catch((cause) => reporter.error(cause, {
|
|
69
124
|
source: "command",
|
|
@@ -89,7 +144,7 @@ function createCommands({ ports: { keyboard, store, reporter }, keys: options =
|
|
|
89
144
|
run(command, context);
|
|
90
145
|
return;
|
|
91
146
|
}
|
|
92
|
-
if (command
|
|
147
|
+
if (!commandEnabled(command)) return;
|
|
93
148
|
const prompt = command.confirm.prompt;
|
|
94
149
|
const bindings = [];
|
|
95
150
|
const release = keyboard.activateScope(confirmScope, { exclusive: true });
|
|
@@ -135,10 +190,10 @@ function createCommands({ ports: { keyboard, store, reporter }, keys: options =
|
|
|
135
190
|
}
|
|
136
191
|
}
|
|
137
192
|
function bindScope(scope) {
|
|
138
|
-
for (const binding of scope.bindings) binding.dispose();
|
|
193
|
+
for (const { binding } of scope.bindings) binding.dispose();
|
|
139
194
|
scope.bindings.length = 0;
|
|
140
195
|
const owners = /* @__PURE__ */ new Map();
|
|
141
|
-
for (const command of scope.commands) for (const key of keys.get(command.id)) {
|
|
196
|
+
for (const [index, command] of scope.commands.entries()) for (const key of scope.keys.get(command.id)) {
|
|
142
197
|
const identity = keyIdentity(key);
|
|
143
198
|
const owner = owners.get(identity);
|
|
144
199
|
if (owner) {
|
|
@@ -154,13 +209,28 @@ function createCommands({ ports: { keyboard, store, reporter }, keys: options =
|
|
|
154
209
|
continue;
|
|
155
210
|
}
|
|
156
211
|
owners.set(identity, command);
|
|
157
|
-
const { parent: _parent, only: _only, ...bindingOptions } = scope.options;
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
212
|
+
const { parent: _parent, only: _only, keys: layer, ...bindingOptions } = scope.options;
|
|
213
|
+
const target = bindingOptions.target;
|
|
214
|
+
const ambientTarget = target && "ownerDocument" in target ? target.ownerDocument : target;
|
|
215
|
+
const enabled = commandEnabled(command);
|
|
216
|
+
scope.bindings.push({
|
|
217
|
+
key: identity,
|
|
218
|
+
commandIndex: index,
|
|
219
|
+
enabled,
|
|
220
|
+
binding: keyboard.bind(key, (event) => {
|
|
221
|
+
const current = scope.commands[index];
|
|
222
|
+
if (current && (!event.repeat || !current.confirm)) invokeOwned(scope, current, { event });
|
|
223
|
+
}, {
|
|
224
|
+
...bindingOptions,
|
|
225
|
+
ignoreInputs: bindingOptions.ignoreInputs ?? "editable",
|
|
226
|
+
...layer ? {
|
|
227
|
+
...ambientTarget ? { target: ambientTarget } : {},
|
|
228
|
+
ignoreInputs: "editable"
|
|
229
|
+
} : {},
|
|
230
|
+
scope: scope.name,
|
|
231
|
+
enabled
|
|
232
|
+
})
|
|
233
|
+
});
|
|
164
234
|
}
|
|
165
235
|
}
|
|
166
236
|
return {
|
|
@@ -168,17 +238,29 @@ function createCommands({ ports: { keyboard, store, reporter }, keys: options =
|
|
|
168
238
|
if (disposed) throw new Error("Commands have been disposed.");
|
|
169
239
|
if (name === "global" && scopeOptions.only) throw new Error("The global scope cannot be exclusive. Use a named layer.");
|
|
170
240
|
if (scopes.has(name) || name === confirmScope) throw new Error(`Command scope "${name}" already has an owner.`);
|
|
241
|
+
const scopeKeys = scopeOptions.keys ? createKeys({
|
|
242
|
+
keyboard,
|
|
243
|
+
reporter,
|
|
244
|
+
options: scopeOptions.keys,
|
|
245
|
+
fallback: keys.get
|
|
246
|
+
}) : keys;
|
|
171
247
|
const release = keyboard.activateScope(name, {
|
|
172
248
|
...scopeOptions.parent === void 0 ? {} : { parent: scopeOptions.parent },
|
|
173
249
|
exclusive: scopeOptions.only ?? false
|
|
174
250
|
});
|
|
175
251
|
const target = scopeOptions.target;
|
|
176
|
-
const focusDocument = target && "
|
|
177
|
-
const onFocus = () =>
|
|
252
|
+
const focusDocument = target && "document" in target ? target.document : target && "defaultView" in target ? target : target?.ownerDocument ?? globalThis.document;
|
|
253
|
+
const onFocus = () => {
|
|
254
|
+
publishKeymap();
|
|
255
|
+
queueMicrotask(() => {
|
|
256
|
+
if (scopes.get(name) === scope) publishKeymap();
|
|
257
|
+
});
|
|
258
|
+
};
|
|
178
259
|
focusDocument?.addEventListener("focusin", onFocus, true);
|
|
179
260
|
focusDocument?.addEventListener("focusout", onFocus, true);
|
|
180
261
|
const scope = {
|
|
181
262
|
name,
|
|
263
|
+
keys: scopeKeys,
|
|
182
264
|
commands: [...commands],
|
|
183
265
|
options: { ...scopeOptions },
|
|
184
266
|
release() {
|
|
@@ -191,7 +273,7 @@ function createCommands({ ports: { keyboard, store, reporter }, keys: options =
|
|
|
191
273
|
unregister() {
|
|
192
274
|
if (scopes.get(name) !== scope) return;
|
|
193
275
|
scopes.delete(name);
|
|
194
|
-
for (const binding of scope.bindings) binding.dispose();
|
|
276
|
+
for (const { binding } of scope.bindings) binding.dispose();
|
|
195
277
|
scope.release();
|
|
196
278
|
if (pending?.scope === scope) cancel();
|
|
197
279
|
else publishKeymap();
|
|
@@ -199,6 +281,7 @@ function createCommands({ ports: { keyboard, store, reporter }, keys: options =
|
|
|
199
281
|
};
|
|
200
282
|
scopes.set(name, scope);
|
|
201
283
|
try {
|
|
284
|
+
validateLayers();
|
|
202
285
|
bindScope(scope);
|
|
203
286
|
publishKeymap();
|
|
204
287
|
} catch (cause) {
|
|
@@ -207,6 +290,29 @@ function createCommands({ ports: { keyboard, store, reporter }, keys: options =
|
|
|
207
290
|
}
|
|
208
291
|
return scope.unregister;
|
|
209
292
|
},
|
|
293
|
+
update(name, commands) {
|
|
294
|
+
if (disposed) return;
|
|
295
|
+
const scope = scopes.get(name);
|
|
296
|
+
if (!scope) throw new Error(`Command scope "${name}" has no owner.`);
|
|
297
|
+
if (commands.length !== scope.commands.length || commands.some((command, index) => command.id !== scope.commands[index]?.id)) throw new Error(`Command scope "${name}" updates must preserve ordered command ids.`);
|
|
298
|
+
scope.commands = [...commands];
|
|
299
|
+
store.batch(() => {
|
|
300
|
+
if (pending?.scope === scope) {
|
|
301
|
+
const command = commands.find((candidate) => candidate.id === pending?.command.id);
|
|
302
|
+
const current = confirm.get();
|
|
303
|
+
if (command && current) {
|
|
304
|
+
pending.command = command;
|
|
305
|
+
const prompt = command.confirm?.prompt ?? current.prompt;
|
|
306
|
+
if (current.label !== command.label || current.prompt !== prompt) confirm.set({
|
|
307
|
+
...current,
|
|
308
|
+
label: command.label,
|
|
309
|
+
prompt
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
publishKeymap();
|
|
314
|
+
});
|
|
315
|
+
},
|
|
210
316
|
invoke(id, context = {}) {
|
|
211
317
|
if (disposed || pending) return;
|
|
212
318
|
for (const scope of activeScopes(scopes)) {
|
|
@@ -227,16 +333,23 @@ function createCommands({ ports: { keyboard, store, reporter }, keys: options =
|
|
|
227
333
|
get: () => confirm.get(),
|
|
228
334
|
subscribe: (listener) => confirm.subscribe(listener)
|
|
229
335
|
},
|
|
230
|
-
keys: (id) => keys.get(id),
|
|
336
|
+
keys: (id, scope) => scope === void 0 ? keys.get(id) : scopes.get(scope)?.keys.get(id) ?? [],
|
|
231
337
|
accept,
|
|
232
338
|
cancel,
|
|
233
339
|
setOverrides(overrides) {
|
|
234
340
|
if (disposed) return;
|
|
341
|
+
const previous = keys.overrides();
|
|
235
342
|
const validated = keys.replace(overrides);
|
|
343
|
+
try {
|
|
344
|
+
validateLayers();
|
|
345
|
+
} catch (cause) {
|
|
346
|
+
keys.replace(previous);
|
|
347
|
+
throw cause;
|
|
348
|
+
}
|
|
236
349
|
store.batch(() => {
|
|
237
350
|
cancel();
|
|
238
351
|
for (const scope of scopes.values()) bindScope(scope);
|
|
239
|
-
publishKeymap();
|
|
352
|
+
publishKeymap(true);
|
|
240
353
|
});
|
|
241
354
|
options.onChange?.(validated);
|
|
242
355
|
},
|
|
@@ -250,6 +363,6 @@ function createCommands({ ports: { keyboard, store, reporter }, keys: options =
|
|
|
250
363
|
}
|
|
251
364
|
};
|
|
252
365
|
}
|
|
253
|
-
export { createCommands, tradingPreset };
|
|
366
|
+
export { createCommands, shiftedTreePreset, tradingPreset, treePreset };
|
|
254
367
|
|
|
255
368
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/commands/index.ts"],"sourcesContent":["import type { Ports } from \"../kernel.js\";\nimport type { KeyboardBinding } from \"../ports/keyboard.js\";\nimport { createKeys, keyIdentity } from \"./keys.js\";\nimport { activeScopes, scopeHasFocus } from \"./scopes.js\";\nimport type { Scope } from \"./scopes.js\";\nimport type {\n Command,\n CommandContext,\n Commands,\n KeymapEntry,\n KeysOptions,\n PendingConfirm,\n} from \"./types.js\";\n\nexport type {\n Command,\n CommandContext,\n CommandId,\n Commands,\n CommandScopeOptions,\n KeymapEntry,\n KeyOverrideId,\n KeyOverrides,\n KeysOptions,\n PendingConfirm,\n Preset,\n Register,\n ValidatedKeyOverrides,\n} from \"./types.js\";\nexport { tradingPreset } from \"./trading-preset.js\";\n\nlet nextConfirmScope = 0;\n\n/** Only port interfaces enter this owner; bare and default kernels share the same layer. */\nexport function createCommands({\n ports: { keyboard, store, reporter },\n keys: options = {},\n}: {\n ports: Pick<Ports, \"keyboard\" | \"store\" | \"reporter\">;\n keys?: KeysOptions;\n}): Commands {\n const keys = createKeys({ keyboard, reporter, options });\n const keymap = store.create<readonly KeymapEntry[]>([]);\n const confirm = store.create<PendingConfirm | null>(null);\n const scopes = new Map<string, Scope>();\n const confirmScope = `densedash:confirm:${nextConfirmScope++}`;\n let pending: {\n scope: Scope;\n command: Command;\n context: CommandContext;\n release: () => void;\n } | null = null;\n let disposed = false;\n\n function publishKeymap(): void {\n const entries: KeymapEntry[] = [];\n const claimed = new Set<string>();\n for (const scope of pending ? [] : activeScopes(scopes).filter(scopeHasFocus)) {\n for (const command of scope.commands) {\n const available = keys.get(command.id).filter((key) => {\n const identity = keyIdentity(key);\n if (claimed.has(identity)) return false;\n claimed.add(identity);\n return true;\n });\n if (command.hidden || (keys.get(command.id).length > 0 && available.length === 0)) continue;\n entries.push({\n command: command.id,\n label: command.label,\n scope: scope.name,\n ...(command.group === undefined ? {} : { group: command.group }),\n keys: available.map((key) => keyboard.format(key)),\n });\n }\n }\n if (pending) {\n entries.push({\n command: \"confirm\",\n label: \"Confirm\",\n scope: confirmScope,\n keys: keys.get(\"confirm\").map((key) => keyboard.format(key)),\n });\n entries.push({\n command: \"cancel\",\n label: \"Cancel\",\n scope: confirmScope,\n keys: keys.get(\"cancel\").map((key) => keyboard.format(key)),\n });\n }\n keymap.set(entries);\n }\n\n function cancel(): void {\n if (!pending) return;\n const captured = pending;\n pending = null;\n captured.release();\n store.batch(() => {\n confirm.set(null);\n publishKeymap();\n });\n }\n\n function run(command: Command, context: CommandContext): void {\n try {\n if (command.enabled?.() === false) return;\n const result = reporter.trace\n ? reporter.trace(command.id, () => command.run(context))\n : command.run(context);\n if (result !== undefined) {\n void Promise.resolve(result).catch((cause: unknown) =>\n reporter.error(cause, { source: \"command\", id: command.id }),\n );\n }\n } catch (cause) {\n reporter.error(cause, { source: \"command\", id: command.id });\n }\n }\n\n function accept(): void {\n const captured = pending;\n if (!captured) return;\n cancel();\n if (scopes.get(captured.scope.name) === captured.scope && !disposed) {\n run(captured.command, captured.context);\n }\n }\n\n function invokeOwned(scope: Scope, command: Command, context: CommandContext): void {\n if (disposed || pending || scopes.get(scope.name) !== scope) return;\n try {\n if (!command.confirm) {\n run(command, context);\n return;\n }\n if (command.enabled?.() === false) return;\n const prompt = command.confirm.prompt;\n const bindings: KeyboardBinding[] = [];\n const release = keyboard.activateScope(confirmScope, { exclusive: true });\n const releaseConfirm = () => {\n for (const binding of bindings) binding.dispose();\n release();\n };\n try {\n for (const key of keys.get(\"confirm\")) {\n bindings.push(\n keyboard.bind(\n key,\n (event) => {\n if (!event.repeat) accept();\n },\n { scope: confirmScope, ignoreInputs: false },\n ),\n );\n }\n for (const key of keys.get(\"cancel\")) {\n bindings.push(keyboard.bind(key, cancel, { scope: confirmScope, ignoreInputs: false }));\n }\n } catch (cause) {\n releaseConfirm();\n throw cause;\n }\n pending = {\n scope,\n command,\n context,\n release: releaseConfirm,\n };\n store.batch(() => {\n confirm.set({ command: command.id, label: command.label, prompt, scope: scope.name });\n publishKeymap();\n });\n } catch (cause) {\n reporter.error(cause, { source: \"command\", id: command.id });\n }\n }\n\n function bindScope(scope: Scope): void {\n for (const binding of scope.bindings) binding.dispose();\n scope.bindings.length = 0;\n const owners = new Map<string, Command>();\n for (const command of scope.commands) {\n for (const key of keys.get(command.id)) {\n const identity = keyIdentity(key);\n const owner = owners.get(identity);\n if (owner) {\n const warning = JSON.stringify([identity, owner.id, command.id]);\n if (owner.id !== command.id && !scope.warned.has(warning)) {\n scope.warned.add(warning);\n reporter.warn(\n `Commands \"${owner.id}\" and \"${command.id}\" share ${keyboard.format(key)} in scope \"${scope.name}\"; \"${owner.id}\" keeps the key.`,\n );\n }\n continue;\n }\n owners.set(identity, command);\n const { parent: _parent, only: _only, ...bindingOptions } = scope.options;\n scope.bindings.push(\n keyboard.bind(\n key,\n (event) => {\n if (!event.repeat || !command.confirm) invokeOwned(scope, command, { event });\n },\n { ...bindingOptions, scope: scope.name },\n ),\n );\n }\n }\n }\n\n return {\n register(name, commands, scopeOptions = {}) {\n if (disposed) throw new Error(\"Commands have been disposed.\");\n if (name === \"global\" && scopeOptions.only)\n throw new Error(\"The global scope cannot be exclusive. Use a named layer.\");\n if (scopes.has(name) || name === confirmScope)\n throw new Error(`Command scope \"${name}\" already has an owner.`);\n const release = keyboard.activateScope(name, {\n ...(scopeOptions.parent === undefined ? {} : { parent: scopeOptions.parent }),\n exclusive: scopeOptions.only ?? false,\n });\n const target = scopeOptions.target;\n const focusDocument = target && \"ownerDocument\" in target ? target.ownerDocument : undefined;\n const onFocus = () => publishKeymap();\n focusDocument?.addEventListener(\"focusin\", onFocus, true);\n focusDocument?.addEventListener(\"focusout\", onFocus, true);\n const scope: Scope = {\n name,\n commands: [...commands],\n options: { ...scopeOptions },\n release() {\n focusDocument?.removeEventListener(\"focusin\", onFocus, true);\n focusDocument?.removeEventListener(\"focusout\", onFocus, true);\n release();\n },\n bindings: [],\n warned: new Set(),\n unregister() {\n if (scopes.get(name) !== scope) return;\n scopes.delete(name);\n for (const binding of scope.bindings) binding.dispose();\n scope.release();\n if (pending?.scope === scope) cancel();\n else publishKeymap();\n },\n };\n scopes.set(name, scope);\n try {\n bindScope(scope);\n publishKeymap();\n } catch (cause) {\n scope.unregister();\n throw cause;\n }\n return scope.unregister;\n },\n invoke(id, context = {}) {\n if (disposed || pending) return;\n for (const scope of activeScopes(scopes)) {\n if (context.scope !== undefined && context.scope !== scope.name) continue;\n if (!scopeHasFocus(scope)) continue;\n const command = scope.commands.find((candidate) => candidate.id === id);\n if (command) {\n invokeOwned(scope, command, context);\n return;\n }\n }\n },\n keymap: { get: () => keymap.get(), subscribe: (listener) => keymap.subscribe(listener) },\n confirm: { get: () => confirm.get(), subscribe: (listener) => confirm.subscribe(listener) },\n keys: (id) => keys.get(id),\n accept,\n cancel,\n setOverrides(overrides) {\n if (disposed) return;\n const validated = keys.replace(overrides);\n store.batch(() => {\n cancel();\n for (const scope of scopes.values()) bindScope(scope);\n publishKeymap();\n });\n options.onChange?.(validated);\n },\n dispose() {\n if (disposed) return;\n disposed = true;\n store.batch(() => {\n cancel();\n for (const scope of scopes.values()) scope.unregister();\n });\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/commands/index.ts"],"sourcesContent":["import type { Ports } from \"../kernel.js\";\nimport type { KeyboardBinding } from \"../ports/keyboard.js\";\nimport { createKeys, keyIdentity } from \"./keys.js\";\nimport { activeScopes, keymapScopes, scopeHasFocus } from \"./scopes.js\";\nimport type { Scope } from \"./scopes.js\";\nimport type {\n Command,\n CommandContext,\n Commands,\n KeymapEntry,\n KeysOptions,\n PendingConfirm,\n} from \"./types.js\";\n\nexport type {\n Command,\n CommandContext,\n CommandId,\n Commands,\n CommandScopeOptions,\n KeymapEntry,\n KeyLayer,\n KeyOverrideId,\n KeyOverrides,\n KeysOptions,\n PendingConfirm,\n Preset,\n Register,\n ValidatedKeyOverrides,\n} from \"./types.js\";\nexport { shiftedTreePreset, tradingPreset, treePreset } from \"./trading-preset.js\";\n\nlet nextConfirmScope = 0;\n\n/** Only port interfaces enter this owner; bare and default kernels share the same layer. */\nexport function createCommands({\n ports: { keyboard, store, reporter },\n keys: options = {},\n}: {\n ports: Pick<Ports, \"keyboard\" | \"store\" | \"reporter\">;\n keys?: KeysOptions;\n}): Commands {\n const keys = createKeys({ keyboard, reporter, options });\n const keymap = store.create<readonly KeymapEntry[]>([]);\n const confirm = store.create<PendingConfirm | null>(null);\n const scopes = new Map<string, Scope>();\n const confirmScope = `densedash:confirm:${nextConfirmScope++}`;\n let pending: {\n scope: Scope;\n command: Command;\n context: CommandContext;\n release: () => void;\n } | null = null;\n let disposed = false;\n\n function commandEnabled(command: Command): boolean {\n try {\n return command.enabled?.() !== false;\n } catch (cause) {\n reporter.error(cause, { source: \"command\", id: command.id });\n return false;\n }\n }\n\n function validateLayers(): void {\n const owners = new Map<string, Scope>();\n for (const scope of scopes.values()) {\n if (!scope.options.keys) continue;\n for (const command of scope.commands) {\n for (const key of scope.keys.get(command.id)) {\n const identity = keyIdentity(key);\n const owner = owners.get(identity);\n if (owner && owner !== scope) {\n throw new Error(\n `Key layers \"${owner.options.keys?.label}\" (${owner.name}) and \"${scope.options.keys.label}\" (${scope.name}) share ${keyboard.format(key)}.`,\n );\n }\n owners.set(identity, scope);\n }\n }\n }\n }\n\n function publishKeymap(keysChanged = false): void {\n // Reserve layer keys before element-target handlers can consume them on the way up.\n // Only the keyboard port matches events and filters editable targets.\n const layered = new Set<string>();\n for (const scope of activeScopes(scopes)) {\n if (!scope.options.keys) continue;\n for (const owned of scope.bindings) {\n if (commandEnabled(scope.commands[owned.commandIndex]!)) layered.add(owned.key);\n }\n }\n for (const scope of scopes.values()) {\n for (const owned of scope.bindings) {\n const enabled =\n commandEnabled(scope.commands[owned.commandIndex]!) &&\n (scope.options.keys !== undefined || !layered.has(owned.key));\n if (enabled === owned.enabled) continue;\n owned.enabled = enabled;\n owned.binding.update({ enabled });\n }\n }\n const entries: KeymapEntry[] = [];\n const claimed = new Map<string, Scope>();\n for (const scope of pending ? [] : keymapScopes(scopes)) {\n // Duplicate keys keep their first owner within a scope, even when disabled.\n const reserved = new Set<string>();\n for (const command of scope.commands) {\n const enabled = commandEnabled(command);\n const available = scope.keys.get(command.id).filter((key) => {\n const identity = keyIdentity(key);\n if (reserved.has(identity)) return false;\n reserved.add(identity);\n if (claimed.has(identity)) return false;\n const first = typeof key === \"string\" ? undefined : key[0];\n const prefixOwner = first === undefined ? undefined : claimed.get(keyIdentity(first));\n const prefixTarget = prefixOwner?.options.keys ? undefined : prefixOwner?.options.target;\n // A widget chord stops the sequence prefix before it reaches an outer target.\n if (\n prefixTarget &&\n \"ownerDocument\" in prefixTarget &&\n prefixTarget.ownerDocument &&\n prefixTarget !== scope.options.target\n )\n return false;\n if (enabled) claimed.set(identity, scope);\n return true;\n });\n if (command.hidden || (scope.keys.get(command.id).length > 0 && available.length === 0))\n continue;\n entries.push({\n command: command.id,\n label: command.label,\n scope: scope.name,\n ...(scope.options.keys ? { layer: scope.options.keys.label } : {}),\n ...(command.group === undefined ? {} : { group: command.group }),\n ...(command.groupLabel === undefined ? {} : { groupLabel: command.groupLabel }),\n ...(command.legend === undefined ? {} : { legend: command.legend }),\n keys: available.map((key) => keyboard.format(key)),\n enabled,\n });\n }\n }\n if (pending) {\n entries.push({\n command: \"confirm\",\n label: \"Confirm\",\n scope: confirmScope,\n keys: keys.get(\"confirm\").map((key) => keyboard.format(key)),\n enabled: true,\n });\n entries.push({\n command: \"cancel\",\n label: \"Cancel\",\n scope: confirmScope,\n keys: keys.get(\"cancel\").map((key) => keyboard.format(key)),\n enabled: true,\n });\n }\n const previous = keymap.get();\n const equal =\n previous.length === entries.length &&\n entries.every((entry, index) => {\n const before = previous[index];\n return (\n before !== undefined &&\n before.command === entry.command &&\n before.label === entry.label &&\n before.group === entry.group &&\n before.groupLabel === entry.groupLabel &&\n before.legend === entry.legend &&\n before.scope === entry.scope &&\n before.layer === entry.layer &&\n before.enabled === entry.enabled &&\n before.keys.length === entry.keys.length &&\n before.keys.every((key, keyIndex) => key === entry.keys[keyIndex])\n );\n });\n // keys() consumers also use this store to follow overrides outside focused scopes.\n if (keysChanged || !equal) keymap.set(entries);\n }\n\n function cancel(): void {\n if (!pending) return;\n const captured = pending;\n pending = null;\n captured.release();\n store.batch(() => {\n confirm.set(null);\n publishKeymap();\n });\n }\n\n function run(command: Command, context: CommandContext): void {\n try {\n if (!commandEnabled(command)) return;\n const result = reporter.trace\n ? reporter.trace(command.id, () => command.run(context))\n : command.run(context);\n if (result !== undefined) {\n void Promise.resolve(result).catch((cause: unknown) =>\n reporter.error(cause, { source: \"command\", id: command.id }),\n );\n }\n } catch (cause) {\n reporter.error(cause, { source: \"command\", id: command.id });\n }\n }\n\n function accept(): void {\n const captured = pending;\n if (!captured) return;\n cancel();\n if (scopes.get(captured.scope.name) === captured.scope && !disposed) {\n run(captured.command, captured.context);\n }\n }\n\n function invokeOwned(scope: Scope, command: Command, context: CommandContext): void {\n if (disposed || pending || scopes.get(scope.name) !== scope) return;\n try {\n if (!command.confirm) {\n run(command, context);\n return;\n }\n if (!commandEnabled(command)) return;\n const prompt = command.confirm.prompt;\n const bindings: KeyboardBinding[] = [];\n const release = keyboard.activateScope(confirmScope, { exclusive: true });\n const releaseConfirm = () => {\n for (const binding of bindings) binding.dispose();\n release();\n };\n try {\n for (const key of keys.get(\"confirm\")) {\n bindings.push(\n keyboard.bind(\n key,\n (event) => {\n if (!event.repeat) accept();\n },\n { scope: confirmScope, ignoreInputs: false },\n ),\n );\n }\n for (const key of keys.get(\"cancel\")) {\n bindings.push(keyboard.bind(key, cancel, { scope: confirmScope, ignoreInputs: false }));\n }\n } catch (cause) {\n releaseConfirm();\n throw cause;\n }\n pending = {\n scope,\n command,\n context,\n release: releaseConfirm,\n };\n store.batch(() => {\n confirm.set({ command: command.id, label: command.label, prompt, scope: scope.name });\n publishKeymap();\n });\n } catch (cause) {\n reporter.error(cause, { source: \"command\", id: command.id });\n }\n }\n\n function bindScope(scope: Scope): void {\n for (const { binding } of scope.bindings) binding.dispose();\n scope.bindings.length = 0;\n const owners = new Map<string, Command>();\n for (const [index, command] of scope.commands.entries()) {\n for (const key of scope.keys.get(command.id)) {\n const identity = keyIdentity(key);\n const owner = owners.get(identity);\n if (owner) {\n const warning = JSON.stringify([identity, owner.id, command.id]);\n if (owner.id !== command.id && !scope.warned.has(warning)) {\n scope.warned.add(warning);\n reporter.warn(\n `Commands \"${owner.id}\" and \"${command.id}\" share ${keyboard.format(key)} in scope \"${scope.name}\"; \"${owner.id}\" keeps the key.`,\n );\n }\n continue;\n }\n owners.set(identity, command);\n const { parent: _parent, only: _only, keys: layer, ...bindingOptions } = scope.options;\n const target = bindingOptions.target;\n const ambientTarget = target && \"ownerDocument\" in target ? target.ownerDocument : target;\n const enabled = commandEnabled(command);\n scope.bindings.push({\n key: identity,\n commandIndex: index,\n enabled,\n binding: keyboard.bind(\n key,\n (event) => {\n const current = scope.commands[index];\n if (current && (!event.repeat || !current.confirm))\n invokeOwned(scope, current, { event });\n },\n {\n ...bindingOptions,\n // Commands own their declared keys across non-editable widgets. The port's\n // automatic ARIA filtering otherwise reserves every printable tree key.\n ignoreInputs: bindingOptions.ignoreInputs ?? \"editable\",\n ...(layer\n ? {\n ...(ambientTarget ? { target: ambientTarget } : {}),\n ignoreInputs: \"editable\" as const,\n }\n : {}),\n scope: scope.name,\n enabled,\n },\n ),\n });\n }\n }\n }\n\n return {\n register(name, commands, scopeOptions = {}) {\n if (disposed) throw new Error(\"Commands have been disposed.\");\n if (name === \"global\" && scopeOptions.only)\n throw new Error(\"The global scope cannot be exclusive. Use a named layer.\");\n if (scopes.has(name) || name === confirmScope)\n throw new Error(`Command scope \"${name}\" already has an owner.`);\n const scopeKeys = scopeOptions.keys\n ? createKeys({ keyboard, reporter, options: scopeOptions.keys, fallback: keys.get })\n : keys;\n const release = keyboard.activateScope(name, {\n ...(scopeOptions.parent === undefined ? {} : { parent: scopeOptions.parent }),\n exclusive: scopeOptions.only ?? false,\n });\n const target = scopeOptions.target;\n const focusDocument =\n target && \"document\" in target\n ? target.document\n : target && \"defaultView\" in target\n ? target\n : (target?.ownerDocument ?? globalThis.document);\n const onFocus = () => {\n publishKeymap();\n // During focusout activeElement still names the departing control.\n queueMicrotask(() => {\n if (scopes.get(name) === scope) publishKeymap();\n });\n };\n focusDocument?.addEventListener(\"focusin\", onFocus, true);\n focusDocument?.addEventListener(\"focusout\", onFocus, true);\n const scope: Scope = {\n name,\n keys: scopeKeys,\n commands: [...commands],\n options: { ...scopeOptions },\n release() {\n focusDocument?.removeEventListener(\"focusin\", onFocus, true);\n focusDocument?.removeEventListener(\"focusout\", onFocus, true);\n release();\n },\n bindings: [],\n warned: new Set(),\n unregister() {\n if (scopes.get(name) !== scope) return;\n scopes.delete(name);\n for (const { binding } of scope.bindings) binding.dispose();\n scope.release();\n if (pending?.scope === scope) cancel();\n else publishKeymap();\n },\n };\n scopes.set(name, scope);\n try {\n validateLayers();\n bindScope(scope);\n publishKeymap();\n } catch (cause) {\n scope.unregister();\n throw cause;\n }\n return scope.unregister;\n },\n update(name, commands) {\n if (disposed) return;\n const scope = scopes.get(name);\n if (!scope) throw new Error(`Command scope \"${name}\" has no owner.`);\n if (\n commands.length !== scope.commands.length ||\n commands.some((command, index) => command.id !== scope.commands[index]?.id)\n ) {\n throw new Error(`Command scope \"${name}\" updates must preserve ordered command ids.`);\n }\n scope.commands = [...commands];\n store.batch(() => {\n if (pending?.scope === scope) {\n const command = commands.find((candidate) => candidate.id === pending?.command.id);\n const current = confirm.get();\n if (command && current) {\n pending.command = command;\n const prompt = command.confirm?.prompt ?? current.prompt;\n if (current.label !== command.label || current.prompt !== prompt) {\n confirm.set({ ...current, label: command.label, prompt });\n }\n }\n }\n publishKeymap();\n });\n },\n invoke(id, context = {}) {\n if (disposed || pending) return;\n for (const scope of activeScopes(scopes)) {\n if (context.scope !== undefined && context.scope !== scope.name) continue;\n if (!scopeHasFocus(scope)) continue;\n const command = scope.commands.find((candidate) => candidate.id === id);\n if (command) {\n invokeOwned(scope, command, context);\n return;\n }\n }\n },\n keymap: { get: () => keymap.get(), subscribe: (listener) => keymap.subscribe(listener) },\n confirm: { get: () => confirm.get(), subscribe: (listener) => confirm.subscribe(listener) },\n keys: (id, scope) =>\n scope === undefined ? keys.get(id) : (scopes.get(scope)?.keys.get(id) ?? []),\n accept,\n cancel,\n setOverrides(overrides) {\n if (disposed) return;\n const previous = keys.overrides();\n const validated = keys.replace(overrides);\n try {\n validateLayers();\n } catch (cause) {\n keys.replace(previous);\n throw cause;\n }\n store.batch(() => {\n cancel();\n for (const scope of scopes.values()) bindScope(scope);\n publishKeymap(true);\n });\n options.onChange?.(validated);\n },\n dispose() {\n if (disposed) return;\n disposed = true;\n store.batch(() => {\n cancel();\n for (const scope of scopes.values()) scope.unregister();\n });\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/commands/keys.js
CHANGED
|
@@ -4,13 +4,13 @@ import { tradingPreset } from "./trading-preset.js";
|
|
|
4
4
|
function keyIdentity(hotkey) {
|
|
5
5
|
return JSON.stringify(typeof hotkey === "string" ? [hotkey] : hotkey);
|
|
6
6
|
}
|
|
7
|
-
function createKeys({ keyboard, reporter, options }) {
|
|
7
|
+
function createKeys({ keyboard, reporter, options, fallback }) {
|
|
8
8
|
const preset = /* @__PURE__ */ new Map();
|
|
9
9
|
const configured = options.preset ? {
|
|
10
10
|
confirm: tradingPreset.confirm,
|
|
11
11
|
cancel: tradingPreset.cancel,
|
|
12
12
|
...options.preset
|
|
13
|
-
} : tradingPreset;
|
|
13
|
+
} : fallback ? {} : tradingPreset;
|
|
14
14
|
for (const [id, keys] of Object.entries(configured)) {
|
|
15
15
|
const normalized = [];
|
|
16
16
|
for (const key of keys) {
|
|
@@ -43,7 +43,8 @@ function createKeys({ keyboard, reporter, options }) {
|
|
|
43
43
|
}
|
|
44
44
|
replace(options.overrides ?? {});
|
|
45
45
|
return {
|
|
46
|
-
get: (id) => overrides.get(id) ?? preset.get(id) ?? [],
|
|
46
|
+
get: (id) => overrides.get(id) ?? preset.get(id) ?? (options.preset ? void 0 : fallback?.(id)) ?? [],
|
|
47
|
+
overrides: () => Object.fromEntries(overrides),
|
|
47
48
|
replace
|
|
48
49
|
};
|
|
49
50
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keys.js","names":[],"sources":["../../src/commands/keys.ts"],"sourcesContent":["import type { KeyboardHotkey, KeyboardPort } from \"../ports/keyboard.js\";\nimport type { ReporterPort } from \"../ports/reporter.js\";\nimport { tradingPreset } from \"./trading-preset.js\";\nimport type { KeyOverrides, KeysOptions, Preset, ValidatedKeyOverrides } from \"./types.js\";\n\n/** parse() supplies canonical identities for aliases as well as validating saved keys. */\nexport function keyIdentity(hotkey: KeyboardHotkey): string {\n return JSON.stringify(typeof hotkey === \"string\" ? [hotkey] : hotkey);\n}\n\nexport function createKeys({\n keyboard,\n reporter,\n options,\n}: {\n keyboard: KeyboardPort;\n reporter: ReporterPort;\n options: KeysOptions;\n}) {\n const preset = new Map<string, readonly KeyboardHotkey[]>();\n const configured: Preset = options.preset\n ? { confirm: tradingPreset.confirm, cancel: tradingPreset.cancel, ...options.preset }\n : tradingPreset;\n for (const [id, keys] of Object.entries(configured)) {\n const normalized: KeyboardHotkey[] = [];\n for (const key of keys) {\n const parsed = keyboard.parse(key);\n if (!parsed.ok) throw new Error(`Invalid preset key for \"${id}\": ${parsed.reason}`);\n normalized.push(parsed.hotkey);\n }\n preset.set(id, normalized);\n }\n let overrides = new Map<string, readonly KeyboardHotkey[]>();\n\n function replace(input: KeyOverrides): ValidatedKeyOverrides {\n const next = new Map<string, readonly KeyboardHotkey[]>();\n for (const [id, value] of Object.entries(input)) {\n const normalized: KeyboardHotkey[] = [];\n let reason: string | undefined;\n if (!Array.isArray(value)) {\n reason = \"Expected an array of hotkeys.\";\n } else {\n for (const key of value) {\n const parsed = keyboard.parse(key);\n if (!parsed.ok) {\n reason = parsed.reason;\n break;\n }\n normalized.push(parsed.hotkey);\n }\n }\n if (reason !== undefined) {\n reporter.warn(`Invalid key override for \"${id}\": ${reason} Keeping preset keys.`);\n } else {\n next.set(id, normalized);\n }\n }\n overrides = next;\n return Object.fromEntries(next);\n }\n\n replace(options.overrides ?? {});\n return {\n get: (id: string): readonly KeyboardHotkey[]
|
|
1
|
+
{"version":3,"file":"keys.js","names":[],"sources":["../../src/commands/keys.ts"],"sourcesContent":["import type { KeyboardHotkey, KeyboardPort } from \"../ports/keyboard.js\";\nimport type { ReporterPort } from \"../ports/reporter.js\";\nimport { tradingPreset } from \"./trading-preset.js\";\nimport type { KeyOverrides, KeysOptions, Preset, ValidatedKeyOverrides } from \"./types.js\";\n\n/** parse() supplies canonical identities for aliases as well as validating saved keys. */\nexport function keyIdentity(hotkey: KeyboardHotkey): string {\n return JSON.stringify(typeof hotkey === \"string\" ? [hotkey] : hotkey);\n}\n\nexport function createKeys({\n keyboard,\n reporter,\n options,\n fallback,\n}: {\n keyboard: KeyboardPort;\n reporter: ReporterPort;\n options: KeysOptions;\n fallback?: (id: string) => readonly KeyboardHotkey[];\n}) {\n const preset = new Map<string, readonly KeyboardHotkey[]>();\n const configured: Preset = options.preset\n ? { confirm: tradingPreset.confirm, cancel: tradingPreset.cancel, ...options.preset }\n : fallback\n ? {}\n : tradingPreset;\n for (const [id, keys] of Object.entries(configured)) {\n const normalized: KeyboardHotkey[] = [];\n for (const key of keys) {\n const parsed = keyboard.parse(key);\n if (!parsed.ok) throw new Error(`Invalid preset key for \"${id}\": ${parsed.reason}`);\n normalized.push(parsed.hotkey);\n }\n preset.set(id, normalized);\n }\n let overrides = new Map<string, readonly KeyboardHotkey[]>();\n\n function replace(input: KeyOverrides): ValidatedKeyOverrides {\n const next = new Map<string, readonly KeyboardHotkey[]>();\n for (const [id, value] of Object.entries(input)) {\n const normalized: KeyboardHotkey[] = [];\n let reason: string | undefined;\n if (!Array.isArray(value)) {\n reason = \"Expected an array of hotkeys.\";\n } else {\n for (const key of value) {\n const parsed = keyboard.parse(key);\n if (!parsed.ok) {\n reason = parsed.reason;\n break;\n }\n normalized.push(parsed.hotkey);\n }\n }\n if (reason !== undefined) {\n reporter.warn(`Invalid key override for \"${id}\": ${reason} Keeping preset keys.`);\n } else {\n next.set(id, normalized);\n }\n }\n overrides = next;\n return Object.fromEntries(next);\n }\n\n replace(options.overrides ?? {});\n return {\n get: (id: string): readonly KeyboardHotkey[] =>\n overrides.get(id) ?? preset.get(id) ?? (options.preset ? undefined : fallback?.(id)) ?? [],\n overrides: (): ValidatedKeyOverrides => Object.fromEntries(overrides),\n replace,\n };\n}\n"],"mappings":";;;AAMA,SAAgB,YAAY,QAAgC;CAC1D,OAAO,KAAK,UAAU,OAAO,WAAW,WAAW,CAAC,MAAM,IAAI,MAAM;AACtE;AAEA,SAAgB,WAAW,EACzB,UACA,UACA,SACA,YAMC;CACD,MAAM,yBAAS,IAAI,IAAuC;CAC1D,MAAM,aAAqB,QAAQ,SAC/B;EAAE,SAAS,cAAc;EAAS,QAAQ,cAAc;EAAQ,GAAG,QAAQ;CAAO,IAClF,WACE,CAAC,IACD;CACN,KAAK,MAAM,CAAC,IAAI,SAAS,OAAO,QAAQ,UAAU,GAAG;EACnD,MAAM,aAA+B,CAAC;EACtC,KAAK,MAAM,OAAO,MAAM;GACtB,MAAM,SAAS,SAAS,MAAM,GAAG;GACjC,IAAI,CAAC,OAAO,IAAI,MAAM,IAAI,MAAM,2BAA2B,GAAG,KAAK,OAAO,QAAQ;GAClF,WAAW,KAAK,OAAO,MAAM;EAC/B;EACA,OAAO,IAAI,IAAI,UAAU;CAC3B;CACA,IAAI,4BAAY,IAAI,IAAuC;CAE3D,SAAS,QAAQ,OAA4C;EAC3D,MAAM,uBAAO,IAAI,IAAuC;EACxD,KAAK,MAAM,CAAC,IAAI,UAAU,OAAO,QAAQ,KAAK,GAAG;GAC/C,MAAM,aAA+B,CAAC;GACtC,IAAI;GACJ,IAAI,CAAC,MAAM,QAAQ,KAAK,GACtB,SAAS;QAET,KAAK,MAAM,OAAO,OAAO;IACvB,MAAM,SAAS,SAAS,MAAM,GAAG;IACjC,IAAI,CAAC,OAAO,IAAI;KACd,SAAS,OAAO;KAChB;IACF;IACA,WAAW,KAAK,OAAO,MAAM;GAC/B;GAEF,IAAI,WAAW,KAAA,GACb,SAAS,KAAK,6BAA6B,GAAG,KAAK,OAAO,sBAAsB;QAEhF,KAAK,IAAI,IAAI,UAAU;EAE3B;EACA,YAAY;EACZ,OAAO,OAAO,YAAY,IAAI;CAChC;CAEA,QAAQ,QAAQ,aAAa,CAAC,CAAC;CAC/B,OAAO;EACL,MAAM,OACJ,UAAU,IAAI,EAAE,KAAK,OAAO,IAAI,EAAE,MAAM,QAAQ,SAAS,KAAA,IAAY,WAAW,EAAE,MAAM,CAAC;EAC3F,iBAAwC,OAAO,YAAY,SAAS;EACpE;CACF;AACF"}
|