@fairyhunter13/opentui-core 0.1.88 → 0.1.90

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 (570) hide show
  1. package/dev/keypress-debug-renderer.ts +148 -0
  2. package/dev/keypress-debug.ts +43 -0
  3. package/dev/print-env-vars.ts +32 -0
  4. package/dev/test-tmux-graphics-334.sh +68 -0
  5. package/dev/thai-debug-test.ts +68 -0
  6. package/docs/development.md +141 -0
  7. package/docs/env-vars.md +140 -0
  8. package/docs/getting-started.md +353 -0
  9. package/docs/renderables-vs-constructs.md +159 -0
  10. package/docs/tree-sitter.md +311 -0
  11. package/package.json +61 -52
  12. package/scripts/build.ts +400 -0
  13. package/scripts/publish.ts +60 -0
  14. package/src/3d/SpriteResourceManager.ts +286 -0
  15. package/src/3d/SpriteUtils.ts +71 -0
  16. package/src/3d/TextureUtils.ts +196 -0
  17. package/src/3d/ThreeRenderable.ts +197 -0
  18. package/src/3d/WGPURenderer.ts +294 -0
  19. package/src/3d/animation/ExplodingSpriteEffect.ts +513 -0
  20. package/src/3d/animation/PhysicsExplodingSpriteEffect.ts +429 -0
  21. package/src/3d/animation/SpriteAnimator.ts +633 -0
  22. package/src/3d/animation/SpriteParticleGenerator.ts +435 -0
  23. package/src/3d/canvas.ts +464 -0
  24. package/src/3d/index.ts +12 -0
  25. package/src/3d/physics/PlanckPhysicsAdapter.ts +72 -0
  26. package/src/3d/physics/RapierPhysicsAdapter.ts +66 -0
  27. package/src/3d/physics/physics-interface.ts +31 -0
  28. package/src/3d/shaders/supersampling.wgsl +201 -0
  29. package/src/3d.ts +3 -0
  30. package/src/NativeSpanFeed.ts +300 -0
  31. package/src/Renderable.ts +1698 -0
  32. package/src/__snapshots__/buffer.test.ts.snap +28 -0
  33. package/src/animation/Timeline.test.ts +2709 -0
  34. package/src/animation/Timeline.ts +598 -0
  35. package/src/ansi.ts +18 -0
  36. package/src/benchmark/latest-all-bench-run.json +707 -0
  37. package/src/benchmark/latest-async-bench-run.json +336 -0
  38. package/src/benchmark/latest-default-bench-run.json +657 -0
  39. package/src/benchmark/latest-large-bench-run.json +707 -0
  40. package/src/benchmark/latest-quick-bench-run.json +207 -0
  41. package/src/benchmark/markdown-benchmark.ts +1804 -0
  42. package/src/benchmark/native-span-feed-async-benchmark.ts +355 -0
  43. package/src/benchmark/native-span-feed-benchmark.md +56 -0
  44. package/src/benchmark/native-span-feed-benchmark.ts +596 -0
  45. package/src/benchmark/native-span-feed-compare.ts +280 -0
  46. package/src/benchmark/renderer-benchmark.ts +754 -0
  47. package/src/benchmark/text-table-benchmark.ts +947 -0
  48. package/src/buffer.test.ts +291 -0
  49. package/src/buffer.ts +519 -0
  50. package/src/console.test.ts +612 -0
  51. package/src/console.ts +1255 -0
  52. package/src/edit-buffer.test.ts +1769 -0
  53. package/src/edit-buffer.ts +411 -0
  54. package/src/editor-view.test.ts +1032 -0
  55. package/src/editor-view.ts +284 -0
  56. package/src/examples/ascii-font-selection-demo.ts +245 -0
  57. package/src/examples/assets/Water_2_M_Normal.jpg +0 -0
  58. package/src/examples/assets/concrete.png +0 -0
  59. package/src/examples/assets/crate.png +0 -0
  60. package/src/examples/assets/crate_emissive.png +0 -0
  61. package/src/examples/assets/forrest_background.png +0 -0
  62. package/src/examples/assets/hast-example.json +1018 -0
  63. package/src/examples/assets/heart.png +0 -0
  64. package/src/examples/assets/main_char_heavy_attack.png +0 -0
  65. package/src/examples/assets/main_char_idle.png +0 -0
  66. package/src/examples/assets/main_char_jump_end.png +0 -0
  67. package/src/examples/assets/main_char_jump_landing.png +0 -0
  68. package/src/examples/assets/main_char_jump_start.png +0 -0
  69. package/src/examples/assets/main_char_run_loop.png +0 -0
  70. package/src/examples/assets/roughness_map.jpg +0 -0
  71. package/src/examples/build.ts +115 -0
  72. package/src/examples/code-demo.ts +584 -0
  73. package/src/examples/console-demo.ts +358 -0
  74. package/src/examples/core-plugin-slots-demo.ts +759 -0
  75. package/src/examples/diff-demo.ts +699 -0
  76. package/src/examples/draggable-three-demo.ts +259 -0
  77. package/src/examples/editor-demo.ts +322 -0
  78. package/src/examples/extmarks-demo.ts +204 -0
  79. package/src/examples/focus-restore-demo.ts +310 -0
  80. package/src/examples/fonts.ts +245 -0
  81. package/src/examples/fractal-shader-demo.ts +268 -0
  82. package/src/examples/framebuffer-demo.ts +674 -0
  83. package/src/examples/full-unicode-demo.ts +181 -0
  84. package/src/examples/golden-star-demo.ts +933 -0
  85. package/src/examples/grayscale-buffer-demo.ts +249 -0
  86. package/src/examples/hast-syntax-highlighting-demo.ts +129 -0
  87. package/src/examples/index.ts +925 -0
  88. package/src/examples/input-demo.ts +377 -0
  89. package/src/examples/input-select-layout-demo.ts +425 -0
  90. package/src/examples/install.sh +143 -0
  91. package/src/examples/keypress-debug-demo.ts +452 -0
  92. package/src/examples/lib/HexList.ts +122 -0
  93. package/src/examples/lib/PaletteGrid.ts +125 -0
  94. package/src/examples/lib/standalone-keys.ts +25 -0
  95. package/src/examples/lib/tab-controller.ts +243 -0
  96. package/src/examples/lights-phong-demo.ts +290 -0
  97. package/src/examples/link-demo.ts +220 -0
  98. package/src/examples/live-state-demo.ts +480 -0
  99. package/src/examples/markdown-demo.ts +620 -0
  100. package/src/examples/mouse-interaction-demo.ts +428 -0
  101. package/src/examples/nested-zindex-demo.ts +357 -0
  102. package/src/examples/opacity-example.ts +235 -0
  103. package/src/examples/opentui-demo.ts +1057 -0
  104. package/src/examples/physx-planck-2d-demo.ts +507 -0
  105. package/src/examples/physx-rapier-2d-demo.ts +526 -0
  106. package/src/examples/relative-positioning-demo.ts +323 -0
  107. package/src/examples/scroll-example.ts +214 -0
  108. package/src/examples/scrollbox-mouse-test.ts +112 -0
  109. package/src/examples/scrollbox-overlay-hit-test.ts +206 -0
  110. package/src/examples/select-demo.ts +237 -0
  111. package/src/examples/shader-cube-demo.ts +772 -0
  112. package/src/examples/simple-layout-example.ts +591 -0
  113. package/src/examples/slider-demo.ts +617 -0
  114. package/src/examples/split-mode-demo.ts +445 -0
  115. package/src/examples/sprite-animation-demo.ts +443 -0
  116. package/src/examples/sprite-particle-generator-demo.ts +486 -0
  117. package/src/examples/static-sprite-demo.ts +193 -0
  118. package/src/examples/sticky-scroll-example.ts +308 -0
  119. package/src/examples/styled-text-demo.ts +282 -0
  120. package/src/examples/tab-select-demo.ts +219 -0
  121. package/src/examples/terminal-title.ts +29 -0
  122. package/src/examples/terminal.ts +305 -0
  123. package/src/examples/text-node-demo.ts +416 -0
  124. package/src/examples/text-selection-demo.ts +377 -0
  125. package/src/examples/text-table-demo.ts +503 -0
  126. package/src/examples/text-truncation-demo.ts +481 -0
  127. package/src/examples/text-wrap.ts +757 -0
  128. package/src/examples/texture-loading-demo.ts +259 -0
  129. package/src/examples/timeline-example.ts +670 -0
  130. package/src/examples/transparency-demo.ts +241 -0
  131. package/src/examples/vnode-composition-demo.ts +404 -0
  132. package/src/index.ts +22 -0
  133. package/src/lib/KeyHandler.integration.test.ts +292 -0
  134. package/src/lib/KeyHandler.stopPropagation.test.ts +289 -0
  135. package/src/lib/KeyHandler.test.ts +662 -0
  136. package/src/lib/KeyHandler.ts +222 -0
  137. package/src/lib/RGBA.test.ts +984 -0
  138. package/src/lib/RGBA.ts +204 -0
  139. package/src/lib/ascii.font.ts +330 -0
  140. package/src/lib/border.test.ts +83 -0
  141. package/src/lib/border.ts +168 -0
  142. package/src/lib/bunfs.test.ts +27 -0
  143. package/src/lib/bunfs.ts +18 -0
  144. package/src/lib/clipboard.test.ts +41 -0
  145. package/src/lib/clipboard.ts +47 -0
  146. package/src/lib/clock.ts +31 -0
  147. package/src/lib/data-paths.test.ts +133 -0
  148. package/src/lib/data-paths.ts +109 -0
  149. package/src/lib/debounce.ts +106 -0
  150. package/src/lib/detect-links.test.ts +98 -0
  151. package/src/lib/detect-links.ts +56 -0
  152. package/src/lib/env.test.ts +228 -0
  153. package/src/lib/env.ts +209 -0
  154. package/src/lib/extmarks-history.ts +51 -0
  155. package/src/lib/extmarks-multiwidth.test.ts +322 -0
  156. package/src/lib/extmarks.test.ts +3457 -0
  157. package/src/lib/extmarks.ts +843 -0
  158. package/src/lib/fonts/block.json +405 -0
  159. package/src/lib/fonts/grid.json +265 -0
  160. package/src/lib/fonts/huge.json +741 -0
  161. package/src/lib/fonts/pallet.json +314 -0
  162. package/src/lib/fonts/shade.json +591 -0
  163. package/src/lib/fonts/slick.json +321 -0
  164. package/src/lib/fonts/tiny.json +69 -0
  165. package/src/lib/hast-styled-text.ts +59 -0
  166. package/src/lib/index.ts +21 -0
  167. package/src/lib/keymapping.test.ts +280 -0
  168. package/src/lib/keymapping.ts +87 -0
  169. package/src/lib/objects-in-viewport.test.ts +787 -0
  170. package/src/lib/objects-in-viewport.ts +153 -0
  171. package/src/lib/output.capture.ts +58 -0
  172. package/src/lib/parse.keypress-kitty.protocol.test.ts +340 -0
  173. package/src/lib/parse.keypress-kitty.test.ts +663 -0
  174. package/src/lib/parse.keypress-kitty.ts +439 -0
  175. package/src/lib/parse.keypress.test.ts +1849 -0
  176. package/src/lib/parse.keypress.ts +397 -0
  177. package/src/lib/parse.mouse.test.ts +552 -0
  178. package/src/lib/parse.mouse.ts +232 -0
  179. package/src/lib/paste.ts +16 -0
  180. package/src/lib/queue.ts +65 -0
  181. package/src/lib/renderable.validations.test.ts +87 -0
  182. package/src/lib/renderable.validations.ts +83 -0
  183. package/src/lib/scroll-acceleration.ts +98 -0
  184. package/src/lib/selection.ts +240 -0
  185. package/src/lib/singleton.ts +28 -0
  186. package/src/lib/stdin-parser.test.ts +1676 -0
  187. package/src/lib/stdin-parser.ts +1248 -0
  188. package/src/lib/styled-text.ts +178 -0
  189. package/src/lib/terminal-capability-detection.test.ts +202 -0
  190. package/src/lib/terminal-capability-detection.ts +79 -0
  191. package/src/lib/terminal-palette.test.ts +878 -0
  192. package/src/lib/terminal-palette.ts +383 -0
  193. package/src/lib/tree-sitter/assets/README.md +118 -0
  194. package/src/lib/tree-sitter/assets/update.ts +331 -0
  195. package/src/lib/tree-sitter/assets.d.ts +9 -0
  196. package/src/lib/tree-sitter/cache.test.ts +270 -0
  197. package/src/lib/tree-sitter/client.test.ts +1061 -0
  198. package/src/lib/tree-sitter/client.ts +615 -0
  199. package/src/lib/tree-sitter/default-parsers.ts +80 -0
  200. package/src/lib/tree-sitter/download-utils.ts +148 -0
  201. package/src/lib/tree-sitter/index.ts +28 -0
  202. package/src/lib/tree-sitter/parser.worker.ts +1001 -0
  203. package/src/lib/tree-sitter/parsers-config.ts +75 -0
  204. package/src/lib/tree-sitter/resolve-ft.ts +62 -0
  205. package/src/lib/tree-sitter/types.ts +81 -0
  206. package/src/lib/tree-sitter-styled-text.test.ts +1253 -0
  207. package/src/lib/tree-sitter-styled-text.ts +306 -0
  208. package/src/lib/validate-dir-name.ts +55 -0
  209. package/src/lib/yoga.options.test.ts +628 -0
  210. package/src/lib/yoga.options.ts +346 -0
  211. package/src/plugins/core-slot.ts +579 -0
  212. package/src/plugins/registry.ts +377 -0
  213. package/src/plugins/types.ts +46 -0
  214. package/src/post/filters.ts +888 -0
  215. package/src/renderables/ASCIIFont.ts +219 -0
  216. package/src/renderables/Box.test.ts +160 -0
  217. package/src/renderables/Box.ts +295 -0
  218. package/src/renderables/Code.test.ts +2062 -0
  219. package/src/renderables/Code.ts +357 -0
  220. package/src/renderables/Diff.regression.test.ts +226 -0
  221. package/src/renderables/Diff.test.ts +3027 -0
  222. package/src/renderables/Diff.ts +1209 -0
  223. package/src/renderables/EditBufferRenderable.ts +764 -0
  224. package/src/renderables/FrameBuffer.ts +47 -0
  225. package/src/renderables/Input.test.ts +1228 -0
  226. package/src/renderables/Input.ts +245 -0
  227. package/src/renderables/LineNumberRenderable.ts +675 -0
  228. package/src/renderables/Markdown.ts +1106 -0
  229. package/src/renderables/ScrollBar.ts +422 -0
  230. package/src/renderables/ScrollBox.ts +883 -0
  231. package/src/renderables/Select.test.ts +1010 -0
  232. package/src/renderables/Select.ts +523 -0
  233. package/src/renderables/Slider.test.ts +456 -0
  234. package/src/renderables/Slider.ts +347 -0
  235. package/src/renderables/TabSelect.test.ts +197 -0
  236. package/src/renderables/TabSelect.ts +455 -0
  237. package/src/renderables/Text.selection-buffer.test.ts +123 -0
  238. package/src/renderables/Text.test.ts +2660 -0
  239. package/src/renderables/Text.ts +147 -0
  240. package/src/renderables/TextBufferRenderable.ts +518 -0
  241. package/src/renderables/TextNode.test.ts +1058 -0
  242. package/src/renderables/TextNode.ts +325 -0
  243. package/src/renderables/TextTable.test.ts +1421 -0
  244. package/src/renderables/TextTable.ts +1344 -0
  245. package/src/renderables/Textarea.ts +732 -0
  246. package/src/renderables/TimeToFirstDraw.ts +89 -0
  247. package/src/renderables/__snapshots__/Code.test.ts.snap +13 -0
  248. package/src/renderables/__snapshots__/Diff.test.ts.snap +785 -0
  249. package/src/renderables/__snapshots__/Text.test.ts.snap +421 -0
  250. package/src/renderables/__snapshots__/TextTable.test.ts.snap +215 -0
  251. package/src/renderables/__tests__/LineNumberRenderable.scrollbox-simple.test.ts +144 -0
  252. package/src/renderables/__tests__/LineNumberRenderable.scrollbox.test.ts +816 -0
  253. package/src/renderables/__tests__/LineNumberRenderable.test.ts +1787 -0
  254. package/src/renderables/__tests__/LineNumberRenderable.wrapping.test.ts +85 -0
  255. package/src/renderables/__tests__/Markdown.test.ts +2287 -0
  256. package/src/renderables/__tests__/MultiRenderable.selection.test.ts +87 -0
  257. package/src/renderables/__tests__/Textarea.buffer.test.ts +682 -0
  258. package/src/renderables/__tests__/Textarea.destroyed-events.test.ts +675 -0
  259. package/src/renderables/__tests__/Textarea.editing.test.ts +2041 -0
  260. package/src/renderables/__tests__/Textarea.error-handling.test.ts +35 -0
  261. package/src/renderables/__tests__/Textarea.events.test.ts +738 -0
  262. package/src/renderables/__tests__/Textarea.highlights.test.ts +590 -0
  263. package/src/renderables/__tests__/Textarea.keybinding.test.ts +3149 -0
  264. package/src/renderables/__tests__/Textarea.paste.test.ts +357 -0
  265. package/src/renderables/__tests__/Textarea.rendering.test.ts +1864 -0
  266. package/src/renderables/__tests__/Textarea.scroll.test.ts +733 -0
  267. package/src/renderables/__tests__/Textarea.selection.test.ts +1590 -0
  268. package/src/renderables/__tests__/Textarea.stress.test.ts +670 -0
  269. package/src/renderables/__tests__/Textarea.undo-redo.test.ts +383 -0
  270. package/src/renderables/__tests__/Textarea.visual-lines.test.ts +310 -0
  271. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.code.test.ts.snap +221 -0
  272. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox-simple.test.ts.snap +89 -0
  273. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox.test.ts.snap +457 -0
  274. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.test.ts.snap +158 -0
  275. package/src/renderables/__tests__/__snapshots__/Textarea.rendering.test.ts.snap +387 -0
  276. package/src/renderables/__tests__/markdown-parser.test.ts +217 -0
  277. package/src/renderables/__tests__/renderable-test-utils.ts +60 -0
  278. package/src/renderables/composition/README.md +8 -0
  279. package/src/renderables/composition/VRenderable.ts +32 -0
  280. package/src/renderables/composition/constructs.ts +127 -0
  281. package/src/renderables/composition/vnode.ts +289 -0
  282. package/src/renderables/index.ts +22 -0
  283. package/src/renderables/markdown-parser.ts +66 -0
  284. package/src/renderer.ts +2363 -0
  285. package/src/runtime-plugin-support.ts +39 -0
  286. package/src/runtime-plugin.ts +144 -0
  287. package/src/syntax-style.test.ts +841 -0
  288. package/src/syntax-style.ts +264 -0
  289. package/src/testing/README.md +210 -0
  290. package/src/testing/capture-spans.test.ts +194 -0
  291. package/src/testing/integration.test.ts +276 -0
  292. package/src/testing/manual-clock.ts +106 -0
  293. package/src/testing/mock-keys.test.ts +1356 -0
  294. package/src/testing/mock-keys.ts +449 -0
  295. package/src/testing/mock-mouse.test.ts +218 -0
  296. package/src/testing/mock-mouse.ts +247 -0
  297. package/src/testing/mock-tree-sitter-client.ts +73 -0
  298. package/src/testing/spy.ts +13 -0
  299. package/src/testing/test-recorder.test.ts +415 -0
  300. package/src/testing/test-recorder.ts +145 -0
  301. package/src/testing/test-renderer.ts +116 -0
  302. package/src/testing.ts +7 -0
  303. package/src/tests/__snapshots__/absolute-positioning.snapshot.test.ts.snap +481 -0
  304. package/src/tests/__snapshots__/renderable.snapshot.test.ts.snap +19 -0
  305. package/src/tests/__snapshots__/scrollbox.test.ts.snap +29 -0
  306. package/src/tests/absolute-positioning.snapshot.test.ts +638 -0
  307. package/src/tests/allocator-stats.test.ts +38 -0
  308. package/src/tests/destroy-during-render.test.ts +200 -0
  309. package/src/tests/hover-cursor.test.ts +98 -0
  310. package/src/tests/native-span-feed-async.test.ts +173 -0
  311. package/src/tests/native-span-feed-close.test.ts +120 -0
  312. package/src/tests/native-span-feed-coverage.test.ts +227 -0
  313. package/src/tests/native-span-feed-edge-cases.test.ts +352 -0
  314. package/src/tests/native-span-feed-use-after-free.test.ts +45 -0
  315. package/src/tests/opacity.test.ts +123 -0
  316. package/src/tests/renderable.snapshot.test.ts +524 -0
  317. package/src/tests/renderable.test.ts +1281 -0
  318. package/src/tests/renderer.console-startup.test.ts +65 -0
  319. package/src/tests/renderer.control.test.ts +364 -0
  320. package/src/tests/renderer.core-slot-binding.test.ts +952 -0
  321. package/src/tests/renderer.cursor.test.ts +26 -0
  322. package/src/tests/renderer.destroy-during-render.test.ts +110 -0
  323. package/src/tests/renderer.focus-restore.test.ts +228 -0
  324. package/src/tests/renderer.focus.test.ts +251 -0
  325. package/src/tests/renderer.idle.test.ts +219 -0
  326. package/src/tests/renderer.input.test.ts +2145 -0
  327. package/src/tests/renderer.kitty-flags.test.ts +195 -0
  328. package/src/tests/renderer.mouse.test.ts +1269 -0
  329. package/src/tests/renderer.palette.test.ts +629 -0
  330. package/src/tests/renderer.selection.test.ts +49 -0
  331. package/src/tests/renderer.slot-registry.test.ts +649 -0
  332. package/src/tests/renderer.useMouse.test.ts +50 -0
  333. package/src/tests/runtime-plugin-support.fixture.ts +11 -0
  334. package/src/tests/runtime-plugin-support.test.ts +28 -0
  335. package/src/tests/runtime-plugin.fixture.ts +40 -0
  336. package/src/tests/runtime-plugin.test.ts +190 -0
  337. package/src/tests/scrollbox-culling-bug.test.ts +114 -0
  338. package/src/tests/scrollbox-hitgrid-resize.test.ts +136 -0
  339. package/src/tests/scrollbox-hitgrid.test.ts +909 -0
  340. package/src/tests/scrollbox.test.ts +1530 -0
  341. package/src/tests/wrap-resize-perf.test.ts +229 -0
  342. package/src/tests/yoga-setters.test.ts +921 -0
  343. package/src/text-buffer-view.test.ts +705 -0
  344. package/src/text-buffer-view.ts +189 -0
  345. package/src/text-buffer.test.ts +347 -0
  346. package/src/text-buffer.ts +250 -0
  347. package/src/types.ts +152 -0
  348. package/src/utils.ts +88 -0
  349. package/src/zig/ansi.zig +268 -0
  350. package/src/zig/bench/README.md +50 -0
  351. package/src/zig/bench/buffer-draw-text-buffer_bench.zig +887 -0
  352. package/src/zig/bench/edit-buffer_bench.zig +476 -0
  353. package/src/zig/bench/native-span-feed_bench.zig +100 -0
  354. package/src/zig/bench/rope-markers_bench.zig +713 -0
  355. package/src/zig/bench/rope_bench.zig +514 -0
  356. package/src/zig/bench/styled-text_bench.zig +470 -0
  357. package/src/zig/bench/text-buffer-coords_bench.zig +362 -0
  358. package/src/zig/bench/text-buffer-view_bench.zig +459 -0
  359. package/src/zig/bench/text-chunk-graphemes_bench.zig +273 -0
  360. package/src/zig/bench/utf8_bench.zig +799 -0
  361. package/src/zig/bench-utils.zig +431 -0
  362. package/src/zig/bench.zig +217 -0
  363. package/src/zig/buffer.zig +2223 -0
  364. package/src/zig/build.zig +289 -0
  365. package/src/zig/build.zig.zon +16 -0
  366. package/src/zig/edit-buffer.zig +825 -0
  367. package/src/zig/editor-view.zig +802 -0
  368. package/src/zig/event-bus.zig +13 -0
  369. package/src/zig/event-emitter.zig +65 -0
  370. package/src/zig/file-logger.zig +92 -0
  371. package/src/zig/grapheme.zig +599 -0
  372. package/src/zig/lib.zig +1834 -0
  373. package/src/zig/link.zig +333 -0
  374. package/src/zig/logger.zig +43 -0
  375. package/src/zig/mem-registry.zig +125 -0
  376. package/src/zig/native-span-feed-bench-lib.zig +7 -0
  377. package/src/zig/native-span-feed.zig +708 -0
  378. package/src/zig/renderer.zig +1386 -0
  379. package/src/zig/rope.zig +1220 -0
  380. package/src/zig/syntax-style.zig +161 -0
  381. package/src/zig/terminal.zig +975 -0
  382. package/src/zig/test.zig +70 -0
  383. package/src/zig/tests/README.md +18 -0
  384. package/src/zig/tests/buffer_test.zig +2526 -0
  385. package/src/zig/tests/edit-buffer-history_test.zig +271 -0
  386. package/src/zig/tests/edit-buffer_test.zig +1689 -0
  387. package/src/zig/tests/editor-view_test.zig +3299 -0
  388. package/src/zig/tests/event-emitter_test.zig +249 -0
  389. package/src/zig/tests/grapheme_test.zig +1304 -0
  390. package/src/zig/tests/link_test.zig +190 -0
  391. package/src/zig/tests/mem-registry_test.zig +473 -0
  392. package/src/zig/tests/memory_leak_regression_test.zig +159 -0
  393. package/src/zig/tests/native-span-feed_test.zig +1264 -0
  394. package/src/zig/tests/renderer_test.zig +1010 -0
  395. package/src/zig/tests/rope-nested_test.zig +712 -0
  396. package/src/zig/tests/rope_fuzz_test.zig +238 -0
  397. package/src/zig/tests/rope_test.zig +2362 -0
  398. package/src/zig/tests/segment-merge.test.zig +148 -0
  399. package/src/zig/tests/syntax-style_test.zig +557 -0
  400. package/src/zig/tests/terminal_test.zig +719 -0
  401. package/src/zig/tests/text-buffer-drawing_test.zig +3237 -0
  402. package/src/zig/tests/text-buffer-highlights_test.zig +666 -0
  403. package/src/zig/tests/text-buffer-iterators_test.zig +776 -0
  404. package/src/zig/tests/text-buffer-segment_test.zig +320 -0
  405. package/src/zig/tests/text-buffer-selection_test.zig +1035 -0
  406. package/src/zig/tests/text-buffer-selection_viewport_test.zig +358 -0
  407. package/src/zig/tests/text-buffer-view_test.zig +3649 -0
  408. package/src/zig/tests/text-buffer_test.zig +2191 -0
  409. package/src/zig/tests/unicode-width-map.zon +3909 -0
  410. package/src/zig/tests/utf8_no_zwj_test.zig +260 -0
  411. package/src/zig/tests/utf8_test.zig +4057 -0
  412. package/src/zig/tests/utf8_wcwidth_cursor_test.zig +267 -0
  413. package/src/zig/tests/utf8_wcwidth_test.zig +357 -0
  414. package/src/zig/tests/word-wrap-editing_test.zig +498 -0
  415. package/src/zig/tests/wrap-cache-perf_test.zig +113 -0
  416. package/src/zig/text-buffer-iterators.zig +499 -0
  417. package/src/zig/text-buffer-segment.zig +404 -0
  418. package/src/zig/text-buffer-view.zig +1371 -0
  419. package/src/zig/text-buffer.zig +1180 -0
  420. package/src/zig/utf8.zig +1948 -0
  421. package/src/zig/utils.zig +9 -0
  422. package/src/zig-structs.ts +261 -0
  423. package/src/zig.ts +3843 -0
  424. package/tsconfig.build.json +22 -0
  425. package/tsconfig.json +28 -0
  426. package/3d/SpriteResourceManager.d.ts +0 -74
  427. package/3d/SpriteUtils.d.ts +0 -13
  428. package/3d/TextureUtils.d.ts +0 -24
  429. package/3d/ThreeRenderable.d.ts +0 -40
  430. package/3d/WGPURenderer.d.ts +0 -61
  431. package/3d/animation/ExplodingSpriteEffect.d.ts +0 -71
  432. package/3d/animation/PhysicsExplodingSpriteEffect.d.ts +0 -76
  433. package/3d/animation/SpriteAnimator.d.ts +0 -124
  434. package/3d/animation/SpriteParticleGenerator.d.ts +0 -62
  435. package/3d/canvas.d.ts +0 -44
  436. package/3d/index.d.ts +0 -12
  437. package/3d/physics/PlanckPhysicsAdapter.d.ts +0 -19
  438. package/3d/physics/RapierPhysicsAdapter.d.ts +0 -19
  439. package/3d/physics/physics-interface.d.ts +0 -27
  440. package/3d.d.ts +0 -2
  441. package/3d.js +0 -34042
  442. package/3d.js.map +0 -155
  443. package/LICENSE +0 -21
  444. package/NativeSpanFeed.d.ts +0 -41
  445. package/Renderable.d.ts +0 -334
  446. package/animation/Timeline.d.ts +0 -126
  447. package/ansi.d.ts +0 -13
  448. package/buffer.d.ts +0 -107
  449. package/console.d.ts +0 -143
  450. package/edit-buffer.d.ts +0 -98
  451. package/editor-view.d.ts +0 -73
  452. package/index-e4hzc2j2.js +0 -113
  453. package/index-e4hzc2j2.js.map +0 -10
  454. package/index-nkrr8a4c.js +0 -18415
  455. package/index-nkrr8a4c.js.map +0 -64
  456. package/index-nyw5p3ep.js +0 -12619
  457. package/index-nyw5p3ep.js.map +0 -43
  458. package/index.d.ts +0 -21
  459. package/index.js +0 -430
  460. package/index.js.map +0 -9
  461. package/lib/KeyHandler.d.ts +0 -61
  462. package/lib/RGBA.d.ts +0 -25
  463. package/lib/ascii.font.d.ts +0 -508
  464. package/lib/border.d.ts +0 -49
  465. package/lib/bunfs.d.ts +0 -7
  466. package/lib/clipboard.d.ts +0 -17
  467. package/lib/clock.d.ts +0 -15
  468. package/lib/data-paths.d.ts +0 -26
  469. package/lib/debounce.d.ts +0 -42
  470. package/lib/detect-links.d.ts +0 -6
  471. package/lib/env.d.ts +0 -42
  472. package/lib/extmarks-history.d.ts +0 -17
  473. package/lib/extmarks.d.ts +0 -89
  474. package/lib/hast-styled-text.d.ts +0 -17
  475. package/lib/index.d.ts +0 -21
  476. package/lib/keymapping.d.ts +0 -25
  477. package/lib/objects-in-viewport.d.ts +0 -24
  478. package/lib/output.capture.d.ts +0 -24
  479. package/lib/parse.keypress-kitty.d.ts +0 -2
  480. package/lib/parse.keypress.d.ts +0 -26
  481. package/lib/parse.mouse.d.ts +0 -30
  482. package/lib/paste.d.ts +0 -7
  483. package/lib/queue.d.ts +0 -15
  484. package/lib/renderable.validations.d.ts +0 -12
  485. package/lib/scroll-acceleration.d.ts +0 -43
  486. package/lib/selection.d.ts +0 -63
  487. package/lib/singleton.d.ts +0 -7
  488. package/lib/stdin-parser.d.ts +0 -76
  489. package/lib/styled-text.d.ts +0 -63
  490. package/lib/terminal-capability-detection.d.ts +0 -30
  491. package/lib/terminal-palette.d.ts +0 -50
  492. package/lib/tree-sitter/assets/update.d.ts +0 -11
  493. package/lib/tree-sitter/client.d.ts +0 -47
  494. package/lib/tree-sitter/default-parsers.d.ts +0 -2
  495. package/lib/tree-sitter/download-utils.d.ts +0 -21
  496. package/lib/tree-sitter/index.d.ts +0 -8
  497. package/lib/tree-sitter/parser.worker.d.ts +0 -1
  498. package/lib/tree-sitter/parsers-config.d.ts +0 -38
  499. package/lib/tree-sitter/resolve-ft.d.ts +0 -2
  500. package/lib/tree-sitter/types.d.ts +0 -81
  501. package/lib/tree-sitter-styled-text.d.ts +0 -14
  502. package/lib/validate-dir-name.d.ts +0 -1
  503. package/lib/yoga.options.d.ts +0 -32
  504. package/parser.worker.js +0 -869
  505. package/parser.worker.js.map +0 -12
  506. package/plugins/core-slot.d.ts +0 -72
  507. package/plugins/registry.d.ts +0 -38
  508. package/plugins/types.d.ts +0 -34
  509. package/post/filters.d.ts +0 -105
  510. package/renderables/ASCIIFont.d.ts +0 -52
  511. package/renderables/Box.d.ts +0 -72
  512. package/renderables/Code.d.ts +0 -78
  513. package/renderables/Diff.d.ts +0 -142
  514. package/renderables/EditBufferRenderable.d.ts +0 -162
  515. package/renderables/FrameBuffer.d.ts +0 -16
  516. package/renderables/Input.d.ts +0 -67
  517. package/renderables/LineNumberRenderable.d.ts +0 -74
  518. package/renderables/Markdown.d.ts +0 -173
  519. package/renderables/ScrollBar.d.ts +0 -77
  520. package/renderables/ScrollBox.d.ts +0 -124
  521. package/renderables/Select.d.ts +0 -115
  522. package/renderables/Slider.d.ts +0 -44
  523. package/renderables/TabSelect.d.ts +0 -96
  524. package/renderables/Text.d.ts +0 -36
  525. package/renderables/TextBufferRenderable.d.ts +0 -105
  526. package/renderables/TextNode.d.ts +0 -91
  527. package/renderables/TextTable.d.ts +0 -140
  528. package/renderables/Textarea.d.ts +0 -114
  529. package/renderables/TimeToFirstDraw.d.ts +0 -24
  530. package/renderables/__tests__/renderable-test-utils.d.ts +0 -12
  531. package/renderables/composition/VRenderable.d.ts +0 -16
  532. package/renderables/composition/constructs.d.ts +0 -35
  533. package/renderables/composition/vnode.d.ts +0 -46
  534. package/renderables/index.d.ts +0 -22
  535. package/renderables/markdown-parser.d.ts +0 -10
  536. package/renderer.d.ts +0 -388
  537. package/runtime-plugin-support.d.ts +0 -3
  538. package/runtime-plugin-support.js +0 -29
  539. package/runtime-plugin-support.js.map +0 -10
  540. package/runtime-plugin.d.ts +0 -11
  541. package/runtime-plugin.js +0 -16
  542. package/runtime-plugin.js.map +0 -9
  543. package/syntax-style.d.ts +0 -54
  544. package/testing/manual-clock.d.ts +0 -16
  545. package/testing/mock-keys.d.ts +0 -81
  546. package/testing/mock-mouse.d.ts +0 -38
  547. package/testing/mock-tree-sitter-client.d.ts +0 -23
  548. package/testing/spy.d.ts +0 -7
  549. package/testing/test-recorder.d.ts +0 -61
  550. package/testing/test-renderer.d.ts +0 -23
  551. package/testing.d.ts +0 -6
  552. package/testing.js +0 -675
  553. package/testing.js.map +0 -15
  554. package/text-buffer-view.d.ts +0 -42
  555. package/text-buffer.d.ts +0 -67
  556. package/types.d.ts +0 -131
  557. package/utils.d.ts +0 -14
  558. package/zig-structs.d.ts +0 -155
  559. package/zig.d.ts +0 -351
  560. /package/{assets → src/lib/tree-sitter/assets}/javascript/highlights.scm +0 -0
  561. /package/{assets → src/lib/tree-sitter/assets}/javascript/tree-sitter-javascript.wasm +0 -0
  562. /package/{assets → src/lib/tree-sitter/assets}/markdown/highlights.scm +0 -0
  563. /package/{assets → src/lib/tree-sitter/assets}/markdown/injections.scm +0 -0
  564. /package/{assets → src/lib/tree-sitter/assets}/markdown/tree-sitter-markdown.wasm +0 -0
  565. /package/{assets → src/lib/tree-sitter/assets}/markdown_inline/highlights.scm +0 -0
  566. /package/{assets → src/lib/tree-sitter/assets}/markdown_inline/tree-sitter-markdown_inline.wasm +0 -0
  567. /package/{assets → src/lib/tree-sitter/assets}/typescript/highlights.scm +0 -0
  568. /package/{assets → src/lib/tree-sitter/assets}/typescript/tree-sitter-typescript.wasm +0 -0
  569. /package/{assets → src/lib/tree-sitter/assets}/zig/highlights.scm +0 -0
  570. /package/{assets → src/lib/tree-sitter/assets}/zig/tree-sitter-zig.wasm +0 -0
