@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,787 @@
1
+ import { test, expect, describe } from "bun:test"
2
+ import { getObjectsInViewport } from "./objects-in-viewport.js"
3
+ import type { ViewportBounds } from "../types.js"
4
+
5
+ interface TestObject {
6
+ x: number
7
+ y: number
8
+ width: number
9
+ height: number
10
+ zIndex: number
11
+ id: string
12
+ }
13
+
14
+ function createObject(id: string, x: number, y: number, width: number, height: number, zIndex: number = 0): TestObject {
15
+ return { id, x, y, width, height, zIndex }
16
+ }
17
+
18
+ describe("getObjectsInViewport", () => {
19
+ describe("basic functionality", () => {
20
+ test("returns empty array for empty input", () => {
21
+ const viewport: ViewportBounds = { x: 0, y: 0, width: 100, height: 100 }
22
+ const result = getObjectsInViewport(viewport, [])
23
+ expect(result).toEqual([])
24
+ })
25
+
26
+ test("returns all objects when count is below minTriggerSize", () => {
27
+ const viewport: ViewportBounds = { x: 0, y: 0, width: 100, height: 100 }
28
+ const objects = [createObject("1", 0, 0, 10, 10), createObject("2", 200, 200, 10, 10)]
29
+ const result = getObjectsInViewport(viewport, objects, "column", 10, 16)
30
+ expect(result).toEqual(objects)
31
+ })
32
+
33
+ test("filters objects outside viewport in column direction", () => {
34
+ const viewport: ViewportBounds = { x: 0, y: 100, width: 100, height: 100 }
35
+ const objects = Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, i * 20, 100, 20))
36
+
37
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
38
+ const visibleIds = result.map((o) => o.id)
39
+
40
+ expect(visibleIds).toContain("obj-5")
41
+ expect(visibleIds).toContain("obj-6")
42
+ expect(visibleIds).toContain("obj-9")
43
+ expect(visibleIds).not.toContain("obj-0")
44
+ expect(visibleIds).not.toContain("obj-15")
45
+ })
46
+
47
+ test("filters objects outside viewport in row direction", () => {
48
+ const viewport: ViewportBounds = { x: 100, y: 0, width: 100, height: 100 }
49
+ const objects = Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, i * 20, 0, 20, 100))
50
+
51
+ const result = getObjectsInViewport(viewport, objects, "row", 0, 16)
52
+ const visibleIds = result.map((o) => o.id)
53
+
54
+ expect(visibleIds).toContain("obj-5")
55
+ expect(visibleIds).toContain("obj-6")
56
+ expect(visibleIds).toContain("obj-9")
57
+ expect(visibleIds).not.toContain("obj-0")
58
+ expect(visibleIds).not.toContain("obj-15")
59
+ })
60
+ })
61
+
62
+ describe("padding behavior", () => {
63
+ test("includes objects within padding distance", () => {
64
+ const viewport: ViewportBounds = { x: 0, y: 100, width: 100, height: 100 }
65
+ const objects = Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, i * 20, 100, 20))
66
+
67
+ const result = getObjectsInViewport(viewport, objects, "column", 20, 16)
68
+ const visibleIds = result.map((o) => o.id)
69
+
70
+ expect(visibleIds).toContain("obj-4")
71
+ expect(visibleIds).toContain("obj-10")
72
+ })
73
+
74
+ test("respects custom padding values", () => {
75
+ const viewport: ViewportBounds = { x: 0, y: 100, width: 100, height: 100 }
76
+ const objects = Array.from({ length: 30 }, (_, i) => createObject(`obj-${i}`, 0, i * 20, 100, 20))
77
+
78
+ const resultNoPadding = getObjectsInViewport(viewport, objects, "column", 0, 16)
79
+ const resultWithPadding = getObjectsInViewport(viewport, objects, "column", 50, 16)
80
+
81
+ expect(resultWithPadding.length).toBeGreaterThan(resultNoPadding.length)
82
+ })
83
+ })
84
+
85
+ describe("zIndex sorting", () => {
86
+ test("sorts visible objects by zIndex", () => {
87
+ const viewport: ViewportBounds = { x: 0, y: 0, width: 100, height: 100 }
88
+ const objects = Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, i * 10, 100, 10, 20 - i))
89
+
90
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
91
+
92
+ for (let i = 1; i < result.length; i++) {
93
+ expect(result[i].zIndex).toBeGreaterThanOrEqual(result[i - 1].zIndex)
94
+ }
95
+ })
96
+
97
+ test("handles objects with same zIndex", () => {
98
+ const viewport: ViewportBounds = { x: 0, y: 0, width: 100, height: 100 }
99
+ const objects = Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, i * 10, 100, 10, 5))
100
+
101
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
102
+ expect(result.every((obj) => obj.zIndex === 5)).toBe(true)
103
+ })
104
+
105
+ test("handles mixed zIndex values", () => {
106
+ const viewport: ViewportBounds = { x: 0, y: 0, width: 100, height: 100 }
107
+ const objects = Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, i * 10, 100, 10, i % 3))
108
+
109
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
110
+
111
+ for (let i = 1; i < result.length; i++) {
112
+ expect(result[i].zIndex).toBeGreaterThanOrEqual(result[i - 1].zIndex)
113
+ }
114
+ })
115
+ })
116
+
117
+ describe("edge cases - boundary conditions", () => {
118
+ test("includes object that starts at viewport top", () => {
119
+ const viewport: ViewportBounds = { x: 0, y: 100, width: 100, height: 100 }
120
+ const objects = Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, i * 20, 100, 20))
121
+
122
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
123
+ const visibleIds = result.map((o) => o.id)
124
+
125
+ expect(visibleIds).toContain("obj-5")
126
+ })
127
+
128
+ test("excludes object that ends exactly at viewport start (no padding)", () => {
129
+ const viewport: ViewportBounds = { x: 0, y: 100, width: 100, height: 100 }
130
+ const objects = [
131
+ createObject("before", 0, 50, 100, 50),
132
+ ...Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, (i + 5) * 20, 100, 20)),
133
+ ]
134
+
135
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
136
+ const visibleIds = result.map((o) => o.id)
137
+
138
+ expect(visibleIds).not.toContain("before")
139
+ })
140
+
141
+ test("excludes object that starts exactly at viewport end (no padding)", () => {
142
+ const viewport: ViewportBounds = { x: 0, y: 100, width: 100, height: 100 }
143
+ const objects = [
144
+ createObject("after", 0, 200, 100, 20),
145
+ ...Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, i * 20, 100, 20)),
146
+ ]
147
+
148
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
149
+ const visibleIds = result.map((o) => o.id)
150
+
151
+ expect(visibleIds).not.toContain("after")
152
+ })
153
+ })
154
+
155
+ describe("cross-axis filtering", () => {
156
+ test("filters objects outside viewport on cross-axis (column mode)", () => {
157
+ const viewport: ViewportBounds = { x: 50, y: 100, width: 100, height: 100 }
158
+ const objects = Array.from({ length: 20 }, (_, i) =>
159
+ createObject(`obj-${i}`, i % 2 === 0 ? 0 : 60, i * 20, 40, 20),
160
+ )
161
+
162
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
163
+
164
+ result.forEach((obj) => {
165
+ const objectRight = obj.x + obj.width
166
+ expect(objectRight).toBeGreaterThan(viewport.x)
167
+ expect(obj.x).toBeLessThan(viewport.x + viewport.width)
168
+ })
169
+ })
170
+
171
+ test("filters objects outside viewport on cross-axis (row mode)", () => {
172
+ const viewport: ViewportBounds = { x: 100, y: 50, width: 100, height: 100 }
173
+ const objects = Array.from({ length: 20 }, (_, i) =>
174
+ createObject(`obj-${i}`, i * 20, i % 2 === 0 ? 0 : 60, 20, 40),
175
+ )
176
+
177
+ const result = getObjectsInViewport(viewport, objects, "row", 0, 16)
178
+
179
+ result.forEach((obj) => {
180
+ const objectBottom = obj.y + obj.height
181
+ expect(objectBottom).toBeGreaterThan(viewport.y)
182
+ expect(obj.y).toBeLessThan(viewport.y + viewport.height)
183
+ })
184
+ })
185
+ })
186
+
187
+ describe("scrolling simulation - vertical", () => {
188
+ const createScrollList = () => {
189
+ return Array.from({ length: 100 }, (_, i) => createObject(`item-${i}`, 0, i * 50, 200, 50, i % 10))
190
+ }
191
+
192
+ test("viewport at top", () => {
193
+ const viewport: ViewportBounds = { x: 0, y: 0, width: 200, height: 300 }
194
+ const objects = createScrollList()
195
+
196
+ const result = getObjectsInViewport(viewport, objects, "column", 10, 16)
197
+ const visibleIds = result.map((o) => o.id)
198
+
199
+ expect(visibleIds).toContain("item-0")
200
+ expect(visibleIds).toContain("item-5")
201
+ expect(visibleIds).not.toContain("item-20")
202
+ })
203
+
204
+ test("viewport scrolled to middle", () => {
205
+ const viewport: ViewportBounds = { x: 0, y: 2000, width: 200, height: 300 }
206
+ const objects = createScrollList()
207
+
208
+ const result = getObjectsInViewport(viewport, objects, "column", 10, 16)
209
+ const visibleIds = result.map((o) => o.id)
210
+
211
+ expect(visibleIds).toContain("item-40")
212
+ expect(visibleIds).toContain("item-45")
213
+ expect(visibleIds).not.toContain("item-0")
214
+ expect(visibleIds).not.toContain("item-99")
215
+ })
216
+
217
+ test("viewport at bottom", () => {
218
+ const viewport: ViewportBounds = { x: 0, y: 4700, width: 200, height: 300 }
219
+ const objects = createScrollList()
220
+
221
+ const result = getObjectsInViewport(viewport, objects, "column", 10, 16)
222
+ const visibleIds = result.map((o) => o.id)
223
+
224
+ expect(visibleIds).toContain("item-94")
225
+ expect(visibleIds).toContain("item-99")
226
+ expect(visibleIds).not.toContain("item-0")
227
+ expect(visibleIds).not.toContain("item-50")
228
+ })
229
+
230
+ test("small incremental scrolls", () => {
231
+ const objects = createScrollList()
232
+
233
+ for (let scrollY = 0; scrollY < 1000; scrollY += 10) {
234
+ const viewport: ViewportBounds = { x: 0, y: scrollY, width: 200, height: 300 }
235
+ const result = getObjectsInViewport(viewport, objects, "column", 10, 16)
236
+
237
+ result.forEach((obj) => {
238
+ const objectBottom = obj.y + obj.height
239
+ expect(objectBottom).toBeGreaterThan(viewport.y - 10)
240
+ expect(obj.y).toBeLessThan(viewport.y + viewport.height + 10)
241
+ })
242
+ }
243
+ })
244
+ })
245
+
246
+ describe("scrolling simulation - horizontal", () => {
247
+ const createHorizontalList = () => {
248
+ return Array.from({ length: 100 }, (_, i) => createObject(`item-${i}`, i * 50, 0, 50, 200, i % 10))
249
+ }
250
+
251
+ test("viewport at left", () => {
252
+ const viewport: ViewportBounds = { x: 0, y: 0, width: 300, height: 200 }
253
+ const objects = createHorizontalList()
254
+
255
+ const result = getObjectsInViewport(viewport, objects, "row", 10, 16)
256
+ const visibleIds = result.map((o) => o.id)
257
+
258
+ expect(visibleIds).toContain("item-0")
259
+ expect(visibleIds).toContain("item-5")
260
+ expect(visibleIds).not.toContain("item-20")
261
+ })
262
+
263
+ test("viewport scrolled to middle", () => {
264
+ const viewport: ViewportBounds = { x: 2000, y: 0, width: 300, height: 200 }
265
+ const objects = createHorizontalList()
266
+
267
+ const result = getObjectsInViewport(viewport, objects, "row", 10, 16)
268
+ const visibleIds = result.map((o) => o.id)
269
+
270
+ expect(visibleIds).toContain("item-40")
271
+ expect(visibleIds).toContain("item-45")
272
+ expect(visibleIds).not.toContain("item-0")
273
+ expect(visibleIds).not.toContain("item-99")
274
+ })
275
+
276
+ test("viewport at right", () => {
277
+ const viewport: ViewportBounds = { x: 4700, y: 0, width: 300, height: 200 }
278
+ const objects = createHorizontalList()
279
+
280
+ const result = getObjectsInViewport(viewport, objects, "row", 10, 16)
281
+ const visibleIds = result.map((o) => o.id)
282
+
283
+ expect(visibleIds).toContain("item-94")
284
+ expect(visibleIds).toContain("item-99")
285
+ expect(visibleIds).not.toContain("item-0")
286
+ })
287
+ })
288
+
289
+ describe("large objects", () => {
290
+ test("handles objects much larger than viewport", () => {
291
+ const viewport: ViewportBounds = { x: 0, y: 500, width: 100, height: 100 }
292
+ const objects = [
293
+ ...Array.from({ length: 20 }, (_, i) => createObject(`filler-${i}`, 0, i * 100, 100, 50)),
294
+ createObject("huge", 0, 100, 100, 1000),
295
+ createObject("tiny-after", 0, 1200, 100, 10),
296
+ ].sort((a, b) => a.y - b.y)
297
+
298
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
299
+ const visibleIds = result.map((o) => o.id)
300
+
301
+ expect(visibleIds).toContain("huge")
302
+ })
303
+
304
+ test("large object with many small items before viewport (realistic background panel)", () => {
305
+ // Simulates a background panel spanning entire list with small list items
306
+ const objects = [
307
+ createObject("background-panel", 0, 0, 100, 3000), // Large spanning background
308
+ ...Array.from({ length: 30 }, (_, i) => createObject(`item-${i}`, 0, i * 60, 100, 50)), // List items
309
+ ...Array.from({ length: 20 }, (_, i) => createObject(`filler-${i}`, 0, i * 100 + 3000, 100, 50)),
310
+ ]
311
+
312
+ // Viewport at y=1500, background spans 0-3000, with ~25 items between them
313
+ const viewport: ViewportBounds = { x: 0, y: 1500, width: 100, height: 100 }
314
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
315
+ const visibleIds = result.map((o) => o.id)
316
+
317
+ expect(visibleIds).toContain("background-panel")
318
+ })
319
+
320
+ test("handles very tall objects in vertical scroll", () => {
321
+ const objects = [
322
+ createObject("small-1", 0, 0, 100, 50),
323
+ createObject("tall-1", 0, 100, 100, 500),
324
+ createObject("small-2", 0, 650, 100, 50),
325
+ createObject("tall-2", 0, 750, 100, 800),
326
+ createObject("small-3", 0, 1600, 100, 50),
327
+ ...Array.from({ length: 20 }, (_, i) => createObject(`filler-${i}`, 0, i * 100 + 2000, 100, 50)),
328
+ ]
329
+
330
+ for (let scrollY = 0; scrollY < 2000; scrollY += 100) {
331
+ const viewport: ViewportBounds = { x: 0, y: scrollY, width: 100, height: 200 }
332
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
333
+
334
+ result.forEach((obj) => {
335
+ const objectBottom = obj.y + obj.height
336
+ expect(objectBottom).toBeGreaterThan(viewport.y)
337
+ expect(obj.y).toBeLessThan(viewport.y + viewport.height)
338
+ })
339
+ }
340
+ })
341
+
342
+ test("handles very wide objects in horizontal scroll", () => {
343
+ const objects = [
344
+ createObject("small-1", 0, 0, 50, 100),
345
+ createObject("wide-1", 100, 0, 500, 100),
346
+ createObject("small-2", 650, 0, 50, 100),
347
+ createObject("wide-2", 750, 0, 800, 100),
348
+ ...Array.from({ length: 20 }, (_, i) => createObject(`filler-${i}`, i * 100 + 2000, 0, 50, 100)),
349
+ ]
350
+
351
+ for (let scrollX = 0; scrollX < 2000; scrollX += 100) {
352
+ const viewport: ViewportBounds = { x: scrollX, y: 0, width: 200, height: 100 }
353
+ const result = getObjectsInViewport(viewport, objects, "row", 0, 16)
354
+
355
+ result.forEach((obj) => {
356
+ const objectRight = obj.x + obj.width
357
+ expect(objectRight).toBeGreaterThan(viewport.x)
358
+ expect(obj.x).toBeLessThan(viewport.x + viewport.width)
359
+ })
360
+ }
361
+ })
362
+ })
363
+
364
+ describe("viewport size variations", () => {
365
+ const objects = Array.from({ length: 100 }, (_, i) => createObject(`item-${i}`, 0, i * 30, 200, 30, i % 5))
366
+
367
+ test("very small viewport", () => {
368
+ const viewport: ViewportBounds = { x: 0, y: 500, width: 50, height: 50 }
369
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
370
+
371
+ expect(result.length).toBeGreaterThan(0)
372
+ expect(result.length).toBeLessThan(10)
373
+ })
374
+
375
+ test("very large viewport", () => {
376
+ const viewport: ViewportBounds = { x: 0, y: 500, width: 1000, height: 1000 }
377
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
378
+
379
+ expect(result.length).toBeGreaterThan(30)
380
+ })
381
+
382
+ test("viewport larger than all objects", () => {
383
+ const viewport: ViewportBounds = { x: 0, y: 0, width: 500, height: 5000 }
384
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
385
+
386
+ expect(result.length).toBe(objects.length)
387
+ })
388
+ })
389
+
390
+ describe("negative coordinates", () => {
391
+ test("handles negative viewport coordinates", () => {
392
+ const viewport: ViewportBounds = { x: -50, y: -50, width: 100, height: 100 }
393
+ const objects = Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, -100, i * 20 - 100, 200, 20))
394
+
395
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
396
+
397
+ result.forEach((obj) => {
398
+ const objectBottom = obj.y + obj.height
399
+ expect(objectBottom).toBeGreaterThan(viewport.y)
400
+ expect(obj.y).toBeLessThan(viewport.y + viewport.height)
401
+ })
402
+ })
403
+
404
+ test("handles negative object coordinates", () => {
405
+ const viewport: ViewportBounds = { x: 0, y: 0, width: 100, height: 100 }
406
+ const objects = [createObject("negative-y", 0, -50, 100, 100), createObject("positive-y", 0, 50, 100, 100)]
407
+ objects.push(...Array.from({ length: 20 }, (_, i) => createObject(`filler-${i}`, 0, i * 20, 100, 20)))
408
+
409
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
410
+ const visibleIds = result.map((o) => o.id)
411
+
412
+ expect(visibleIds).toContain("negative-y")
413
+ expect(visibleIds).toContain("positive-y")
414
+ })
415
+ })
416
+
417
+ describe("sparse object distributions", () => {
418
+ test("handles large gaps between objects", () => {
419
+ const viewport: ViewportBounds = { x: 0, y: 5000, width: 100, height: 100 }
420
+ const objects = Array.from({ length: 50 }, (_, i) => createObject(`obj-${i}`, 0, i * 1000, 100, 50))
421
+
422
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
423
+ const visibleIds = result.map((o) => o.id)
424
+
425
+ expect(visibleIds).toContain("obj-5")
426
+ expect(result.length).toBeLessThan(5)
427
+ })
428
+
429
+ test("handles clustered objects", () => {
430
+ const viewport: ViewportBounds = { x: 0, y: 500, width: 100, height: 100 }
431
+ const objects = [
432
+ ...Array.from({ length: 10 }, (_, i) => createObject(`cluster-${i}`, 0, 490 + i * 2, 100, 2)),
433
+ ...Array.from({ length: 10 }, (_, i) => createObject(`filler-${i}`, 0, i * 100, 100, 20)),
434
+ ]
435
+
436
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
437
+
438
+ expect(result.length).toBeGreaterThan(5)
439
+ })
440
+ })
441
+
442
+ describe("minTriggerSize parameter", () => {
443
+ test("bypasses optimization when object count is below threshold", () => {
444
+ const viewport: ViewportBounds = { x: 0, y: 0, width: 100, height: 100 }
445
+ const objects = [createObject("far-away", 0, 10000, 100, 100), createObject("visible", 0, 50, 100, 100)]
446
+
447
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 100)
448
+ expect(result.length).toBe(2)
449
+ expect(result.map((o) => o.id)).toContain("far-away")
450
+ })
451
+
452
+ test("applies optimization when object count meets threshold", () => {
453
+ const viewport: ViewportBounds = { x: 0, y: 0, width: 100, height: 100 }
454
+ const objects = [
455
+ ...Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, i * 20, 100, 20)),
456
+ createObject("far-away", 0, 10000, 100, 100),
457
+ ]
458
+
459
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
460
+ expect(result.map((o) => o.id)).not.toContain("far-away")
461
+ })
462
+
463
+ test("performs overlap checks when minTriggerSize is 0", () => {
464
+ const viewport: ViewportBounds = { x: 0, y: 10, width: 40, height: 1 }
465
+ const objects = [createObject("above-viewport", 0, 0, 40, 5), createObject("in-viewport", 0, 10, 40, 1)]
466
+
467
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 0)
468
+ expect(result.length).toBe(1)
469
+ expect(result[0].id).toBe("in-viewport")
470
+ })
471
+
472
+ test("filters out objects outside viewport when minTriggerSize is 0", () => {
473
+ const viewport: ViewportBounds = { x: 0, y: 10, width: 40, height: 5 }
474
+ const objects = [
475
+ createObject("above-1", 0, 0, 40, 3),
476
+ createObject("above-2", 0, 5, 40, 4),
477
+ createObject("in-viewport", 0, 12, 40, 2),
478
+ createObject("below", 0, 20, 40, 5),
479
+ ]
480
+
481
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 0)
482
+ expect(result.length).toBe(1)
483
+ expect(result[0].id).toBe("in-viewport")
484
+ })
485
+
486
+ test("respects exact boundary conditions with minTriggerSize 0", () => {
487
+ const viewport: ViewportBounds = { x: 0, y: 100, width: 100, height: 100 }
488
+ const objects = [
489
+ createObject("ends-at-start", 0, 50, 100, 50),
490
+ createObject("overlaps-start", 0, 50, 100, 51),
491
+ createObject("inside", 0, 150, 100, 20),
492
+ createObject("overlaps-end", 0, 199, 100, 10),
493
+ createObject("starts-at-end", 0, 200, 100, 50),
494
+ ]
495
+
496
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 0)
497
+ const visibleIds = result.map((o) => o.id)
498
+
499
+ expect(visibleIds).not.toContain("ends-at-start")
500
+ expect(visibleIds).toContain("overlaps-start")
501
+ expect(visibleIds).toContain("inside")
502
+ expect(visibleIds).toContain("overlaps-end")
503
+ expect(visibleIds).not.toContain("starts-at-end")
504
+ })
505
+ })
506
+
507
+ describe("overlapping objects", () => {
508
+ test("handles completely overlapping objects", () => {
509
+ const viewport: ViewportBounds = { x: 0, y: 100, width: 100, height: 100 }
510
+ const objects = [
511
+ ...Array.from({ length: 10 }, (_, i) => createObject(`filler-before-${i}`, 0, i * 20, 100, 20)),
512
+ createObject("back", 0, 100, 100, 100, 0),
513
+ createObject("middle", 0, 100, 100, 100, 1),
514
+ createObject("front", 0, 100, 100, 100, 2),
515
+ ...Array.from({ length: 10 }, (_, i) => createObject(`filler-after-${i}`, 0, (i + 10) * 20, 100, 20)),
516
+ ]
517
+
518
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
519
+ const overlapping = result.filter(
520
+ (obj) => obj.id.includes("back") || obj.id.includes("middle") || obj.id.includes("front"),
521
+ )
522
+
523
+ expect(overlapping[0].id).toBe("back")
524
+ expect(overlapping[1].id).toBe("middle")
525
+ expect(overlapping[2].id).toBe("front")
526
+ })
527
+
528
+ test("handles partially overlapping objects", () => {
529
+ const viewport: ViewportBounds = { x: 0, y: 100, width: 100, height: 100 }
530
+ const objects = Array.from({ length: 30 }, (_, i) => createObject(`obj-${i}`, 0, i * 15, 100, 30, i % 3))
531
+
532
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
533
+
534
+ for (let i = 1; i < result.length; i++) {
535
+ expect(result[i].zIndex).toBeGreaterThanOrEqual(result[i - 1].zIndex)
536
+ }
537
+ })
538
+ })
539
+
540
+ describe("extreme values", () => {
541
+ test("zero-sized viewport returns empty array (zero width)", () => {
542
+ const viewport: ViewportBounds = { x: 100, y: 100, width: 0, height: 100 }
543
+ const objects = Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, i * 20, 100, 20))
544
+
545
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
546
+ expect(result.length).toBe(0)
547
+ })
548
+
549
+ test("zero-sized viewport returns empty array (zero height)", () => {
550
+ const viewport: ViewportBounds = { x: 100, y: 100, width: 100, height: 0 }
551
+ const objects = Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, i * 20, 100, 20))
552
+
553
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
554
+ expect(result.length).toBe(0)
555
+ })
556
+
557
+ test("zero-sized viewport returns empty array (both zero)", () => {
558
+ const viewport: ViewportBounds = { x: 100, y: 100, width: 0, height: 0 }
559
+ const objects = Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, i * 20, 100, 20))
560
+
561
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
562
+ expect(result.length).toBe(0)
563
+ })
564
+
565
+ test("handles zero-sized objects", () => {
566
+ const viewport: ViewportBounds = { x: 0, y: 100, width: 100, height: 100 }
567
+ const objects = Array.from({ length: 20 }, (_, i) => createObject(`obj-${i}`, 0, i * 20, 100, 0))
568
+
569
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
570
+ expect(result).toBeDefined()
571
+ })
572
+
573
+ test("handles very large coordinates", () => {
574
+ const viewport: ViewportBounds = { x: 1000000, y: 1000000, width: 100, height: 100 }
575
+ const objects = Array.from({ length: 50 }, (_, i) => createObject(`obj-${i}`, 1000000, 1000000 + i * 20, 100, 20))
576
+
577
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
578
+ expect(result.length).toBeGreaterThan(0)
579
+ })
580
+ })
581
+
582
+ describe("performance characteristics", () => {
583
+ test("handles 1000 objects efficiently", () => {
584
+ const viewport: ViewportBounds = { x: 0, y: 50000, width: 100, height: 100 }
585
+ const objects = Array.from({ length: 1000 }, (_, i) => createObject(`obj-${i}`, 0, i * 100, 100, 100, i % 10))
586
+
587
+ const start = performance.now()
588
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
589
+ const duration = performance.now() - start
590
+
591
+ expect(result.length).toBeGreaterThan(0)
592
+ expect(result.length).toBeLessThan(20)
593
+ expect(duration).toBeLessThan(10)
594
+ })
595
+
596
+ test("handles 10000 objects efficiently", () => {
597
+ const viewport: ViewportBounds = { x: 0, y: 500000, width: 100, height: 100 }
598
+ const objects = Array.from({ length: 10000 }, (_, i) => createObject(`obj-${i}`, 0, i * 100, 100, 100, i % 10))
599
+
600
+ const start = performance.now()
601
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
602
+ const duration = performance.now() - start
603
+
604
+ expect(result.length).toBeGreaterThan(0)
605
+ expect(result.length).toBeLessThan(20)
606
+ expect(duration).toBeLessThan(50)
607
+ })
608
+ })
609
+
610
+ describe("additional edge cases", () => {
611
+ test("object that starts before viewport and extends through it", () => {
612
+ const viewport: ViewportBounds = { x: 0, y: 500, width: 100, height: 100 }
613
+ const objects = Array.from({ length: 30 }, (_, i) => {
614
+ if (i === 2) {
615
+ return createObject("spanning", 0, 200, 100, 500)
616
+ }
617
+ return createObject(`obj-${i}`, 0, i * 50, 100, 40)
618
+ })
619
+
620
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
621
+ const visibleIds = result.map((o) => o.id)
622
+
623
+ expect(visibleIds).toContain("spanning")
624
+ })
625
+
626
+ test("multiple large overlapping objects during scroll", () => {
627
+ const objects = [
628
+ createObject("bg-1", 0, 0, 200, 1000, 0),
629
+ createObject("bg-2", 0, 500, 200, 1000, 0),
630
+ createObject("bg-3", 0, 1000, 200, 1000, 0),
631
+ ...Array.from({ length: 50 }, (_, i) => createObject(`small-${i}`, 0, i * 50, 200, 40, 1)),
632
+ ]
633
+
634
+ for (let scrollY = 0; scrollY <= 1500; scrollY += 100) {
635
+ const viewport: ViewportBounds = { x: 0, y: scrollY, width: 200, height: 300 }
636
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
637
+
638
+ result.forEach((obj) => {
639
+ const objectBottom = obj.y + obj.height
640
+ expect(objectBottom).toBeGreaterThan(viewport.y)
641
+ expect(obj.y).toBeLessThan(viewport.y + viewport.height)
642
+ })
643
+ }
644
+ })
645
+
646
+ test("viewport moves down through very tall object", () => {
647
+ const objects = [
648
+ ...Array.from({ length: 5 }, (_, i) => createObject(`before-${i}`, 0, i * 50, 100, 40)),
649
+ createObject("very-tall", 0, 300, 100, 2000),
650
+ ...Array.from({ length: 20 }, (_, i) => createObject(`after-${i}`, 0, 2400 + i * 50, 100, 40)),
651
+ ]
652
+
653
+ for (let scrollY = 0; scrollY <= 2500; scrollY += 200) {
654
+ const viewport: ViewportBounds = { x: 0, y: scrollY, width: 100, height: 200 }
655
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
656
+
657
+ if (scrollY >= 100 && scrollY <= 2100) {
658
+ const visibleIds = result.map((o) => o.id)
659
+ expect(visibleIds).toContain("very-tall")
660
+ }
661
+ }
662
+ })
663
+
664
+ test("objects with zero width or height", () => {
665
+ const viewport: ViewportBounds = { x: 0, y: 100, width: 100, height: 100 }
666
+ const objects = [
667
+ createObject("zero-height", 0, 150, 100, 0),
668
+ createObject("zero-width", 0, 160, 0, 40),
669
+ createObject("point", 0, 170, 0, 0),
670
+ ...Array.from({ length: 20 }, (_, i) => createObject(`normal-${i}`, 0, i * 20, 100, 15)),
671
+ ]
672
+
673
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
674
+
675
+ expect(result).toBeDefined()
676
+ })
677
+
678
+ test("viewport positioned between objects (should return empty)", () => {
679
+ const viewport: ViewportBounds = { x: 0, y: 1000, width: 100, height: 100 }
680
+ const objects = [
681
+ ...Array.from({ length: 10 }, (_, i) => createObject(`before-${i}`, 0, i * 50, 100, 40)),
682
+ ...Array.from({ length: 10 }, (_, i) => createObject(`after-${i}`, 0, 2000 + i * 50, 100, 40)),
683
+ ]
684
+
685
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
686
+
687
+ expect(result.length).toBe(0)
688
+ })
689
+
690
+ test("single pixel gaps between objects and viewport", () => {
691
+ const viewport: ViewportBounds = { x: 0, y: 1000, width: 100, height: 100 }
692
+ const objects = [
693
+ createObject("one-pixel-before", 0, 899, 100, 100), // ends at 999, gap of 1px
694
+ createObject("touching-before", 0, 999, 100, 1), // ends exactly at 1000
695
+ createObject("inside", 0, 1050, 100, 10), // fully inside
696
+ createObject("touching-after", 0, 1100, 100, 1), // starts exactly at 1100
697
+ createObject("one-pixel-after", 0, 1101, 100, 100), // starts at 1101, gap of 1px
698
+ ...Array.from({ length: 20 }, (_, i) => createObject(`filler-${i}`, 0, i * 100, 100, 50)),
699
+ ]
700
+
701
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
702
+ const visibleIds = result.map((o) => o.id)
703
+
704
+ // Objects with even 1px gap should be excluded (no overlap)
705
+ expect(visibleIds).not.toContain("one-pixel-before")
706
+ expect(visibleIds).not.toContain("touching-before")
707
+ expect(visibleIds).toContain("inside")
708
+ expect(visibleIds).not.toContain("touching-after")
709
+ expect(visibleIds).not.toContain("one-pixel-after")
710
+ })
711
+ })
712
+
713
+ describe("stress test - continuous scrolling", () => {
714
+ test("scrolling through 1000 objects with varying heights", () => {
715
+ const heights = [20, 50, 30, 100, 40, 60, 25, 80, 35, 70]
716
+ let currentY = 0
717
+ const objects = Array.from({ length: 1000 }, (_, i) => {
718
+ const height = heights[i % heights.length]
719
+ const obj = createObject(`item-${i}`, 0, currentY, 200, height, i % 5)
720
+ currentY += height + 2
721
+ return obj
722
+ })
723
+
724
+ const totalHeight = currentY
725
+ const viewportHeight = 400
726
+
727
+ for (let scrollY = 0; scrollY < totalHeight - viewportHeight; scrollY += 100) {
728
+ const viewport: ViewportBounds = { x: 0, y: scrollY, width: 200, height: viewportHeight }
729
+ const result = getObjectsInViewport(viewport, objects, "column", 50, 16)
730
+
731
+ result.forEach((obj) => {
732
+ const objectBottom = obj.y + obj.height
733
+ expect(objectBottom).toBeGreaterThan(viewport.y - 50)
734
+ expect(obj.y).toBeLessThan(viewport.y + viewport.height + 50)
735
+ })
736
+
737
+ expect(result.length).toBeGreaterThan(0)
738
+ expect(result.length).toBeLessThan(50)
739
+ }
740
+ })
741
+ })
742
+
743
+ describe("realistic scroll scenarios", () => {
744
+ test("chat-like interface with variable height messages", () => {
745
+ const heights = [30, 60, 45, 90, 120, 35, 50, 75, 40, 100]
746
+ let currentY = 0
747
+ const objects = Array.from({ length: 100 }, (_, i) => {
748
+ const height = heights[i % heights.length]
749
+ const obj = createObject(`msg-${i}`, 0, currentY, 300, height, 0)
750
+ currentY += height + 5
751
+ return obj
752
+ })
753
+
754
+ for (let scroll = 0; scroll < currentY - 500; scroll += 50) {
755
+ const viewport: ViewportBounds = { x: 0, y: scroll, width: 300, height: 500 }
756
+ const result = getObjectsInViewport(viewport, objects, "column", 20, 16)
757
+
758
+ result.forEach((obj) => {
759
+ const objectBottom = obj.y + obj.height
760
+ expect(objectBottom).toBeGreaterThan(viewport.y - 20)
761
+ expect(obj.y).toBeLessThan(viewport.y + viewport.height + 20)
762
+ })
763
+ }
764
+ })
765
+
766
+ test("grid layout with multiple columns", () => {
767
+ const objects = Array.from({ length: 200 }, (_, i) => {
768
+ const col = i % 4
769
+ const row = Math.floor(i / 4)
770
+ return createObject(`item-${i}`, col * 110, row * 110, 100, 100, 0)
771
+ })
772
+
773
+ const viewport: ViewportBounds = { x: 0, y: 1000, width: 440, height: 400 }
774
+ const result = getObjectsInViewport(viewport, objects, "column", 0, 16)
775
+
776
+ expect(result.length).toBeGreaterThan(0)
777
+ expect(result.length).toBeLessThan(30)
778
+
779
+ result.forEach((obj) => {
780
+ expect(obj.y + obj.height).toBeGreaterThan(viewport.y)
781
+ expect(obj.y).toBeLessThan(viewport.y + viewport.height)
782
+ expect(obj.x + obj.width).toBeGreaterThan(viewport.x)
783
+ expect(obj.x).toBeLessThan(viewport.x + viewport.width)
784
+ })
785
+ })
786
+ })
787
+ })