@effect-tui/react 0.1.1 → 0.1.4

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 (442) hide show
  1. package/dist/jsx-runtime.d.ts +13 -0
  2. package/dist/jsx-runtime.d.ts.map +1 -1
  3. package/dist/jsx-runtime.js.map +1 -1
  4. package/dist/src/codeblock.d.ts.map +1 -1
  5. package/dist/src/codeblock.js.map +1 -1
  6. package/dist/src/components/Divider.d.ts +18 -0
  7. package/dist/src/components/Divider.d.ts.map +1 -0
  8. package/dist/src/components/Divider.js +17 -0
  9. package/dist/src/components/Divider.js.map +1 -0
  10. package/dist/src/components/Markdown.d.ts +66 -0
  11. package/dist/src/components/Markdown.d.ts.map +1 -0
  12. package/dist/src/components/Markdown.js +226 -0
  13. package/dist/src/components/Markdown.js.map +1 -0
  14. package/dist/src/components/MultilineTextInput.d.ts +65 -0
  15. package/dist/src/components/MultilineTextInput.d.ts.map +1 -0
  16. package/dist/src/components/MultilineTextInput.js +607 -0
  17. package/dist/src/components/MultilineTextInput.js.map +1 -0
  18. package/dist/src/components/Overlay.d.ts +46 -0
  19. package/dist/src/components/Overlay.d.ts.map +1 -0
  20. package/dist/src/components/Overlay.js +11 -0
  21. package/dist/src/components/Overlay.js.map +1 -0
  22. package/dist/src/components/Static.d.ts +44 -0
  23. package/dist/src/components/Static.d.ts.map +1 -0
  24. package/dist/src/components/Static.js +53 -0
  25. package/dist/src/components/Static.js.map +1 -0
  26. package/dist/src/components/TextInput.d.ts +53 -0
  27. package/dist/src/components/TextInput.d.ts.map +1 -0
  28. package/dist/src/components/TextInput.js +210 -0
  29. package/dist/src/components/TextInput.js.map +1 -0
  30. package/dist/src/components/index.d.ts +7 -0
  31. package/dist/src/components/index.d.ts.map +1 -0
  32. package/dist/src/components/index.js +7 -0
  33. package/dist/src/components/index.js.map +1 -0
  34. package/dist/src/components/text-editing.d.ts +62 -0
  35. package/dist/src/components/text-editing.d.ts.map +1 -0
  36. package/dist/src/components/text-editing.js +385 -0
  37. package/dist/src/components/text-editing.js.map +1 -0
  38. package/dist/src/console/ConsoleCapture.d.ts +36 -0
  39. package/dist/src/console/ConsoleCapture.d.ts.map +1 -0
  40. package/dist/src/console/ConsoleCapture.js +210 -0
  41. package/dist/src/console/ConsoleCapture.js.map +1 -0
  42. package/dist/src/console/ConsolePopover.d.ts +18 -0
  43. package/dist/src/console/ConsolePopover.d.ts.map +1 -0
  44. package/dist/src/console/ConsolePopover.js +324 -0
  45. package/dist/src/console/ConsolePopover.js.map +1 -0
  46. package/dist/src/console/clipboard.d.ts +10 -0
  47. package/dist/src/console/clipboard.d.ts.map +1 -0
  48. package/dist/src/console/clipboard.js +74 -0
  49. package/dist/src/console/clipboard.js.map +1 -0
  50. package/dist/src/console/index.d.ts +5 -0
  51. package/dist/src/console/index.d.ts.map +1 -0
  52. package/dist/src/console/index.js +33 -0
  53. package/dist/src/console/index.js.map +1 -0
  54. package/dist/src/console/useConsole.d.ts +44 -0
  55. package/dist/src/console/useConsole.d.ts.map +1 -0
  56. package/dist/src/console/useConsole.js +91 -0
  57. package/dist/src/console/useConsole.js.map +1 -0
  58. package/dist/src/debug/DebugOverlay.d.ts +49 -0
  59. package/dist/src/debug/DebugOverlay.d.ts.map +1 -0
  60. package/dist/src/debug/DebugOverlay.js +438 -0
  61. package/dist/src/debug/DebugOverlay.js.map +1 -0
  62. package/dist/src/debug/DiagnosticsPanel.d.ts.map +1 -1
  63. package/dist/src/debug/DiagnosticsPanel.js.map +1 -1
  64. package/dist/src/dev/Toast.d.ts +19 -0
  65. package/dist/src/dev/Toast.d.ts.map +1 -0
  66. package/dist/src/dev/Toast.js +72 -0
  67. package/dist/src/dev/Toast.js.map +1 -0
  68. package/dist/src/dev/index.d.ts +2 -0
  69. package/dist/src/dev/index.d.ts.map +1 -0
  70. package/dist/src/dev/index.js +3 -0
  71. package/dist/src/dev/index.js.map +1 -0
  72. package/dist/src/dev.d.ts +114 -0
  73. package/dist/src/dev.d.ts.map +1 -0
  74. package/dist/src/dev.js +373 -0
  75. package/dist/src/dev.js.map +1 -0
  76. package/dist/src/highlight.d.ts +3 -3
  77. package/dist/src/highlight.d.ts.map +1 -1
  78. package/dist/src/highlight.js.map +1 -1
  79. package/dist/src/hmr-plugin.d.ts +2 -0
  80. package/dist/src/hmr-plugin.d.ts.map +1 -0
  81. package/dist/src/hmr-plugin.js +53 -0
  82. package/dist/src/hmr-plugin.js.map +1 -0
  83. package/dist/src/hooks/index.d.ts +4 -0
  84. package/dist/src/hooks/index.d.ts.map +1 -1
  85. package/dist/src/hooks/index.js +2 -0
  86. package/dist/src/hooks/index.js.map +1 -1
  87. package/dist/src/hooks/use-keyboard.d.ts +11 -0
  88. package/dist/src/hooks/use-keyboard.d.ts.map +1 -1
  89. package/dist/src/hooks/use-keyboard.js +22 -4
  90. package/dist/src/hooks/use-keyboard.js.map +1 -1
  91. package/dist/src/hooks/use-mouse.d.ts +24 -0
  92. package/dist/src/hooks/use-mouse.d.ts.map +1 -0
  93. package/dist/src/hooks/use-mouse.js +41 -0
  94. package/dist/src/hooks/use-mouse.js.map +1 -0
  95. package/dist/src/hooks/use-paste.d.ts +11 -0
  96. package/dist/src/hooks/use-paste.d.ts.map +1 -1
  97. package/dist/src/hooks/use-paste.js +17 -3
  98. package/dist/src/hooks/use-paste.js.map +1 -1
  99. package/dist/src/hooks/use-scroll.d.ts +79 -0
  100. package/dist/src/hooks/use-scroll.d.ts.map +1 -0
  101. package/dist/src/hooks/use-scroll.js +239 -0
  102. package/dist/src/hooks/use-scroll.js.map +1 -0
  103. package/dist/src/hooks/useFrameStats.js.map +1 -1
  104. package/dist/src/hosts/base.d.ts +62 -1
  105. package/dist/src/hosts/base.d.ts.map +1 -1
  106. package/dist/src/hosts/base.js +118 -5
  107. package/dist/src/hosts/base.js.map +1 -1
  108. package/dist/src/hosts/box.d.ts +7 -7
  109. package/dist/src/hosts/box.d.ts.map +1 -1
  110. package/dist/src/hosts/box.js +31 -26
  111. package/dist/src/hosts/box.js.map +1 -1
  112. package/dist/src/hosts/canvas.d.ts +8 -8
  113. package/dist/src/hosts/canvas.d.ts.map +1 -1
  114. package/dist/src/hosts/canvas.js +13 -22
  115. package/dist/src/hosts/canvas.js.map +1 -1
  116. package/dist/src/hosts/codeblock.d.ts +7 -7
  117. package/dist/src/hosts/codeblock.d.ts.map +1 -1
  118. package/dist/src/hosts/codeblock.js +11 -20
  119. package/dist/src/hosts/codeblock.js.map +1 -1
  120. package/dist/src/hosts/flex-container.d.ts +45 -0
  121. package/dist/src/hosts/flex-container.d.ts.map +1 -0
  122. package/dist/src/hosts/flex-container.js +90 -0
  123. package/dist/src/hosts/flex-container.js.map +1 -0
  124. package/dist/src/hosts/hstack.d.ts +6 -11
  125. package/dist/src/hosts/hstack.d.ts.map +1 -1
  126. package/dist/src/hosts/hstack.js +6 -41
  127. package/dist/src/hosts/hstack.js.map +1 -1
  128. package/dist/src/hosts/index.d.ts +4 -0
  129. package/dist/src/hosts/index.d.ts.map +1 -1
  130. package/dist/src/hosts/index.js +10 -0
  131. package/dist/src/hosts/index.js.map +1 -1
  132. package/dist/src/hosts/overlay-item.d.ts +32 -0
  133. package/dist/src/hosts/overlay-item.d.ts.map +1 -0
  134. package/dist/src/hosts/overlay-item.js +54 -0
  135. package/dist/src/hosts/overlay-item.js.map +1 -0
  136. package/dist/src/hosts/overlay.d.ts +30 -0
  137. package/dist/src/hosts/overlay.d.ts.map +1 -0
  138. package/dist/src/hosts/overlay.js +105 -0
  139. package/dist/src/hosts/overlay.js.map +1 -0
  140. package/dist/src/hosts/scroll.d.ts +56 -0
  141. package/dist/src/hosts/scroll.d.ts.map +1 -0
  142. package/dist/src/hosts/scroll.js +204 -0
  143. package/dist/src/hosts/scroll.js.map +1 -0
  144. package/dist/src/hosts/single-child.d.ts +16 -0
  145. package/dist/src/hosts/single-child.d.ts.map +1 -0
  146. package/dist/src/hosts/single-child.js +45 -0
  147. package/dist/src/hosts/single-child.js.map +1 -0
  148. package/dist/src/hosts/spacer.d.ts.map +1 -1
  149. package/dist/src/hosts/spacer.js +7 -3
  150. package/dist/src/hosts/spacer.js.map +1 -1
  151. package/dist/src/hosts/text.d.ts +9 -6
  152. package/dist/src/hosts/text.d.ts.map +1 -1
  153. package/dist/src/hosts/text.js +49 -22
  154. package/dist/src/hosts/text.js.map +1 -1
  155. package/dist/src/hosts/vstack.d.ts +6 -11
  156. package/dist/src/hosts/vstack.d.ts.map +1 -1
  157. package/dist/src/hosts/vstack.js +6 -41
  158. package/dist/src/hosts/vstack.js.map +1 -1
  159. package/dist/src/hosts/zstack.d.ts.map +1 -1
  160. package/dist/src/hosts/zstack.js +16 -5
  161. package/dist/src/hosts/zstack.js.map +1 -1
  162. package/dist/src/index.d.ts +9 -2
  163. package/dist/src/index.d.ts.map +1 -1
  164. package/dist/src/index.js +10 -2
  165. package/dist/src/index.js.map +1 -1
  166. package/dist/src/inline/index.d.ts.map +1 -1
  167. package/dist/src/inline/index.js.map +1 -1
  168. package/dist/src/motion/color-motion-value.d.ts.map +1 -1
  169. package/dist/src/motion/color-motion-value.js.map +1 -1
  170. package/dist/src/motion/color.d.ts +1 -29
  171. package/dist/src/motion/color.d.ts.map +1 -1
  172. package/dist/src/motion/color.js +2 -170
  173. package/dist/src/motion/color.js.map +1 -1
  174. package/dist/src/motion/color.test.js.map +1 -1
  175. package/dist/src/motion/event-emitter.d.ts.map +1 -1
  176. package/dist/src/motion/event-emitter.js.map +1 -1
  177. package/dist/src/motion/frame.js.map +1 -1
  178. package/dist/src/motion/hooks.d.ts.map +1 -1
  179. package/dist/src/motion/hooks.js +8 -3
  180. package/dist/src/motion/hooks.js.map +1 -1
  181. package/dist/src/motion/index.d.ts.map +1 -1
  182. package/dist/src/motion/index.js.map +1 -1
  183. package/dist/src/motion/motion-value.d.ts.map +1 -1
  184. package/dist/src/motion/motion-value.js.map +1 -1
  185. package/dist/src/motion/motion-value.test.js.map +1 -1
  186. package/dist/src/motion/spring-math.d.ts +6 -1
  187. package/dist/src/motion/spring-math.d.ts.map +1 -1
  188. package/dist/src/motion/spring-math.js +6 -1
  189. package/dist/src/motion/spring-math.js.map +1 -1
  190. package/dist/src/motion/types.d.ts.map +1 -1
  191. package/dist/src/motion/types.js.map +1 -1
  192. package/dist/src/profiler.js.map +1 -1
  193. package/dist/src/reconciler/host-config.d.ts +5 -5
  194. package/dist/src/reconciler/host-config.d.ts.map +1 -1
  195. package/dist/src/reconciler/host-config.js +43 -51
  196. package/dist/src/reconciler/host-config.js.map +1 -1
  197. package/dist/src/reconciler/noop-methods.d.ts +29 -0
  198. package/dist/src/reconciler/noop-methods.d.ts.map +1 -0
  199. package/dist/src/reconciler/noop-methods.js +43 -0
  200. package/dist/src/reconciler/noop-methods.js.map +1 -0
  201. package/dist/src/reconciler/types.d.ts +68 -14
  202. package/dist/src/reconciler/types.d.ts.map +1 -1
  203. package/dist/src/remote/Procedures.d.ts +22 -0
  204. package/dist/src/remote/Procedures.d.ts.map +1 -0
  205. package/dist/src/remote/Procedures.js +42 -0
  206. package/dist/src/remote/Procedures.js.map +1 -0
  207. package/dist/src/remote/Router.d.ts +20 -0
  208. package/dist/src/remote/Router.d.ts.map +1 -0
  209. package/dist/src/remote/Router.js +26 -0
  210. package/dist/src/remote/Router.js.map +1 -0
  211. package/dist/src/remote/Server.d.ts +6 -0
  212. package/dist/src/remote/Server.d.ts.map +1 -0
  213. package/dist/src/remote/Server.js +53 -0
  214. package/dist/src/remote/Server.js.map +1 -0
  215. package/dist/src/remote/index.d.ts +18 -0
  216. package/dist/src/remote/index.d.ts.map +1 -0
  217. package/dist/src/remote/index.js +74 -0
  218. package/dist/src/remote/index.js.map +1 -0
  219. package/dist/src/renderer/core/FrameBuilder.d.ts +18 -0
  220. package/dist/src/renderer/core/FrameBuilder.d.ts.map +1 -0
  221. package/dist/src/renderer/core/FrameBuilder.js +38 -0
  222. package/dist/src/renderer/core/FrameBuilder.js.map +1 -0
  223. package/dist/src/renderer/core/RendererState.d.ts +41 -0
  224. package/dist/src/renderer/core/RendererState.d.ts.map +1 -0
  225. package/dist/src/renderer/core/RendererState.js +70 -0
  226. package/dist/src/renderer/core/RendererState.js.map +1 -0
  227. package/dist/src/renderer/core/index.d.ts +3 -0
  228. package/dist/src/renderer/core/index.d.ts.map +1 -0
  229. package/dist/src/renderer/core/index.js +3 -0
  230. package/dist/src/renderer/core/index.js.map +1 -0
  231. package/dist/src/renderer/input/InputProcessor.d.ts +25 -0
  232. package/dist/src/renderer/input/InputProcessor.d.ts.map +1 -0
  233. package/dist/src/renderer/input/InputProcessor.js +81 -0
  234. package/dist/src/renderer/input/InputProcessor.js.map +1 -0
  235. package/dist/src/renderer/input/index.d.ts +2 -0
  236. package/dist/src/renderer/input/index.d.ts.map +1 -0
  237. package/dist/src/renderer/input/index.js +2 -0
  238. package/dist/src/renderer/input/index.js.map +1 -0
  239. package/dist/src/renderer/lifecycle/EventBus.d.ts +41 -0
  240. package/dist/src/renderer/lifecycle/EventBus.d.ts.map +1 -0
  241. package/dist/src/renderer/lifecycle/EventBus.js +78 -0
  242. package/dist/src/renderer/lifecycle/EventBus.js.map +1 -0
  243. package/dist/src/renderer/lifecycle/ResizeManager.d.ts +34 -0
  244. package/dist/src/renderer/lifecycle/ResizeManager.d.ts.map +1 -0
  245. package/dist/src/renderer/lifecycle/ResizeManager.js +47 -0
  246. package/dist/src/renderer/lifecycle/ResizeManager.js.map +1 -0
  247. package/dist/src/renderer/lifecycle/TerminalSetup.d.ts +36 -0
  248. package/dist/src/renderer/lifecycle/TerminalSetup.d.ts.map +1 -0
  249. package/dist/src/renderer/lifecycle/TerminalSetup.js +82 -0
  250. package/dist/src/renderer/lifecycle/TerminalSetup.js.map +1 -0
  251. package/dist/src/renderer/lifecycle/index.d.ts +4 -0
  252. package/dist/src/renderer/lifecycle/index.d.ts.map +1 -0
  253. package/dist/src/renderer/lifecycle/index.js +4 -0
  254. package/dist/src/renderer/lifecycle/index.js.map +1 -0
  255. package/dist/src/renderer/modes/FullscreenRenderer.d.ts +12 -0
  256. package/dist/src/renderer/modes/FullscreenRenderer.d.ts.map +1 -0
  257. package/dist/src/renderer/modes/FullscreenRenderer.js +52 -0
  258. package/dist/src/renderer/modes/FullscreenRenderer.js.map +1 -0
  259. package/dist/src/renderer/modes/InlineRenderer.d.ts +22 -0
  260. package/dist/src/renderer/modes/InlineRenderer.d.ts.map +1 -0
  261. package/dist/src/renderer/modes/InlineRenderer.js +154 -0
  262. package/dist/src/renderer/modes/InlineRenderer.js.map +1 -0
  263. package/dist/src/renderer/modes/RendererMode.d.ts +42 -0
  264. package/dist/src/renderer/modes/RendererMode.d.ts.map +1 -0
  265. package/dist/src/renderer/modes/RendererMode.js +2 -0
  266. package/dist/src/renderer/modes/RendererMode.js.map +1 -0
  267. package/dist/src/renderer/modes/StaticContentRenderer.d.ts +25 -0
  268. package/dist/src/renderer/modes/StaticContentRenderer.d.ts.map +1 -0
  269. package/dist/src/renderer/modes/StaticContentRenderer.js +47 -0
  270. package/dist/src/renderer/modes/StaticContentRenderer.js.map +1 -0
  271. package/dist/src/renderer/modes/index.d.ts +5 -0
  272. package/dist/src/renderer/modes/index.d.ts.map +1 -0
  273. package/dist/src/renderer/modes/index.js +4 -0
  274. package/dist/src/renderer/modes/index.js.map +1 -0
  275. package/dist/src/renderer-context.d.ts +9 -0
  276. package/dist/src/renderer-context.d.ts.map +1 -0
  277. package/dist/src/renderer-context.js +22 -0
  278. package/dist/src/renderer-context.js.map +1 -0
  279. package/dist/src/renderer-types.d.ts +103 -0
  280. package/dist/src/renderer-types.d.ts.map +1 -0
  281. package/dist/src/renderer-types.js +2 -0
  282. package/dist/src/renderer-types.js.map +1 -0
  283. package/dist/src/renderer.d.ts +4 -86
  284. package/dist/src/renderer.d.ts.map +1 -1
  285. package/dist/src/renderer.js +213 -384
  286. package/dist/src/renderer.js.map +1 -1
  287. package/dist/src/test/index.d.ts.map +1 -1
  288. package/dist/src/test/index.js.map +1 -1
  289. package/dist/src/test/mock-streams.d.ts.map +1 -1
  290. package/dist/src/test/mock-streams.js.map +1 -1
  291. package/dist/src/test/render-tui.d.ts.map +1 -1
  292. package/dist/src/test/render-tui.js +2 -5
  293. package/dist/src/test/render-tui.js.map +1 -1
  294. package/dist/src/trace/SpanTree.d.ts.map +1 -1
  295. package/dist/src/trace/SpanTree.js +21 -11
  296. package/dist/src/trace/SpanTree.js.map +1 -1
  297. package/dist/src/trace/format-value.d.ts +15 -0
  298. package/dist/src/trace/format-value.d.ts.map +1 -0
  299. package/dist/src/trace/format-value.js +77 -0
  300. package/dist/src/trace/format-value.js.map +1 -0
  301. package/dist/src/trace/index.d.ts.map +1 -1
  302. package/dist/src/trace/index.js.map +1 -1
  303. package/dist/src/trace/location.js +1 -1
  304. package/dist/src/trace/location.js.map +1 -1
  305. package/dist/src/trace/span-processor.d.ts.map +1 -1
  306. package/dist/src/trace/span-processor.js.map +1 -1
  307. package/dist/src/trace/span-state.d.ts +19 -2
  308. package/dist/src/trace/span-state.d.ts.map +1 -1
  309. package/dist/src/trace/span-state.js +62 -31
  310. package/dist/src/trace/span-state.js.map +1 -1
  311. package/dist/src/trace/tui-logger.js.map +1 -1
  312. package/dist/src/utils/border.d.ts +1 -1
  313. package/dist/src/utils/border.d.ts.map +1 -1
  314. package/dist/src/utils/border.js +6 -0
  315. package/dist/src/utils/border.js.map +1 -1
  316. package/dist/src/utils/flex-layout.d.ts +2 -1
  317. package/dist/src/utils/flex-layout.d.ts.map +1 -1
  318. package/dist/src/utils/flex-layout.js +22 -33
  319. package/dist/src/utils/flex-layout.js.map +1 -1
  320. package/dist/src/utils/index.d.ts +1 -1
  321. package/dist/src/utils/index.d.ts.map +1 -1
  322. package/dist/src/utils/index.js +1 -1
  323. package/dist/src/utils/index.js.map +1 -1
  324. package/dist/src/utils/padding.d.ts.map +1 -1
  325. package/dist/src/utils/padding.js.map +1 -1
  326. package/dist/src/utils/styles.d.ts +20 -1
  327. package/dist/src/utils/styles.d.ts.map +1 -1
  328. package/dist/src/utils/styles.js +36 -1
  329. package/dist/src/utils/styles.js.map +1 -1
  330. package/dist/src/visualize/index.d.ts +8 -19
  331. package/dist/src/visualize/index.d.ts.map +1 -1
  332. package/dist/src/visualize/index.js +11 -25
  333. package/dist/src/visualize/index.js.map +1 -1
  334. package/dist/tsconfig.tsbuildinfo +1 -1
  335. package/jsx-dev-runtime.ts +5 -0
  336. package/jsx-runtime.ts +54 -0
  337. package/package.json +124 -92
  338. package/src/codeblock.tsx +34 -34
  339. package/src/components/Divider.tsx +23 -0
  340. package/src/components/Markdown.tsx +380 -0
  341. package/src/components/MultilineTextInput.tsx +749 -0
  342. package/src/components/Overlay.tsx +56 -0
  343. package/src/components/Static.tsx +68 -0
  344. package/src/components/TextInput.tsx +285 -0
  345. package/src/components/index.ts +6 -0
  346. package/src/components/text-editing.ts +464 -0
  347. package/src/console/ConsoleCapture.ts +272 -0
  348. package/src/console/ConsolePopover.tsx +487 -0
  349. package/src/console/clipboard.ts +81 -0
  350. package/src/console/index.ts +42 -0
  351. package/src/console/useConsole.ts +129 -0
  352. package/src/debug/DebugOverlay.ts +557 -0
  353. package/src/debug/DiagnosticsPanel.tsx +27 -27
  354. package/src/dev/Toast.tsx +117 -0
  355. package/src/dev/index.ts +2 -0
  356. package/src/dev.tsx +489 -0
  357. package/src/highlight.ts +46 -46
  358. package/src/hmr-plugin.ts +61 -0
  359. package/src/hooks/index.ts +4 -0
  360. package/src/hooks/use-keyboard.ts +44 -24
  361. package/src/hooks/use-mouse.ts +51 -0
  362. package/src/hooks/use-paste.ts +21 -6
  363. package/src/hooks/use-scroll.ts +386 -0
  364. package/src/hooks/useFrameStats.ts +17 -17
  365. package/src/hosts/base.ts +180 -59
  366. package/src/hosts/box.ts +117 -96
  367. package/src/hosts/canvas.ts +137 -141
  368. package/src/hosts/codeblock.ts +117 -133
  369. package/src/hosts/flex-container.ts +124 -0
  370. package/src/hosts/hstack.ts +11 -59
  371. package/src/hosts/index.ts +24 -14
  372. package/src/hosts/overlay-item.ts +72 -0
  373. package/src/hosts/overlay.ts +125 -0
  374. package/src/hosts/scroll.ts +255 -0
  375. package/src/hosts/single-child.ts +52 -0
  376. package/src/hosts/spacer.ts +30 -26
  377. package/src/hosts/text.ts +198 -164
  378. package/src/hosts/vstack.ts +11 -59
  379. package/src/hosts/zstack.ts +79 -67
  380. package/src/index.ts +44 -19
  381. package/src/inline/index.tsx +123 -123
  382. package/src/motion/color-motion-value.ts +67 -67
  383. package/src/motion/color.test.ts +107 -107
  384. package/src/motion/color.ts +9 -190
  385. package/src/motion/event-emitter.ts +20 -20
  386. package/src/motion/frame.ts +35 -35
  387. package/src/motion/hooks.ts +144 -139
  388. package/src/motion/index.ts +10 -10
  389. package/src/motion/motion-value.test.ts +207 -207
  390. package/src/motion/motion-value.ts +112 -112
  391. package/src/motion/spring-math.ts +88 -83
  392. package/src/motion/types.ts +25 -25
  393. package/src/profiler.ts +50 -50
  394. package/src/reconciler/host-config.ts +152 -174
  395. package/src/reconciler/noop-methods.ts +55 -0
  396. package/src/reconciler/types.ts +112 -46
  397. package/src/remote/Procedures.ts +52 -0
  398. package/src/remote/Router.ts +58 -0
  399. package/src/remote/Server.ts +76 -0
  400. package/src/remote/index.ts +90 -0
  401. package/src/renderer/core/FrameBuilder.ts +49 -0
  402. package/src/renderer/core/RendererState.ts +80 -0
  403. package/src/renderer/core/index.ts +2 -0
  404. package/src/renderer/input/InputProcessor.ts +94 -0
  405. package/src/renderer/input/index.ts +1 -0
  406. package/src/renderer/lifecycle/EventBus.ts +90 -0
  407. package/src/renderer/lifecycle/ResizeManager.ts +65 -0
  408. package/src/renderer/lifecycle/TerminalSetup.ts +105 -0
  409. package/src/renderer/lifecycle/index.ts +3 -0
  410. package/src/renderer/modes/FullscreenRenderer.ts +53 -0
  411. package/src/renderer/modes/InlineRenderer.ts +178 -0
  412. package/src/renderer/modes/RendererMode.ts +46 -0
  413. package/src/renderer/modes/StaticContentRenderer.ts +56 -0
  414. package/src/renderer/modes/index.ts +4 -0
  415. package/src/renderer-context.ts +27 -0
  416. package/src/renderer-types.ts +109 -0
  417. package/src/renderer.ts +391 -642
  418. package/src/test/index.ts +5 -5
  419. package/src/test/mock-streams.ts +115 -115
  420. package/src/test/render-tui.ts +84 -87
  421. package/src/utils/border.ts +79 -73
  422. package/src/utils/flex-layout.ts +80 -93
  423. package/src/utils/index.ts +1 -1
  424. package/src/utils/padding.ts +27 -27
  425. package/src/utils/styles.ts +50 -7
  426. package/src/visualize/index.tsx +225 -240
  427. package/dist/src/output.d.ts +0 -47
  428. package/dist/src/output.d.ts.map +0 -1
  429. package/dist/src/output.js +0 -125
  430. package/dist/src/output.js.map +0 -1
  431. package/dist/src/terminal.d.ts +0 -37
  432. package/dist/src/terminal.d.ts.map +0 -1
  433. package/dist/src/terminal.js +0 -65
  434. package/dist/src/terminal.js.map +0 -1
  435. package/src/output.ts +0 -156
  436. package/src/terminal.ts +0 -67
  437. package/src/trace/SpanTree.tsx +0 -195
  438. package/src/trace/index.tsx +0 -205
  439. package/src/trace/location.ts +0 -90
  440. package/src/trace/span-processor.ts +0 -65
  441. package/src/trace/span-state.ts +0 -286
  442. package/src/trace/tui-logger.ts +0 -72
