@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.
Files changed (191) hide show
  1. package/README.md +571 -0
  2. package/dist/ansi-css-Sk5mWtdK.d.ts +119 -0
  3. package/dist/ansi-css-V6JIHGsM.d.ts +119 -0
  4. package/dist/ansi-css-_3eSEU9d.d.ts +119 -0
  5. package/dist/chunk-3EFDH7PK.js +5235 -0
  6. package/dist/chunk-3RG5ZIWI.js +10 -0
  7. package/dist/chunk-3X5IR6WE.js +884 -0
  8. package/dist/chunk-4FV5ZDCE.js +5236 -0
  9. package/dist/chunk-4OVMSF2J.js +243 -0
  10. package/dist/chunk-63FEETIS.js +4048 -0
  11. package/dist/chunk-B43KP7XJ.js +884 -0
  12. package/dist/chunk-BMTJXWUV.js +655 -0
  13. package/dist/chunk-C3SVH4N7.js +882 -0
  14. package/dist/chunk-EVWR7Y47.js +874 -0
  15. package/dist/chunk-F6A5VWUC.js +1285 -0
  16. package/dist/chunk-FD7KW7GE.js +882 -0
  17. package/dist/chunk-GBQ6UD6I.js +655 -0
  18. package/dist/chunk-GMDD3M6U.js +5227 -0
  19. package/dist/chunk-JBHRXOXM.js +1058 -0
  20. package/dist/chunk-JFOO3EYO.js +1182 -0
  21. package/dist/chunk-JQ5H3WXL.js +1291 -0
  22. package/dist/chunk-JQD5NASE.js +234 -0
  23. package/dist/chunk-KRHJP5R7.js +592 -0
  24. package/dist/chunk-KWF6WVJE.js +962 -0
  25. package/dist/chunk-LHYQVN3H.js +1038 -0
  26. package/dist/chunk-M3TLQLGC.js +1032 -0
  27. package/dist/chunk-MVW4Q5OP.js +240 -0
  28. package/dist/chunk-NXCZSWLU.js +1294 -0
  29. package/dist/chunk-O25TNRO6.js +607 -0
  30. package/dist/chunk-PNECDA2I.js +884 -0
  31. package/dist/chunk-QIHWRLJR.js +962 -0
  32. package/dist/chunk-QW5YMQ7K.js +882 -0
  33. package/dist/chunk-R5U7XKVJ.js +16 -0
  34. package/dist/chunk-RP2MVQLR.js +962 -0
  35. package/dist/chunk-TP6RXGXA.js +1087 -0
  36. package/dist/chunk-TQQSTITZ.js +655 -0
  37. package/dist/chunk-X24GWXQV.js +1281 -0
  38. package/dist/components/index.d.ts +802 -0
  39. package/dist/components/index.js +149 -0
  40. package/dist/data/index.d.ts +2554 -0
  41. package/dist/data/index.js +51 -0
  42. package/dist/forms/index.d.ts +1596 -0
  43. package/dist/forms/index.js +464 -0
  44. package/dist/index-CQRFZntR.d.ts +867 -0
  45. package/dist/index.d.ts +579 -0
  46. package/dist/index.js +786 -0
  47. package/dist/interactive-D0JkWosD.d.ts +217 -0
  48. package/dist/keyboard/index.d.ts +2 -0
  49. package/dist/keyboard/index.js +43 -0
  50. package/dist/renderers/index.d.ts +546 -0
  51. package/dist/renderers/index.js +2157 -0
  52. package/dist/storybook/index.d.ts +396 -0
  53. package/dist/storybook/index.js +641 -0
  54. package/dist/theme/index.d.ts +1339 -0
  55. package/dist/theme/index.js +123 -0
  56. package/dist/types-Bxu5PAgA.d.ts +710 -0
  57. package/dist/types-CIlop5Ji.d.ts +701 -0
  58. package/dist/types-Ca8p_p5X.d.ts +710 -0
  59. package/package.json +90 -0
  60. package/src/__tests__/components/data/card.test.ts +458 -0
  61. package/src/__tests__/components/data/list.test.ts +473 -0
  62. package/src/__tests__/components/data/metrics.test.ts +541 -0
  63. package/src/__tests__/components/data/table.test.ts +448 -0
  64. package/src/__tests__/components/input/field.test.ts +555 -0
  65. package/src/__tests__/components/input/form.test.ts +870 -0
  66. package/src/__tests__/components/input/search.test.ts +1238 -0
  67. package/src/__tests__/components/input/select.test.ts +658 -0
  68. package/src/__tests__/components/navigation/breadcrumb.test.ts +923 -0
  69. package/src/__tests__/components/navigation/command-palette.test.ts +1095 -0
  70. package/src/__tests__/components/navigation/sidebar.test.ts +1018 -0
  71. package/src/__tests__/components/navigation/tabs.test.ts +995 -0
  72. package/src/__tests__/components.test.tsx +1197 -0
  73. package/src/__tests__/core/compiler.test.ts +986 -0
  74. package/src/__tests__/core/parser.test.ts +785 -0
  75. package/src/__tests__/core/tier-switcher.test.ts +1103 -0
  76. package/src/__tests__/core/types.test.ts +1398 -0
  77. package/src/__tests__/data/collections.test.ts +1337 -0
  78. package/src/__tests__/data/db.test.ts +1265 -0
  79. package/src/__tests__/data/reactive.test.ts +1010 -0
  80. package/src/__tests__/data/sync.test.ts +1614 -0
  81. package/src/__tests__/errors.test.ts +660 -0
  82. package/src/__tests__/forms/integration.test.ts +444 -0
  83. package/src/__tests__/integration.test.ts +905 -0
  84. package/src/__tests__/keyboard.test.ts +1791 -0
  85. package/src/__tests__/renderer.test.ts +489 -0
  86. package/src/__tests__/renderers/ansi-css.test.ts +948 -0
  87. package/src/__tests__/renderers/ansi.test.ts +1366 -0
  88. package/src/__tests__/renderers/ascii.test.ts +1360 -0
  89. package/src/__tests__/renderers/interactive.test.ts +2353 -0
  90. package/src/__tests__/renderers/markdown.test.ts +1483 -0
  91. package/src/__tests__/renderers/text.test.ts +1369 -0
  92. package/src/__tests__/renderers/unicode.test.ts +1307 -0
  93. package/src/__tests__/theme.test.ts +639 -0
  94. package/src/__tests__/utils/assertions.ts +685 -0
  95. package/src/__tests__/utils/index.ts +115 -0
  96. package/src/__tests__/utils/test-renderer.ts +381 -0
  97. package/src/__tests__/utils/utils.test.ts +560 -0
  98. package/src/components/containers/card.ts +56 -0
  99. package/src/components/containers/dialog.ts +53 -0
  100. package/src/components/containers/index.ts +9 -0
  101. package/src/components/containers/panel.ts +59 -0
  102. package/src/components/feedback/badge.ts +40 -0
  103. package/src/components/feedback/index.ts +8 -0
  104. package/src/components/feedback/spinner.ts +23 -0
  105. package/src/components/helpers.ts +81 -0
  106. package/src/components/index.ts +153 -0
  107. package/src/components/layout/breadcrumb.ts +31 -0
  108. package/src/components/layout/index.ts +10 -0
  109. package/src/components/layout/list.ts +29 -0
  110. package/src/components/layout/sidebar.ts +79 -0
  111. package/src/components/layout/table.ts +62 -0
  112. package/src/components/primitives/box.ts +95 -0
  113. package/src/components/primitives/button.ts +54 -0
  114. package/src/components/primitives/index.ts +11 -0
  115. package/src/components/primitives/input.ts +88 -0
  116. package/src/components/primitives/select.ts +97 -0
  117. package/src/components/primitives/text.ts +60 -0
  118. package/src/components/render.ts +155 -0
  119. package/src/components/templates/app.ts +43 -0
  120. package/src/components/templates/index.ts +8 -0
  121. package/src/components/templates/site.ts +54 -0
  122. package/src/components/types.ts +777 -0
  123. package/src/core/compiler.ts +718 -0
  124. package/src/core/parser.ts +127 -0
  125. package/src/core/tier-switcher.ts +607 -0
  126. package/src/core/types.ts +672 -0
  127. package/src/data/collection.ts +316 -0
  128. package/src/data/collections.ts +50 -0
  129. package/src/data/context.tsx +174 -0
  130. package/src/data/db.ts +127 -0
  131. package/src/data/hooks.ts +532 -0
  132. package/src/data/index.ts +138 -0
  133. package/src/data/reactive.ts +1225 -0
  134. package/src/data/saas-collections.ts +375 -0
  135. package/src/data/sync.ts +1213 -0
  136. package/src/data/types.ts +660 -0
  137. package/src/forms/converters.ts +512 -0
  138. package/src/forms/index.ts +133 -0
  139. package/src/forms/schemas.ts +403 -0
  140. package/src/forms/types.ts +476 -0
  141. package/src/index.ts +542 -0
  142. package/src/keyboard/focus.ts +748 -0
  143. package/src/keyboard/index.ts +96 -0
  144. package/src/keyboard/integration.ts +371 -0
  145. package/src/keyboard/manager.ts +377 -0
  146. package/src/keyboard/presets.ts +90 -0
  147. package/src/renderers/ansi-css.ts +576 -0
  148. package/src/renderers/ansi.ts +802 -0
  149. package/src/renderers/ascii.ts +680 -0
  150. package/src/renderers/breadcrumb.ts +480 -0
  151. package/src/renderers/command-palette.ts +802 -0
  152. package/src/renderers/components/field.ts +210 -0
  153. package/src/renderers/components/form.ts +327 -0
  154. package/src/renderers/components/index.ts +21 -0
  155. package/src/renderers/components/search.ts +449 -0
  156. package/src/renderers/components/select.ts +222 -0
  157. package/src/renderers/index.ts +101 -0
  158. package/src/renderers/interactive/component-handlers.ts +622 -0
  159. package/src/renderers/interactive/cursor-manager.ts +147 -0
  160. package/src/renderers/interactive/focus-manager.ts +279 -0
  161. package/src/renderers/interactive/index.ts +661 -0
  162. package/src/renderers/interactive/input-handler.ts +164 -0
  163. package/src/renderers/interactive/keyboard-handler.ts +212 -0
  164. package/src/renderers/interactive/mouse-handler.ts +167 -0
  165. package/src/renderers/interactive/state-manager.ts +109 -0
  166. package/src/renderers/interactive/types.ts +338 -0
  167. package/src/renderers/interactive-string.ts +299 -0
  168. package/src/renderers/interactive.ts +59 -0
  169. package/src/renderers/markdown.ts +950 -0
  170. package/src/renderers/sidebar.ts +549 -0
  171. package/src/renderers/tabs.ts +682 -0
  172. package/src/renderers/text.ts +791 -0
  173. package/src/renderers/unicode.ts +917 -0
  174. package/src/renderers/utils.ts +942 -0
  175. package/src/router/adapters.ts +383 -0
  176. package/src/router/types.ts +140 -0
  177. package/src/router/utils.ts +452 -0
  178. package/src/schemas.ts +205 -0
  179. package/src/storybook/index.ts +91 -0
  180. package/src/storybook/interactive-decorator.tsx +659 -0
  181. package/src/storybook/keyboard-simulator.ts +501 -0
  182. package/src/theme/ansi-codes.ts +80 -0
  183. package/src/theme/box-drawing.ts +132 -0
  184. package/src/theme/color-convert.ts +254 -0
  185. package/src/theme/color-support.ts +321 -0
  186. package/src/theme/index.ts +134 -0
  187. package/src/theme/strip-ansi.ts +50 -0
  188. package/src/theme/tailwind-map.ts +469 -0
  189. package/src/theme/text-styles.ts +206 -0
  190. package/src/theme/theme-system.ts +568 -0
  191. 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'