@opentui/keymap 0.2.0 → 0.2.2

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.
@@ -0,0 +1,24 @@
1
+ import type { KeySequencePart } from "../types.js";
2
+ export type KeyModifierName = "ctrl" | "shift" | "meta" | "super" | "hyper";
3
+ export interface KeySequenceFormatPart {
4
+ stroke: KeySequencePart["stroke"];
5
+ display: string;
6
+ match?: KeySequencePart["match"];
7
+ tokenName?: string;
8
+ }
9
+ export type TokenDisplayResolver = Readonly<Record<string, string>> | ((tokenName: string, part: KeySequenceFormatPart) => string | undefined);
10
+ export interface FormatKeySequenceOptions {
11
+ tokenDisplay?: TokenDisplayResolver;
12
+ keyNameAliases?: Readonly<Record<string, string>>;
13
+ modifierAliases?: Partial<Record<KeyModifierName, string>>;
14
+ separator?: string;
15
+ }
16
+ export interface FormatCommandBindingsOptions extends FormatKeySequenceOptions {
17
+ bindingSeparator?: string;
18
+ dedupe?: boolean;
19
+ }
20
+ export interface SequenceBindingLike {
21
+ sequence: readonly KeySequenceFormatPart[];
22
+ }
23
+ export declare function formatKeySequence(parts: readonly KeySequenceFormatPart[] | undefined, options?: FormatKeySequenceOptions): string;
24
+ export declare function formatCommandBindings(bindings: readonly SequenceBindingLike[] | undefined, options?: FormatCommandBindingsOptions): string | undefined;
@@ -0,0 +1,6 @@
1
+ export { resolveBindingSections } from "./binding-sections.js";
2
+ export { commandBindings } from "./command-bindings.js";
3
+ export { formatCommandBindings, formatKeySequence } from "./formatting.js";
4
+ export type { BindingSectionConfig, BindingSectionItem, BindingSectionsConfig, BindingValue, ResolveBindingSectionsOptions, ResolvedBindingSections, } from "./binding-sections.js";
5
+ export type { FormatCommandBindingsOptions, FormatKeySequenceOptions, KeySequenceFormatPart, KeyModifierName, SequenceBindingLike, TokenDisplayResolver, } from "./formatting.js";
6
+ export type { CommandBindingMap, CommandBindingsOptions, CommandBindingsOverrideWarning, CommandBindingsError, } from "./command-bindings.js";
@@ -0,0 +1,226 @@
1
+ // @bun
2
+ // src/extras/binding-sections.ts
3
+ var hasOwn = Object.prototype.hasOwnProperty;
4
+ function isObject(value) {
5
+ return !!value && typeof value === "object" && !Array.isArray(value);
6
+ }
7
+ function isKeyLike(value) {
8
+ return typeof value === "string" || isObject(value);
9
+ }
10
+ function cloneKeyLike(key) {
11
+ if (typeof key === "string") {
12
+ return key;
13
+ }
14
+ return { ...key };
15
+ }
16
+ function invalidBindingValue(section, command, index) {
17
+ const location = index === undefined ? `"${section}.${command}"` : `"${section}.${command}" at index ${index}`;
18
+ return new Error(`Invalid binding value for ${location}: expected false, a key, a binding object, or an array of keys/binding objects`);
19
+ }
20
+ function resolveBindingItem(section, command, item, index) {
21
+ if (!isKeyLike(item)) {
22
+ throw invalidBindingValue(section, command, index);
23
+ }
24
+ if (typeof item === "string" || !("key" in item)) {
25
+ return {
26
+ key: cloneKeyLike(item),
27
+ cmd: command
28
+ };
29
+ }
30
+ const key = item.key;
31
+ if (!isKeyLike(key)) {
32
+ throw invalidBindingValue(section, command, index);
33
+ }
34
+ return {
35
+ ...item,
36
+ key: cloneKeyLike(key),
37
+ cmd: command
38
+ };
39
+ }
40
+ function resolveBindingValue(section, command, value) {
41
+ if (value === false || value === "none") {
42
+ return;
43
+ }
44
+ if (Array.isArray(value)) {
45
+ if (value.length === 0) {
46
+ return;
47
+ }
48
+ const items = value;
49
+ const bindings = new Array(items.length);
50
+ for (let index = 0;index < items.length; index += 1) {
51
+ bindings[index] = resolveBindingItem(section, command, items[index], index);
52
+ }
53
+ return bindings;
54
+ }
55
+ return [resolveBindingItem(section, command, value)];
56
+ }
57
+ function resolveBindingSections(config, options) {
58
+ const sections = {};
59
+ const lookups = new Map;
60
+ for (const section of options?.sections ?? []) {
61
+ sections[section] = [];
62
+ lookups.set(section, new Map);
63
+ }
64
+ for (const section in config) {
65
+ if (!hasOwn.call(config, section)) {
66
+ continue;
67
+ }
68
+ const sectionConfig = config[section];
69
+ if (!isObject(sectionConfig)) {
70
+ throw new Error(`Invalid binding section "${section}": expected an object`);
71
+ }
72
+ const sectionLookup = new Map;
73
+ for (const rawCommand in sectionConfig) {
74
+ if (!hasOwn.call(sectionConfig, rawCommand)) {
75
+ continue;
76
+ }
77
+ const command = rawCommand.trim();
78
+ const bindings = resolveBindingValue(section, command, sectionConfig[rawCommand]);
79
+ if (!bindings) {
80
+ sectionLookup.delete(command);
81
+ continue;
82
+ }
83
+ sectionLookup.set(command, bindings);
84
+ }
85
+ let sectionBindingCount = 0;
86
+ for (const bindings of sectionLookup.values()) {
87
+ sectionBindingCount += bindings.length;
88
+ }
89
+ const sectionBindings = new Array(sectionBindingCount);
90
+ let bindingIndex = 0;
91
+ for (const bindings of sectionLookup.values()) {
92
+ for (let index = 0;index < bindings.length; index += 1) {
93
+ sectionBindings[bindingIndex] = bindings[index];
94
+ bindingIndex += 1;
95
+ }
96
+ }
97
+ sections[section] = sectionBindings;
98
+ lookups.set(section, sectionLookup);
99
+ }
100
+ return {
101
+ sections,
102
+ get(section, cmd) {
103
+ return lookups.get(section)?.get(cmd.trim());
104
+ }
105
+ };
106
+ }
107
+ // src/extras/command-bindings.ts
108
+ function isCommandBindingKey(value) {
109
+ return typeof value === "string" || !!value && typeof value === "object" && !Array.isArray(value);
110
+ }
111
+ function commandBindings(bindings, options) {
112
+ const normalized = [];
113
+ const indexesByCommand = new Map;
114
+ for (const [command, key] of Object.entries(bindings)) {
115
+ if (!isCommandBindingKey(key)) {
116
+ const error = new Error(`Invalid command binding for "${command}": command bindings must map command strings to key strings or keystroke objects`);
117
+ options?.onError?.({
118
+ code: "invalid-command-binding",
119
+ command,
120
+ value: key,
121
+ reason: error
122
+ });
123
+ continue;
124
+ }
125
+ const normalizedCommand = command.trim();
126
+ const nextBinding = {
127
+ key: typeof key === "string" ? key : { ...key },
128
+ cmd: normalizedCommand
129
+ };
130
+ const existingIndex = indexesByCommand.get(normalizedCommand);
131
+ if (existingIndex !== undefined) {
132
+ const previousBinding = normalized[existingIndex];
133
+ options?.onWarning?.({
134
+ code: "command-binding-override",
135
+ command: normalizedCommand,
136
+ previousKey: previousBinding.key,
137
+ nextKey: nextBinding.key
138
+ });
139
+ normalized[existingIndex] = nextBinding;
140
+ continue;
141
+ }
142
+ indexesByCommand.set(normalizedCommand, normalized.length);
143
+ normalized.push(nextBinding);
144
+ }
145
+ return normalized;
146
+ }
147
+ // src/extras/formatting.ts
148
+ function formatStroke(part, options) {
149
+ if (part.tokenName) {
150
+ const tokenDisplay = options.tokenDisplay;
151
+ if (!tokenDisplay)
152
+ return part.display;
153
+ if (typeof tokenDisplay === "function")
154
+ return tokenDisplay(part.tokenName, part) ?? part.display;
155
+ return tokenDisplay[part.tokenName] ?? part.display;
156
+ }
157
+ const stroke = part.stroke;
158
+ const modifierAliases = options.modifierAliases;
159
+ let formatted = "";
160
+ let pieceCount = 0;
161
+ if (stroke.ctrl) {
162
+ formatted = modifierAliases?.ctrl ?? "ctrl";
163
+ pieceCount = 1;
164
+ }
165
+ if (stroke.shift) {
166
+ const alias = modifierAliases?.shift ?? "shift";
167
+ formatted = pieceCount === 0 ? alias : `${formatted}+${alias}`;
168
+ pieceCount += 1;
169
+ }
170
+ if (stroke.meta) {
171
+ const alias = modifierAliases?.meta ?? "meta";
172
+ formatted = pieceCount === 0 ? alias : `${formatted}+${alias}`;
173
+ pieceCount += 1;
174
+ }
175
+ if (stroke.super) {
176
+ const alias = modifierAliases?.super ?? "super";
177
+ formatted = pieceCount === 0 ? alias : `${formatted}+${alias}`;
178
+ pieceCount += 1;
179
+ }
180
+ if (stroke.hyper) {
181
+ const alias = modifierAliases?.hyper ?? "hyper";
182
+ formatted = pieceCount === 0 ? alias : `${formatted}+${alias}`;
183
+ pieceCount += 1;
184
+ }
185
+ const name = stroke.name === "return" ? "enter" : stroke.name;
186
+ const keyName = options.keyNameAliases?.[name] ?? name;
187
+ return pieceCount === 0 ? keyName : `${formatted}+${keyName}`;
188
+ }
189
+ function formatKeySequence(parts, options = {}) {
190
+ if (!parts || parts.length === 0)
191
+ return "";
192
+ const separator = options.separator ?? " ";
193
+ let formatted = formatStroke(parts[0], options);
194
+ for (let index = 1;index < parts.length; index += 1) {
195
+ formatted += separator + formatStroke(parts[index], options);
196
+ }
197
+ return formatted;
198
+ }
199
+ function formatCommandBindings(bindings, options = {}) {
200
+ if (!bindings?.length)
201
+ return;
202
+ const bindingSeparator = options.bindingSeparator ?? ", ";
203
+ const dedupe = options.dedupe !== false;
204
+ const seen = dedupe ? new Set : undefined;
205
+ let formatted = "";
206
+ let itemCount = 0;
207
+ for (const binding of bindings) {
208
+ const item = formatKeySequence(binding.sequence, options);
209
+ if (!item)
210
+ continue;
211
+ if (seen) {
212
+ if (seen.has(item))
213
+ continue;
214
+ seen.add(item);
215
+ }
216
+ formatted = itemCount === 0 ? item : `${formatted}${bindingSeparator}${item}`;
217
+ itemCount += 1;
218
+ }
219
+ return formatted;
220
+ }
221
+ export {
222
+ resolveBindingSections,
223
+ formatKeySequence,
224
+ formatCommandBindings,
225
+ commandBindings
226
+ };