@@ -0,0 +1,386 @@
1
+ // use-scroll.ts — Hook for managing scroll state with keyboard/mouse input
2
+ import { useState, useCallback, useRef, useMemo, useLayoutEffect } from "react"
3
+ import type { KeyMsg } from "@effect-tui/core"
4
+ import { useKeyboard } from "./use-keyboard.js"
5
+ import { useTerminalSize } from "../renderer.js"
6
+
7
+ // ============================================================================
8
+ // Scroll Acceleration (macOS-style)
9
+ // ============================================================================
10
+
11
+ interface ScrollAcceleration {
12
+ tick(now?: number): number
13
+ reset(): void
14
+ }
15
+
16
+ /**
17
+ * macOS-inspired scroll acceleration.
18
+ * Tracks velocity via moving window of tick intervals.
19
+ * Fast scrolling gets progressively faster.
20
+ */
21
+ class MacOSScrollAccel implements ScrollAcceleration {
22
+ private lastTickTime = 0
23
+ private velocityHistory: number[] = []
24
+ private readonly historySize = 3
25
+ private readonly streakTimeout = 150
26
+ // Some terminals send duplicate ticks ~4ms apart (Ghostty workaround)
27
+ private readonly minTickInterval = 6
28
+
29
+ constructor(
30
+ private opts: {
31
+ A?: number // Amplitude (default 0.8)
32
+ tau?: number // Time constant (default 3)
33
+ maxMultiplier?: number // Cap (default 6)
34
+ } = {},
35
+ ) {}
36
+
37
+ tick(now = Date.now()): number {
38
+ const A = this.opts.A ?? 0.8
39
+ const tau = this.opts.tau ?? 3
40
+ const maxMultiplier = this.opts.maxMultiplier ?? 6
41
+
42
+ const dt = this.lastTickTime ? now - this.lastTickTime : Infinity
43
+
44
+ // Reset if too much time passed
45
+ if (dt === Infinity || dt > this.streakTimeout) {
46
+ this.lastTickTime = now
47
+ this.velocityHistory = []
48
+ return 1
49
+ }
50
+
51
+ // Ignore duplicate ticks (terminal quirks)
52
+ if (dt < this.minTickInterval) {
53
+ return 1
54
+ }
55
+
56
+ this.lastTickTime = now
57
+ this.velocityHistory.push(dt)
58
+ if (this.velocityHistory.length > this.historySize) {
59
+ this.velocityHistory.shift()
60
+ }
61
+
62
+ // Calculate average interval (lower = faster scrolling)
63
+ const avgInterval = this.velocityHistory.reduce((a, b) => a + b, 0) / this.velocityHistory.length
64
+
65
+ // Convert to velocity: faster ticks = higher velocity
66
+ const referenceInterval = 100
67
+ const velocity = referenceInterval / avgInterval
68
+
69
+ // Apply exponential curve
70
+ const x = velocity / tau
71
+ const multiplier = 1 + A * (Math.exp(x) - 1)
72
+
73
+ return Math.min(multiplier, maxMultiplier)
74
+ }
75
+
76
+ reset(): void {
77
+ this.lastTickTime = 0
78
+ this.velocityHistory = []
79
+ }
80
+ }
81
+
82
+ // ============================================================================
83
+ // Scroll State
84
+ // ============================================================================
85
+
86
+ export interface ScrollState {
87
+ /** Current scroll offset (pixels from start) */
88
+ offset: number
89
+ /** Maximum scroll offset */
90
+ maxOffset: number
91
+ /** Viewport height (or width for horizontal) */
92
+ viewportSize: number
93
+ /** Total content size */
94
+ contentSize: number
95
+ /** Whether we're at the start edge */
96
+ atStart: boolean
97
+ /** Whether we're at the end edge */
98
+ atEnd: boolean
99
+ }
100
+
101
+ export interface UseScrollOptions {
102
+ /** Scroll axis: "vertical" (default) or "horizontal" */
103
+ axis?: "vertical" | "horizontal"
104
+ /** Initial scroll offset */
105
+ initialOffset?: number
106
+ /** Enable keyboard navigation (default: true) */
107
+ enableKeyboard?: boolean
108
+ /** Enable mouse wheel (default: true) */
109
+ enableMouseWheel?: boolean
110
+ /** Enable scroll acceleration (default: true) */
111
+ enableAcceleration?: boolean
112
+ /** Sticky scroll - auto-scroll to end when content grows (default: false) */
113
+ sticky?: boolean
114
+ /** Scroll speed for arrow keys (pixels, default: 1) */
115
+ arrowSpeed?: number
116
+ /** Scroll speed for page up/down (fraction of viewport, default: 0.5) */
117
+ pageSpeed?: number
118
+ }
119
+
120
+ export interface UseScrollReturn {
121
+ /** Current scroll state */
122
+ state: ScrollState
123
+ /** Set scroll offset directly */
124
+ setOffset: (offset: number) => void
125
+ /** Scroll by delta pixels */
126
+ scrollBy: (delta: number) => void
127
+ /** Scroll to start */
128
+ scrollToStart: () => void
129
+ /** Scroll to end */
130
+ scrollToEnd: () => void
131
+ /**
132
+ * Scroll to make a position visible in the viewport.
133
+ * Useful for keeping a selected item in view.
134
+ * @param position - The position (row/col index or pixel offset)
135
+ * @param itemSize - Size of each item (default: 1 for row-based lists)
136
+ * @param padding - Extra padding around the item (default: 0)
137
+ */
138
+ scrollToVisible: (position: number, itemSize?: number, padding?: number) => void
139
+ /** Props to spread on <scroll> element */
140
+ scrollProps: {
141
+ offset: number
142
+ axis: "vertical" | "horizontal"
143
+ onContentSize: (width: number, height: number) => void
144
+ onViewportSize: (width: number, height: number) => void
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Hook for managing scroll state with keyboard/mouse input.
150
+ *
151
+ * @example
152
+ * ```tsx
153
+ * function ScrollableList({ items }) {
154
+ * const { state, scrollProps } = useScroll({ sticky: true })
155
+ *
156
+ * return (
157
+ * <scroll {...scrollProps}>
158
+ * <vstack>
159
+ * {items.map(item => <text key={item.id}>{item.text}</text>)}
160
+ * </vstack>
161
+ * </scroll>
162
+ * )
163
+ * }
164
+ * ```
165
+ */
166
+ export function useScroll(options: UseScrollOptions = {}): UseScrollReturn {
167
+ const {
168
+ axis = "vertical",
169
+ initialOffset = 0,
170
+ enableKeyboard = true,
171
+ enableMouseWheel = true,
172
+ enableAcceleration = true,
173
+ sticky = false,
174
+ arrowSpeed = 1,
175
+ pageSpeed = 0.5,
176
+ } = options
177
+
178
+ const { width: termWidth, height: termHeight } = useTerminalSize()
179
+
180
+ // Scroll state
181
+ const [offset, setOffsetRaw] = useState(initialOffset)
182
+ const [contentSize, setContentSize] = useState(0)
183
+ // Use terminal size as initial estimate, but scroll component will report actual size
184
+ const [viewportSize, setViewportSize] = useState(axis === "vertical" ? termHeight : termWidth)
185
+
186
+ // Refs for sticky scroll behavior
187
+ const wasAtEndRef = useRef(sticky)
188
+ const prevContentSizeRef = useRef(0)
189
+
190
+ // Scroll acceleration
191
+ const accel = useMemo(
192
+ () => (enableAcceleration ? new MacOSScrollAccel() : { tick: () => 1, reset: () => {} }),
193
+ [enableAcceleration],
194
+ )
195
+
196
+ // Fractional accumulator for smooth sub-pixel scrolling
197
+ const accumulatorRef = useRef(0)
198
+
199
+ // Ref for scrollToVisible so it doesn't change on every scroll
200
+ const offsetRef = useRef(offset)
201
+ offsetRef.current = offset
202
+
203
+ // Calculate derived state
204
+ const maxOffset = Math.max(0, contentSize - viewportSize)
205
+ const atStart = offset <= 0
206
+ const atEnd = offset >= maxOffset
207
+
208
+ // Clamp and set offset
209
+ const setOffset = useCallback(
210
+ (newOffset: number) => {
211
+ const clamped = Math.max(0, Math.min(maxOffset, newOffset))
212
+ setOffsetRaw(clamped)
213
+ // Track if we're at end for sticky behavior
214
+ wasAtEndRef.current = clamped >= maxOffset - 1
215
+ },
216
+ [maxOffset],
217
+ )
218
+
219
+ // Scroll by delta with accumulator for fractional scrolling
220
+ const scrollBy = useCallback(
221
+ (delta: number) => {
222
+ accumulatorRef.current += delta
223
+ const integerDelta = Math.trunc(accumulatorRef.current)
224
+ if (integerDelta !== 0) {
225
+ setOffset(offset + integerDelta)
226
+ accumulatorRef.current -= integerDelta
227
+ }
228
+ },
229
+ [offset, setOffset],
230
+ )
231
+
232
+ const scrollToStart = useCallback(() => {
233
+ setOffset(0)
234
+ accumulatorRef.current = 0
235
+ accel.reset()
236
+ }, [setOffset, accel])
237
+
238
+ const scrollToEnd = useCallback(() => {
239
+ setOffset(maxOffset)
240
+ accumulatorRef.current = 0
241
+ accel.reset()
242
+ }, [setOffset, maxOffset, accel])
243
+
244
+ // Handle content size changes (for sticky scroll)
245
+ const handleContentSize = useCallback(
246
+ (width: number, height: number) => {
247
+ const newSize = axis === "vertical" ? height : width
248
+ setContentSize(newSize)
249
+ prevContentSizeRef.current = newSize
250
+ },
251
+ [axis],
252
+ )
253
+
254
+ // Synchronous sticky scroll: snap to end when content grows
255
+ // useLayoutEffect runs synchronously after React commit but before paint
256
+ useLayoutEffect(() => {
257
+ if (!sticky) return
258
+ const newMaxOffset = Math.max(0, contentSize - viewportSize)
259
+ if (wasAtEndRef.current && contentSize > 0) {
260
+ setOffsetRaw(newMaxOffset)
261
+ }
262
+ }, [sticky, contentSize, viewportSize])
263
+
264
+ // Handle viewport size changes (reported by scroll component)
265
+ const handleViewportSize = useCallback(
266
+ (width: number, height: number) => {
267
+ const newSize = axis === "vertical" ? height : width
268
+ setViewportSize(newSize)
269
+ },
270
+ [axis],
271
+ )
272
+
273
+ // Keyboard handler
274
+ const handleKey = useCallback(
275
+ (key: KeyMsg) => {
276
+ // Mouse wheel comes as pageup/pagedown with meta=true
277
+ // Handle separately from keyboard since enableKeyboard shouldn't disable mouse
278
+ if (key.meta && enableMouseWheel && (key.name === "pageup" || key.name === "pagedown")) {
279
+ const multiplier = accel.tick()
280
+ const delta = Math.ceil(arrowSpeed * multiplier)
281
+ scrollBy(key.name === "pageup" ? -delta : delta)
282
+ return
283
+ }
284
+
285
+ if (!enableKeyboard) return
286
+
287
+ const isVertical = axis === "vertical"
288
+ const upKey = isVertical ? "up" : "left"
289
+ const downKey = isVertical ? "down" : "right"
290
+
291
+ switch (key.name) {
292
+ case upKey:
293
+ scrollBy(-arrowSpeed)
294
+ break
295
+ case downKey:
296
+ scrollBy(arrowSpeed)
297
+ break
298
+ case "pageup":
299
+ scrollBy(-Math.floor(viewportSize * pageSpeed))
300
+ break
301
+ case "pagedown":
302
+ scrollBy(Math.floor(viewportSize * pageSpeed))
303
+ break
304
+ case "home":
305
+ scrollToStart()
306
+ break
307
+ case "end":
308
+ scrollToEnd()
309
+ break
310
+ }
311
+ },
312
+ [
313
+ enableKeyboard,
314
+ axis,
315
+ arrowSpeed,
316
+ pageSpeed,
317
+ viewportSize,
318
+ enableMouseWheel,
319
+ accel,
320
+ scrollBy,
321
+ scrollToStart,
322
+ scrollToEnd,
323
+ ],
324
+ )
325
+
326
+ useKeyboard(handleKey)
327
+
328
+ // Scroll to make a position visible (for keeping selection in view)
329
+ // Uses refs to avoid changing on every scroll - only triggers when selection changes
330
+ const scrollToVisible = useCallback(
331
+ (position: number, itemSize = 1, padding = 0) => {
332
+ const currentOffset = offsetRef.current
333
+ const itemStart = position * itemSize
334
+ const itemEnd = itemStart + itemSize
335
+
336
+ // If item is above viewport, scroll up to show it
337
+ if (itemStart < currentOffset + padding) {
338
+ setOffset(Math.max(0, itemStart - padding))
339
+ }
340
+ // If item is below viewport, scroll down to show it
341
+ else if (itemEnd > currentOffset + viewportSize - padding) {
342
+ const currentMaxOffset = Math.max(0, contentSize - viewportSize)
343
+ setOffset(Math.min(currentMaxOffset, itemEnd - viewportSize + padding))
344
+ }
345
+ },
346
+ [viewportSize, contentSize, setOffset],
347
+ )
348
+
349
+ const state: ScrollState = {
350
+ offset,
351
+ maxOffset,
352
+ viewportSize,
353
+ contentSize,
354
+ atStart,
355
+ atEnd,
356
+ }
357
+
358
+ // Handle effective offset sync from host (when sticky adjusts the offset)
359
+ const handleEffectiveOffset = useCallback(
360
+ (effectiveOffset: number) => {
361
+ // Sync the ref so scrollToVisible uses the actual rendered position
362
+ offsetRef.current = effectiveOffset
363
+ // Also update state to keep in sync (but don't trigger wasAtEnd change)
364
+ setOffsetRaw(effectiveOffset)
365
+ },
366
+ [],
367
+ )
368
+
369
+ const scrollProps = {
370
+ offset,
371
+ axis,
372
+ onContentSize: handleContentSize,
373
+ onViewportSize: handleViewportSize,
374
+ onEffectiveOffset: handleEffectiveOffset,
375
+ }
376
+
377
+ return {
378
+ state,
379
+ setOffset,
380
+ scrollBy,
381
+ scrollToStart,
382
+ scrollToEnd,
383
+ scrollToVisible,
384
+ scrollProps,
385
+ }
386
+ }
@@ -7,26 +7,26 @@ import type { FrameStats } from "../renderer.js"
7
7
  * Falls back to null when the renderer does not support onFrameStats.
8
8
  */
9
9
  export function useFrameStats(sampleMs = 200): FrameStats | null {
10
- const renderer = useRenderer()
11
- const [stats, setStats] = useState<FrameStats | null>(null)
10
+ const renderer = useRenderer()
11
+ const [stats, setStats] = useState<FrameStats | null>(null)
12
12
 
13
- useEffect(() => {
14
- if (!renderer.onFrameStats) return
13
+ useEffect(() => {
14
+ if (!renderer.onFrameStats) return
15
15
 
16
- let last: FrameStats | null = null
17
- const unsub = renderer.onFrameStats((s) => {
18
- last = s
19
- })
16
+ let last: FrameStats | null = null
17
+ const unsub = renderer.onFrameStats((s) => {
18
+ last = s
19
+ })
20
20
 
21
- const id = setInterval(() => {
22
- if (last) setStats(last)
23
- }, sampleMs)
21
+ const id = setInterval(() => {
22
+ if (last) setStats(last)
23
+ }, sampleMs)
24
24
 
25
- return () => {
26
- clearInterval(id)
27
- unsub?.()
28
- }
29
- }, [renderer, sampleMs])
25
+ return () => {
26
+ clearInterval(id)
27
+ unsub?.()
28
+ }
29
+ }, [renderer, sampleMs])
30
30
 
31
- return stats
31
+ return stats
32
32
  }
package/src/hosts/base.ts CHANGED
@@ -1,65 +1,186 @@
1
- import type { CellBuffer, Palette } from "@effect-tui/core"
1
+ import type { CellBuffer, Palette, Color } from "@effect-tui/core"
2
2
  import type { HostInstance, Rect, Size, HostContext, CommonProps } from "../reconciler/types.js"
3
3
 
4
+ /** Host that may have a background color (e.g., BoxHost) */
5
+ export interface HostWithBg extends HostInstance {
6
+ bg?: Color
7
+ }
8
+
9
+ /** Type guard for hosts with bg property */
10
+ function hasBackground(host: HostInstance): host is HostWithBg {
11
+ return "bg" in host
12
+ }
13
+
14
+ /** Walk up parent chain to find inherited background color */
15
+ export function getInheritedBg(startParent: HostInstance | null): Color | undefined {
16
+ let p: HostInstance | null = startParent
17
+ while (p) {
18
+ if (hasBackground(p) && p.bg !== undefined) {
19
+ return p.bg
20
+ }
21
+ p = p.parent
22
+ }
23
+ return undefined
24
+ }
25
+
4
26
  let idCounter = 0
5
27
 
6
28
  export abstract class BaseHost implements HostInstance {
7
- id: string
8
- type: string
9
- parent: HostInstance | null = null
10
- children: HostInstance[] = []
11
- rect: Rect | null = null
12
-
13
- // Common flex props
14
- flexGrow = 0
15
- flexShrink = 1
16
-
17
- protected ctx: HostContext
18
-
19
- constructor(type: string, props: CommonProps, ctx: HostContext) {
20
- this.id = `${type}-${idCounter++}`
21
- this.type = type
22
- this.ctx = ctx
23
- this.updateProps(props)
24
- }
25
-
26
- abstract measure(maxW: number, maxH: number): Size
27
- abstract render(buffer: CellBuffer, palette: Palette): void
28
-
29
- layout(rect: Rect): void {
30
- this.rect = rect
31
- }
32
-
33
- updateProps(props: Record<string, unknown>): void {
34
- if (props.flexGrow !== undefined) this.flexGrow = props.flexGrow as number
35
- if (props.flexShrink !== undefined) this.flexShrink = props.flexShrink as number
36
- }
37
-
38
- destroy(): void {
39
- // Override in subclasses if cleanup needed
40
- }
41
-
42
- // Child management helpers
43
- appendChild(child: HostInstance): void {
44
- this.children.push(child)
45
- child.parent = this
46
- }
47
-
48
- removeChild(child: HostInstance): void {
49
- const idx = this.children.indexOf(child)
50
- if (idx >= 0) {
51
- this.children.splice(idx, 1)
52
- child.parent = null
53
- }
54
- }
55
-
56
- insertBefore(child: HostInstance, before: HostInstance): void {
57
- const idx = this.children.indexOf(before)
58
- if (idx >= 0) {
59
- this.children.splice(idx, 0, child)
60
- } else {
61
- this.children.push(child)
62
- }
63
- child.parent = this
64
- }
29
+ id: string
30
+ type: string
31
+ parent: HostInstance | null = null
32
+ children: HostInstance[] = []
33
+ rect: Rect | null = null
34
+
35
+ // Common flex props
36
+ flexGrow = 0
37
+ flexShrink = 1
38
+
39
+ // ─────────────────────────────────────────────────────────────
40
+ // Frame constraints (like SwiftUI's .frame modifier)
41
+ // See CommonProps for detailed documentation
42
+ // ─────────────────────────────────────────────────────────────
43
+ frameWidth?: number
44
+ frameHeight?: number
45
+ frameMinWidth?: number
46
+ frameMaxWidth?: number
47
+ frameMinHeight?: number
48
+ frameMaxHeight?: number
49
+
50
+ /** @internal Marks this node as static content (for Static component) */
51
+ __static?: boolean
52
+
53
+ protected ctx: HostContext
54
+
55
+ constructor(type: string, props: CommonProps, ctx: HostContext) {
56
+ this.id = `${type}-${idCounter++}`
57
+ this.type = type
58
+ this.ctx = ctx
59
+ // NOTE: Subclasses must call this.updateProps(props) after super()
60
+ // because JavaScript field initializers run AFTER parent constructor,
61
+ // which would overwrite any values set here.
62
+ }
63
+
64
+ abstract measure(maxW: number, maxH: number): Size
65
+ abstract render(buffer: CellBuffer, palette: Palette): void
66
+
67
+ layout(rect: Rect): void {
68
+ this.rect = rect
69
+ }
70
+
71
+ /**
72
+ * Apply frame constraints to a proposed size before passing to children.
73
+ *
74
+ * This is the first half of SwiftUI-style frame semantics. When a parent
75
+ * proposes a size to us, we modify it based on our constraints before
76
+ * passing it down to our children.
77
+ *
78
+ * @example
79
+ * ```
80
+ * Parent proposes: 100x100
81
+ * Our height constraint: 10
82
+ * We propose to children: 100x10
83
+ * ```
84
+ *
85
+ * This prevents greedy children (like scroll) from measuring larger than
86
+ * our constrained size.
87
+ *
88
+ * @param maxW - Width proposed by parent
89
+ * @param maxH - Height proposed by parent
90
+ * @returns Constrained size to propose to children
91
+ */
92
+ protected constrainProposal(maxW: number, maxH: number): { w: number; h: number } {
93
+ let w = maxW
94
+ let h = maxH
95
+
96
+ // Fixed width/height override everything
97
+ if (this.frameWidth !== undefined) w = this.frameWidth
98
+ if (this.frameHeight !== undefined) h = this.frameHeight
99
+
100
+ // Apply min/max clamps
101
+ if (this.frameMinWidth !== undefined) w = Math.max(this.frameMinWidth, w)
102
+ if (this.frameMaxWidth !== undefined) w = Math.min(this.frameMaxWidth, w)
103
+ if (this.frameMinHeight !== undefined) h = Math.max(this.frameMinHeight, h)
104
+ if (this.frameMaxHeight !== undefined) h = Math.min(this.frameMaxHeight, h)
105
+
106
+ return { w, h }
107
+ }
108
+
109
+ /**
110
+ * Apply frame constraints to our measured size before returning to parent.
111
+ *
112
+ * This is the second half of SwiftUI-style frame semantics. After measuring
113
+ * our children, we modify our reported size based on our constraints before
114
+ * returning it to our parent.
115
+ *
116
+ * @example
117
+ * ```
118
+ * Children measured: 5x1 (small content)
119
+ * Our height constraint: 10
120
+ * We report to parent: 5x10
121
+ * ```
122
+ *
123
+ * This ensures we report our constrained size regardless of children's
124
+ * natural size, which is essential for layout composition (e.g., spacer
125
+ * needs to know our fixed size to calculate remaining space).
126
+ *
127
+ * @param size - Natural size from measuring children
128
+ * @returns Constrained size to report to parent
129
+ */
130
+ protected constrainResult(size: Size): Size {
131
+ let { w, h } = size
132
+
133
+ // Fixed width/height override the measured size
134
+ if (this.frameWidth !== undefined) w = this.frameWidth
135
+ if (this.frameHeight !== undefined) h = this.frameHeight
136
+
137
+ // Apply min/max clamps
138
+ if (this.frameMinWidth !== undefined) w = Math.max(this.frameMinWidth, w)
139
+ if (this.frameMaxWidth !== undefined) w = Math.min(this.frameMaxWidth, w)
140
+ if (this.frameMinHeight !== undefined) h = Math.max(this.frameMinHeight, h)
141
+ if (this.frameMaxHeight !== undefined) h = Math.min(this.frameMaxHeight, h)
142
+
143
+ return { w, h }
144
+ }
145
+
146
+ updateProps(props: Record<string, unknown>): void {
147
+ this.flexGrow = (props.flexGrow as number | undefined) ?? 0
148
+ this.flexShrink = (props.flexShrink as number | undefined) ?? 1
149
+
150
+ // Frame constraints
151
+ this.frameWidth = props.width as number | undefined
152
+ this.frameHeight = props.height as number | undefined
153
+ this.frameMinWidth = props.minWidth as number | undefined
154
+ this.frameMaxWidth = props.maxWidth as number | undefined
155
+ this.frameMinHeight = props.minHeight as number | undefined
156
+ this.frameMaxHeight = props.maxHeight as number | undefined
157
+ }
158
+
159
+ destroy(): void {
160
+ // Override in subclasses if cleanup needed
161
+ }
162
+
163
+ // Child management helpers
164
+ appendChild(child: HostInstance): void {
165
+ this.children.push(child)
166
+ child.parent = this
167
+ }
168
+
169
+ removeChild(child: HostInstance): void {
170
+ const idx = this.children.indexOf(child)
171
+ if (idx >= 0) {
172
+ this.children.splice(idx, 1)
173
+ child.parent = null
174
+ }
175
+ }
176
+
177
+ insertBefore(child: HostInstance, before: HostInstance): void {
178
+ const idx = this.children.indexOf(before)
179
+ if (idx >= 0) {
180
+ this.children.splice(idx, 0, child)
181
+ } else {
182
+ this.children.push(child)
183
+ }
184
+ child.parent = this
185
+ }
65
186
  }