@fairyhunter13/opentui-core 0.1.112 → 0.1.114

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 (591) 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 +144 -0
  7. package/package.json +63 -51
  8. package/scripts/build.ts +400 -0
  9. package/scripts/publish.ts +60 -0
  10. package/src/3d/SpriteResourceManager.ts +286 -0
  11. package/src/3d/SpriteUtils.ts +70 -0
  12. package/src/3d/TextureUtils.ts +196 -0
  13. package/src/3d/ThreeRenderable.ts +197 -0
  14. package/src/3d/WGPURenderer.ts +294 -0
  15. package/src/3d/animation/ExplodingSpriteEffect.ts +513 -0
  16. package/src/3d/animation/PhysicsExplodingSpriteEffect.ts +429 -0
  17. package/src/3d/animation/SpriteAnimator.ts +633 -0
  18. package/src/3d/animation/SpriteParticleGenerator.ts +435 -0
  19. package/src/3d/canvas.ts +464 -0
  20. package/src/3d/index.ts +12 -0
  21. package/src/3d/physics/PlanckPhysicsAdapter.ts +72 -0
  22. package/src/3d/physics/RapierPhysicsAdapter.ts +66 -0
  23. package/src/3d/physics/physics-interface.ts +31 -0
  24. package/src/3d/shaders/supersampling.wgsl +201 -0
  25. package/src/3d.ts +3 -0
  26. package/src/NativeSpanFeed.ts +300 -0
  27. package/src/Renderable.ts +1704 -0
  28. package/src/__snapshots__/buffer.test.ts.snap +28 -0
  29. package/src/animation/Timeline.test.ts +2709 -0
  30. package/src/animation/Timeline.ts +598 -0
  31. package/src/ansi.ts +18 -0
  32. package/src/benchmark/attenuation-benchmark.ts +81 -0
  33. package/src/benchmark/colormatrix-benchmark.ts +128 -0
  34. package/src/benchmark/gain-benchmark.ts +80 -0
  35. package/src/benchmark/latest-all-bench-run.json +707 -0
  36. package/src/benchmark/latest-async-bench-run.json +336 -0
  37. package/src/benchmark/latest-default-bench-run.json +657 -0
  38. package/src/benchmark/latest-large-bench-run.json +707 -0
  39. package/src/benchmark/latest-quick-bench-run.json +207 -0
  40. package/src/benchmark/markdown-benchmark.ts +1796 -0
  41. package/src/benchmark/native-span-feed-async-benchmark.ts +355 -0
  42. package/src/benchmark/native-span-feed-benchmark.md +56 -0
  43. package/src/benchmark/native-span-feed-benchmark.ts +596 -0
  44. package/src/benchmark/native-span-feed-compare.ts +280 -0
  45. package/src/benchmark/renderer-benchmark.ts +754 -0
  46. package/src/benchmark/text-table-benchmark.ts +948 -0
  47. package/src/buffer.test.ts +291 -0
  48. package/src/buffer.ts +554 -0
  49. package/src/console.test.ts +612 -0
  50. package/src/console.ts +1254 -0
  51. package/src/edit-buffer.test.ts +1769 -0
  52. package/src/edit-buffer.ts +411 -0
  53. package/src/editor-view.test.ts +1032 -0
  54. package/src/editor-view.ts +284 -0
  55. package/src/examples/ascii-font-selection-demo.ts +245 -0
  56. package/src/examples/assets/Water_2_M_Normal.jpg +0 -0
  57. package/src/examples/assets/concrete.png +0 -0
  58. package/src/examples/assets/crate.png +0 -0
  59. package/src/examples/assets/crate_emissive.png +0 -0
  60. package/src/examples/assets/forrest_background.png +0 -0
  61. package/src/examples/assets/hast-example.json +1018 -0
  62. package/src/examples/assets/heart.png +0 -0
  63. package/src/examples/assets/main_char_heavy_attack.png +0 -0
  64. package/src/examples/assets/main_char_idle.png +0 -0
  65. package/src/examples/assets/main_char_jump_end.png +0 -0
  66. package/src/examples/assets/main_char_jump_landing.png +0 -0
  67. package/src/examples/assets/main_char_jump_start.png +0 -0
  68. package/src/examples/assets/main_char_run_loop.png +0 -0
  69. package/src/examples/assets/roughness_map.jpg +0 -0
  70. package/src/examples/build.ts +115 -0
  71. package/src/examples/code-demo.ts +924 -0
  72. package/src/examples/console-demo.ts +358 -0
  73. package/src/examples/core-plugin-slots-demo.ts +759 -0
  74. package/src/examples/diff-demo.ts +701 -0
  75. package/src/examples/draggable-three-demo.ts +259 -0
  76. package/src/examples/editor-demo.ts +322 -0
  77. package/src/examples/extmarks-demo.ts +196 -0
  78. package/src/examples/focus-restore-demo.ts +310 -0
  79. package/src/examples/fonts.ts +245 -0
  80. package/src/examples/fractal-shader-demo.ts +268 -0
  81. package/src/examples/framebuffer-demo.ts +674 -0
  82. package/src/examples/full-unicode-demo.ts +241 -0
  83. package/src/examples/golden-star-demo.ts +933 -0
  84. package/src/examples/grayscale-buffer-demo.ts +249 -0
  85. package/src/examples/hast-syntax-highlighting-demo.ts +129 -0
  86. package/src/examples/index.ts +926 -0
  87. package/src/examples/input-demo.ts +377 -0
  88. package/src/examples/input-select-layout-demo.ts +425 -0
  89. package/src/examples/install.sh +143 -0
  90. package/src/examples/keypress-debug-demo.ts +452 -0
  91. package/src/examples/lib/HexList.ts +122 -0
  92. package/src/examples/lib/PaletteGrid.ts +125 -0
  93. package/src/examples/lib/standalone-keys.ts +25 -0
  94. package/src/examples/lib/tab-controller.ts +243 -0
  95. package/src/examples/lights-phong-demo.ts +290 -0
  96. package/src/examples/link-demo.ts +220 -0
  97. package/src/examples/live-state-demo.ts +480 -0
  98. package/src/examples/markdown-demo.ts +725 -0
  99. package/src/examples/mouse-interaction-demo.ts +428 -0
  100. package/src/examples/nested-zindex-demo.ts +357 -0
  101. package/src/examples/opacity-example.ts +235 -0
  102. package/src/examples/opentui-demo.ts +1057 -0
  103. package/src/examples/physx-planck-2d-demo.ts +623 -0
  104. package/src/examples/physx-rapier-2d-demo.ts +655 -0
  105. package/src/examples/relative-positioning-demo.ts +323 -0
  106. package/src/examples/scroll-example.ts +214 -0
  107. package/src/examples/scrollbox-mouse-test.ts +112 -0
  108. package/src/examples/scrollbox-overlay-hit-test.ts +206 -0
  109. package/src/examples/select-demo.ts +237 -0
  110. package/src/examples/shader-cube-demo.ts +1015 -0
  111. package/src/examples/simple-layout-example.ts +591 -0
  112. package/src/examples/slider-demo.ts +617 -0
  113. package/src/examples/split-mode-demo.ts +453 -0
  114. package/src/examples/sprite-animation-demo.ts +443 -0
  115. package/src/examples/sprite-particle-generator-demo.ts +486 -0
  116. package/src/examples/static-sprite-demo.ts +193 -0
  117. package/src/examples/sticky-scroll-example.ts +308 -0
  118. package/src/examples/styled-text-demo.ts +282 -0
  119. package/src/examples/tab-select-demo.ts +219 -0
  120. package/src/examples/terminal-title.ts +29 -0
  121. package/src/examples/terminal.ts +305 -0
  122. package/src/examples/text-node-demo.ts +416 -0
  123. package/src/examples/text-selection-demo.ts +377 -0
  124. package/src/examples/text-table-demo.ts +503 -0
  125. package/src/examples/text-truncation-demo.ts +481 -0
  126. package/src/examples/text-wrap.ts +757 -0
  127. package/src/examples/texture-loading-demo.ts +259 -0
  128. package/src/examples/timeline-example.ts +670 -0
  129. package/src/examples/transparency-demo.ts +400 -0
  130. package/src/examples/vnode-composition-demo.ts +404 -0
  131. package/src/examples/wide-grapheme-overlay-demo.ts +280 -0
  132. package/src/index.ts +24 -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 +170 -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 +35 -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 +317 -0
  168. package/src/lib/keymapping.ts +115 -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 +2290 -0
  187. package/src/lib/stdin-parser.ts +1810 -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 +334 -0
  195. package/src/lib/tree-sitter/assets.d.ts +9 -0
  196. package/src/lib/tree-sitter/cache.test.ts +273 -0
  197. package/src/lib/tree-sitter/client.test.ts +1165 -0
  198. package/src/lib/tree-sitter/client.ts +607 -0
  199. package/src/lib/tree-sitter/default-parsers.ts +86 -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 +1042 -0
  203. package/src/lib/tree-sitter/parsers-config.ts +81 -0
  204. package/src/lib/tree-sitter/resolve-ft.test.ts +55 -0
  205. package/src/lib/tree-sitter/resolve-ft.ts +189 -0
  206. package/src/lib/tree-sitter/types.ts +82 -0
  207. package/src/lib/tree-sitter-styled-text.test.ts +1253 -0
  208. package/src/lib/tree-sitter-styled-text.ts +306 -0
  209. package/src/lib/validate-dir-name.ts +55 -0
  210. package/src/lib/yoga.options.test.ts +628 -0
  211. package/src/lib/yoga.options.ts +346 -0
  212. package/src/plugins/core-slot.ts +579 -0
  213. package/src/plugins/registry.ts +402 -0
  214. package/src/plugins/types.ts +46 -0
  215. package/src/post/effects.ts +930 -0
  216. package/src/post/filters.ts +489 -0
  217. package/src/post/matrices.ts +288 -0
  218. package/src/renderables/ASCIIFont.ts +219 -0
  219. package/src/renderables/Box.test.ts +205 -0
  220. package/src/renderables/Box.ts +326 -0
  221. package/src/renderables/Code.test.ts +2062 -0
  222. package/src/renderables/Code.ts +357 -0
  223. package/src/renderables/Diff.regression.test.ts +226 -0
  224. package/src/renderables/Diff.test.ts +3101 -0
  225. package/src/renderables/Diff.ts +1211 -0
  226. package/src/renderables/EditBufferRenderable.test.ts +288 -0
  227. package/src/renderables/EditBufferRenderable.ts +1166 -0
  228. package/src/renderables/FrameBuffer.ts +47 -0
  229. package/src/renderables/Input.test.ts +1228 -0
  230. package/src/renderables/Input.ts +247 -0
  231. package/src/renderables/LineNumberRenderable.ts +724 -0
  232. package/src/renderables/Markdown.ts +1393 -0
  233. package/src/renderables/ScrollBar.ts +422 -0
  234. package/src/renderables/ScrollBox.ts +883 -0
  235. package/src/renderables/Select.test.ts +1033 -0
  236. package/src/renderables/Select.ts +524 -0
  237. package/src/renderables/Slider.test.ts +456 -0
  238. package/src/renderables/Slider.ts +342 -0
  239. package/src/renderables/TabSelect.test.ts +197 -0
  240. package/src/renderables/TabSelect.ts +455 -0
  241. package/src/renderables/Text.selection-buffer.test.ts +123 -0
  242. package/src/renderables/Text.test.ts +2660 -0
  243. package/src/renderables/Text.ts +147 -0
  244. package/src/renderables/TextBufferRenderable.ts +518 -0
  245. package/src/renderables/TextNode.test.ts +1058 -0
  246. package/src/renderables/TextNode.ts +325 -0
  247. package/src/renderables/TextTable.test.ts +1421 -0
  248. package/src/renderables/TextTable.ts +1344 -0
  249. package/src/renderables/Textarea.ts +430 -0
  250. package/src/renderables/TimeToFirstDraw.ts +89 -0
  251. package/src/renderables/__snapshots__/Code.test.ts.snap +13 -0
  252. package/src/renderables/__snapshots__/Diff.test.ts.snap +785 -0
  253. package/src/renderables/__snapshots__/Text.test.ts.snap +421 -0
  254. package/src/renderables/__snapshots__/TextTable.test.ts.snap +215 -0
  255. package/src/renderables/__tests__/LineNumberRenderable.scrollbox-simple.test.ts +144 -0
  256. package/src/renderables/__tests__/LineNumberRenderable.scrollbox.test.ts +816 -0
  257. package/src/renderables/__tests__/LineNumberRenderable.test.ts +1865 -0
  258. package/src/renderables/__tests__/LineNumberRenderable.wrapping.test.ts +85 -0
  259. package/src/renderables/__tests__/Markdown.code-colors.test.ts +242 -0
  260. package/src/renderables/__tests__/Markdown.test.ts +2518 -0
  261. package/src/renderables/__tests__/MultiRenderable.selection.test.ts +87 -0
  262. package/src/renderables/__tests__/Textarea.buffer.test.ts +682 -0
  263. package/src/renderables/__tests__/Textarea.destroyed-events.test.ts +675 -0
  264. package/src/renderables/__tests__/Textarea.editing.test.ts +2041 -0
  265. package/src/renderables/__tests__/Textarea.error-handling.test.ts +35 -0
  266. package/src/renderables/__tests__/Textarea.events.test.ts +738 -0
  267. package/src/renderables/__tests__/Textarea.highlights.test.ts +590 -0
  268. package/src/renderables/__tests__/Textarea.keybinding.test.ts +3149 -0
  269. package/src/renderables/__tests__/Textarea.paste.test.ts +357 -0
  270. package/src/renderables/__tests__/Textarea.rendering.test.ts +1866 -0
  271. package/src/renderables/__tests__/Textarea.scroll.test.ts +733 -0
  272. package/src/renderables/__tests__/Textarea.selection.test.ts +1590 -0
  273. package/src/renderables/__tests__/Textarea.stress.test.ts +670 -0
  274. package/src/renderables/__tests__/Textarea.undo-redo.test.ts +383 -0
  275. package/src/renderables/__tests__/Textarea.visual-lines.test.ts +310 -0
  276. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.code.test.ts.snap +221 -0
  277. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox-simple.test.ts.snap +89 -0
  278. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.scrollbox.test.ts.snap +457 -0
  279. package/src/renderables/__tests__/__snapshots__/LineNumberRenderable.test.ts.snap +158 -0
  280. package/src/renderables/__tests__/__snapshots__/Textarea.rendering.test.ts.snap +387 -0
  281. package/src/renderables/__tests__/markdown-parser.test.ts +217 -0
  282. package/src/renderables/__tests__/renderable-test-utils.ts +60 -0
  283. package/src/renderables/composition/README.md +8 -0
  284. package/src/renderables/composition/VRenderable.ts +32 -0
  285. package/src/renderables/composition/constructs.ts +127 -0
  286. package/src/renderables/composition/vnode.ts +289 -0
  287. package/src/renderables/index.ts +23 -0
  288. package/src/renderables/markdown-parser.ts +66 -0
  289. package/src/renderer.ts +2681 -0
  290. package/src/runtime-plugin-support.ts +39 -0
  291. package/src/runtime-plugin.ts +615 -0
  292. package/src/syntax-style.test.ts +841 -0
  293. package/src/syntax-style.ts +257 -0
  294. package/src/testing/README.md +210 -0
  295. package/src/testing/capture-spans.test.ts +194 -0
  296. package/src/testing/integration.test.ts +276 -0
  297. package/src/testing/manual-clock.ts +117 -0
  298. package/src/testing/mock-keys.test.ts +1378 -0
  299. package/src/testing/mock-keys.ts +457 -0
  300. package/src/testing/mock-mouse.test.ts +218 -0
  301. package/src/testing/mock-mouse.ts +247 -0
  302. package/src/testing/mock-tree-sitter-client.ts +73 -0
  303. package/src/testing/spy.ts +13 -0
  304. package/src/testing/test-recorder.test.ts +415 -0
  305. package/src/testing/test-recorder.ts +145 -0
  306. package/src/testing/test-renderer.ts +132 -0
  307. package/src/testing.ts +7 -0
  308. package/src/tests/__snapshots__/absolute-positioning.snapshot.test.ts.snap +481 -0
  309. package/src/tests/__snapshots__/renderable.snapshot.test.ts.snap +19 -0
  310. package/src/tests/__snapshots__/scrollbox.test.ts.snap +29 -0
  311. package/src/tests/absolute-positioning.snapshot.test.ts +638 -0
  312. package/src/tests/allocator-stats.test.ts +38 -0
  313. package/src/tests/destroy-during-render.test.ts +200 -0
  314. package/src/tests/destroy-on-exit.fixture.ts +36 -0
  315. package/src/tests/destroy-on-exit.test.ts +41 -0
  316. package/src/tests/hover-cursor.test.ts +98 -0
  317. package/src/tests/native-span-feed-async.test.ts +173 -0
  318. package/src/tests/native-span-feed-close.test.ts +120 -0
  319. package/src/tests/native-span-feed-coverage.test.ts +227 -0
  320. package/src/tests/native-span-feed-edge-cases.test.ts +352 -0
  321. package/src/tests/native-span-feed-use-after-free.test.ts +45 -0
  322. package/src/tests/opacity.test.ts +123 -0
  323. package/src/tests/renderable.snapshot.test.ts +524 -0
  324. package/src/tests/renderable.test.ts +1281 -0
  325. package/src/tests/renderer.clock.test.ts +158 -0
  326. package/src/tests/renderer.console-startup.test.ts +185 -0
  327. package/src/tests/renderer.control.test.ts +425 -0
  328. package/src/tests/renderer.core-slot-binding.test.ts +952 -0
  329. package/src/tests/renderer.cursor.test.ts +26 -0
  330. package/src/tests/renderer.destroy-during-render.test.ts +147 -0
  331. package/src/tests/renderer.focus-restore.test.ts +257 -0
  332. package/src/tests/renderer.focus.test.ts +294 -0
  333. package/src/tests/renderer.idle.test.ts +219 -0
  334. package/src/tests/renderer.input.test.ts +2237 -0
  335. package/src/tests/renderer.kitty-flags.test.ts +195 -0
  336. package/src/tests/renderer.mouse.test.ts +1274 -0
  337. package/src/tests/renderer.palette.test.ts +629 -0
  338. package/src/tests/renderer.selection.test.ts +49 -0
  339. package/src/tests/renderer.slot-registry.test.ts +684 -0
  340. package/src/tests/renderer.useMouse.test.ts +47 -0
  341. package/src/tests/runtime-plugin-node-modules-cycle.fixture.ts +76 -0
  342. package/src/tests/runtime-plugin-node-modules-mjs.fixture.ts +43 -0
  343. package/src/tests/runtime-plugin-node-modules-no-bare-rewrite.fixture.ts +67 -0
  344. package/src/tests/runtime-plugin-node-modules-package-type-cache.fixture.ts +72 -0
  345. package/src/tests/runtime-plugin-node-modules-runtime-specifier.fixture.ts +44 -0
  346. package/src/tests/runtime-plugin-node-modules-scoped-package-bare-rewrite.fixture.ts +85 -0
  347. package/src/tests/runtime-plugin-path-alias.fixture.ts +43 -0
  348. package/src/tests/runtime-plugin-resolve-roots.fixture.ts +65 -0
  349. package/src/tests/runtime-plugin-support.fixture.ts +11 -0
  350. package/src/tests/runtime-plugin-support.test.ts +19 -0
  351. package/src/tests/runtime-plugin-windows-file-url.fixture.ts +30 -0
  352. package/src/tests/runtime-plugin.fixture.ts +40 -0
  353. package/src/tests/runtime-plugin.test.ts +354 -0
  354. package/src/tests/scrollbox-culling-bug.test.ts +114 -0
  355. package/src/tests/scrollbox-hitgrid-resize.test.ts +136 -0
  356. package/src/tests/scrollbox-hitgrid.test.ts +909 -0
  357. package/src/tests/scrollbox.test.ts +1530 -0
  358. package/src/tests/wrap-resize-perf.test.ts +276 -0
  359. package/src/tests/yoga-setters.test.ts +921 -0
  360. package/src/text-buffer-view.test.ts +705 -0
  361. package/src/text-buffer-view.ts +189 -0
  362. package/src/text-buffer.test.ts +347 -0
  363. package/src/text-buffer.ts +250 -0
  364. package/src/types.ts +161 -0
  365. package/src/utils.ts +88 -0
  366. package/src/zig/ansi.zig +268 -0
  367. package/src/zig/bench/README.md +50 -0
  368. package/src/zig/bench/buffer-draw-text-buffer_bench.zig +887 -0
  369. package/src/zig/bench/edit-buffer_bench.zig +476 -0
  370. package/src/zig/bench/native-span-feed_bench.zig +100 -0
  371. package/src/zig/bench/rope-markers_bench.zig +713 -0
  372. package/src/zig/bench/rope_bench.zig +514 -0
  373. package/src/zig/bench/styled-text_bench.zig +470 -0
  374. package/src/zig/bench/text-buffer-coords_bench.zig +362 -0
  375. package/src/zig/bench/text-buffer-view_bench.zig +459 -0
  376. package/src/zig/bench/text-chunk-graphemes_bench.zig +273 -0
  377. package/src/zig/bench/utf8_bench.zig +799 -0
  378. package/src/zig/bench-utils.zig +431 -0
  379. package/src/zig/bench.zig +217 -0
  380. package/src/zig/buffer-methods.zig +211 -0
  381. package/src/zig/buffer.zig +2281 -0
  382. package/src/zig/build.zig +289 -0
  383. package/src/zig/build.zig.zon +16 -0
  384. package/src/zig/edit-buffer.zig +825 -0
  385. package/src/zig/editor-view.zig +802 -0
  386. package/src/zig/event-bus.zig +13 -0
  387. package/src/zig/event-emitter.zig +65 -0
  388. package/src/zig/file-logger.zig +92 -0
  389. package/src/zig/grapheme.zig +599 -0
  390. package/src/zig/lib.zig +1854 -0
  391. package/src/zig/link.zig +333 -0
  392. package/src/zig/logger.zig +43 -0
  393. package/src/zig/mem-registry.zig +125 -0
  394. package/src/zig/native-span-feed-bench-lib.zig +7 -0
  395. package/src/zig/native-span-feed.zig +708 -0
  396. package/src/zig/renderer.zig +1393 -0
  397. package/src/zig/rope.zig +1220 -0
  398. package/src/zig/syntax-style.zig +161 -0
  399. package/src/zig/terminal.zig +987 -0
  400. package/src/zig/test.zig +72 -0
  401. package/src/zig/tests/README.md +18 -0
  402. package/src/zig/tests/buffer-methods_test.zig +1109 -0
  403. package/src/zig/tests/buffer_test.zig +2557 -0
  404. package/src/zig/tests/edit-buffer-history_test.zig +271 -0
  405. package/src/zig/tests/edit-buffer_test.zig +1689 -0
  406. package/src/zig/tests/editor-view_test.zig +3299 -0
  407. package/src/zig/tests/event-emitter_test.zig +249 -0
  408. package/src/zig/tests/grapheme_test.zig +1304 -0
  409. package/src/zig/tests/link_test.zig +190 -0
  410. package/src/zig/tests/mem-registry_test.zig +473 -0
  411. package/src/zig/tests/memory_leak_regression_test.zig +159 -0
  412. package/src/zig/tests/native-span-feed_test.zig +1264 -0
  413. package/src/zig/tests/renderer_test.zig +1017 -0
  414. package/src/zig/tests/rope-nested_test.zig +712 -0
  415. package/src/zig/tests/rope_fuzz_test.zig +238 -0
  416. package/src/zig/tests/rope_test.zig +2362 -0
  417. package/src/zig/tests/segment-merge.test.zig +148 -0
  418. package/src/zig/tests/syntax-style_test.zig +557 -0
  419. package/src/zig/tests/terminal_test.zig +754 -0
  420. package/src/zig/tests/text-buffer-drawing_test.zig +3237 -0
  421. package/src/zig/tests/text-buffer-highlights_test.zig +666 -0
  422. package/src/zig/tests/text-buffer-iterators_test.zig +776 -0
  423. package/src/zig/tests/text-buffer-segment_test.zig +320 -0
  424. package/src/zig/tests/text-buffer-selection_test.zig +1035 -0
  425. package/src/zig/tests/text-buffer-selection_viewport_test.zig +358 -0
  426. package/src/zig/tests/text-buffer-view_test.zig +3649 -0
  427. package/src/zig/tests/text-buffer_test.zig +2191 -0
  428. package/src/zig/tests/unicode-width-map.zon +3909 -0
  429. package/src/zig/tests/utf8_no_zwj_test.zig +260 -0
  430. package/src/zig/tests/utf8_test.zig +4057 -0
  431. package/src/zig/tests/utf8_wcwidth_cursor_test.zig +267 -0
  432. package/src/zig/tests/utf8_wcwidth_test.zig +357 -0
  433. package/src/zig/tests/word-wrap-editing_test.zig +498 -0
  434. package/src/zig/tests/wrap-cache-perf_test.zig +113 -0
  435. package/src/zig/text-buffer-iterators.zig +499 -0
  436. package/src/zig/text-buffer-segment.zig +404 -0
  437. package/src/zig/text-buffer-view.zig +1371 -0
  438. package/src/zig/text-buffer.zig +1180 -0
  439. package/src/zig/utf8.zig +1948 -0
  440. package/src/zig/utils.zig +9 -0
  441. package/src/zig-structs.ts +261 -0
  442. package/src/zig.ts +3884 -0
  443. package/tsconfig.build.json +24 -0
  444. package/tsconfig.json +27 -0
  445. package/3d/SpriteResourceManager.d.ts +0 -74
  446. package/3d/SpriteUtils.d.ts +0 -13
  447. package/3d/TextureUtils.d.ts +0 -24
  448. package/3d/ThreeRenderable.d.ts +0 -40
  449. package/3d/WGPURenderer.d.ts +0 -61
  450. package/3d/animation/ExplodingSpriteEffect.d.ts +0 -71
  451. package/3d/animation/PhysicsExplodingSpriteEffect.d.ts +0 -76
  452. package/3d/animation/SpriteAnimator.d.ts +0 -124
  453. package/3d/animation/SpriteParticleGenerator.d.ts +0 -62
  454. package/3d/canvas.d.ts +0 -44
  455. package/3d/index.d.ts +0 -12
  456. package/3d/physics/PlanckPhysicsAdapter.d.ts +0 -19
  457. package/3d/physics/RapierPhysicsAdapter.d.ts +0 -19
  458. package/3d/physics/physics-interface.d.ts +0 -27
  459. package/3d.d.ts +0 -2
  460. package/3d.js +0 -34041
  461. package/3d.js.map +0 -155
  462. package/LICENSE +0 -21
  463. package/NativeSpanFeed.d.ts +0 -41
  464. package/Renderable.d.ts +0 -334
  465. package/animation/Timeline.d.ts +0 -126
  466. package/ansi.d.ts +0 -13
  467. package/buffer.d.ts +0 -111
  468. package/console.d.ts +0 -144
  469. package/edit-buffer.d.ts +0 -98
  470. package/editor-view.d.ts +0 -73
  471. package/index-8fks7yv1.js +0 -411
  472. package/index-8fks7yv1.js.map +0 -10
  473. package/index-egy5e2rs.js +0 -12267
  474. package/index-egy5e2rs.js.map +0 -42
  475. package/index-tse8gzh0.js +0 -20614
  476. package/index-tse8gzh0.js.map +0 -67
  477. package/index.d.ts +0 -23
  478. package/index.js +0 -478
  479. package/index.js.map +0 -9
  480. package/lib/KeyHandler.d.ts +0 -61
  481. package/lib/RGBA.d.ts +0 -25
  482. package/lib/ascii.font.d.ts +0 -508
  483. package/lib/border.d.ts +0 -51
  484. package/lib/bunfs.d.ts +0 -7
  485. package/lib/clipboard.d.ts +0 -17
  486. package/lib/clock.d.ts +0 -15
  487. package/lib/data-paths.d.ts +0 -26
  488. package/lib/debounce.d.ts +0 -42
  489. package/lib/detect-links.d.ts +0 -6
  490. package/lib/env.d.ts +0 -42
  491. package/lib/extmarks-history.d.ts +0 -17
  492. package/lib/extmarks.d.ts +0 -89
  493. package/lib/hast-styled-text.d.ts +0 -17
  494. package/lib/index.d.ts +0 -21
  495. package/lib/keymapping.d.ts +0 -25
  496. package/lib/objects-in-viewport.d.ts +0 -24
  497. package/lib/output.capture.d.ts +0 -24
  498. package/lib/parse.keypress-kitty.d.ts +0 -2
  499. package/lib/parse.keypress.d.ts +0 -26
  500. package/lib/parse.mouse.d.ts +0 -30
  501. package/lib/paste.d.ts +0 -7
  502. package/lib/queue.d.ts +0 -15
  503. package/lib/renderable.validations.d.ts +0 -12
  504. package/lib/scroll-acceleration.d.ts +0 -43
  505. package/lib/selection.d.ts +0 -63
  506. package/lib/singleton.d.ts +0 -7
  507. package/lib/stdin-parser.d.ts +0 -87
  508. package/lib/styled-text.d.ts +0 -63
  509. package/lib/terminal-capability-detection.d.ts +0 -30
  510. package/lib/terminal-palette.d.ts +0 -50
  511. package/lib/tree-sitter/assets/update.d.ts +0 -11
  512. package/lib/tree-sitter/client.d.ts +0 -47
  513. package/lib/tree-sitter/default-parsers.d.ts +0 -2
  514. package/lib/tree-sitter/download-utils.d.ts +0 -21
  515. package/lib/tree-sitter/index.d.ts +0 -8
  516. package/lib/tree-sitter/parser.worker.d.ts +0 -1
  517. package/lib/tree-sitter/parsers-config.d.ts +0 -53
  518. package/lib/tree-sitter/resolve-ft.d.ts +0 -5
  519. package/lib/tree-sitter/types.d.ts +0 -82
  520. package/lib/tree-sitter-styled-text.d.ts +0 -14
  521. package/lib/validate-dir-name.d.ts +0 -1
  522. package/lib/yoga.options.d.ts +0 -32
  523. package/parser.worker.js +0 -899
  524. package/parser.worker.js.map +0 -12
  525. package/plugins/core-slot.d.ts +0 -72
  526. package/plugins/registry.d.ts +0 -42
  527. package/plugins/types.d.ts +0 -34
  528. package/post/effects.d.ts +0 -147
  529. package/post/filters.d.ts +0 -65
  530. package/post/matrices.d.ts +0 -20
  531. package/renderables/ASCIIFont.d.ts +0 -52
  532. package/renderables/Box.d.ts +0 -81
  533. package/renderables/Code.d.ts +0 -78
  534. package/renderables/Diff.d.ts +0 -142
  535. package/renderables/EditBufferRenderable.d.ts +0 -237
  536. package/renderables/FrameBuffer.d.ts +0 -16
  537. package/renderables/Input.d.ts +0 -67
  538. package/renderables/LineNumberRenderable.d.ts +0 -78
  539. package/renderables/Markdown.d.ts +0 -185
  540. package/renderables/ScrollBar.d.ts +0 -77
  541. package/renderables/ScrollBox.d.ts +0 -124
  542. package/renderables/Select.d.ts +0 -115
  543. package/renderables/Slider.d.ts +0 -47
  544. package/renderables/TabSelect.d.ts +0 -96
  545. package/renderables/Text.d.ts +0 -36
  546. package/renderables/TextBufferRenderable.d.ts +0 -105
  547. package/renderables/TextNode.d.ts +0 -91
  548. package/renderables/TextTable.d.ts +0 -140
  549. package/renderables/Textarea.d.ts +0 -63
  550. package/renderables/TimeToFirstDraw.d.ts +0 -24
  551. package/renderables/__tests__/renderable-test-utils.d.ts +0 -12
  552. package/renderables/composition/VRenderable.d.ts +0 -16
  553. package/renderables/composition/constructs.d.ts +0 -35
  554. package/renderables/composition/vnode.d.ts +0 -46
  555. package/renderables/index.d.ts +0 -23
  556. package/renderables/markdown-parser.d.ts +0 -10
  557. package/renderer.d.ts +0 -419
  558. package/runtime-plugin-support.d.ts +0 -3
  559. package/runtime-plugin-support.js +0 -29
  560. package/runtime-plugin-support.js.map +0 -10
  561. package/runtime-plugin.d.ts +0 -16
  562. package/runtime-plugin.js +0 -16
  563. package/runtime-plugin.js.map +0 -9
  564. package/syntax-style.d.ts +0 -54
  565. package/testing/manual-clock.d.ts +0 -17
  566. package/testing/mock-keys.d.ts +0 -81
  567. package/testing/mock-mouse.d.ts +0 -38
  568. package/testing/mock-tree-sitter-client.d.ts +0 -23
  569. package/testing/spy.d.ts +0 -7
  570. package/testing/test-recorder.d.ts +0 -61
  571. package/testing/test-renderer.d.ts +0 -23
  572. package/testing.d.ts +0 -6
  573. package/testing.js +0 -697
  574. package/testing.js.map +0 -15
  575. package/text-buffer-view.d.ts +0 -42
  576. package/text-buffer.d.ts +0 -67
  577. package/types.d.ts +0 -139
  578. package/utils.d.ts +0 -14
  579. package/zig-structs.d.ts +0 -155
  580. package/zig.d.ts +0 -353
  581. /package/{assets → src/lib/tree-sitter/assets}/javascript/highlights.scm +0 -0
  582. /package/{assets → src/lib/tree-sitter/assets}/javascript/tree-sitter-javascript.wasm +0 -0
  583. /package/{assets → src/lib/tree-sitter/assets}/markdown/highlights.scm +0 -0
  584. /package/{assets → src/lib/tree-sitter/assets}/markdown/injections.scm +0 -0
  585. /package/{assets → src/lib/tree-sitter/assets}/markdown/tree-sitter-markdown.wasm +0 -0
  586. /package/{assets → src/lib/tree-sitter/assets}/markdown_inline/highlights.scm +0 -0
  587. /package/{assets → src/lib/tree-sitter/assets}/markdown_inline/tree-sitter-markdown_inline.wasm +0 -0
  588. /package/{assets → src/lib/tree-sitter/assets}/typescript/highlights.scm +0 -0
  589. /package/{assets → src/lib/tree-sitter/assets}/typescript/tree-sitter-typescript.wasm +0 -0
  590. /package/{assets → src/lib/tree-sitter/assets}/zig/highlights.scm +0 -0
  591. /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
+ })