@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,1038 @@
1
+ // src/renderers/interactive-string.ts
2
+ function renderInteractive(node, _context) {
3
+ return renderInteractiveToString(node, 0);
4
+ }
5
+ function renderInteractiveToString(node, depth) {
6
+ const props = node.props || {};
7
+ switch (node.type) {
8
+ case "text":
9
+ return props.content || "";
10
+ case "list":
11
+ return renderInteractiveList(node);
12
+ case "table":
13
+ return renderInteractiveTable(node);
14
+ case "card":
15
+ return renderInteractiveCard(node);
16
+ case "metrics":
17
+ return renderInteractiveMetrics(node);
18
+ case "metric":
19
+ return renderInteractiveSingleMetric(node);
20
+ default:
21
+ if (node.children && Array.isArray(node.children) && node.children.length > 0) {
22
+ return node.children.map((child) => renderInteractiveToString(child, depth + 1)).join("\n");
23
+ }
24
+ return "";
25
+ }
26
+ }
27
+ function renderInteractiveList(node) {
28
+ const props = node.props || {};
29
+ const items = props.items;
30
+ const numbered = props.numbered ?? false;
31
+ const taskList = props.taskList ?? false;
32
+ const style = props.style || "unordered";
33
+ const rawChildren = node.children;
34
+ const children = Array.isArray(rawChildren) ? rawChildren : [];
35
+ const lines = [];
36
+ if (items && items.length > 0) {
37
+ items.forEach((item, index) => {
38
+ let text;
39
+ let marker;
40
+ if (typeof item === "string") {
41
+ text = item;
42
+ marker = numbered || style === "ordered" ? `${index + 1}. ` : "- ";
43
+ } else {
44
+ text = item.text ?? "";
45
+ if (taskList && "checked" in item) {
46
+ marker = item.checked ? "[x] " : "[ ] ";
47
+ } else {
48
+ marker = numbered || style === "ordered" ? `${index + 1}. ` : "- ";
49
+ }
50
+ }
51
+ lines.push(marker + text);
52
+ });
53
+ }
54
+ children.forEach((child, index) => {
55
+ if (child.type === "list-item") {
56
+ const content = child.props?.content || "";
57
+ let marker;
58
+ if (numbered || style === "ordered") {
59
+ marker = `${index + 1}. `;
60
+ } else if (taskList || style === "checklist") {
61
+ const checked = child.props?.checked;
62
+ marker = checked ? "[x] " : "[ ] ";
63
+ } else {
64
+ marker = "- ";
65
+ }
66
+ lines.push(marker + content);
67
+ const childChildren = child.children;
68
+ if (childChildren && Array.isArray(childChildren) && childChildren.length > 0) {
69
+ for (const nestedChild of childChildren) {
70
+ if (nestedChild.type === "list") {
71
+ const nestedLines = renderNestedInteractiveList(nestedChild, 1);
72
+ lines.push(...nestedLines);
73
+ }
74
+ }
75
+ }
76
+ }
77
+ });
78
+ return lines.join("\n");
79
+ }
80
+ function renderNestedInteractiveList(node, depth) {
81
+ const props = node.props || {};
82
+ const style = props.style || "unordered";
83
+ const numbered = props.numbered ?? false;
84
+ const rawChildren = node.children;
85
+ const children = Array.isArray(rawChildren) ? rawChildren : [];
86
+ const lines = [];
87
+ const indent = " ".repeat(depth);
88
+ children.forEach((child, index) => {
89
+ if (child.type === "list-item") {
90
+ const content = child.props?.content || "";
91
+ let marker;
92
+ if (numbered || style === "ordered") {
93
+ marker = `${index + 1}. `;
94
+ } else {
95
+ marker = "- ";
96
+ }
97
+ lines.push(indent + marker + content);
98
+ const childChildren = child.children;
99
+ if (childChildren && Array.isArray(childChildren) && childChildren.length > 0) {
100
+ for (const nestedChild of childChildren) {
101
+ if (nestedChild.type === "list") {
102
+ const nestedLines = renderNestedInteractiveList(nestedChild, depth + 1);
103
+ lines.push(...nestedLines);
104
+ }
105
+ }
106
+ }
107
+ }
108
+ });
109
+ return lines;
110
+ }
111
+ function renderInteractiveTable(node) {
112
+ const props = node.props || {};
113
+ const columns = props.columns || [];
114
+ const nodeData = node.data;
115
+ const propsData = props.data;
116
+ const data = nodeData ?? propsData ?? [];
117
+ if (columns.length === 0) return "";
118
+ const lines = [];
119
+ const headers = columns.map((c) => c.header).join(" ");
120
+ lines.push(headers);
121
+ for (const row of data) {
122
+ const cells = columns.map((c) => {
123
+ const val = row[c.key];
124
+ return val != null ? String(val) : "";
125
+ });
126
+ lines.push(cells.join(" "));
127
+ }
128
+ return lines.join("\n");
129
+ }
130
+ function renderInteractiveCard(node) {
131
+ const props = node.props || {};
132
+ const title = props.title;
133
+ const subtitle = props.subtitle;
134
+ const badge = props.badge;
135
+ const titleAction = props.titleAction;
136
+ const pairs = props.pairs;
137
+ const actions = props.actions;
138
+ const contentLines = [];
139
+ if (title) {
140
+ let titleLine = title;
141
+ if (badge) {
142
+ titleLine += ` [${badge.content}]`;
143
+ }
144
+ if (titleAction) {
145
+ titleLine += ` | ${titleAction.label}`;
146
+ }
147
+ contentLines.push(titleLine);
148
+ }
149
+ if (subtitle) {
150
+ contentLines.push(subtitle);
151
+ }
152
+ if (pairs && pairs.length > 0) {
153
+ for (const pair of pairs) {
154
+ const val = pair.value != null ? String(pair.value) : "";
155
+ contentLines.push(`${pair.key}: ${val}`);
156
+ }
157
+ }
158
+ const rawChildren = node.children;
159
+ const children = Array.isArray(rawChildren) ? rawChildren : [];
160
+ if (children.length > 0) {
161
+ const childContent = children.map((child) => renderInteractiveToString(child, 0)).filter((s) => s).join("\n");
162
+ if (childContent) {
163
+ contentLines.push(childContent);
164
+ }
165
+ }
166
+ if (actions && actions.length > 0) {
167
+ const actionLabels = actions.map((a) => `[ ${a.label} ]`).join(" ");
168
+ contentLines.push(actionLabels);
169
+ }
170
+ return contentLines.join("\n");
171
+ }
172
+ function renderInteractiveMetrics(node) {
173
+ const props = node.props || {};
174
+ const metrics = props.metrics;
175
+ if (!metrics || metrics.length === 0) return "";
176
+ const lines = [];
177
+ for (const m of metrics) {
178
+ const val = m.value != null ? String(m.value) : "";
179
+ let formatted = val;
180
+ if (m.format === "percentage" && !val.includes("%")) {
181
+ formatted = `${val}%`;
182
+ }
183
+ if (m.unit) {
184
+ formatted = `${formatted} ${m.unit}`;
185
+ }
186
+ lines.push(`${m.label}: ${formatted}`);
187
+ }
188
+ return lines.join("\n");
189
+ }
190
+ function renderInteractiveSingleMetric(node) {
191
+ const props = node.props || {};
192
+ const label = props.label;
193
+ const value = props.value;
194
+ const format = props.format;
195
+ const unit = props.unit;
196
+ if (!label) return "";
197
+ const val = value != null ? String(value) : "";
198
+ let formatted = val;
199
+ if (format === "percentage" && !val.includes("%")) {
200
+ formatted = `${val}%`;
201
+ }
202
+ if (unit) {
203
+ formatted = `${formatted} ${unit}`;
204
+ }
205
+ return `${label}: ${formatted}`;
206
+ }
207
+
208
+ // src/renderers/interactive.ts
209
+ async function createInteractiveRenderer(config) {
210
+ const { createCliRenderer } = await import("@opentui/core");
211
+ const cliRenderer = await createCliRenderer();
212
+ let destroyed = false;
213
+ const focusables = /* @__PURE__ */ new Map();
214
+ let focusedId = null;
215
+ let activeGroup = null;
216
+ const focusTrapStack = [];
217
+ let registrationCounter = 0;
218
+ const wrapFocus = config?.wrapFocus ?? true;
219
+ const vimBindings = config?.vimBindings ?? false;
220
+ const sequenceTimeout = config?.sequenceTimeout ?? 500;
221
+ const targetFps = config?.targetFps ?? 60;
222
+ const keyHandlers = /* @__PURE__ */ new Map();
223
+ const sequenceHandlers = /* @__PURE__ */ new Map();
224
+ let pendingSequence = "";
225
+ let sequenceTimer = null;
226
+ let mode = "normal";
227
+ let searchModeHandler = null;
228
+ let cancelHandler = null;
229
+ const clickables = /* @__PURE__ */ new Map();
230
+ const clickHandlers = [];
231
+ let scrollHandler = null;
232
+ let cursorPosition = { x: 0, y: 0 };
233
+ let cursorVisible = true;
234
+ let cursorStyle = "block";
235
+ let cursorBlinking = false;
236
+ const state = /* @__PURE__ */ new Map();
237
+ const stateSubscribers = /* @__PURE__ */ new Map();
238
+ let renderHandler = null;
239
+ const inputs = /* @__PURE__ */ new Map();
240
+ const components = /* @__PURE__ */ new Map();
241
+ const getSortedFocusableIds = () => {
242
+ if (destroyed) return [];
243
+ const entries = Array.from(focusables.values()).filter((e) => {
244
+ if (e.options.tabIndex < 0) return false;
245
+ if (e.options.disabled) return false;
246
+ if (activeGroup !== null && e.options.group !== activeGroup) return false;
247
+ if (focusTrapStack.length > 0) {
248
+ const currentTrap = focusTrapStack[focusTrapStack.length - 1];
249
+ if (e.options.group !== currentTrap.group) return false;
250
+ }
251
+ return true;
252
+ }).sort((a, b) => {
253
+ if (a.options.tabIndex !== b.options.tabIndex) {
254
+ return a.options.tabIndex - b.options.tabIndex;
255
+ }
256
+ return a.registrationOrder - b.registrationOrder;
257
+ });
258
+ return entries.map((e) => e.id);
259
+ };
260
+ const focusByIndex = (index) => {
261
+ const ids = getSortedFocusableIds();
262
+ if (ids.length === 0 || index < 0 || index >= ids.length) return;
263
+ renderer.focusById(ids[index]);
264
+ };
265
+ const getCurrentFocusIndex = () => {
266
+ if (focusedId === null) return -1;
267
+ const ids = getSortedFocusableIds();
268
+ return ids.indexOf(focusedId);
269
+ };
270
+ const handleComponentKey = (key) => {
271
+ if (!focusedId) return false;
272
+ const input = inputs.get(focusedId);
273
+ if (input) {
274
+ return handleInputKey(focusedId, input, key);
275
+ }
276
+ const component = components.get(focusedId);
277
+ if (component) {
278
+ return handleComponentKeyInternal(focusedId, component, key);
279
+ }
280
+ const focusable = focusables.get(focusedId);
281
+ if (focusable) {
282
+ if (key === "enter" && focusable.options.onActivate) {
283
+ focusable.options.onActivate({ id: focusedId });
284
+ return true;
285
+ }
286
+ if (key === "space" && focusable.options.onToggle) {
287
+ focusable.options.onToggle({ id: focusedId });
288
+ return true;
289
+ }
290
+ }
291
+ return false;
292
+ };
293
+ const handleInputKey = (id, input, key) => {
294
+ const { currentValue, currentCursorIndex, onChange, onSubmit, maxLength, validate, mask, multiline } = input;
295
+ if (key === "backspace") {
296
+ if (currentCursorIndex > 0) {
297
+ const newValue = currentValue.slice(0, currentCursorIndex - 1) + currentValue.slice(currentCursorIndex);
298
+ input.currentValue = newValue;
299
+ input.currentCursorIndex = currentCursorIndex - 1;
300
+ onChange?.(newValue);
301
+ }
302
+ return true;
303
+ }
304
+ if (key === "delete") {
305
+ if (currentCursorIndex < currentValue.length) {
306
+ const newValue = currentValue.slice(0, currentCursorIndex) + currentValue.slice(currentCursorIndex + 1);
307
+ input.currentValue = newValue;
308
+ onChange?.(newValue);
309
+ }
310
+ return true;
311
+ }
312
+ if (key === "left") {
313
+ if (currentCursorIndex > 0) {
314
+ input.currentCursorIndex = currentCursorIndex - 1;
315
+ }
316
+ return true;
317
+ }
318
+ if (key === "right") {
319
+ if (currentCursorIndex < currentValue.length) {
320
+ input.currentCursorIndex = currentCursorIndex + 1;
321
+ }
322
+ return true;
323
+ }
324
+ if (key === "home") {
325
+ input.currentCursorIndex = 0;
326
+ return true;
327
+ }
328
+ if (key === "end") {
329
+ input.currentCursorIndex = currentValue.length;
330
+ return true;
331
+ }
332
+ if (key === "enter") {
333
+ if (multiline) {
334
+ const newValue = currentValue + "\n";
335
+ input.currentValue = newValue;
336
+ input.currentCursorIndex = newValue.length;
337
+ onChange?.(newValue);
338
+ } else {
339
+ onSubmit?.(currentValue);
340
+ }
341
+ return true;
342
+ }
343
+ if (key === "ctrl+enter") {
344
+ onSubmit?.(currentValue);
345
+ return true;
346
+ }
347
+ if (key.length === 1) {
348
+ if (maxLength !== void 0 && currentValue.length >= maxLength) {
349
+ return true;
350
+ }
351
+ let newValue = currentValue + key;
352
+ if (mask) {
353
+ const maskChar = mask[newValue.length - 1];
354
+ if (maskChar && maskChar !== "#") {
355
+ newValue = currentValue + maskChar + key;
356
+ }
357
+ }
358
+ if (validate && !validate(newValue)) {
359
+ return true;
360
+ }
361
+ input.currentValue = newValue;
362
+ input.currentCursorIndex = newValue.length;
363
+ onChange?.(newValue);
364
+ return true;
365
+ }
366
+ return false;
367
+ };
368
+ const handleComponentKeyInternal = (id, component, key) => {
369
+ const { type, onChange, onSelect, onToggle, options, selectedIndex, checked, value, min, max, step, searchable, onScroll, nodes, selectedId } = component;
370
+ if (type === "select") {
371
+ const opts = options;
372
+ const idx = selectedIndex;
373
+ if (key === "down" || key === "j") {
374
+ const newIdx = Math.min(idx + 1, opts.length - 1);
375
+ component.selectedIndex = newIdx;
376
+ onChange?.(newIdx, opts[newIdx]);
377
+ return true;
378
+ }
379
+ if (key === "up" || key === "k") {
380
+ const newIdx = Math.max(idx - 1, 0);
381
+ component.selectedIndex = newIdx;
382
+ onChange?.(newIdx, opts[newIdx]);
383
+ return true;
384
+ }
385
+ if (key === "enter") {
386
+ if (onSelect) {
387
+ onSelect(idx, opts[idx]);
388
+ }
389
+ component.isOpen = !component.isOpen;
390
+ return true;
391
+ }
392
+ if (searchable && key.length === 1) {
393
+ const matchIdx = opts.findIndex((o) => o.toLowerCase().startsWith(key.toLowerCase()));
394
+ if (matchIdx !== -1) {
395
+ component.selectedIndex = matchIdx;
396
+ onChange?.(matchIdx, opts[matchIdx]);
397
+ }
398
+ return true;
399
+ }
400
+ return false;
401
+ }
402
+ if (type === "checkbox") {
403
+ if (key === "space" || key === "enter") {
404
+ const newChecked = !checked;
405
+ component.checked = newChecked;
406
+ component.onChange?.(newChecked);
407
+ return true;
408
+ }
409
+ return false;
410
+ }
411
+ if (type === "radiogroup") {
412
+ const opts = options;
413
+ const idx = selectedIndex;
414
+ if (key === "down" || key === "up") {
415
+ const newIdx = key === "down" ? Math.min(idx + 1, opts.length - 1) : Math.max(idx - 1, 0);
416
+ component.selectedIndex = newIdx;
417
+ component.onChange?.(newIdx);
418
+ return true;
419
+ }
420
+ if (key === "space") {
421
+ onSelect?.(idx, opts[idx]);
422
+ return true;
423
+ }
424
+ return false;
425
+ }
426
+ if (type === "slider") {
427
+ const v = value;
428
+ const minV = min;
429
+ const maxV = max;
430
+ const stepV = step ?? 1;
431
+ if (key === "right" || key === "up") {
432
+ const newV = Math.min(v + stepV, maxV);
433
+ component.value = newV;
434
+ component.onChange?.(newV);
435
+ return true;
436
+ }
437
+ if (key === "left" || key === "down") {
438
+ const newV = Math.max(v - stepV, minV);
439
+ component.value = newV;
440
+ component.onChange?.(newV);
441
+ return true;
442
+ }
443
+ if (key === "home") {
444
+ component.value = minV;
445
+ component.onChange?.(minV);
446
+ return true;
447
+ }
448
+ if (key === "end") {
449
+ component.value = maxV;
450
+ component.onChange?.(maxV);
451
+ return true;
452
+ }
453
+ return false;
454
+ }
455
+ if (type === "tree") {
456
+ const nodeList = nodes;
457
+ const currentId = selectedId;
458
+ const currentNode = nodeList.find((n) => n.id === currentId);
459
+ if (key === "enter" && currentNode?.children) {
460
+ const newExpanded = !currentNode.expanded;
461
+ currentNode.expanded = newExpanded;
462
+ onToggle?.(currentId, newExpanded);
463
+ return true;
464
+ }
465
+ if (key === "down") {
466
+ const visibleNodes = getVisibleTreeNodes(nodeList);
467
+ const currentIndex = visibleNodes.findIndex((n) => n.id === currentId);
468
+ if (currentIndex < visibleNodes.length - 1) {
469
+ const nextNode = visibleNodes[currentIndex + 1];
470
+ component.selectedId = nextNode.id;
471
+ component.onSelect?.(nextNode.id);
472
+ }
473
+ return true;
474
+ }
475
+ return false;
476
+ }
477
+ if (type === "scrollview") {
478
+ const currentScrollY = component.scrollY ?? 0;
479
+ const viewportHeight = component.viewportHeight ?? 20;
480
+ if (key === "down") {
481
+ const newScrollY = currentScrollY + 1;
482
+ component.scrollY = newScrollY;
483
+ onScroll?.({ scrollY: newScrollY });
484
+ return true;
485
+ }
486
+ if (key === "up") {
487
+ const newScrollY = Math.max(0, currentScrollY - 1);
488
+ component.scrollY = newScrollY;
489
+ onScroll?.({ scrollY: newScrollY });
490
+ return true;
491
+ }
492
+ if (key === "pagedown") {
493
+ const newScrollY = currentScrollY + viewportHeight;
494
+ component.scrollY = newScrollY;
495
+ onScroll?.({ scrollY: newScrollY });
496
+ return true;
497
+ }
498
+ if (key === "pageup") {
499
+ const newScrollY = Math.max(0, currentScrollY - viewportHeight);
500
+ component.scrollY = newScrollY;
501
+ onScroll?.({ scrollY: newScrollY });
502
+ return true;
503
+ }
504
+ return false;
505
+ }
506
+ return false;
507
+ };
508
+ const getVisibleTreeNodes = (nodes) => {
509
+ const result = [];
510
+ const rootNodes = nodes.filter((n) => !n.parent);
511
+ const addNode = (node) => {
512
+ result.push(node);
513
+ if (node.expanded && node.children) {
514
+ for (const childId of node.children) {
515
+ const child = nodes.find((n) => n.id === childId);
516
+ if (child) addNode(child);
517
+ }
518
+ }
519
+ };
520
+ for (const root of rootNodes) {
521
+ addNode(root);
522
+ }
523
+ return result;
524
+ };
525
+ const setupBuiltInKeyHandlers = () => {
526
+ keyHandlers.set("tab", [{ handler: () => {
527
+ renderer.focusNext();
528
+ return true;
529
+ }, priority: -100 }]);
530
+ keyHandlers.set("shift+tab", [{ handler: () => {
531
+ renderer.focusPrev();
532
+ return true;
533
+ }, priority: -100 }]);
534
+ keyHandlers.set("down", [{ handler: () => {
535
+ if (handleComponentKey("down")) return true;
536
+ renderer.focusNext();
537
+ return true;
538
+ }, priority: -100 }]);
539
+ keyHandlers.set("up", [{ handler: () => {
540
+ if (handleComponentKey("up")) return true;
541
+ renderer.focusPrev();
542
+ return true;
543
+ }, priority: -100 }]);
544
+ keyHandlers.set("left", [{ handler: () => {
545
+ if (handleComponentKey("left")) return true;
546
+ renderer.focusPrev();
547
+ return true;
548
+ }, priority: -100 }]);
549
+ keyHandlers.set("right", [{ handler: () => {
550
+ if (handleComponentKey("right")) return true;
551
+ renderer.focusNext();
552
+ return true;
553
+ }, priority: -100 }]);
554
+ keyHandlers.set("enter", [{ handler: () => handleComponentKey("enter"), priority: -100 }]);
555
+ keyHandlers.set("space", [{ handler: () => handleComponentKey("space"), priority: -100 }]);
556
+ keyHandlers.set("escape", [{ handler: () => {
557
+ pendingSequence = "";
558
+ if (sequenceTimer) {
559
+ clearTimeout(sequenceTimer);
560
+ sequenceTimer = null;
561
+ }
562
+ if (mode === "search") {
563
+ mode = "normal";
564
+ return true;
565
+ }
566
+ cancelHandler?.();
567
+ return true;
568
+ }, priority: -100 }]);
569
+ keyHandlers.set("backspace", [{ handler: () => handleComponentKey("backspace"), priority: -100 }]);
570
+ keyHandlers.set("delete", [{ handler: () => handleComponentKey("delete"), priority: -100 }]);
571
+ keyHandlers.set("home", [{ handler: () => handleComponentKey("home"), priority: -100 }]);
572
+ keyHandlers.set("end", [{ handler: () => handleComponentKey("end"), priority: -100 }]);
573
+ keyHandlers.set("pagedown", [{ handler: () => handleComponentKey("pagedown"), priority: -100 }]);
574
+ keyHandlers.set("pageup", [{ handler: () => handleComponentKey("pageup"), priority: -100 }]);
575
+ keyHandlers.set("ctrl+enter", [{ handler: () => handleComponentKey("ctrl+enter"), priority: -100 }]);
576
+ if (vimBindings) {
577
+ keyHandlers.set("j", [{ handler: () => {
578
+ if (handleComponentKey("j")) return true;
579
+ renderer.focusNext();
580
+ return true;
581
+ }, priority: -100 }]);
582
+ keyHandlers.set("k", [{ handler: () => {
583
+ if (handleComponentKey("k")) return true;
584
+ renderer.focusPrev();
585
+ return true;
586
+ }, priority: -100 }]);
587
+ keyHandlers.set("h", [{ handler: () => {
588
+ const ids = getSortedFocusableIds();
589
+ const currentIdx = getCurrentFocusIndex();
590
+ if (currentIdx <= 0) return true;
591
+ const currentFocusable = focusables.get(focusedId);
592
+ const currentColumn = currentFocusable?.options.column ?? 0;
593
+ for (let i = currentIdx - 1; i >= 0; i--) {
594
+ const f = focusables.get(ids[i]);
595
+ if (f && (f.options.column ?? 0) < currentColumn) {
596
+ renderer.focusById(ids[i]);
597
+ return true;
598
+ }
599
+ }
600
+ renderer.focusPrev();
601
+ return true;
602
+ }, priority: -100 }]);
603
+ keyHandlers.set("l", [{ handler: () => {
604
+ const ids = getSortedFocusableIds();
605
+ const currentIdx = getCurrentFocusIndex();
606
+ if (currentIdx < 0 || currentIdx >= ids.length - 1) return true;
607
+ const currentFocusable = focusables.get(focusedId);
608
+ const currentColumn = currentFocusable?.options.column ?? 0;
609
+ for (let i = currentIdx + 1; i < ids.length; i++) {
610
+ const f = focusables.get(ids[i]);
611
+ if (f && (f.options.column ?? 0) > currentColumn) {
612
+ renderer.focusById(ids[i]);
613
+ return true;
614
+ }
615
+ }
616
+ renderer.focusNext();
617
+ return true;
618
+ }, priority: -100 }]);
619
+ keyHandlers.set("g", [{ handler: () => {
620
+ return false;
621
+ }, priority: -100 }]);
622
+ keyHandlers.set("G", [{ handler: () => {
623
+ const ids = getSortedFocusableIds();
624
+ if (ids.length > 0) {
625
+ renderer.focusById(ids[ids.length - 1]);
626
+ }
627
+ return true;
628
+ }, priority: -100 }]);
629
+ keyHandlers.set("/", [{ handler: () => {
630
+ mode = "search";
631
+ searchModeHandler?.();
632
+ return true;
633
+ }, priority: -100 }]);
634
+ sequenceHandlers.set("gg", () => {
635
+ const ids = getSortedFocusableIds();
636
+ if (ids.length > 0) {
637
+ renderer.focusById(ids[0]);
638
+ }
639
+ });
640
+ }
641
+ };
642
+ const renderer = {
643
+ width: cliRenderer.width,
644
+ height: cliRenderer.height,
645
+ // Lifecycle
646
+ start() {
647
+ if (destroyed) return;
648
+ cliRenderer.start();
649
+ },
650
+ stop() {
651
+ if (destroyed) return;
652
+ cliRenderer.stop();
653
+ },
654
+ destroy() {
655
+ if (destroyed) return;
656
+ destroyed = true;
657
+ cliRenderer.destroy();
658
+ focusables.clear();
659
+ clickables.clear();
660
+ keyHandlers.clear();
661
+ sequenceHandlers.clear();
662
+ inputs.clear();
663
+ components.clear();
664
+ state.clear();
665
+ stateSubscribers.clear();
666
+ clickHandlers.length = 0;
667
+ focusedId = null;
668
+ if (sequenceTimer) {
669
+ clearTimeout(sequenceTimer);
670
+ sequenceTimer = null;
671
+ }
672
+ },
673
+ requestRender() {
674
+ if (destroyed) return;
675
+ cliRenderer.requestRender();
676
+ },
677
+ // Focus management
678
+ focusNext() {
679
+ if (destroyed) return;
680
+ const ids = getSortedFocusableIds();
681
+ if (ids.length === 0) return;
682
+ const currentIdx = getCurrentFocusIndex();
683
+ if (currentIdx === -1) {
684
+ this.focusById(ids[0]);
685
+ } else if (currentIdx < ids.length - 1) {
686
+ this.focusById(ids[currentIdx + 1]);
687
+ } else if (wrapFocus) {
688
+ this.focusById(ids[0]);
689
+ }
690
+ },
691
+ focusPrev() {
692
+ if (destroyed) return;
693
+ const ids = getSortedFocusableIds();
694
+ if (ids.length === 0) return;
695
+ const currentIdx = getCurrentFocusIndex();
696
+ if (currentIdx === -1) {
697
+ this.focusById(ids[ids.length - 1]);
698
+ } else if (currentIdx > 0) {
699
+ this.focusById(ids[currentIdx - 1]);
700
+ } else if (wrapFocus) {
701
+ this.focusById(ids[ids.length - 1]);
702
+ }
703
+ },
704
+ focusById(id) {
705
+ if (destroyed) return;
706
+ const entry = focusables.get(id);
707
+ if (!entry) return;
708
+ if (focusedId === id) return;
709
+ if (focusedId) {
710
+ const prevEntry = focusables.get(focusedId);
711
+ if (prevEntry?.options.onBlur) {
712
+ prevEntry.options.onBlur({ id: focusedId });
713
+ }
714
+ }
715
+ focusedId = id;
716
+ if (entry.options.onFocus) {
717
+ entry.options.onFocus({ id });
718
+ }
719
+ if (entry.options.cursorPosition) {
720
+ cursorPosition = { ...entry.options.cursorPosition };
721
+ }
722
+ if (entry.options.showCursor !== void 0) {
723
+ cursorVisible = entry.options.showCursor;
724
+ }
725
+ },
726
+ getFocusedId() {
727
+ if (destroyed) return null;
728
+ return focusedId;
729
+ },
730
+ getFocusableIds() {
731
+ return getSortedFocusableIds();
732
+ },
733
+ registerFocusable(id, options) {
734
+ if (destroyed) return;
735
+ focusables.delete(id);
736
+ focusables.set(id, { id, options, registrationOrder: registrationCounter++ });
737
+ },
738
+ unregisterFocusable(id) {
739
+ if (destroyed) return;
740
+ focusables.delete(id);
741
+ if (focusedId === id) {
742
+ focusedId = null;
743
+ }
744
+ },
745
+ setActiveGroup(group) {
746
+ if (destroyed) return;
747
+ activeGroup = group;
748
+ },
749
+ pushFocusTrap(group) {
750
+ if (destroyed) return;
751
+ focusTrapStack.push({ group, previousFocusId: focusedId });
752
+ activeGroup = group;
753
+ },
754
+ popFocusTrap() {
755
+ if (destroyed) return;
756
+ const trap = focusTrapStack.pop();
757
+ if (trap) {
758
+ if (trap.previousFocusId) {
759
+ this.focusById(trap.previousFocusId);
760
+ }
761
+ if (focusTrapStack.length > 0) {
762
+ activeGroup = focusTrapStack[focusTrapStack.length - 1].group;
763
+ } else {
764
+ activeGroup = null;
765
+ }
766
+ }
767
+ },
768
+ // Keyboard management
769
+ onKeyPress(key, handler, options) {
770
+ if (destroyed) return;
771
+ const handlers = keyHandlers.get(key) || [];
772
+ if (handlers.some((h) => h.handler === handler)) return;
773
+ handlers.push({ handler, priority: options?.priority ?? 0 });
774
+ handlers.sort((a, b) => b.priority - a.priority);
775
+ keyHandlers.set(key, handlers);
776
+ },
777
+ offKeyPress(key, handler) {
778
+ if (destroyed) return;
779
+ const handlers = keyHandlers.get(key);
780
+ if (handlers) {
781
+ const idx = handlers.findIndex((h) => h.handler === handler);
782
+ if (idx !== -1) {
783
+ handlers.splice(idx, 1);
784
+ }
785
+ }
786
+ },
787
+ emitKey(key) {
788
+ if (destroyed) return;
789
+ if (sequenceHandlers.size > 0) {
790
+ const newSequence = pendingSequence + key;
791
+ const sequenceHandler = sequenceHandlers.get(newSequence);
792
+ if (sequenceHandler) {
793
+ pendingSequence = "";
794
+ if (sequenceTimer) {
795
+ clearTimeout(sequenceTimer);
796
+ sequenceTimer = null;
797
+ }
798
+ sequenceHandler();
799
+ return;
800
+ }
801
+ const couldBeSequence = Array.from(sequenceHandlers.keys()).some((s) => s.startsWith(newSequence));
802
+ if (couldBeSequence) {
803
+ pendingSequence = newSequence;
804
+ if (sequenceTimer) {
805
+ clearTimeout(sequenceTimer);
806
+ }
807
+ sequenceTimer = setTimeout(() => {
808
+ pendingSequence = "";
809
+ sequenceTimer = null;
810
+ }, sequenceTimeout);
811
+ return;
812
+ }
813
+ pendingSequence = "";
814
+ if (sequenceTimer) {
815
+ clearTimeout(sequenceTimer);
816
+ sequenceTimer = null;
817
+ }
818
+ }
819
+ const handlers = keyHandlers.get(key);
820
+ if (handlers) {
821
+ for (const { handler } of handlers) {
822
+ const result = handler();
823
+ if (result === true) {
824
+ return;
825
+ }
826
+ }
827
+ }
828
+ if (key.length === 1 && focusedId) {
829
+ handleComponentKey(key);
830
+ }
831
+ },
832
+ onKeySequence(sequence, handler) {
833
+ if (destroyed) return;
834
+ sequenceHandlers.set(sequence, handler);
835
+ },
836
+ getPendingSequence() {
837
+ if (destroyed) return "";
838
+ return pendingSequence;
839
+ },
840
+ onSearchMode(handler) {
841
+ if (destroyed) return;
842
+ searchModeHandler = handler;
843
+ },
844
+ onCancel(handler) {
845
+ if (destroyed) return;
846
+ cancelHandler = handler;
847
+ },
848
+ getMode() {
849
+ if (destroyed) return "normal";
850
+ return mode;
851
+ },
852
+ // Mouse management
853
+ onClick(handler) {
854
+ if (destroyed) return;
855
+ clickHandlers.push(handler);
856
+ },
857
+ offClick(handler) {
858
+ if (destroyed) return;
859
+ const idx = clickHandlers.indexOf(handler);
860
+ if (idx !== -1) {
861
+ clickHandlers.splice(idx, 1);
862
+ }
863
+ },
864
+ emitClick(x, y, options) {
865
+ if (destroyed) return;
866
+ const clickableEntries = Array.from(clickables.entries()).filter(([, c]) => {
867
+ return x >= c.x && x < c.x + c.width && y >= c.y && y < c.y + c.height;
868
+ }).sort((a, b) => (b[1].zIndex ?? 0) - (a[1].zIndex ?? 0));
869
+ if (clickableEntries.length > 0) {
870
+ const [id, clickable] = clickableEntries[0];
871
+ if (clickable.focusable !== false && focusables.has(id)) {
872
+ this.focusById(id);
873
+ }
874
+ if (options?.button === "right" && clickable.onRightClick) {
875
+ clickable.onRightClick();
876
+ } else if (options?.clickCount === 2 && clickable.onDoubleClick) {
877
+ clickable.onDoubleClick();
878
+ } else if (clickable.onClick) {
879
+ clickable.onClick({ x, y, id });
880
+ }
881
+ }
882
+ for (const handler of clickHandlers) {
883
+ handler({ x, y });
884
+ }
885
+ },
886
+ registerClickable(id, options) {
887
+ if (destroyed) return;
888
+ clickables.set(id, options);
889
+ },
890
+ unregisterClickable(id) {
891
+ if (destroyed) return;
892
+ clickables.delete(id);
893
+ },
894
+ getClickableAreas() {
895
+ if (destroyed) return [];
896
+ return Array.from(clickables.entries()).map(([id, options]) => ({ id, ...options }));
897
+ },
898
+ onScroll(handler) {
899
+ if (destroyed) return;
900
+ scrollHandler = handler;
901
+ },
902
+ emitScroll(_x, _y, options) {
903
+ if (destroyed) return;
904
+ scrollHandler?.(options);
905
+ },
906
+ // Cursor management
907
+ setCursorPosition(x, y) {
908
+ if (destroyed) return;
909
+ cursorPosition = { x, y };
910
+ },
911
+ getCursorPosition() {
912
+ if (destroyed) return { x: 0, y: 0 };
913
+ return { ...cursorPosition };
914
+ },
915
+ showCursor() {
916
+ if (destroyed) return;
917
+ cursorVisible = true;
918
+ },
919
+ hideCursor() {
920
+ if (destroyed) return;
921
+ cursorVisible = false;
922
+ },
923
+ isCursorVisible() {
924
+ if (destroyed) return false;
925
+ return cursorVisible;
926
+ },
927
+ setCursorStyle(style) {
928
+ if (destroyed) return;
929
+ cursorStyle = style;
930
+ },
931
+ getCursorStyle() {
932
+ if (destroyed) return "block";
933
+ return cursorStyle;
934
+ },
935
+ setCursorBlink(blink) {
936
+ if (destroyed) return;
937
+ cursorBlinking = blink;
938
+ },
939
+ isCursorBlinking() {
940
+ if (destroyed) return false;
941
+ return cursorBlinking;
942
+ },
943
+ updateCursorPosition(id, position) {
944
+ if (destroyed) return;
945
+ const entry = focusables.get(id);
946
+ if (entry) {
947
+ entry.options.cursorPosition = position;
948
+ }
949
+ if (focusedId === id) {
950
+ cursorPosition = { ...position };
951
+ }
952
+ },
953
+ // State management
954
+ setState(key, value) {
955
+ if (destroyed) return;
956
+ const oldValue = state.get(key);
957
+ state.set(key, value);
958
+ const subscribers = stateSubscribers.get(key);
959
+ if (subscribers) {
960
+ for (const subscriber of subscribers) {
961
+ subscriber(value, oldValue);
962
+ }
963
+ }
964
+ renderHandler?.();
965
+ },
966
+ getState(key) {
967
+ if (destroyed) return void 0;
968
+ return state.get(key);
969
+ },
970
+ subscribe(key, subscriber) {
971
+ if (destroyed) return () => {
972
+ };
973
+ if (!stateSubscribers.has(key)) {
974
+ stateSubscribers.set(key, /* @__PURE__ */ new Set());
975
+ }
976
+ stateSubscribers.get(key).add(subscriber);
977
+ return () => {
978
+ const subscribers = stateSubscribers.get(key);
979
+ if (subscribers) {
980
+ subscribers.delete(subscriber);
981
+ }
982
+ };
983
+ },
984
+ onRender(handler) {
985
+ if (destroyed) return;
986
+ renderHandler = handler;
987
+ },
988
+ getTargetFps() {
989
+ return targetFps;
990
+ },
991
+ // Input management
992
+ registerInput(id, options) {
993
+ if (destroyed) return;
994
+ inputs.set(id, {
995
+ ...options,
996
+ currentValue: options.value,
997
+ currentCursorIndex: options.cursorIndex ?? options.value.length
998
+ });
999
+ this.registerFocusable(id, { tabIndex: 0 });
1000
+ },
1001
+ getInputCursorIndex(id) {
1002
+ if (destroyed) return 0;
1003
+ const input = inputs.get(id);
1004
+ return input?.currentCursorIndex ?? 0;
1005
+ },
1006
+ // Component management
1007
+ registerComponent(id, options) {
1008
+ if (destroyed) return;
1009
+ components.set(id, { ...options });
1010
+ if (["select", "checkbox", "radiogroup", "slider", "tree", "scrollview"].includes(options.type)) {
1011
+ this.registerFocusable(id, { tabIndex: 0 });
1012
+ }
1013
+ },
1014
+ unregisterComponent(id) {
1015
+ if (destroyed) return;
1016
+ components.delete(id);
1017
+ this.unregisterFocusable(id);
1018
+ },
1019
+ getComponent(id) {
1020
+ if (destroyed) return void 0;
1021
+ return components.get(id);
1022
+ },
1023
+ updateComponent(id, updates) {
1024
+ if (destroyed) return;
1025
+ const existing = components.get(id);
1026
+ if (existing) {
1027
+ Object.assign(existing, updates);
1028
+ }
1029
+ }
1030
+ };
1031
+ setupBuiltInKeyHandlers();
1032
+ return renderer;
1033
+ }
1034
+
1035
+ export {
1036
+ renderInteractive,
1037
+ createInteractiveRenderer
1038
+ };