@mdxui/terminal 2.0.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 +571 -0
- package/dist/ansi-css-Sk5mWtdK.d.ts +119 -0
- package/dist/ansi-css-V6JIHGsM.d.ts +119 -0
- package/dist/ansi-css-_3eSEU9d.d.ts +119 -0
- package/dist/chunk-3EFDH7PK.js +5235 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-3X5IR6WE.js +884 -0
- package/dist/chunk-4FV5ZDCE.js +5236 -0
- package/dist/chunk-4OVMSF2J.js +243 -0
- package/dist/chunk-63FEETIS.js +4048 -0
- package/dist/chunk-B43KP7XJ.js +884 -0
- package/dist/chunk-BMTJXWUV.js +655 -0
- package/dist/chunk-C3SVH4N7.js +882 -0
- package/dist/chunk-EVWR7Y47.js +874 -0
- package/dist/chunk-F6A5VWUC.js +1285 -0
- package/dist/chunk-FD7KW7GE.js +882 -0
- package/dist/chunk-GBQ6UD6I.js +655 -0
- package/dist/chunk-GMDD3M6U.js +5227 -0
- package/dist/chunk-JBHRXOXM.js +1058 -0
- package/dist/chunk-JFOO3EYO.js +1182 -0
- package/dist/chunk-JQ5H3WXL.js +1291 -0
- package/dist/chunk-JQD5NASE.js +234 -0
- package/dist/chunk-KRHJP5R7.js +592 -0
- package/dist/chunk-KWF6WVJE.js +962 -0
- package/dist/chunk-LHYQVN3H.js +1038 -0
- package/dist/chunk-M3TLQLGC.js +1032 -0
- package/dist/chunk-MVW4Q5OP.js +240 -0
- package/dist/chunk-NXCZSWLU.js +1294 -0
- package/dist/chunk-O25TNRO6.js +607 -0
- package/dist/chunk-PNECDA2I.js +884 -0
- package/dist/chunk-QIHWRLJR.js +962 -0
- package/dist/chunk-QW5YMQ7K.js +882 -0
- package/dist/chunk-R5U7XKVJ.js +16 -0
- package/dist/chunk-RP2MVQLR.js +962 -0
- package/dist/chunk-TP6RXGXA.js +1087 -0
- package/dist/chunk-TQQSTITZ.js +655 -0
- package/dist/chunk-X24GWXQV.js +1281 -0
- package/dist/components/index.d.ts +802 -0
- package/dist/components/index.js +149 -0
- package/dist/data/index.d.ts +2554 -0
- package/dist/data/index.js +51 -0
- package/dist/forms/index.d.ts +1596 -0
- package/dist/forms/index.js +464 -0
- package/dist/index-CQRFZntR.d.ts +867 -0
- package/dist/index.d.ts +579 -0
- package/dist/index.js +786 -0
- package/dist/interactive-D0JkWosD.d.ts +217 -0
- package/dist/keyboard/index.d.ts +2 -0
- package/dist/keyboard/index.js +43 -0
- package/dist/renderers/index.d.ts +546 -0
- package/dist/renderers/index.js +2157 -0
- package/dist/storybook/index.d.ts +396 -0
- package/dist/storybook/index.js +641 -0
- package/dist/theme/index.d.ts +1339 -0
- package/dist/theme/index.js +123 -0
- package/dist/types-Bxu5PAgA.d.ts +710 -0
- package/dist/types-CIlop5Ji.d.ts +701 -0
- package/dist/types-Ca8p_p5X.d.ts +710 -0
- package/package.json +90 -0
- package/src/__tests__/components/data/card.test.ts +458 -0
- package/src/__tests__/components/data/list.test.ts +473 -0
- package/src/__tests__/components/data/metrics.test.ts +541 -0
- package/src/__tests__/components/data/table.test.ts +448 -0
- package/src/__tests__/components/input/field.test.ts +555 -0
- package/src/__tests__/components/input/form.test.ts +870 -0
- package/src/__tests__/components/input/search.test.ts +1238 -0
- package/src/__tests__/components/input/select.test.ts +658 -0
- package/src/__tests__/components/navigation/breadcrumb.test.ts +923 -0
- package/src/__tests__/components/navigation/command-palette.test.ts +1095 -0
- package/src/__tests__/components/navigation/sidebar.test.ts +1018 -0
- package/src/__tests__/components/navigation/tabs.test.ts +995 -0
- package/src/__tests__/components.test.tsx +1197 -0
- package/src/__tests__/core/compiler.test.ts +986 -0
- package/src/__tests__/core/parser.test.ts +785 -0
- package/src/__tests__/core/tier-switcher.test.ts +1103 -0
- package/src/__tests__/core/types.test.ts +1398 -0
- package/src/__tests__/data/collections.test.ts +1337 -0
- package/src/__tests__/data/db.test.ts +1265 -0
- package/src/__tests__/data/reactive.test.ts +1010 -0
- package/src/__tests__/data/sync.test.ts +1614 -0
- package/src/__tests__/errors.test.ts +660 -0
- package/src/__tests__/forms/integration.test.ts +444 -0
- package/src/__tests__/integration.test.ts +905 -0
- package/src/__tests__/keyboard.test.ts +1791 -0
- package/src/__tests__/renderer.test.ts +489 -0
- package/src/__tests__/renderers/ansi-css.test.ts +948 -0
- package/src/__tests__/renderers/ansi.test.ts +1366 -0
- package/src/__tests__/renderers/ascii.test.ts +1360 -0
- package/src/__tests__/renderers/interactive.test.ts +2353 -0
- package/src/__tests__/renderers/markdown.test.ts +1483 -0
- package/src/__tests__/renderers/text.test.ts +1369 -0
- package/src/__tests__/renderers/unicode.test.ts +1307 -0
- package/src/__tests__/theme.test.ts +639 -0
- package/src/__tests__/utils/assertions.ts +685 -0
- package/src/__tests__/utils/index.ts +115 -0
- package/src/__tests__/utils/test-renderer.ts +381 -0
- package/src/__tests__/utils/utils.test.ts +560 -0
- package/src/components/containers/card.ts +56 -0
- package/src/components/containers/dialog.ts +53 -0
- package/src/components/containers/index.ts +9 -0
- package/src/components/containers/panel.ts +59 -0
- package/src/components/feedback/badge.ts +40 -0
- package/src/components/feedback/index.ts +8 -0
- package/src/components/feedback/spinner.ts +23 -0
- package/src/components/helpers.ts +81 -0
- package/src/components/index.ts +153 -0
- package/src/components/layout/breadcrumb.ts +31 -0
- package/src/components/layout/index.ts +10 -0
- package/src/components/layout/list.ts +29 -0
- package/src/components/layout/sidebar.ts +79 -0
- package/src/components/layout/table.ts +62 -0
- package/src/components/primitives/box.ts +95 -0
- package/src/components/primitives/button.ts +54 -0
- package/src/components/primitives/index.ts +11 -0
- package/src/components/primitives/input.ts +88 -0
- package/src/components/primitives/select.ts +97 -0
- package/src/components/primitives/text.ts +60 -0
- package/src/components/render.ts +155 -0
- package/src/components/templates/app.ts +43 -0
- package/src/components/templates/index.ts +8 -0
- package/src/components/templates/site.ts +54 -0
- package/src/components/types.ts +777 -0
- package/src/core/compiler.ts +718 -0
- package/src/core/parser.ts +127 -0
- package/src/core/tier-switcher.ts +607 -0
- package/src/core/types.ts +672 -0
- package/src/data/collection.ts +316 -0
- package/src/data/collections.ts +50 -0
- package/src/data/context.tsx +174 -0
- package/src/data/db.ts +127 -0
- package/src/data/hooks.ts +532 -0
- package/src/data/index.ts +138 -0
- package/src/data/reactive.ts +1225 -0
- package/src/data/saas-collections.ts +375 -0
- package/src/data/sync.ts +1213 -0
- package/src/data/types.ts +660 -0
- package/src/forms/converters.ts +512 -0
- package/src/forms/index.ts +133 -0
- package/src/forms/schemas.ts +403 -0
- package/src/forms/types.ts +476 -0
- package/src/index.ts +542 -0
- package/src/keyboard/focus.ts +748 -0
- package/src/keyboard/index.ts +96 -0
- package/src/keyboard/integration.ts +371 -0
- package/src/keyboard/manager.ts +377 -0
- package/src/keyboard/presets.ts +90 -0
- package/src/renderers/ansi-css.ts +576 -0
- package/src/renderers/ansi.ts +802 -0
- package/src/renderers/ascii.ts +680 -0
- package/src/renderers/breadcrumb.ts +480 -0
- package/src/renderers/command-palette.ts +802 -0
- package/src/renderers/components/field.ts +210 -0
- package/src/renderers/components/form.ts +327 -0
- package/src/renderers/components/index.ts +21 -0
- package/src/renderers/components/search.ts +449 -0
- package/src/renderers/components/select.ts +222 -0
- package/src/renderers/index.ts +101 -0
- package/src/renderers/interactive/component-handlers.ts +622 -0
- package/src/renderers/interactive/cursor-manager.ts +147 -0
- package/src/renderers/interactive/focus-manager.ts +279 -0
- package/src/renderers/interactive/index.ts +661 -0
- package/src/renderers/interactive/input-handler.ts +164 -0
- package/src/renderers/interactive/keyboard-handler.ts +212 -0
- package/src/renderers/interactive/mouse-handler.ts +167 -0
- package/src/renderers/interactive/state-manager.ts +109 -0
- package/src/renderers/interactive/types.ts +338 -0
- package/src/renderers/interactive-string.ts +299 -0
- package/src/renderers/interactive.ts +59 -0
- package/src/renderers/markdown.ts +950 -0
- package/src/renderers/sidebar.ts +549 -0
- package/src/renderers/tabs.ts +682 -0
- package/src/renderers/text.ts +791 -0
- package/src/renderers/unicode.ts +917 -0
- package/src/renderers/utils.ts +942 -0
- package/src/router/adapters.ts +383 -0
- package/src/router/types.ts +140 -0
- package/src/router/utils.ts +452 -0
- package/src/schemas.ts +205 -0
- package/src/storybook/index.ts +91 -0
- package/src/storybook/interactive-decorator.tsx +659 -0
- package/src/storybook/keyboard-simulator.ts +501 -0
- package/src/theme/ansi-codes.ts +80 -0
- package/src/theme/box-drawing.ts +132 -0
- package/src/theme/color-convert.ts +254 -0
- package/src/theme/color-support.ts +321 -0
- package/src/theme/index.ts +134 -0
- package/src/theme/strip-ansi.ts +50 -0
- package/src/theme/tailwind-map.ts +469 -0
- package/src/theme/text-styles.ts +206 -0
- package/src/theme/theme-system.ts +568 -0
- package/src/types.ts +103 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyboard Manager
|
|
3
|
+
*
|
|
4
|
+
* Core keyboard binding manager for handling key bindings and sequences.
|
|
5
|
+
* Supports single keys, modifier combinations, and multi-key sequences.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Types
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Modifier key state for a key press event.
|
|
16
|
+
*
|
|
17
|
+
* Tracks which modifier keys are held during a key press.
|
|
18
|
+
*/
|
|
19
|
+
export interface KeyModifiers {
|
|
20
|
+
/** Control key is pressed */
|
|
21
|
+
ctrl: boolean
|
|
22
|
+
/** Alt/Option key is pressed */
|
|
23
|
+
alt: boolean
|
|
24
|
+
/** Shift key is pressed */
|
|
25
|
+
shift: boolean
|
|
26
|
+
/** Meta/Command key is pressed */
|
|
27
|
+
meta: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A keyboard action - either a string action name or a callback function.
|
|
32
|
+
*
|
|
33
|
+
* String actions are dispatched via the onAction handler.
|
|
34
|
+
* Function actions are called directly when the key is pressed.
|
|
35
|
+
*/
|
|
36
|
+
export type KeyboardAction = string | (() => void)
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Maps key names/sequences to actions.
|
|
40
|
+
*
|
|
41
|
+
* Keys can be:
|
|
42
|
+
* - Single characters: 'j', 'k', 'q'
|
|
43
|
+
* - Special keys: 'enter', 'escape', 'tab'
|
|
44
|
+
* - Modifier combinations: 'ctrl+c', 'shift+tab'
|
|
45
|
+
* - Key sequences: 'gg', 'dd' (multi-character without '+')
|
|
46
|
+
*/
|
|
47
|
+
export type KeyBindings = Record<string, KeyboardAction>
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Context passed to action handlers with the triggering key and optional extra data.
|
|
51
|
+
*/
|
|
52
|
+
export interface ActionContext {
|
|
53
|
+
/** The key or sequence that triggered this action */
|
|
54
|
+
key: string
|
|
55
|
+
/** Optional context data set via setContext() */
|
|
56
|
+
context?: Record<string, unknown>
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Callback invoked when a keyboard action is triggered.
|
|
61
|
+
*
|
|
62
|
+
* @param action - The action name (string actions only)
|
|
63
|
+
* @param context - Context including the key and optional data
|
|
64
|
+
*/
|
|
65
|
+
export type ActionHandler = (action: string, context: ActionContext) => void
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Configuration options for creating a keyboard manager.
|
|
69
|
+
*/
|
|
70
|
+
export interface KeyboardManagerOptions {
|
|
71
|
+
/** Key-to-action bindings */
|
|
72
|
+
bindings: KeyBindings
|
|
73
|
+
/** Handler called when string actions are triggered */
|
|
74
|
+
onAction?: ActionHandler
|
|
75
|
+
/** Whether the manager starts enabled (default: true) */
|
|
76
|
+
enabled?: boolean
|
|
77
|
+
/** Optional context data passed to action handlers */
|
|
78
|
+
context?: Record<string, unknown> | null
|
|
79
|
+
/** Timeout in ms for key sequences (default: 1000) */
|
|
80
|
+
sequenceTimeout?: number
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Interface for the keyboard manager returned by createKeyboardManager.
|
|
85
|
+
*/
|
|
86
|
+
export interface KeyboardManager {
|
|
87
|
+
/** Get the action bound to a specific key */
|
|
88
|
+
getAction(key: string): KeyboardAction | undefined
|
|
89
|
+
/** Handle a key press, returns true if the key was handled */
|
|
90
|
+
handleKey(key: string, handler?: ActionHandler): boolean
|
|
91
|
+
/** Add a new key binding */
|
|
92
|
+
addBinding(key: string, action: KeyboardAction): void
|
|
93
|
+
/** Remove a key binding */
|
|
94
|
+
removeBinding(key: string): void
|
|
95
|
+
/** Replace all bindings */
|
|
96
|
+
setBindings(bindings: KeyBindings): void
|
|
97
|
+
/** Get all string bindings (excludes function bindings) */
|
|
98
|
+
getBindings(): Record<string, string>
|
|
99
|
+
/** Disable the keyboard manager */
|
|
100
|
+
disable(): void
|
|
101
|
+
/** Enable the keyboard manager */
|
|
102
|
+
enable(): void
|
|
103
|
+
/** Toggle enabled state */
|
|
104
|
+
toggle(): void
|
|
105
|
+
/** Check if the manager is enabled */
|
|
106
|
+
isEnabled(): boolean
|
|
107
|
+
/** Get the current pending key sequence */
|
|
108
|
+
getPendingSequence(): string
|
|
109
|
+
/** Set context data passed to action handlers */
|
|
110
|
+
setContext(context: Record<string, unknown>): void
|
|
111
|
+
/** Clean up resources (clears pending sequence timer to prevent memory leaks) */
|
|
112
|
+
destroy(): void
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ============================================================================
|
|
116
|
+
// KEY Constants
|
|
117
|
+
// ============================================================================
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Common key constants for keyboard handling.
|
|
121
|
+
* Use these with matchKey() for consistent key comparisons.
|
|
122
|
+
*/
|
|
123
|
+
export const KEY = {
|
|
124
|
+
ENTER: 'enter',
|
|
125
|
+
ESCAPE: 'escape',
|
|
126
|
+
UP: 'up',
|
|
127
|
+
DOWN: 'down',
|
|
128
|
+
LEFT: 'left',
|
|
129
|
+
RIGHT: 'right',
|
|
130
|
+
TAB: 'tab',
|
|
131
|
+
SPACE: 'space',
|
|
132
|
+
BACKSPACE: 'backspace',
|
|
133
|
+
DELETE: 'delete',
|
|
134
|
+
} as const
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Check if a key matches an expected key.
|
|
138
|
+
*
|
|
139
|
+
* @param key - The key to check
|
|
140
|
+
* @param expected - The expected key value
|
|
141
|
+
* @returns true if keys match
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```tsx
|
|
145
|
+
* import { matchKey, KEY } from '@mdxui/terminal'
|
|
146
|
+
*
|
|
147
|
+
* if (matchKey(pressedKey, KEY.ENTER)) {
|
|
148
|
+
* handleSubmit()
|
|
149
|
+
* }
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
export function matchKey(key: string, expected: string): boolean {
|
|
153
|
+
return key === expected
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ============================================================================
|
|
157
|
+
// createKeyboardManager
|
|
158
|
+
// ============================================================================
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Creates a keyboard manager for handling key bindings and sequences.
|
|
162
|
+
*
|
|
163
|
+
* The manager supports:
|
|
164
|
+
* - Single key bindings ('j', 'enter', 'q')
|
|
165
|
+
* - Modifier combinations ('ctrl+c', 'shift+tab')
|
|
166
|
+
* - Multi-key sequences ('gg', 'dd') with configurable timeout
|
|
167
|
+
* - Dynamic binding changes at runtime
|
|
168
|
+
* - Enable/disable toggling
|
|
169
|
+
*
|
|
170
|
+
* @param options - Configuration options
|
|
171
|
+
* @returns A keyboard manager instance
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```tsx
|
|
175
|
+
* const keyboard = createKeyboardManager({
|
|
176
|
+
* bindings: { ...VIM_BINDINGS, ...ARROW_BINDINGS },
|
|
177
|
+
* onAction: (action, ctx) => {
|
|
178
|
+
* switch (action) {
|
|
179
|
+
* case 'move-down': moveDown(); break
|
|
180
|
+
* case 'select': handleSelect(); break
|
|
181
|
+
* }
|
|
182
|
+
* }
|
|
183
|
+
* })
|
|
184
|
+
*
|
|
185
|
+
* // Later, handle input
|
|
186
|
+
* process.stdin.on('keypress', (ch, key) => {
|
|
187
|
+
* keyboard.handleKey(key.name || ch)
|
|
188
|
+
* })
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
export function createKeyboardManager(options: KeyboardManagerOptions): KeyboardManager {
|
|
192
|
+
let bindings = { ...options.bindings }
|
|
193
|
+
let enabled = options.enabled !== false
|
|
194
|
+
let context: Record<string, unknown> | null = options.context ?? null
|
|
195
|
+
const onAction = options.onAction
|
|
196
|
+
const sequenceTimeout = options.sequenceTimeout ?? 1000
|
|
197
|
+
|
|
198
|
+
// Track pending key sequence for multi-key bindings like 'gg'
|
|
199
|
+
let pendingSequence = ''
|
|
200
|
+
let sequenceTimer: ReturnType<typeof setTimeout> | null = null
|
|
201
|
+
|
|
202
|
+
// Find all multi-key bindings (sequences)
|
|
203
|
+
function getSequenceBindings(): string[] {
|
|
204
|
+
return Object.keys(bindings).filter((key) => key.length > 1 && !key.includes('+'))
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Check if a key could start a sequence
|
|
208
|
+
function startsSequence(key: string): boolean {
|
|
209
|
+
const sequences = getSequenceBindings()
|
|
210
|
+
return sequences.some((seq) => seq.startsWith(key))
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Check if a key could continue the pending sequence
|
|
214
|
+
function continuesSequence(key: string): boolean {
|
|
215
|
+
if (!pendingSequence) return false
|
|
216
|
+
const potential = pendingSequence + key
|
|
217
|
+
const sequences = getSequenceBindings()
|
|
218
|
+
return sequences.some((seq) => seq.startsWith(potential))
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function clearSequence(): void {
|
|
222
|
+
pendingSequence = ''
|
|
223
|
+
if (sequenceTimer) {
|
|
224
|
+
clearTimeout(sequenceTimer)
|
|
225
|
+
sequenceTimer = null
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function buildActionContext(key: string): ActionContext {
|
|
230
|
+
const actionContext: ActionContext = { key }
|
|
231
|
+
if (context !== null) {
|
|
232
|
+
actionContext.context = context
|
|
233
|
+
}
|
|
234
|
+
return actionContext
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function handleSequenceTimeout(): void {
|
|
238
|
+
// When sequence times out, try to execute the pending sequence as-is
|
|
239
|
+
if (pendingSequence && bindings[pendingSequence]) {
|
|
240
|
+
const action = bindings[pendingSequence]
|
|
241
|
+
if (typeof action === 'function') {
|
|
242
|
+
action()
|
|
243
|
+
} else if (onAction) {
|
|
244
|
+
onAction(action, buildActionContext(pendingSequence))
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
clearSequence()
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const manager: KeyboardManager = {
|
|
251
|
+
getAction(key: string): KeyboardAction | undefined {
|
|
252
|
+
return bindings[key]
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
handleKey(key: string, handler?: ActionHandler): boolean {
|
|
256
|
+
if (!enabled) return false
|
|
257
|
+
|
|
258
|
+
// Handle escape - clears pending sequence
|
|
259
|
+
if (key === 'escape') {
|
|
260
|
+
if (pendingSequence) {
|
|
261
|
+
clearSequence()
|
|
262
|
+
// Don't trigger escape action if we just cleared a sequence
|
|
263
|
+
return true
|
|
264
|
+
}
|
|
265
|
+
// Fall through to normal handling if no pending sequence
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Check if this key could be part of a sequence
|
|
269
|
+
const potentialSequence = pendingSequence + key
|
|
270
|
+
|
|
271
|
+
// Check if the potential sequence matches a binding
|
|
272
|
+
if (bindings[potentialSequence]) {
|
|
273
|
+
clearSequence()
|
|
274
|
+
const action = bindings[potentialSequence]
|
|
275
|
+
if (typeof action === 'function') {
|
|
276
|
+
action()
|
|
277
|
+
} else {
|
|
278
|
+
const effectiveHandler = handler || onAction
|
|
279
|
+
if (effectiveHandler) {
|
|
280
|
+
effectiveHandler(action, buildActionContext(potentialSequence))
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return true
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Check if this could continue or start a sequence
|
|
287
|
+
if (continuesSequence(key) || (!pendingSequence && startsSequence(key))) {
|
|
288
|
+
pendingSequence = potentialSequence
|
|
289
|
+
|
|
290
|
+
// Set timeout to execute fallback or clear sequence
|
|
291
|
+
if (sequenceTimer) clearTimeout(sequenceTimer)
|
|
292
|
+
sequenceTimer = setTimeout(handleSequenceTimeout, sequenceTimeout)
|
|
293
|
+
|
|
294
|
+
return true
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Not a sequence - check for direct binding
|
|
298
|
+
if (pendingSequence) {
|
|
299
|
+
// Had a pending sequence but this key doesn't continue it
|
|
300
|
+
// Clear and don't handle
|
|
301
|
+
clearSequence()
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const action = bindings[key]
|
|
305
|
+
if (action === undefined) {
|
|
306
|
+
return false
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (typeof action === 'function') {
|
|
310
|
+
action()
|
|
311
|
+
} else {
|
|
312
|
+
const effectiveHandler = handler || onAction
|
|
313
|
+
if (effectiveHandler) {
|
|
314
|
+
effectiveHandler(action, buildActionContext(key))
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return true
|
|
319
|
+
},
|
|
320
|
+
|
|
321
|
+
addBinding(key: string, action: KeyboardAction): void {
|
|
322
|
+
bindings[key] = action
|
|
323
|
+
},
|
|
324
|
+
|
|
325
|
+
removeBinding(key: string): void {
|
|
326
|
+
delete bindings[key]
|
|
327
|
+
},
|
|
328
|
+
|
|
329
|
+
setBindings(newBindings: KeyBindings): void {
|
|
330
|
+
bindings = { ...newBindings }
|
|
331
|
+
},
|
|
332
|
+
|
|
333
|
+
getBindings(): Record<string, string> {
|
|
334
|
+
const result: Record<string, string> = {}
|
|
335
|
+
for (const [key, action] of Object.entries(bindings)) {
|
|
336
|
+
if (typeof action === 'string') {
|
|
337
|
+
result[key] = action
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return result
|
|
341
|
+
},
|
|
342
|
+
|
|
343
|
+
disable(): void {
|
|
344
|
+
enabled = false
|
|
345
|
+
},
|
|
346
|
+
|
|
347
|
+
enable(): void {
|
|
348
|
+
enabled = true
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
toggle(): void {
|
|
352
|
+
enabled = !enabled
|
|
353
|
+
},
|
|
354
|
+
|
|
355
|
+
isEnabled(): boolean {
|
|
356
|
+
return enabled
|
|
357
|
+
},
|
|
358
|
+
|
|
359
|
+
getPendingSequence(): string {
|
|
360
|
+
return pendingSequence
|
|
361
|
+
},
|
|
362
|
+
|
|
363
|
+
setContext(newContext: Record<string, unknown>): void {
|
|
364
|
+
context = newContext
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
destroy(): void {
|
|
368
|
+
if (sequenceTimer) {
|
|
369
|
+
clearTimeout(sequenceTimer)
|
|
370
|
+
sequenceTimer = null
|
|
371
|
+
}
|
|
372
|
+
pendingSequence = ''
|
|
373
|
+
},
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return manager
|
|
377
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preset Key Bindings
|
|
3
|
+
*
|
|
4
|
+
* Common keyboard binding presets for terminal applications.
|
|
5
|
+
* Includes Vim-style, arrow key, and common UI bindings.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { KeyBindings } from './manager'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Vim-style navigation bindings.
|
|
14
|
+
*
|
|
15
|
+
* Includes:
|
|
16
|
+
* - h/j/k/l for directional movement
|
|
17
|
+
* - gg/G for first/last
|
|
18
|
+
* - / and : for search/command
|
|
19
|
+
* - i for insert mode
|
|
20
|
+
* - enter/escape/q for select/back/quit
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* const keyboard = createKeyboardManager({
|
|
25
|
+
* bindings: VIM_BINDINGS,
|
|
26
|
+
* onAction: handleAction
|
|
27
|
+
* })
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export const VIM_BINDINGS: KeyBindings = {
|
|
31
|
+
h: 'move-left',
|
|
32
|
+
j: 'move-down',
|
|
33
|
+
k: 'move-up',
|
|
34
|
+
l: 'move-right',
|
|
35
|
+
gg: 'move-first',
|
|
36
|
+
G: 'move-last',
|
|
37
|
+
'/': 'search',
|
|
38
|
+
':': 'command',
|
|
39
|
+
i: 'insert',
|
|
40
|
+
enter: 'select',
|
|
41
|
+
escape: 'back',
|
|
42
|
+
q: 'quit',
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Arrow key navigation bindings.
|
|
47
|
+
*
|
|
48
|
+
* Maps arrow keys to directional movement actions.
|
|
49
|
+
*/
|
|
50
|
+
export const ARROW_BINDINGS: KeyBindings = {
|
|
51
|
+
up: 'move-up',
|
|
52
|
+
down: 'move-down',
|
|
53
|
+
left: 'move-left',
|
|
54
|
+
right: 'move-right',
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Common UI bindings for Enter, Escape, Tab, and Space.
|
|
59
|
+
*
|
|
60
|
+
* Maps common interaction keys to semantic actions:
|
|
61
|
+
* - enter: 'select'
|
|
62
|
+
* - escape: 'back'
|
|
63
|
+
* - tab/shift+tab: focus navigation
|
|
64
|
+
* - space: 'toggle'
|
|
65
|
+
*/
|
|
66
|
+
export const COMMON_BINDINGS: KeyBindings = {
|
|
67
|
+
enter: 'select',
|
|
68
|
+
escape: 'back',
|
|
69
|
+
tab: 'focus-next',
|
|
70
|
+
'shift+tab': 'focus-prev',
|
|
71
|
+
space: 'toggle',
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Emacs-style navigation bindings.
|
|
76
|
+
*
|
|
77
|
+
* Includes:
|
|
78
|
+
* - ctrl+f/b/n/p for forward/back/next/previous
|
|
79
|
+
* - ctrl+a/e for start/end of line
|
|
80
|
+
* - ctrl+g for cancel
|
|
81
|
+
*/
|
|
82
|
+
export const EMACS_BINDINGS: KeyBindings = {
|
|
83
|
+
'ctrl+f': 'move-right',
|
|
84
|
+
'ctrl+b': 'move-left',
|
|
85
|
+
'ctrl+n': 'move-down',
|
|
86
|
+
'ctrl+p': 'move-up',
|
|
87
|
+
'ctrl+a': 'move-first',
|
|
88
|
+
'ctrl+e': 'move-last',
|
|
89
|
+
'ctrl+g': 'back',
|
|
90
|
+
}
|