@@ -0,0 +1,1010 @@
1
+ import { test, expect, beforeEach, afterEach, describe } from "bun:test"
2
+ import { SelectRenderable, type SelectRenderableOptions, SelectRenderableEvents, type SelectOption } from "./Select.js"
3
+ import { createTestRenderer, type MockInput, type TestRenderer } from "../testing/test-renderer.js"
4
+ import { KeyEvent } from "../lib/KeyHandler.js"
5
+
6
+ // Helper function to create a KeyEvent from a string or object
7
+ function createKeyEvent(
8
+ input: string | { name: string; shift?: boolean; ctrl?: boolean; meta?: boolean; super?: boolean },
9
+ ): KeyEvent {
10
+ if (typeof input === "string") {
11
+ return new KeyEvent({
12
+ name: input,
13
+ sequence: input,
14
+ ctrl: false,
15
+ meta: false,
16
+ shift: false,
17
+ option: false,
18
+ number: false,
19
+ raw: input,
20
+ eventType: "press",
21
+ source: "raw",
22
+ })
23
+ } else {
24
+ return new KeyEvent({
25
+ name: input.name,
26
+ sequence: input.name === "space" ? " " : input.name,
27
+ ctrl: input.ctrl ?? false,
28
+ meta: input.meta ?? false,
29
+ shift: input.shift ?? false,
30
+ super: input.super ?? false,
31
+ option: false,
32
+ number: false,
33
+ raw: input.name,
34
+ eventType: "press",
35
+ source: "raw",
36
+ })
37
+ }
38
+ }
39
+
40
+ let currentRenderer: TestRenderer
41
+ let currentMockInput: MockInput
42
+ let renderOnce: () => Promise<void>
43
+
44
+ const sampleOptions: SelectOption[] = [
45
+ { name: "Option 1", description: "First option" },
46
+ { name: "Option 2", description: "Second option" },
47
+ { name: "Option 3", description: "Third option" },
48
+ { name: "Option 4", description: "Fourth option" },
49
+ { name: "Option 5", description: "Fifth option" },
50
+ ]
51
+
52
+ async function createSelectRenderable(
53
+ renderer: TestRenderer,
54
+ options: SelectRenderableOptions,
55
+ ): Promise<{ select: SelectRenderable; root: any }> {
56
+ const selectRenderable = new SelectRenderable(renderer, { left: 0, top: 0, ...options })
57
+ renderer.root.add(selectRenderable)
58
+ await renderOnce()
59
+
60
+ return { select: selectRenderable, root: renderer.root }
61
+ }
62
+
63
+ beforeEach(async () => {
64
+ ;({ renderer: currentRenderer, mockInput: currentMockInput, renderOnce } = await createTestRenderer({}))
65
+ })
66
+
67
+ afterEach(() => {
68
+ currentRenderer.destroy()
69
+ })
70
+
71
+ describe("SelectRenderable", () => {
72
+ describe("Initialization", () => {
73
+ test("should initialize with default options", async () => {
74
+ const { select } = await createSelectRenderable(currentRenderer, {
75
+ width: 20,
76
+ height: 5,
77
+ options: sampleOptions,
78
+ })
79
+
80
+ expect(select.options).toEqual(sampleOptions)
81
+ expect(select.getSelectedIndex()).toBe(0)
82
+ expect(select.getSelectedOption()).toEqual(sampleOptions[0])
83
+ expect(select.focusable).toBe(true)
84
+ expect(select.showScrollIndicator).toBe(false)
85
+ expect(select.showDescription).toBe(true)
86
+ expect(select.wrapSelection).toBe(false)
87
+ })
88
+
89
+ test("should initialize with custom selected index", async () => {
90
+ const { select } = await createSelectRenderable(currentRenderer, {
91
+ width: 20,
92
+ height: 5,
93
+ options: sampleOptions,
94
+ selectedIndex: 2,
95
+ })
96
+
97
+ expect(select.getSelectedIndex()).toBe(2)
98
+ expect(select.getSelectedOption()).toEqual(sampleOptions[2])
99
+ })
100
+
101
+ test("should initialize with custom options", async () => {
102
+ const { select } = await createSelectRenderable(currentRenderer, {
103
+ width: 20,
104
+ height: 5,
105
+ options: sampleOptions,
106
+ showScrollIndicator: true,
107
+ showDescription: false,
108
+ wrapSelection: true,
109
+ itemSpacing: 1,
110
+ fastScrollStep: 3,
111
+ })
112
+
113
+ expect(select.showScrollIndicator).toBe(true)
114
+ expect(select.showDescription).toBe(false)
115
+ expect(select.wrapSelection).toBe(true)
116
+ })
117
+
118
+ test("should handle empty options array", async () => {
119
+ const { select } = await createSelectRenderable(currentRenderer, {
120
+ width: 20,
121
+ height: 5,
122
+ options: [],
123
+ })
124
+
125
+ expect(select.options).toEqual([])
126
+ expect(select.getSelectedIndex()).toBe(0)
127
+ expect(select.getSelectedOption()).toBe(null)
128
+ })
129
+
130
+ test("should clamp selectedIndex to valid range", async () => {
131
+ const { select } = await createSelectRenderable(currentRenderer, {
132
+ width: 20,
133
+ height: 5,
134
+ options: sampleOptions,
135
+ selectedIndex: 10, // Out of range
136
+ })
137
+
138
+ expect(select.getSelectedIndex()).toBe(sampleOptions.length - 1)
139
+ expect(select.getSelectedOption()).toEqual(sampleOptions[sampleOptions.length - 1])
140
+ })
141
+ })
142
+
143
+ describe("Options Management", () => {
144
+ test("should update options dynamically", async () => {
145
+ const { select } = await createSelectRenderable(currentRenderer, {
146
+ width: 20,
147
+ height: 5,
148
+ options: sampleOptions,
149
+ selectedIndex: 2,
150
+ })
151
+
152
+ const newOptions: SelectOption[] = [
153
+ { name: "New Option 1", description: "New first option" },
154
+ { name: "New Option 2", description: "New second option" },
155
+ ]
156
+
157
+ select.options = newOptions
158
+
159
+ expect(select.options).toEqual(newOptions)
160
+ expect(select.getSelectedIndex()).toBe(1) // Should be clamped to valid index
161
+ expect(select.getSelectedOption()).toEqual(newOptions[1])
162
+ })
163
+
164
+ test("should handle setting empty options", async () => {
165
+ const { select } = await createSelectRenderable(currentRenderer, {
166
+ width: 20,
167
+ height: 5,
168
+ options: sampleOptions,
169
+ selectedIndex: 2,
170
+ })
171
+
172
+ select.options = []
173
+
174
+ expect(select.options).toEqual([])
175
+ expect(select.getSelectedIndex()).toBe(0)
176
+ expect(select.getSelectedOption()).toBe(null)
177
+ })
178
+
179
+ test("should preserve valid selected index when options change", async () => {
180
+ const { select } = await createSelectRenderable(currentRenderer, {
181
+ width: 20,
182
+ height: 5,
183
+ options: sampleOptions,
184
+ selectedIndex: 1,
185
+ })
186
+
187
+ const extendedOptions = [...sampleOptions, { name: "Option 6", description: "Sixth option" }]
188
+ select.options = extendedOptions
189
+
190
+ expect(select.getSelectedIndex()).toBe(1) // Should remain the same
191
+ expect(select.getSelectedOption()).toEqual(sampleOptions[1])
192
+ })
193
+ })
194
+
195
+ describe("Selection Management", () => {
196
+ test("should set selected index programmatically", async () => {
197
+ const { select } = await createSelectRenderable(currentRenderer, {
198
+ width: 20,
199
+ height: 5,
200
+ options: sampleOptions,
201
+ })
202
+
203
+ let selectionChangedFired = false
204
+ let selectionIndex = -1
205
+ let selectionOption: SelectOption | null = null
206
+
207
+ select.on(SelectRenderableEvents.SELECTION_CHANGED, (index: number, option: SelectOption) => {
208
+ selectionChangedFired = true
209
+ selectionIndex = index
210
+ selectionOption = option
211
+ })
212
+
213
+ select.setSelectedIndex(3)
214
+
215
+ expect(select.getSelectedIndex()).toBe(3)
216
+ expect(select.getSelectedOption()).toEqual(sampleOptions[3])
217
+ expect(selectionChangedFired).toBe(true)
218
+ expect(selectionIndex).toBe(3)
219
+ expect(selectionOption).toEqual(sampleOptions[3])
220
+ })
221
+
222
+ test("should ignore invalid selected index", async () => {
223
+ const { select } = await createSelectRenderable(currentRenderer, {
224
+ width: 20,
225
+ height: 5,
226
+ options: sampleOptions,
227
+ selectedIndex: 2,
228
+ })
229
+
230
+ const originalIndex = select.getSelectedIndex()
231
+ const originalOption = select.getSelectedOption()
232
+
233
+ select.setSelectedIndex(-1) // Invalid
234
+ expect(select.getSelectedIndex()).toBe(originalIndex)
235
+
236
+ select.setSelectedIndex(10) // Out of range
237
+ expect(select.getSelectedIndex()).toBe(originalIndex)
238
+
239
+ expect(select.getSelectedOption()).toEqual(originalOption)
240
+ })
241
+
242
+ test("should move up correctly", async () => {
243
+ const { select } = await createSelectRenderable(currentRenderer, {
244
+ width: 20,
245
+ height: 5,
246
+ options: sampleOptions,
247
+ selectedIndex: 2,
248
+ })
249
+
250
+ select.moveUp()
251
+ expect(select.getSelectedIndex()).toBe(1)
252
+
253
+ select.moveUp()
254
+ expect(select.getSelectedIndex()).toBe(0)
255
+
256
+ // Should not move beyond first item without wrap
257
+ select.moveUp()
258
+ expect(select.getSelectedIndex()).toBe(0)
259
+ })
260
+
261
+ test("should move down correctly", async () => {
262
+ const { select } = await createSelectRenderable(currentRenderer, {
263
+ width: 20,
264
+ height: 5,
265
+ options: sampleOptions,
266
+ selectedIndex: 2,
267
+ })
268
+
269
+ select.moveDown()
270
+ expect(select.getSelectedIndex()).toBe(3)
271
+
272
+ select.moveDown()
273
+ expect(select.getSelectedIndex()).toBe(4)
274
+
275
+ // Should not move beyond last item without wrap
276
+ select.moveDown()
277
+ expect(select.getSelectedIndex()).toBe(4)
278
+ })
279
+
280
+ test("should wrap selection when enabled", async () => {
281
+ const { select } = await createSelectRenderable(currentRenderer, {
282
+ width: 20,
283
+ height: 5,
284
+ options: sampleOptions,
285
+ wrapSelection: true,
286
+ })
287
+
288
+ // Move up from first item should wrap to last
289
+ expect(select.getSelectedIndex()).toBe(0)
290
+ select.moveUp()
291
+ expect(select.getSelectedIndex()).toBe(4)
292
+
293
+ // Move down from last item should wrap to first
294
+ select.moveDown()
295
+ expect(select.getSelectedIndex()).toBe(0)
296
+ })
297
+
298
+ test("should move multiple steps", async () => {
299
+ const { select } = await createSelectRenderable(currentRenderer, {
300
+ width: 20,
301
+ height: 5,
302
+ options: sampleOptions,
303
+ selectedIndex: 0,
304
+ })
305
+
306
+ select.moveDown(3)
307
+ expect(select.getSelectedIndex()).toBe(3)
308
+
309
+ select.moveUp(2)
310
+ expect(select.getSelectedIndex()).toBe(1)
311
+ })
312
+
313
+ test("should select current item", async () => {
314
+ const { select } = await createSelectRenderable(currentRenderer, {
315
+ width: 20,
316
+ height: 5,
317
+ options: sampleOptions,
318
+ selectedIndex: 2,
319
+ })
320
+
321
+ let itemSelectedFired = false
322
+ let selectedIndex = -1
323
+ let selectedOption: SelectOption | null = null
324
+
325
+ select.on(SelectRenderableEvents.ITEM_SELECTED, (index: number, option: SelectOption) => {
326
+ itemSelectedFired = true
327
+ selectedIndex = index
328
+ selectedOption = option
329
+ })
330
+
331
+ select.selectCurrent()
332
+
333
+ expect(itemSelectedFired).toBe(true)
334
+ expect(selectedIndex).toBe(2)
335
+ expect(selectedOption).toEqual(sampleOptions[2])
336
+ })
337
+
338
+ test("should not select when no options available", async () => {
339
+ const { select } = await createSelectRenderable(currentRenderer, {
340
+ width: 20,
341
+ height: 5,
342
+ options: [],
343
+ })
344
+
345
+ let itemSelectedFired = false
346
+
347
+ select.on(SelectRenderableEvents.ITEM_SELECTED, () => {
348
+ itemSelectedFired = true
349
+ })
350
+
351
+ select.selectCurrent()
352
+
353
+ expect(itemSelectedFired).toBe(false)
354
+ })
355
+ })
356
+
357
+ describe("Keyboard Interaction", () => {
358
+ test("should handle up/down arrow keys", async () => {
359
+ const { select } = await createSelectRenderable(currentRenderer, {
360
+ width: 20,
361
+ height: 5,
362
+ options: sampleOptions,
363
+ selectedIndex: 1,
364
+ })
365
+
366
+ select.focus()
367
+
368
+ // Test down arrow
369
+ const downHandled = select.handleKeyPress(createKeyEvent("down"))
370
+ expect(downHandled).toBe(true)
371
+ expect(select.getSelectedIndex()).toBe(2)
372
+
373
+ // Test up arrow
374
+ const upHandled = select.handleKeyPress(createKeyEvent("up"))
375
+ expect(upHandled).toBe(true)
376
+ expect(select.getSelectedIndex()).toBe(1)
377
+ })
378
+
379
+ test("should handle j/k keys (vim-style)", async () => {
380
+ const { select } = await createSelectRenderable(currentRenderer, {
381
+ width: 20,
382
+ height: 5,
383
+ options: sampleOptions,
384
+ selectedIndex: 1,
385
+ })
386
+
387
+ select.focus()
388
+
389
+ // Test 'j' (down)
390
+ const jHandled = select.handleKeyPress(createKeyEvent("j"))
391
+ expect(jHandled).toBe(true)
392
+ expect(select.getSelectedIndex()).toBe(2)
393
+
394
+ // Test 'k' (up)
395
+ const kHandled = select.handleKeyPress(createKeyEvent("k"))
396
+ expect(kHandled).toBe(true)
397
+ expect(select.getSelectedIndex()).toBe(1)
398
+ })
399
+
400
+ test("should handle enter key", async () => {
401
+ const { select } = await createSelectRenderable(currentRenderer, {
402
+ width: 20,
403
+ height: 5,
404
+ options: sampleOptions,
405
+ selectedIndex: 2,
406
+ })
407
+
408
+ select.focus()
409
+
410
+ let itemSelectedFired = false
411
+ let selectedIndex = -1
412
+
413
+ select.on(SelectRenderableEvents.ITEM_SELECTED, (index: number) => {
414
+ itemSelectedFired = true
415
+ selectedIndex = index
416
+ })
417
+
418
+ const enterHandled = select.handleKeyPress(createKeyEvent("return"))
419
+ expect(enterHandled).toBe(true)
420
+ expect(itemSelectedFired).toBe(true)
421
+ expect(selectedIndex).toBe(2)
422
+ })
423
+
424
+ test("should handle linefeed key", async () => {
425
+ const { select } = await createSelectRenderable(currentRenderer, {
426
+ width: 20,
427
+ height: 5,
428
+ options: sampleOptions,
429
+ selectedIndex: 2,
430
+ })
431
+
432
+ select.focus()
433
+
434
+ let itemSelectedFired = false
435
+
436
+ select.on(SelectRenderableEvents.ITEM_SELECTED, () => {
437
+ itemSelectedFired = true
438
+ })
439
+
440
+ const linefeedHandled = select.handleKeyPress(createKeyEvent("linefeed"))
441
+ expect(linefeedHandled).toBe(true)
442
+ expect(itemSelectedFired).toBe(true)
443
+ })
444
+
445
+ test("should handle fast scroll with shift modifier", async () => {
446
+ const { select } = await createSelectRenderable(currentRenderer, {
447
+ width: 20,
448
+ height: 5,
449
+ options: sampleOptions,
450
+ selectedIndex: 0,
451
+ fastScrollStep: 3,
452
+ })
453
+
454
+ select.focus()
455
+
456
+ // Test shift+down
457
+ const shiftDownHandled = select.handleKeyPress(createKeyEvent({ name: "down", shift: true }))
458
+ expect(shiftDownHandled).toBe(true)
459
+ expect(select.getSelectedIndex()).toBe(3) // Should move 3 steps
460
+
461
+ // Test shift+up
462
+ const shiftUpHandled = select.handleKeyPress(createKeyEvent({ name: "up", shift: true }))
463
+ expect(shiftUpHandled).toBe(true)
464
+ expect(select.getSelectedIndex()).toBe(0) // Should move back 3 steps
465
+ })
466
+
467
+ test("should ignore unhandled keys", async () => {
468
+ const { select } = await createSelectRenderable(currentRenderer, {
469
+ width: 20,
470
+ height: 5,
471
+ options: sampleOptions,
472
+ selectedIndex: 1,
473
+ })
474
+
475
+ select.focus()
476
+
477
+ const originalIndex = select.getSelectedIndex()
478
+
479
+ // Test unhandled key
480
+ const handled = select.handleKeyPress(createKeyEvent("a"))
481
+ expect(handled).toBe(false)
482
+ expect(select.getSelectedIndex()).toBe(originalIndex)
483
+ })
484
+ })
485
+
486
+ describe("Property Changes", () => {
487
+ test("should update showScrollIndicator", async () => {
488
+ const { select } = await createSelectRenderable(currentRenderer, {
489
+ width: 20,
490
+ height: 5,
491
+ options: sampleOptions,
492
+ showScrollIndicator: false,
493
+ })
494
+
495
+ expect(select.showScrollIndicator).toBe(false)
496
+
497
+ select.showScrollIndicator = true
498
+ expect(select.showScrollIndicator).toBe(true)
499
+ })
500
+
501
+ test("should update showDescription", async () => {
502
+ const { select } = await createSelectRenderable(currentRenderer, {
503
+ width: 20,
504
+ height: 5,
505
+ options: sampleOptions,
506
+ showDescription: true,
507
+ })
508
+
509
+ expect(select.showDescription).toBe(true)
510
+
511
+ select.showDescription = false
512
+ expect(select.showDescription).toBe(false)
513
+ })
514
+
515
+ test("should update wrapSelection", async () => {
516
+ const { select } = await createSelectRenderable(currentRenderer, {
517
+ width: 20,
518
+ height: 5,
519
+ options: sampleOptions,
520
+ wrapSelection: false,
521
+ })
522
+
523
+ expect(select.wrapSelection).toBe(false)
524
+
525
+ select.wrapSelection = true
526
+ expect(select.wrapSelection).toBe(true)
527
+ })
528
+
529
+ test("should update colors", async () => {
530
+ const { select } = await createSelectRenderable(currentRenderer, {
531
+ width: 20,
532
+ height: 5,
533
+ options: sampleOptions,
534
+ })
535
+
536
+ // Test all color setters
537
+ select.backgroundColor = "#ff0000"
538
+ select.textColor = "#00ff00"
539
+ select.focusedBackgroundColor = "#0000ff"
540
+ select.focusedTextColor = "#ffff00"
541
+ select.selectedBackgroundColor = "#ff00ff"
542
+ select.selectedTextColor = "#00ffff"
543
+ select.descriptionColor = "#808080"
544
+ select.selectedDescriptionColor = "#ffffff"
545
+
546
+ // Should not throw errors
547
+ expect(select).toBeDefined()
548
+ })
549
+
550
+ test("should update font", async () => {
551
+ const { select } = await createSelectRenderable(currentRenderer, {
552
+ width: 20,
553
+ height: 5,
554
+ options: sampleOptions,
555
+ })
556
+
557
+ select.font = "tiny"
558
+ // Should not throw errors
559
+ expect(select).toBeDefined()
560
+ })
561
+
562
+ test("should update itemSpacing", async () => {
563
+ const { select } = await createSelectRenderable(currentRenderer, {
564
+ width: 20,
565
+ height: 5,
566
+ options: sampleOptions,
567
+ itemSpacing: 0,
568
+ })
569
+
570
+ select.itemSpacing = 2
571
+ // Should not throw errors
572
+ expect(select).toBeDefined()
573
+ })
574
+
575
+ test("should update fastScrollStep", async () => {
576
+ const { select } = await createSelectRenderable(currentRenderer, {
577
+ width: 20,
578
+ height: 5,
579
+ options: sampleOptions,
580
+ fastScrollStep: 5,
581
+ })
582
+
583
+ select.fastScrollStep = 10
584
+ // Should not throw errors
585
+ expect(select).toBeDefined()
586
+ })
587
+
588
+ test("should update selectedIndex via setter", async () => {
589
+ const { select } = await createSelectRenderable(currentRenderer, {
590
+ width: 20,
591
+ height: 5,
592
+ options: sampleOptions,
593
+ selectedIndex: 0,
594
+ })
595
+
596
+ select.selectedIndex = 3
597
+ expect(select.getSelectedIndex()).toBe(3)
598
+ })
599
+ })
600
+
601
+ describe("Event Emission", () => {
602
+ test("should emit SELECTION_CHANGED when moving", async () => {
603
+ const { select } = await createSelectRenderable(currentRenderer, {
604
+ width: 20,
605
+ height: 5,
606
+ options: sampleOptions,
607
+ selectedIndex: 1,
608
+ })
609
+
610
+ let eventCount = 0
611
+ let lastIndex = -1
612
+ let lastOption: SelectOption | null = null
613
+
614
+ select.on(SelectRenderableEvents.SELECTION_CHANGED, (index: number, option: SelectOption) => {
615
+ eventCount++
616
+ lastIndex = index
617
+ lastOption = option
618
+ })
619
+
620
+ select.moveDown()
621
+ expect(eventCount).toBe(1)
622
+ expect(lastIndex).toBe(2)
623
+ expect(lastOption).toEqual(sampleOptions[2])
624
+
625
+ select.moveUp()
626
+ expect(eventCount).toBe(2)
627
+ expect(lastIndex).toBe(1)
628
+ expect(lastOption).toEqual(sampleOptions[1])
629
+ })
630
+
631
+ test("should emit ITEM_SELECTED when selecting", async () => {
632
+ const { select } = await createSelectRenderable(currentRenderer, {
633
+ width: 20,
634
+ height: 5,
635
+ options: sampleOptions,
636
+ selectedIndex: 2,
637
+ })
638
+
639
+ let eventCount = 0
640
+ let lastIndex = -1
641
+ let lastOption: SelectOption | null = null
642
+
643
+ select.on(SelectRenderableEvents.ITEM_SELECTED, (index: number, option: SelectOption) => {
644
+ eventCount++
645
+ lastIndex = index
646
+ lastOption = option
647
+ })
648
+
649
+ select.selectCurrent()
650
+ expect(eventCount).toBe(1)
651
+ expect(lastIndex).toBe(2)
652
+ expect(lastOption).toEqual(sampleOptions[2])
653
+ })
654
+
655
+ test("should not reuse the same keypress after focusing another select", async () => {
656
+ const { select: first } = await createSelectRenderable(currentRenderer, {
657
+ width: 20,
658
+ height: 5,
659
+ options: sampleOptions,
660
+ selectedIndex: 1,
661
+ })
662
+ const { select: second } = await createSelectRenderable(currentRenderer, {
663
+ width: 20,
664
+ height: 5,
665
+ options: [
666
+ { name: "A", description: "A" },
667
+ { name: "B", description: "B" },
668
+ ],
669
+ })
670
+
671
+ let firstSelections = 0
672
+ let secondSelections = 0
673
+
674
+ first.on(SelectRenderableEvents.ITEM_SELECTED, () => {
675
+ firstSelections++
676
+ second.focus()
677
+ })
678
+ second.on(SelectRenderableEvents.ITEM_SELECTED, () => {
679
+ secondSelections++
680
+ })
681
+
682
+ first.focus()
683
+ currentMockInput.pressKey("RETURN")
684
+
685
+ expect(firstSelections).toBe(1)
686
+ expect(secondSelections).toBe(0)
687
+ expect(second.focused).toBe(true)
688
+ })
689
+
690
+ test("should emit events even when movement is blocked", async () => {
691
+ const { select } = await createSelectRenderable(currentRenderer, {
692
+ width: 20,
693
+ height: 5,
694
+ options: sampleOptions,
695
+ selectedIndex: 0, // At the beginning
696
+ wrapSelection: false,
697
+ })
698
+
699
+ let eventCount = 0
700
+
701
+ select.on(SelectRenderableEvents.SELECTION_CHANGED, () => {
702
+ eventCount++
703
+ })
704
+
705
+ // Try to move up from first item (index stays the same but event is emitted)
706
+ select.moveUp()
707
+ expect(eventCount).toBe(1)
708
+ expect(select.getSelectedIndex()).toBe(0)
709
+
710
+ // Try to move down to last item and then try to move down again
711
+ select.setSelectedIndex(4) // Move to last item
712
+ eventCount = 0 // Reset counter
713
+
714
+ select.moveDown()
715
+ expect(eventCount).toBe(1)
716
+ expect(select.getSelectedIndex()).toBe(4) // Should stay at last item
717
+ })
718
+ })
719
+
720
+ describe("Resize Handling", () => {
721
+ test("should handle resize events", async () => {
722
+ const { select } = await createSelectRenderable(currentRenderer, {
723
+ width: 20,
724
+ height: 5,
725
+ options: sampleOptions,
726
+ })
727
+
728
+ // Simulate resize by calling onResize directly
729
+ // @ts-expect-error - Testing protected method
730
+ select.onResize(30, 10)
731
+
732
+ // Should not throw errors and should be able to continue functioning
733
+ expect(select.getSelectedIndex()).toBe(0)
734
+ expect(select.getSelectedOption()).toEqual(sampleOptions[0])
735
+ })
736
+ })
737
+
738
+ describe("Edge Cases", () => {
739
+ test("should handle options with undefined values", async () => {
740
+ const optionsWithValues: SelectOption[] = [
741
+ { name: "Option 1", description: "First option", value: "value1" },
742
+ { name: "Option 2", description: "Second option", value: undefined },
743
+ { name: "Option 3", description: "Third option" },
744
+ ]
745
+
746
+ const { select } = await createSelectRenderable(currentRenderer, {
747
+ width: 20,
748
+ height: 5,
749
+ options: optionsWithValues,
750
+ })
751
+
752
+ expect(select.options).toEqual(optionsWithValues)
753
+ expect(select.getSelectedOption()?.value).toBe("value1")
754
+
755
+ select.setSelectedIndex(1)
756
+ expect(select.getSelectedOption()?.value).toBe(undefined)
757
+
758
+ select.setSelectedIndex(2)
759
+ expect(select.getSelectedOption()?.value).toBe(undefined)
760
+ })
761
+
762
+ test("should handle single option", async () => {
763
+ const singleOption: SelectOption[] = [{ name: "Only Option", description: "The only choice" }]
764
+
765
+ const { select } = await createSelectRenderable(currentRenderer, {
766
+ width: 20,
767
+ height: 5,
768
+ options: singleOption,
769
+ })
770
+
771
+ expect(select.getSelectedIndex()).toBe(0)
772
+ expect(select.getSelectedOption()).toEqual(singleOption[0])
773
+
774
+ let eventCount = 0
775
+ select.on(SelectRenderableEvents.SELECTION_CHANGED, () => {
776
+ eventCount++
777
+ })
778
+
779
+ // Movement should not change selection but events are still emitted
780
+ select.moveUp()
781
+ expect(select.getSelectedIndex()).toBe(0)
782
+ expect(eventCount).toBe(1)
783
+
784
+ select.moveDown()
785
+ expect(select.getSelectedIndex()).toBe(0)
786
+ expect(eventCount).toBe(2)
787
+ })
788
+
789
+ test("should handle very small dimensions", async () => {
790
+ const { select } = await createSelectRenderable(currentRenderer, {
791
+ width: 1,
792
+ height: 1,
793
+ options: sampleOptions,
794
+ })
795
+
796
+ // Should still function even with minimal space
797
+ expect(select.getSelectedIndex()).toBe(0)
798
+ expect(select.getSelectedOption()).toEqual(sampleOptions[0])
799
+
800
+ select.moveDown()
801
+ expect(select.getSelectedIndex()).toBe(1)
802
+ })
803
+
804
+ test("should handle long option names and descriptions", async () => {
805
+ const longOptions: SelectOption[] = [
806
+ {
807
+ name: "This is a very long option name that exceeds normal width",
808
+ description:
809
+ "This is an extremely long description that definitely exceeds the available width and should be handled gracefully",
810
+ },
811
+ {
812
+ name: "Short",
813
+ description: "Short desc",
814
+ },
815
+ ]
816
+
817
+ const { select } = await createSelectRenderable(currentRenderer, {
818
+ width: 10,
819
+ height: 5,
820
+ options: longOptions,
821
+ })
822
+
823
+ expect(select.getSelectedIndex()).toBe(0)
824
+ expect(select.getSelectedOption()).toEqual(longOptions[0])
825
+
826
+ select.moveDown()
827
+ expect(select.getSelectedIndex()).toBe(1)
828
+ expect(select.getSelectedOption()).toEqual(longOptions[1])
829
+ })
830
+
831
+ test("should handle focus state changes", async () => {
832
+ const { select } = await createSelectRenderable(currentRenderer, {
833
+ width: 20,
834
+ height: 5,
835
+ options: sampleOptions,
836
+ })
837
+
838
+ expect(select.focused).toBe(false)
839
+
840
+ select.focus()
841
+ expect(select.focused).toBe(true)
842
+
843
+ select.blur()
844
+ expect(select.focused).toBe(false)
845
+ })
846
+ })
847
+
848
+ describe("Key Bindings and Aliases", () => {
849
+ test("should support custom key bindings", async () => {
850
+ const { select } = await createSelectRenderable(currentRenderer, {
851
+ width: 20,
852
+ height: 10,
853
+ options: sampleOptions,
854
+ keyBindings: [
855
+ { name: "h", action: "move-up" },
856
+ { name: "l", action: "move-down" },
857
+ ],
858
+ })
859
+
860
+ select.focus()
861
+ expect(select.getSelectedIndex()).toBe(0)
862
+
863
+ // H should move up (but we're at top)
864
+ currentMockInput.pressKey("h")
865
+ expect(select.getSelectedIndex()).toBe(0)
866
+
867
+ // L should move down
868
+ currentMockInput.pressKey("l")
869
+ expect(select.getSelectedIndex()).toBe(1)
870
+ })
871
+
872
+ test("should support key aliases", async () => {
873
+ const { select } = await createSelectRenderable(currentRenderer, {
874
+ width: 20,
875
+ height: 10,
876
+ options: sampleOptions,
877
+ keyAliasMap: {
878
+ enter: "return",
879
+ },
880
+ })
881
+
882
+ select.focus()
883
+ select.setSelectedIndex(1)
884
+
885
+ let itemSelected = false
886
+ select.on(SelectRenderableEvents.ITEM_SELECTED, () => {
887
+ itemSelected = true
888
+ })
889
+
890
+ currentMockInput.pressEnter()
891
+ expect(itemSelected).toBe(true)
892
+ })
893
+
894
+ test("should merge custom bindings with defaults", async () => {
895
+ const { select } = await createSelectRenderable(currentRenderer, {
896
+ width: 20,
897
+ height: 10,
898
+ options: sampleOptions,
899
+ keyBindings: [{ name: "n", action: "move-down" }],
900
+ })
901
+
902
+ select.focus()
903
+ expect(select.getSelectedIndex()).toBe(0)
904
+
905
+ // Default binding should still work
906
+ currentMockInput.pressArrow("down")
907
+ expect(select.getSelectedIndex()).toBe(1)
908
+
909
+ // Custom binding should also work
910
+ currentMockInput.pressKey("n")
911
+ expect(select.getSelectedIndex()).toBe(2)
912
+ })
913
+
914
+ test("should override default bindings with custom ones", async () => {
915
+ const { select } = await createSelectRenderable(currentRenderer, {
916
+ width: 20,
917
+ height: 10,
918
+ options: sampleOptions,
919
+ keyBindings: [
920
+ { name: "k", action: "move-down" }, // Override k to move down instead of up
921
+ ],
922
+ })
923
+
924
+ select.focus()
925
+ expect(select.getSelectedIndex()).toBe(0)
926
+
927
+ // K should now move down instead of up
928
+ currentMockInput.pressKey("k")
929
+ expect(select.getSelectedIndex()).toBe(1)
930
+ })
931
+
932
+ test("should support fast scroll with shift by default", async () => {
933
+ const { select } = await createSelectRenderable(currentRenderer, {
934
+ width: 20,
935
+ height: 10,
936
+ options: sampleOptions,
937
+ fastScrollStep: 3,
938
+ })
939
+
940
+ select.focus()
941
+ expect(select.getSelectedIndex()).toBe(0)
942
+
943
+ // Shift+Down should fast scroll
944
+ currentMockInput.pressArrow("down", { shift: true })
945
+ expect(select.getSelectedIndex()).toBe(3)
946
+ })
947
+
948
+ test("should allow custom bindings for fast scroll", async () => {
949
+ const { select } = await createSelectRenderable(currentRenderer, {
950
+ width: 20,
951
+ height: 10,
952
+ options: sampleOptions,
953
+ fastScrollStep: 2,
954
+ keyBindings: [{ name: "down", ctrl: true, action: "move-down-fast" }],
955
+ })
956
+
957
+ select.focus()
958
+ expect(select.getSelectedIndex()).toBe(0)
959
+
960
+ // Ctrl+Down should fast scroll down
961
+ currentMockInput.pressArrow("down", { ctrl: true })
962
+ expect(select.getSelectedIndex()).toBe(2)
963
+ })
964
+
965
+ test("should allow updating key bindings dynamically", async () => {
966
+ const { select } = await createSelectRenderable(currentRenderer, {
967
+ width: 20,
968
+ height: 10,
969
+ options: sampleOptions,
970
+ })
971
+
972
+ select.focus()
973
+ expect(select.getSelectedIndex()).toBe(0)
974
+
975
+ // Move down with default binding
976
+ currentMockInput.pressArrow("down")
977
+ expect(select.getSelectedIndex()).toBe(1)
978
+
979
+ // Update bindings
980
+ select.keyBindings = [{ name: "x", action: "move-down" }]
981
+
982
+ // X should now move down
983
+ currentMockInput.pressKey("x")
984
+ expect(select.getSelectedIndex()).toBe(2)
985
+ })
986
+
987
+ test("should handle modifiers in custom bindings", async () => {
988
+ const { select } = await createSelectRenderable(currentRenderer, {
989
+ width: 20,
990
+ height: 10,
991
+ options: sampleOptions,
992
+ keyBindings: [
993
+ { name: "n", ctrl: true, action: "move-down" },
994
+ { name: "p", ctrl: true, action: "move-up" },
995
+ ],
996
+ })
997
+
998
+ select.focus()
999
+ select.setSelectedIndex(2)
1000
+
1001
+ // Ctrl+P should move up
1002
+ currentMockInput.pressKey("p", { ctrl: true })
1003
+ expect(select.getSelectedIndex()).toBe(1)
1004
+
1005
+ // Ctrl+N should move down
1006
+ currentMockInput.pressKey("n", { ctrl: true })
1007
+ expect(select.getSelectedIndex()).toBe(2)
1008
+ })
1009
+ })
1010
+ })