@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,661 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mdxui/terminal Interactive Renderer
|
|
3
|
+
*
|
|
4
|
+
* The Interactive renderer is the highest-capability tier providing:
|
|
5
|
+
* - Full TUI with keyboard navigation
|
|
6
|
+
* - Focus management and Tab cycling
|
|
7
|
+
* - Mouse click support
|
|
8
|
+
* - Cursor positioning
|
|
9
|
+
* - Real-time updates
|
|
10
|
+
* - Input field handling
|
|
11
|
+
* - Component state management
|
|
12
|
+
*
|
|
13
|
+
* @packageDocumentation
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// NOTE: @opentui/core is imported dynamically inside createInteractiveRenderer()
|
|
17
|
+
// to avoid loading .scm files when only the string-based renderer is needed
|
|
18
|
+
|
|
19
|
+
// Re-export types
|
|
20
|
+
export type {
|
|
21
|
+
InteractiveRendererConfig,
|
|
22
|
+
FocusableOptions,
|
|
23
|
+
ClickableOptions,
|
|
24
|
+
InputOptions,
|
|
25
|
+
ComponentOptions,
|
|
26
|
+
InteractiveRenderer,
|
|
27
|
+
UINode,
|
|
28
|
+
// Internal types for advanced usage
|
|
29
|
+
FocusableEntry,
|
|
30
|
+
FocusTrapEntry,
|
|
31
|
+
KeyHandler,
|
|
32
|
+
InputState,
|
|
33
|
+
FocusManagerState,
|
|
34
|
+
KeyboardManagerState,
|
|
35
|
+
CursorManagerState,
|
|
36
|
+
StateManagerState,
|
|
37
|
+
MouseManagerState,
|
|
38
|
+
InputManagerState,
|
|
39
|
+
ComponentManagerState,
|
|
40
|
+
} from './types'
|
|
41
|
+
|
|
42
|
+
// Import manager creators and functions
|
|
43
|
+
import {
|
|
44
|
+
createFocusManagerState,
|
|
45
|
+
getSortedFocusableIds,
|
|
46
|
+
getCurrentFocusIndex,
|
|
47
|
+
focusNext as focusNextFn,
|
|
48
|
+
focusPrev as focusPrevFn,
|
|
49
|
+
focusById as focusByIdFn,
|
|
50
|
+
getFocusedId as getFocusedIdFn,
|
|
51
|
+
getFocusableIds as getFocusableIdsFn,
|
|
52
|
+
registerFocusable as registerFocusableFn,
|
|
53
|
+
unregisterFocusable as unregisterFocusableFn,
|
|
54
|
+
setActiveGroup as setActiveGroupFn,
|
|
55
|
+
pushFocusTrap as pushFocusTrapFn,
|
|
56
|
+
popFocusTrap as popFocusTrapFn,
|
|
57
|
+
} from './focus-manager'
|
|
58
|
+
|
|
59
|
+
import {
|
|
60
|
+
createKeyboardManagerState,
|
|
61
|
+
onKeyPress as onKeyPressFn,
|
|
62
|
+
offKeyPress as offKeyPressFn,
|
|
63
|
+
emitKey as emitKeyFn,
|
|
64
|
+
onKeySequence as onKeySequenceFn,
|
|
65
|
+
getPendingSequence as getPendingSequenceFn,
|
|
66
|
+
onSearchMode as onSearchModeFn,
|
|
67
|
+
onCancel as onCancelFn,
|
|
68
|
+
getMode as getModeFn,
|
|
69
|
+
clearKeyboardState,
|
|
70
|
+
} from './keyboard-handler'
|
|
71
|
+
|
|
72
|
+
import {
|
|
73
|
+
createCursorManagerState,
|
|
74
|
+
setCursorPosition as setCursorPositionFn,
|
|
75
|
+
getCursorPosition as getCursorPositionFn,
|
|
76
|
+
showCursor as showCursorFn,
|
|
77
|
+
hideCursor as hideCursorFn,
|
|
78
|
+
isCursorVisible as isCursorVisibleFn,
|
|
79
|
+
setCursorStyle as setCursorStyleFn,
|
|
80
|
+
getCursorStyle as getCursorStyleFn,
|
|
81
|
+
setCursorBlink as setCursorBlinkFn,
|
|
82
|
+
isCursorBlinking as isCursorBlinkingFn,
|
|
83
|
+
updateCursorPosition as updateCursorPositionFn,
|
|
84
|
+
} from './cursor-manager'
|
|
85
|
+
|
|
86
|
+
import {
|
|
87
|
+
createStateManagerState,
|
|
88
|
+
setState as setStateFn,
|
|
89
|
+
getState as getStateFn,
|
|
90
|
+
subscribe as subscribeFn,
|
|
91
|
+
onRender as onRenderFn,
|
|
92
|
+
getTargetFps as getTargetFpsFn,
|
|
93
|
+
clearStateManager,
|
|
94
|
+
} from './state-manager'
|
|
95
|
+
|
|
96
|
+
import {
|
|
97
|
+
createMouseManagerState,
|
|
98
|
+
onClick as onClickFn,
|
|
99
|
+
offClick as offClickFn,
|
|
100
|
+
emitClick as emitClickFn,
|
|
101
|
+
registerClickable as registerClickableFn,
|
|
102
|
+
unregisterClickable as unregisterClickableFn,
|
|
103
|
+
getClickableAreas as getClickableAreasFn,
|
|
104
|
+
onScroll as onScrollFn,
|
|
105
|
+
emitScroll as emitScrollFn,
|
|
106
|
+
clearMouseState,
|
|
107
|
+
} from './mouse-handler'
|
|
108
|
+
|
|
109
|
+
import {
|
|
110
|
+
createInputManagerState,
|
|
111
|
+
registerInput as registerInputFn,
|
|
112
|
+
getInputCursorIndex as getInputCursorIndexFn,
|
|
113
|
+
clearInputState,
|
|
114
|
+
} from './input-handler'
|
|
115
|
+
|
|
116
|
+
import {
|
|
117
|
+
createComponentManagerState,
|
|
118
|
+
registerComponent as registerComponentFn,
|
|
119
|
+
unregisterComponent as unregisterComponentFn,
|
|
120
|
+
getComponent as getComponentFn,
|
|
121
|
+
updateComponent as updateComponentFn,
|
|
122
|
+
handleComponentKey,
|
|
123
|
+
clearComponentState,
|
|
124
|
+
} from './component-handlers'
|
|
125
|
+
|
|
126
|
+
import type {
|
|
127
|
+
InteractiveRendererConfig,
|
|
128
|
+
InteractiveRenderer,
|
|
129
|
+
FocusableOptions,
|
|
130
|
+
ClickableOptions,
|
|
131
|
+
InputOptions,
|
|
132
|
+
ComponentOptions,
|
|
133
|
+
} from './types'
|
|
134
|
+
|
|
135
|
+
// Re-export registerInteractiveNode
|
|
136
|
+
export { registerInteractiveNode } from './component-handlers'
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Creates an interactive renderer instance.
|
|
140
|
+
*/
|
|
141
|
+
export async function createInteractiveRenderer(
|
|
142
|
+
config?: InteractiveRendererConfig
|
|
143
|
+
): Promise<InteractiveRenderer> {
|
|
144
|
+
// Dynamic import to avoid .scm file loading at module load time
|
|
145
|
+
const { createCliRenderer } = await import('@opentui/core')
|
|
146
|
+
// Get base renderer from @opentui/core
|
|
147
|
+
const cliRenderer = await createCliRenderer()
|
|
148
|
+
|
|
149
|
+
// State
|
|
150
|
+
let destroyed = false
|
|
151
|
+
const wrapFocus = config?.wrapFocus ?? true
|
|
152
|
+
const vimBindings = config?.vimBindings ?? false
|
|
153
|
+
const sequenceTimeout = config?.sequenceTimeout ?? 500
|
|
154
|
+
const targetFps = config?.targetFps ?? 60
|
|
155
|
+
|
|
156
|
+
// Create manager states
|
|
157
|
+
const focusState = createFocusManagerState(wrapFocus)
|
|
158
|
+
const keyboardState = createKeyboardManagerState(vimBindings, sequenceTimeout)
|
|
159
|
+
const cursorState = createCursorManagerState()
|
|
160
|
+
const stateManagerState = createStateManagerState(targetFps)
|
|
161
|
+
const mouseState = createMouseManagerState()
|
|
162
|
+
const inputState = createInputManagerState()
|
|
163
|
+
const componentState = createComponentManagerState()
|
|
164
|
+
|
|
165
|
+
// Helper: emit key to focused component's handlers
|
|
166
|
+
const handleComponentKeyWrapper = (key: string): boolean => {
|
|
167
|
+
return handleComponentKey(
|
|
168
|
+
focusState.focusedId,
|
|
169
|
+
inputState,
|
|
170
|
+
componentState,
|
|
171
|
+
focusState,
|
|
172
|
+
key
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Setup built-in key handlers
|
|
177
|
+
const setupBuiltInKeyHandlers = () => {
|
|
178
|
+
// Tab handling
|
|
179
|
+
keyboardState.keyHandlers.set('tab', [
|
|
180
|
+
{
|
|
181
|
+
handler: () => {
|
|
182
|
+
renderer.focusNext()
|
|
183
|
+
return true
|
|
184
|
+
},
|
|
185
|
+
priority: -100,
|
|
186
|
+
},
|
|
187
|
+
])
|
|
188
|
+
keyboardState.keyHandlers.set('shift+tab', [
|
|
189
|
+
{
|
|
190
|
+
handler: () => {
|
|
191
|
+
renderer.focusPrev()
|
|
192
|
+
return true
|
|
193
|
+
},
|
|
194
|
+
priority: -100,
|
|
195
|
+
},
|
|
196
|
+
])
|
|
197
|
+
|
|
198
|
+
// Arrow keys for focus navigation
|
|
199
|
+
keyboardState.keyHandlers.set('down', [
|
|
200
|
+
{
|
|
201
|
+
handler: () => {
|
|
202
|
+
if (handleComponentKeyWrapper('down')) return true
|
|
203
|
+
renderer.focusNext()
|
|
204
|
+
return true
|
|
205
|
+
},
|
|
206
|
+
priority: -100,
|
|
207
|
+
},
|
|
208
|
+
])
|
|
209
|
+
|
|
210
|
+
keyboardState.keyHandlers.set('up', [
|
|
211
|
+
{
|
|
212
|
+
handler: () => {
|
|
213
|
+
if (handleComponentKeyWrapper('up')) return true
|
|
214
|
+
renderer.focusPrev()
|
|
215
|
+
return true
|
|
216
|
+
},
|
|
217
|
+
priority: -100,
|
|
218
|
+
},
|
|
219
|
+
])
|
|
220
|
+
|
|
221
|
+
keyboardState.keyHandlers.set('left', [
|
|
222
|
+
{
|
|
223
|
+
handler: () => {
|
|
224
|
+
if (handleComponentKeyWrapper('left')) return true
|
|
225
|
+
renderer.focusPrev()
|
|
226
|
+
return true
|
|
227
|
+
},
|
|
228
|
+
priority: -100,
|
|
229
|
+
},
|
|
230
|
+
])
|
|
231
|
+
|
|
232
|
+
keyboardState.keyHandlers.set('right', [
|
|
233
|
+
{
|
|
234
|
+
handler: () => {
|
|
235
|
+
if (handleComponentKeyWrapper('right')) return true
|
|
236
|
+
renderer.focusNext()
|
|
237
|
+
return true
|
|
238
|
+
},
|
|
239
|
+
priority: -100,
|
|
240
|
+
},
|
|
241
|
+
])
|
|
242
|
+
|
|
243
|
+
// Enter/Space for activation
|
|
244
|
+
keyboardState.keyHandlers.set('enter', [
|
|
245
|
+
{ handler: () => handleComponentKeyWrapper('enter'), priority: -100 },
|
|
246
|
+
])
|
|
247
|
+
keyboardState.keyHandlers.set('space', [
|
|
248
|
+
{ handler: () => handleComponentKeyWrapper('space'), priority: -100 },
|
|
249
|
+
])
|
|
250
|
+
|
|
251
|
+
// Escape handling
|
|
252
|
+
keyboardState.keyHandlers.set('escape', [
|
|
253
|
+
{
|
|
254
|
+
handler: () => {
|
|
255
|
+
// Clear pending sequence
|
|
256
|
+
keyboardState.pendingSequence = ''
|
|
257
|
+
if (keyboardState.sequenceTimer) {
|
|
258
|
+
clearTimeout(keyboardState.sequenceTimer)
|
|
259
|
+
keyboardState.sequenceTimer = null
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Exit search mode
|
|
263
|
+
if (keyboardState.mode === 'search') {
|
|
264
|
+
keyboardState.mode = 'normal'
|
|
265
|
+
return true
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Call cancel handler
|
|
269
|
+
keyboardState.cancelHandler?.()
|
|
270
|
+
return true
|
|
271
|
+
},
|
|
272
|
+
priority: -100,
|
|
273
|
+
},
|
|
274
|
+
])
|
|
275
|
+
|
|
276
|
+
// Other special keys
|
|
277
|
+
keyboardState.keyHandlers.set('backspace', [
|
|
278
|
+
{ handler: () => handleComponentKeyWrapper('backspace'), priority: -100 },
|
|
279
|
+
])
|
|
280
|
+
keyboardState.keyHandlers.set('delete', [
|
|
281
|
+
{ handler: () => handleComponentKeyWrapper('delete'), priority: -100 },
|
|
282
|
+
])
|
|
283
|
+
keyboardState.keyHandlers.set('home', [
|
|
284
|
+
{ handler: () => handleComponentKeyWrapper('home'), priority: -100 },
|
|
285
|
+
])
|
|
286
|
+
keyboardState.keyHandlers.set('end', [
|
|
287
|
+
{ handler: () => handleComponentKeyWrapper('end'), priority: -100 },
|
|
288
|
+
])
|
|
289
|
+
keyboardState.keyHandlers.set('pagedown', [
|
|
290
|
+
{ handler: () => handleComponentKeyWrapper('pagedown'), priority: -100 },
|
|
291
|
+
])
|
|
292
|
+
keyboardState.keyHandlers.set('pageup', [
|
|
293
|
+
{ handler: () => handleComponentKeyWrapper('pageup'), priority: -100 },
|
|
294
|
+
])
|
|
295
|
+
keyboardState.keyHandlers.set('ctrl+enter', [
|
|
296
|
+
{ handler: () => handleComponentKeyWrapper('ctrl+enter'), priority: -100 },
|
|
297
|
+
])
|
|
298
|
+
|
|
299
|
+
// Vim bindings
|
|
300
|
+
if (vimBindings) {
|
|
301
|
+
keyboardState.keyHandlers.set('j', [
|
|
302
|
+
{
|
|
303
|
+
handler: () => {
|
|
304
|
+
if (handleComponentKeyWrapper('j')) return true
|
|
305
|
+
renderer.focusNext()
|
|
306
|
+
return true
|
|
307
|
+
},
|
|
308
|
+
priority: -100,
|
|
309
|
+
},
|
|
310
|
+
])
|
|
311
|
+
|
|
312
|
+
keyboardState.keyHandlers.set('k', [
|
|
313
|
+
{
|
|
314
|
+
handler: () => {
|
|
315
|
+
if (handleComponentKeyWrapper('k')) return true
|
|
316
|
+
renderer.focusPrev()
|
|
317
|
+
return true
|
|
318
|
+
},
|
|
319
|
+
priority: -100,
|
|
320
|
+
},
|
|
321
|
+
])
|
|
322
|
+
|
|
323
|
+
keyboardState.keyHandlers.set('h', [
|
|
324
|
+
{
|
|
325
|
+
handler: () => {
|
|
326
|
+
// Horizontal navigation - find prev column
|
|
327
|
+
const ids = getSortedFocusableIds(focusState, destroyed)
|
|
328
|
+
const currentIdx = getCurrentFocusIndex(focusState, destroyed)
|
|
329
|
+
if (currentIdx <= 0) return true
|
|
330
|
+
|
|
331
|
+
const currentFocusable = focusState.focusables.get(focusState.focusedId!)
|
|
332
|
+
const currentColumn = currentFocusable?.options.column ?? 0
|
|
333
|
+
|
|
334
|
+
// Find previous element with lower column
|
|
335
|
+
for (let i = currentIdx - 1; i >= 0; i--) {
|
|
336
|
+
const f = focusState.focusables.get(ids[i])
|
|
337
|
+
if (f && (f.options.column ?? 0) < currentColumn) {
|
|
338
|
+
renderer.focusById(ids[i])
|
|
339
|
+
return true
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Just go prev
|
|
344
|
+
renderer.focusPrev()
|
|
345
|
+
return true
|
|
346
|
+
},
|
|
347
|
+
priority: -100,
|
|
348
|
+
},
|
|
349
|
+
])
|
|
350
|
+
|
|
351
|
+
keyboardState.keyHandlers.set('l', [
|
|
352
|
+
{
|
|
353
|
+
handler: () => {
|
|
354
|
+
// Horizontal navigation - find next column
|
|
355
|
+
const ids = getSortedFocusableIds(focusState, destroyed)
|
|
356
|
+
const currentIdx = getCurrentFocusIndex(focusState, destroyed)
|
|
357
|
+
if (currentIdx < 0 || currentIdx >= ids.length - 1) return true
|
|
358
|
+
|
|
359
|
+
const currentFocusable = focusState.focusables.get(focusState.focusedId!)
|
|
360
|
+
const currentColumn = currentFocusable?.options.column ?? 0
|
|
361
|
+
|
|
362
|
+
// Find next element with higher column
|
|
363
|
+
for (let i = currentIdx + 1; i < ids.length; i++) {
|
|
364
|
+
const f = focusState.focusables.get(ids[i])
|
|
365
|
+
if (f && (f.options.column ?? 0) > currentColumn) {
|
|
366
|
+
renderer.focusById(ids[i])
|
|
367
|
+
return true
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Just go next
|
|
372
|
+
renderer.focusNext()
|
|
373
|
+
return true
|
|
374
|
+
},
|
|
375
|
+
priority: -100,
|
|
376
|
+
},
|
|
377
|
+
])
|
|
378
|
+
|
|
379
|
+
keyboardState.keyHandlers.set('g', [
|
|
380
|
+
{
|
|
381
|
+
handler: () => {
|
|
382
|
+
// Part of gg sequence - handled by sequence handler
|
|
383
|
+
return false
|
|
384
|
+
},
|
|
385
|
+
priority: -100,
|
|
386
|
+
},
|
|
387
|
+
])
|
|
388
|
+
|
|
389
|
+
keyboardState.keyHandlers.set('G', [
|
|
390
|
+
{
|
|
391
|
+
handler: () => {
|
|
392
|
+
// Jump to last
|
|
393
|
+
const ids = getSortedFocusableIds(focusState, destroyed)
|
|
394
|
+
if (ids.length > 0) {
|
|
395
|
+
renderer.focusById(ids[ids.length - 1])
|
|
396
|
+
}
|
|
397
|
+
return true
|
|
398
|
+
},
|
|
399
|
+
priority: -100,
|
|
400
|
+
},
|
|
401
|
+
])
|
|
402
|
+
|
|
403
|
+
keyboardState.keyHandlers.set('/', [
|
|
404
|
+
{
|
|
405
|
+
handler: () => {
|
|
406
|
+
keyboardState.mode = 'search'
|
|
407
|
+
keyboardState.searchModeHandler?.()
|
|
408
|
+
return true
|
|
409
|
+
},
|
|
410
|
+
priority: -100,
|
|
411
|
+
},
|
|
412
|
+
])
|
|
413
|
+
|
|
414
|
+
// Register gg sequence
|
|
415
|
+
keyboardState.sequenceHandlers.set('gg', () => {
|
|
416
|
+
const ids = getSortedFocusableIds(focusState, destroyed)
|
|
417
|
+
if (ids.length > 0) {
|
|
418
|
+
renderer.focusById(ids[0])
|
|
419
|
+
}
|
|
420
|
+
})
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const renderer: InteractiveRenderer = {
|
|
425
|
+
width: cliRenderer.width,
|
|
426
|
+
height: cliRenderer.height,
|
|
427
|
+
|
|
428
|
+
// Lifecycle
|
|
429
|
+
start() {
|
|
430
|
+
if (destroyed) return
|
|
431
|
+
cliRenderer.start()
|
|
432
|
+
},
|
|
433
|
+
|
|
434
|
+
stop() {
|
|
435
|
+
if (destroyed) return
|
|
436
|
+
cliRenderer.stop()
|
|
437
|
+
},
|
|
438
|
+
|
|
439
|
+
destroy() {
|
|
440
|
+
if (destroyed) return
|
|
441
|
+
destroyed = true
|
|
442
|
+
cliRenderer.destroy()
|
|
443
|
+
focusState.focusables.clear()
|
|
444
|
+
clearMouseState(mouseState)
|
|
445
|
+
clearKeyboardState(keyboardState)
|
|
446
|
+
clearInputState(inputState)
|
|
447
|
+
clearComponentState(componentState)
|
|
448
|
+
clearStateManager(stateManagerState)
|
|
449
|
+
focusState.focusedId = null
|
|
450
|
+
},
|
|
451
|
+
|
|
452
|
+
requestRender() {
|
|
453
|
+
if (destroyed) return
|
|
454
|
+
cliRenderer.requestRender()
|
|
455
|
+
},
|
|
456
|
+
|
|
457
|
+
// Focus management
|
|
458
|
+
focusNext() {
|
|
459
|
+
focusNextFn(focusState, cursorState, destroyed)
|
|
460
|
+
},
|
|
461
|
+
|
|
462
|
+
focusPrev() {
|
|
463
|
+
focusPrevFn(focusState, cursorState, destroyed)
|
|
464
|
+
},
|
|
465
|
+
|
|
466
|
+
focusById(id: string) {
|
|
467
|
+
focusByIdFn(focusState, cursorState, id, destroyed)
|
|
468
|
+
},
|
|
469
|
+
|
|
470
|
+
getFocusedId() {
|
|
471
|
+
return getFocusedIdFn(focusState, destroyed)
|
|
472
|
+
},
|
|
473
|
+
|
|
474
|
+
getFocusableIds() {
|
|
475
|
+
return getFocusableIdsFn(focusState, destroyed)
|
|
476
|
+
},
|
|
477
|
+
|
|
478
|
+
registerFocusable(id: string, options: FocusableOptions) {
|
|
479
|
+
registerFocusableFn(focusState, id, options, destroyed)
|
|
480
|
+
},
|
|
481
|
+
|
|
482
|
+
unregisterFocusable(id: string) {
|
|
483
|
+
unregisterFocusableFn(focusState, id, destroyed)
|
|
484
|
+
},
|
|
485
|
+
|
|
486
|
+
setActiveGroup(group: string) {
|
|
487
|
+
setActiveGroupFn(focusState, group, destroyed)
|
|
488
|
+
},
|
|
489
|
+
|
|
490
|
+
pushFocusTrap(group: string) {
|
|
491
|
+
pushFocusTrapFn(focusState, group, destroyed)
|
|
492
|
+
},
|
|
493
|
+
|
|
494
|
+
popFocusTrap() {
|
|
495
|
+
popFocusTrapFn(focusState, cursorState, destroyed)
|
|
496
|
+
},
|
|
497
|
+
|
|
498
|
+
// Keyboard management
|
|
499
|
+
onKeyPress(key: string, handler: () => boolean | void, options?: { priority?: number }) {
|
|
500
|
+
onKeyPressFn(keyboardState, key, handler, options, destroyed)
|
|
501
|
+
},
|
|
502
|
+
|
|
503
|
+
offKeyPress(key: string, handler: () => boolean | void) {
|
|
504
|
+
offKeyPressFn(keyboardState, key, handler, destroyed)
|
|
505
|
+
},
|
|
506
|
+
|
|
507
|
+
emitKey(key: string) {
|
|
508
|
+
emitKeyFn(keyboardState, key, handleComponentKeyWrapper, focusState.focusedId, destroyed)
|
|
509
|
+
},
|
|
510
|
+
|
|
511
|
+
onKeySequence(sequence: string, handler: () => void) {
|
|
512
|
+
onKeySequenceFn(keyboardState, sequence, handler, destroyed)
|
|
513
|
+
},
|
|
514
|
+
|
|
515
|
+
getPendingSequence() {
|
|
516
|
+
return getPendingSequenceFn(keyboardState, destroyed)
|
|
517
|
+
},
|
|
518
|
+
|
|
519
|
+
onSearchMode(handler: () => void) {
|
|
520
|
+
onSearchModeFn(keyboardState, handler, destroyed)
|
|
521
|
+
},
|
|
522
|
+
|
|
523
|
+
onCancel(handler: () => void) {
|
|
524
|
+
onCancelFn(keyboardState, handler, destroyed)
|
|
525
|
+
},
|
|
526
|
+
|
|
527
|
+
getMode() {
|
|
528
|
+
return getModeFn(keyboardState, destroyed)
|
|
529
|
+
},
|
|
530
|
+
|
|
531
|
+
// Mouse management
|
|
532
|
+
onClick(handler: (event: { x: number; y: number }) => void) {
|
|
533
|
+
onClickFn(mouseState, handler, destroyed)
|
|
534
|
+
},
|
|
535
|
+
|
|
536
|
+
offClick(handler: (event: { x: number; y: number }) => void) {
|
|
537
|
+
offClickFn(mouseState, handler, destroyed)
|
|
538
|
+
},
|
|
539
|
+
|
|
540
|
+
emitClick(x: number, y: number, options?: { clickCount?: number; button?: string }) {
|
|
541
|
+
emitClickFn(mouseState, focusState, cursorState, x, y, options, destroyed)
|
|
542
|
+
},
|
|
543
|
+
|
|
544
|
+
registerClickable(id: string, options: ClickableOptions) {
|
|
545
|
+
registerClickableFn(mouseState, id, options, destroyed)
|
|
546
|
+
},
|
|
547
|
+
|
|
548
|
+
unregisterClickable(id: string) {
|
|
549
|
+
unregisterClickableFn(mouseState, id, destroyed)
|
|
550
|
+
},
|
|
551
|
+
|
|
552
|
+
getClickableAreas() {
|
|
553
|
+
return getClickableAreasFn(mouseState, destroyed)
|
|
554
|
+
},
|
|
555
|
+
|
|
556
|
+
onScroll(handler: (event: { deltaY: number }) => void) {
|
|
557
|
+
onScrollFn(mouseState, handler, destroyed)
|
|
558
|
+
},
|
|
559
|
+
|
|
560
|
+
emitScroll(x: number, y: number, options: { deltaY: number }) {
|
|
561
|
+
emitScrollFn(mouseState, x, y, options, destroyed)
|
|
562
|
+
},
|
|
563
|
+
|
|
564
|
+
// Cursor management
|
|
565
|
+
setCursorPosition(x: number, y: number) {
|
|
566
|
+
setCursorPositionFn(cursorState, x, y, destroyed)
|
|
567
|
+
},
|
|
568
|
+
|
|
569
|
+
getCursorPosition() {
|
|
570
|
+
return getCursorPositionFn(cursorState, destroyed)
|
|
571
|
+
},
|
|
572
|
+
|
|
573
|
+
showCursor() {
|
|
574
|
+
showCursorFn(cursorState, destroyed)
|
|
575
|
+
},
|
|
576
|
+
|
|
577
|
+
hideCursor() {
|
|
578
|
+
hideCursorFn(cursorState, destroyed)
|
|
579
|
+
},
|
|
580
|
+
|
|
581
|
+
isCursorVisible() {
|
|
582
|
+
return isCursorVisibleFn(cursorState, destroyed)
|
|
583
|
+
},
|
|
584
|
+
|
|
585
|
+
setCursorStyle(style: 'block' | 'underline' | 'bar') {
|
|
586
|
+
setCursorStyleFn(cursorState, style, destroyed)
|
|
587
|
+
},
|
|
588
|
+
|
|
589
|
+
getCursorStyle() {
|
|
590
|
+
return getCursorStyleFn(cursorState, destroyed)
|
|
591
|
+
},
|
|
592
|
+
|
|
593
|
+
setCursorBlink(blink: boolean) {
|
|
594
|
+
setCursorBlinkFn(cursorState, blink, destroyed)
|
|
595
|
+
},
|
|
596
|
+
|
|
597
|
+
isCursorBlinking() {
|
|
598
|
+
return isCursorBlinkingFn(cursorState, destroyed)
|
|
599
|
+
},
|
|
600
|
+
|
|
601
|
+
updateCursorPosition(id: string, position: { x: number; y: number }) {
|
|
602
|
+
updateCursorPositionFn(cursorState, focusState, id, position, destroyed)
|
|
603
|
+
},
|
|
604
|
+
|
|
605
|
+
// State management
|
|
606
|
+
setState(key: string, value: unknown) {
|
|
607
|
+
setStateFn(stateManagerState, key, value, destroyed)
|
|
608
|
+
},
|
|
609
|
+
|
|
610
|
+
getState(key: string) {
|
|
611
|
+
return getStateFn(stateManagerState, key, destroyed)
|
|
612
|
+
},
|
|
613
|
+
|
|
614
|
+
subscribe(key: string, subscriber: (newValue: unknown, oldValue: unknown) => void) {
|
|
615
|
+
return subscribeFn(stateManagerState, key, subscriber, destroyed)
|
|
616
|
+
},
|
|
617
|
+
|
|
618
|
+
onRender(handler: () => void) {
|
|
619
|
+
onRenderFn(stateManagerState, handler, destroyed)
|
|
620
|
+
},
|
|
621
|
+
|
|
622
|
+
getTargetFps() {
|
|
623
|
+
return getTargetFpsFn(stateManagerState)
|
|
624
|
+
},
|
|
625
|
+
|
|
626
|
+
// Input management
|
|
627
|
+
registerInput(id: string, options: InputOptions) {
|
|
628
|
+
registerInputFn(inputState, focusState, id, options, destroyed)
|
|
629
|
+
},
|
|
630
|
+
|
|
631
|
+
getInputCursorIndex(id: string) {
|
|
632
|
+
return getInputCursorIndexFn(inputState, id, destroyed)
|
|
633
|
+
},
|
|
634
|
+
|
|
635
|
+
// Component management
|
|
636
|
+
registerComponent(id: string, options: ComponentOptions) {
|
|
637
|
+
registerComponentFn(componentState, focusState, id, options, destroyed)
|
|
638
|
+
},
|
|
639
|
+
|
|
640
|
+
unregisterComponent(id: string) {
|
|
641
|
+
unregisterComponentFn(componentState, focusState, id, destroyed)
|
|
642
|
+
},
|
|
643
|
+
|
|
644
|
+
getComponent(id: string) {
|
|
645
|
+
return getComponentFn(componentState, id, destroyed)
|
|
646
|
+
},
|
|
647
|
+
|
|
648
|
+
updateComponent(id: string, updates: Partial<ComponentOptions>) {
|
|
649
|
+
updateComponentFn(componentState, id, updates, destroyed)
|
|
650
|
+
},
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Setup built-in handlers
|
|
654
|
+
setupBuiltInKeyHandlers()
|
|
655
|
+
|
|
656
|
+
return renderer
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Re-export the string-based renderer for test compatibility
|
|
660
|
+
export { renderInteractive } from '../interactive-string'
|
|
661
|
+
export type { InteractiveRenderOptions } from '../interactive-string'
|