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