@effect-tui/react 0.15.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (313) hide show
  1. package/README.md +11 -2
  2. package/dist/src/codeblock.d.ts +1 -1
  3. package/dist/src/codeblock.d.ts.map +1 -1
  4. package/dist/src/codeblock.js +2 -2
  5. package/dist/src/codeblock.js.map +1 -1
  6. package/dist/src/components/ListView.d.ts +4 -4
  7. package/dist/src/components/ListView.d.ts.map +1 -1
  8. package/dist/src/components/ListView.js +16 -17
  9. package/dist/src/components/ListView.js.map +1 -1
  10. package/dist/src/components/Markdown.js +3 -3
  11. package/dist/src/components/Markdown.js.map +1 -1
  12. package/dist/src/components/MultilineTextInput.d.ts.map +1 -1
  13. package/dist/src/components/MultilineTextInput.js +133 -305
  14. package/dist/src/components/MultilineTextInput.js.map +1 -1
  15. package/dist/src/components/TextInput.d.ts.map +1 -1
  16. package/dist/src/components/TextInput.js +51 -98
  17. package/dist/src/components/TextInput.js.map +1 -1
  18. package/dist/src/components/text-editing.d.ts +61 -0
  19. package/dist/src/components/text-editing.d.ts.map +1 -1
  20. package/dist/src/components/text-editing.js +131 -0
  21. package/dist/src/components/text-editing.js.map +1 -1
  22. package/dist/src/console/ConsolePopover.d.ts +7 -1
  23. package/dist/src/console/ConsolePopover.d.ts.map +1 -1
  24. package/dist/src/console/ConsolePopover.js +55 -74
  25. package/dist/src/console/ConsolePopover.js.map +1 -1
  26. package/dist/src/debug/DebugOverlay.d.ts.map +1 -1
  27. package/dist/src/debug/DebugOverlay.js +3 -57
  28. package/dist/src/debug/DebugOverlay.js.map +1 -1
  29. package/dist/src/debug/DiagnosticsPanel.js +1 -1
  30. package/dist/src/debug/DiagnosticsPanel.js.map +1 -1
  31. package/dist/src/dev.d.ts +5 -117
  32. package/dist/src/dev.d.ts.map +1 -1
  33. package/dist/src/dev.js +3 -333
  34. package/dist/src/dev.js.map +1 -1
  35. package/dist/src/hooks/use-scroll.d.ts +31 -35
  36. package/dist/src/hooks/use-scroll.d.ts.map +1 -1
  37. package/dist/src/hooks/use-scroll.js +51 -90
  38. package/dist/src/hooks/use-scroll.js.map +1 -1
  39. package/dist/src/hosts/base.d.ts +13 -2
  40. package/dist/src/hosts/base.d.ts.map +1 -1
  41. package/dist/src/hosts/base.js +74 -2
  42. package/dist/src/hosts/base.js.map +1 -1
  43. package/dist/src/hosts/box.d.ts +2 -2
  44. package/dist/src/hosts/box.d.ts.map +1 -1
  45. package/dist/src/hosts/box.js +29 -2
  46. package/dist/src/hosts/box.js.map +1 -1
  47. package/dist/src/hosts/canvas.d.ts +24 -4
  48. package/dist/src/hosts/canvas.d.ts.map +1 -1
  49. package/dist/src/hosts/canvas.js +107 -41
  50. package/dist/src/hosts/canvas.js.map +1 -1
  51. package/dist/src/hosts/codeblock.d.ts +10 -12
  52. package/dist/src/hosts/codeblock.d.ts.map +1 -1
  53. package/dist/src/hosts/codeblock.js +38 -35
  54. package/dist/src/hosts/codeblock.js.map +1 -1
  55. package/dist/src/hosts/flex-container.d.ts +3 -3
  56. package/dist/src/hosts/flex-container.d.ts.map +1 -1
  57. package/dist/src/hosts/flex-container.js +20 -5
  58. package/dist/src/hosts/flex-container.js.map +1 -1
  59. package/dist/src/hosts/index.d.ts +3 -2
  60. package/dist/src/hosts/index.d.ts.map +1 -1
  61. package/dist/src/hosts/index.js +2 -1
  62. package/dist/src/hosts/index.js.map +1 -1
  63. package/dist/src/hosts/layout-helpers.d.ts +10 -0
  64. package/dist/src/hosts/layout-helpers.d.ts.map +1 -0
  65. package/dist/src/hosts/layout-helpers.js +10 -0
  66. package/dist/src/hosts/layout-helpers.js.map +1 -0
  67. package/dist/src/hosts/leaf.d.ts +14 -0
  68. package/dist/src/hosts/leaf.d.ts.map +1 -0
  69. package/dist/src/hosts/leaf.js +31 -0
  70. package/dist/src/hosts/leaf.js.map +1 -0
  71. package/dist/src/hosts/overlay-item.d.ts +2 -2
  72. package/dist/src/hosts/overlay-item.d.ts.map +1 -1
  73. package/dist/src/hosts/overlay-item.js +7 -2
  74. package/dist/src/hosts/overlay-item.js.map +1 -1
  75. package/dist/src/hosts/overlay.d.ts +2 -2
  76. package/dist/src/hosts/overlay.d.ts.map +1 -1
  77. package/dist/src/hosts/overlay.js +6 -9
  78. package/dist/src/hosts/overlay.js.map +1 -1
  79. package/dist/src/hosts/scroll.d.ts +54 -26
  80. package/dist/src/hosts/scroll.d.ts.map +1 -1
  81. package/dist/src/hosts/scroll.js +185 -87
  82. package/dist/src/hosts/scroll.js.map +1 -1
  83. package/dist/src/hosts/single-child.d.ts.map +1 -1
  84. package/dist/src/hosts/single-child.js +2 -0
  85. package/dist/src/hosts/single-child.js.map +1 -1
  86. package/dist/src/hosts/spacer.d.ts +3 -3
  87. package/dist/src/hosts/spacer.d.ts.map +1 -1
  88. package/dist/src/hosts/spacer.js +8 -3
  89. package/dist/src/hosts/spacer.js.map +1 -1
  90. package/dist/src/hosts/text.d.ts +22 -18
  91. package/dist/src/hosts/text.d.ts.map +1 -1
  92. package/dist/src/hosts/text.js +108 -131
  93. package/dist/src/hosts/text.js.map +1 -1
  94. package/dist/src/hosts/vstack.js +1 -1
  95. package/dist/src/hosts/vstack.js.map +1 -1
  96. package/dist/src/hosts/zstack.d.ts +3 -3
  97. package/dist/src/hosts/zstack.d.ts.map +1 -1
  98. package/dist/src/hosts/zstack.js +13 -8
  99. package/dist/src/hosts/zstack.js.map +1 -1
  100. package/dist/src/index.d.ts +2 -2
  101. package/dist/src/index.d.ts.map +1 -1
  102. package/dist/src/internal/dev/hmr.d.ts +20 -0
  103. package/dist/src/internal/dev/hmr.d.ts.map +1 -0
  104. package/dist/src/internal/dev/hmr.js +93 -0
  105. package/dist/src/internal/dev/hmr.js.map +1 -0
  106. package/dist/src/internal/dev/runtime.d.ts +24 -0
  107. package/dist/src/internal/dev/runtime.d.ts.map +1 -0
  108. package/dist/src/internal/dev/runtime.js +135 -0
  109. package/dist/src/internal/dev/runtime.js.map +1 -0
  110. package/dist/src/internal/dev/ui.d.ts +13 -0
  111. package/dist/src/internal/dev/ui.d.ts.map +1 -0
  112. package/dist/src/internal/dev/ui.js +51 -0
  113. package/dist/src/internal/dev/ui.js.map +1 -0
  114. package/dist/src/internal/renderer/context.d.ts +9 -0
  115. package/dist/src/internal/renderer/context.d.ts.map +1 -0
  116. package/dist/src/internal/renderer/context.js +22 -0
  117. package/dist/src/internal/renderer/context.js.map +1 -0
  118. package/dist/src/internal/renderer/core/FrameBuilder.d.ts +18 -0
  119. package/dist/src/internal/renderer/core/FrameBuilder.d.ts.map +1 -0
  120. package/dist/src/internal/renderer/core/FrameBuilder.js +40 -0
  121. package/dist/src/internal/renderer/core/FrameBuilder.js.map +1 -0
  122. package/dist/src/internal/renderer/core/RendererState.d.ts +41 -0
  123. package/dist/src/internal/renderer/core/RendererState.d.ts.map +1 -0
  124. package/dist/src/internal/renderer/core/RendererState.js +70 -0
  125. package/dist/src/internal/renderer/core/RendererState.js.map +1 -0
  126. package/dist/src/internal/renderer/core/index.d.ts +3 -0
  127. package/dist/src/internal/renderer/core/index.d.ts.map +1 -0
  128. package/dist/src/internal/renderer/core/index.js +3 -0
  129. package/dist/src/internal/renderer/core/index.js.map +1 -0
  130. package/dist/src/internal/renderer/index.d.ts +40 -0
  131. package/dist/src/internal/renderer/index.d.ts.map +1 -0
  132. package/dist/src/internal/renderer/index.js +543 -0
  133. package/dist/src/internal/renderer/index.js.map +1 -0
  134. package/dist/src/internal/renderer/input/InputProcessor.d.ts +30 -0
  135. package/dist/src/internal/renderer/input/InputProcessor.d.ts.map +1 -0
  136. package/dist/src/internal/renderer/input/InputProcessor.js +122 -0
  137. package/dist/src/internal/renderer/input/InputProcessor.js.map +1 -0
  138. package/dist/src/internal/renderer/input/index.d.ts +2 -0
  139. package/dist/src/internal/renderer/input/index.d.ts.map +1 -0
  140. package/dist/src/internal/renderer/input/index.js +2 -0
  141. package/dist/src/internal/renderer/input/index.js.map +1 -0
  142. package/dist/src/internal/renderer/lifecycle/EventBus.d.ts +42 -0
  143. package/dist/src/internal/renderer/lifecycle/EventBus.d.ts.map +1 -0
  144. package/dist/src/internal/renderer/lifecycle/EventBus.js +97 -0
  145. package/dist/src/internal/renderer/lifecycle/EventBus.js.map +1 -0
  146. package/dist/src/internal/renderer/lifecycle/ProcessLifecycle.d.ts +13 -0
  147. package/dist/src/internal/renderer/lifecycle/ProcessLifecycle.d.ts.map +1 -0
  148. package/dist/src/internal/renderer/lifecycle/ProcessLifecycle.js +111 -0
  149. package/dist/src/internal/renderer/lifecycle/ProcessLifecycle.js.map +1 -0
  150. package/dist/src/internal/renderer/lifecycle/RenderCache.d.ts +3 -0
  151. package/dist/src/internal/renderer/lifecycle/RenderCache.d.ts.map +1 -0
  152. package/dist/src/internal/renderer/lifecycle/RenderCache.js +9 -0
  153. package/dist/src/internal/renderer/lifecycle/RenderCache.js.map +1 -0
  154. package/dist/src/internal/renderer/lifecycle/index.d.ts +4 -0
  155. package/dist/src/internal/renderer/lifecycle/index.d.ts.map +1 -0
  156. package/dist/src/internal/renderer/lifecycle/index.js +4 -0
  157. package/dist/src/internal/renderer/lifecycle/index.js.map +1 -0
  158. package/dist/src/internal/renderer/modes/FullscreenRenderer.d.ts +12 -0
  159. package/dist/src/internal/renderer/modes/FullscreenRenderer.d.ts.map +1 -0
  160. package/dist/src/internal/renderer/modes/FullscreenRenderer.js +54 -0
  161. package/dist/src/internal/renderer/modes/FullscreenRenderer.js.map +1 -0
  162. package/dist/src/internal/renderer/modes/InlineRenderer.d.ts +25 -0
  163. package/dist/src/internal/renderer/modes/InlineRenderer.d.ts.map +1 -0
  164. package/dist/src/internal/renderer/modes/InlineRenderer.js +166 -0
  165. package/dist/src/internal/renderer/modes/InlineRenderer.js.map +1 -0
  166. package/dist/src/internal/renderer/modes/RendererMode.d.ts +42 -0
  167. package/dist/src/internal/renderer/modes/RendererMode.d.ts.map +1 -0
  168. package/dist/src/internal/renderer/modes/RendererMode.js +2 -0
  169. package/dist/src/internal/renderer/modes/RendererMode.js.map +1 -0
  170. package/dist/src/internal/renderer/modes/StaticContentRenderer.d.ts +25 -0
  171. package/dist/src/internal/renderer/modes/StaticContentRenderer.d.ts.map +1 -0
  172. package/dist/src/internal/renderer/modes/StaticContentRenderer.js +49 -0
  173. package/dist/src/internal/renderer/modes/StaticContentRenderer.js.map +1 -0
  174. package/dist/src/internal/renderer/modes/index.d.ts +5 -0
  175. package/dist/src/internal/renderer/modes/index.d.ts.map +1 -0
  176. package/dist/src/internal/renderer/modes/index.js +4 -0
  177. package/dist/src/internal/renderer/modes/index.js.map +1 -0
  178. package/dist/src/internal/renderer/terminal/KeyboardCapabilityProbe.d.ts +13 -0
  179. package/dist/src/internal/renderer/terminal/KeyboardCapabilityProbe.d.ts.map +1 -0
  180. package/dist/src/internal/renderer/terminal/KeyboardCapabilityProbe.js +75 -0
  181. package/dist/src/internal/renderer/terminal/KeyboardCapabilityProbe.js.map +1 -0
  182. package/dist/src/internal/renderer/terminal/TerminalSetup.d.ts +29 -0
  183. package/dist/src/internal/renderer/terminal/TerminalSetup.d.ts.map +1 -0
  184. package/dist/src/internal/renderer/terminal/TerminalSetup.js +82 -0
  185. package/dist/src/internal/renderer/terminal/TerminalSetup.js.map +1 -0
  186. package/dist/src/internal/renderer/terminal/index.d.ts +3 -0
  187. package/dist/src/internal/renderer/terminal/index.d.ts.map +1 -0
  188. package/dist/src/internal/renderer/terminal/index.js +3 -0
  189. package/dist/src/internal/renderer/terminal/index.js.map +1 -0
  190. package/dist/src/internal/renderer/types.d.ts +122 -0
  191. package/dist/src/internal/renderer/types.d.ts.map +1 -0
  192. package/dist/src/internal/renderer/types.js +2 -0
  193. package/dist/src/internal/renderer/types.js.map +1 -0
  194. package/dist/src/motion/hooks.d.ts +1 -1
  195. package/dist/src/motion/hooks.js +1 -1
  196. package/dist/src/reconciler/host-config.js +2 -2
  197. package/dist/src/reconciler/host-config.js.map +1 -1
  198. package/dist/src/reconciler/types.d.ts +5 -1
  199. package/dist/src/reconciler/types.d.ts.map +1 -1
  200. package/dist/src/renderer-context.d.ts +1 -8
  201. package/dist/src/renderer-context.d.ts.map +1 -1
  202. package/dist/src/renderer-context.js +1 -21
  203. package/dist/src/renderer-context.js.map +1 -1
  204. package/dist/src/renderer-types.d.ts +1 -115
  205. package/dist/src/renderer-types.d.ts.map +1 -1
  206. package/dist/src/renderer.d.ts +1 -31
  207. package/dist/src/renderer.d.ts.map +1 -1
  208. package/dist/src/renderer.js +1 -495
  209. package/dist/src/renderer.js.map +1 -1
  210. package/dist/src/test/render-tui.d.ts +3 -3
  211. package/dist/src/test/render-tui.d.ts.map +1 -1
  212. package/dist/src/test/render-tui.js +16 -9
  213. package/dist/src/test/render-tui.js.map +1 -1
  214. package/dist/src/utils/alignment.d.ts +1 -1
  215. package/dist/src/utils/alignment.d.ts.map +1 -1
  216. package/dist/src/utils/alignment.js +0 -2
  217. package/dist/src/utils/alignment.js.map +1 -1
  218. package/dist/src/utils/border.d.ts +1 -1
  219. package/dist/src/utils/border.d.ts.map +1 -1
  220. package/dist/src/utils/border.js +2 -0
  221. package/dist/src/utils/border.js.map +1 -1
  222. package/dist/src/utils/console-helpers.d.ts +19 -0
  223. package/dist/src/utils/console-helpers.d.ts.map +1 -0
  224. package/dist/src/utils/console-helpers.js +61 -0
  225. package/dist/src/utils/console-helpers.js.map +1 -0
  226. package/dist/src/utils/index.d.ts +2 -1
  227. package/dist/src/utils/index.d.ts.map +1 -1
  228. package/dist/src/utils/index.js +2 -1
  229. package/dist/src/utils/index.js.map +1 -1
  230. package/dist/src/utils/styles.d.ts +8 -1
  231. package/dist/src/utils/styles.d.ts.map +1 -1
  232. package/dist/src/utils/styles.js +10 -8
  233. package/dist/src/utils/styles.js.map +1 -1
  234. package/dist/src/utils/text-layout.d.ts +22 -0
  235. package/dist/src/utils/text-layout.d.ts.map +1 -0
  236. package/dist/src/utils/text-layout.js +37 -0
  237. package/dist/src/utils/text-layout.js.map +1 -0
  238. package/dist/src/utils/text-wrap.d.ts +31 -1
  239. package/dist/src/utils/text-wrap.d.ts.map +1 -1
  240. package/dist/src/utils/text-wrap.js +205 -48
  241. package/dist/src/utils/text-wrap.js.map +1 -1
  242. package/dist/src/visualize/index.js +1 -1
  243. package/dist/src/visualize/index.js.map +1 -1
  244. package/dist/tsconfig.tsbuildinfo +1 -1
  245. package/package.json +2 -2
  246. package/src/codeblock.tsx +2 -2
  247. package/src/components/ListView.tsx +21 -23
  248. package/src/components/Markdown.tsx +3 -3
  249. package/src/components/MultilineTextInput.tsx +138 -344
  250. package/src/components/TextInput.tsx +54 -99
  251. package/src/components/text-editing.ts +180 -0
  252. package/src/console/ConsolePopover.tsx +124 -107
  253. package/src/debug/DebugOverlay.ts +15 -74
  254. package/src/debug/DiagnosticsPanel.tsx +1 -1
  255. package/src/dev.tsx +5 -458
  256. package/src/hooks/use-scroll.ts +85 -145
  257. package/src/hosts/base.ts +86 -3
  258. package/src/hosts/box.ts +37 -2
  259. package/src/hosts/canvas.ts +128 -42
  260. package/src/hosts/codeblock.ts +48 -35
  261. package/src/hosts/flex-container.ts +25 -6
  262. package/src/hosts/index.ts +11 -2
  263. package/src/hosts/layout-helpers.ts +20 -0
  264. package/src/hosts/leaf.ts +36 -0
  265. package/src/hosts/overlay-item.ts +8 -2
  266. package/src/hosts/overlay.ts +13 -11
  267. package/src/hosts/scroll.ts +228 -106
  268. package/src/hosts/single-child.ts +2 -0
  269. package/src/hosts/spacer.ts +8 -3
  270. package/src/hosts/text.ts +126 -132
  271. package/src/hosts/vstack.ts +1 -1
  272. package/src/hosts/zstack.ts +14 -9
  273. package/src/index.ts +2 -2
  274. package/src/internal/dev/hmr.ts +101 -0
  275. package/src/internal/dev/runtime.ts +170 -0
  276. package/src/internal/dev/ui.tsx +87 -0
  277. package/src/internal/renderer/context.ts +27 -0
  278. package/src/{renderer → internal/renderer}/core/FrameBuilder.ts +2 -2
  279. package/src/internal/renderer/index.ts +689 -0
  280. package/src/{renderer → internal/renderer}/input/InputProcessor.ts +10 -1
  281. package/src/{renderer → internal/renderer}/lifecycle/EventBus.ts +9 -1
  282. package/src/internal/renderer/lifecycle/ProcessLifecycle.ts +125 -0
  283. package/src/internal/renderer/lifecycle/index.ts +3 -0
  284. package/src/{renderer → internal/renderer}/modes/InlineRenderer.ts +5 -2
  285. package/src/{renderer → internal/renderer}/modes/RendererMode.ts +1 -1
  286. package/src/{renderer → internal/renderer}/modes/StaticContentRenderer.ts +5 -2
  287. package/src/internal/renderer/terminal/KeyboardCapabilityProbe.ts +91 -0
  288. package/src/{renderer/lifecycle → internal/renderer/terminal}/TerminalSetup.ts +4 -22
  289. package/src/internal/renderer/terminal/index.ts +2 -0
  290. package/src/internal/renderer/types.ts +129 -0
  291. package/src/motion/hooks.ts +1 -1
  292. package/src/reconciler/host-config.ts +2 -2
  293. package/src/reconciler/types.ts +7 -1
  294. package/src/renderer-context.ts +1 -27
  295. package/src/renderer-types.ts +10 -123
  296. package/src/renderer.ts +1 -619
  297. package/src/test/render-tui.ts +16 -10
  298. package/src/utils/alignment.ts +1 -3
  299. package/src/utils/border.ts +11 -1
  300. package/src/utils/console-helpers.ts +86 -0
  301. package/src/utils/index.ts +15 -1
  302. package/src/utils/styles.ts +16 -4
  303. package/src/utils/text-layout.ts +65 -0
  304. package/src/utils/text-wrap.ts +261 -48
  305. package/src/visualize/index.tsx +1 -1
  306. package/src/renderer/lifecycle/ResizeManager.ts +0 -65
  307. package/src/renderer/lifecycle/index.ts +0 -4
  308. /package/src/{renderer → internal/renderer}/core/RendererState.ts +0 -0
  309. /package/src/{renderer → internal/renderer}/core/index.ts +0 -0
  310. /package/src/{renderer → internal/renderer}/input/index.ts +0 -0
  311. /package/src/{renderer → internal/renderer}/lifecycle/RenderCache.ts +0 -0
  312. /package/src/{renderer → internal/renderer}/modes/FullscreenRenderer.ts +0 -0
  313. /package/src/{renderer → internal/renderer}/modes/index.ts +0 -0
@@ -0,0 +1,689 @@
1
+ import { performance } from "node:perf_hooks"
2
+ import { fileURLToPath } from "node:url"
3
+ import {
4
+ ANSI,
5
+ bufferToString,
6
+ setEmojiWidth,
7
+ type KeyMsg,
8
+ type MouseMsg,
9
+ } from "@effect-tui/core"
10
+ import React, { type ReactNode } from "react"
11
+ import { createTerminalWriter, writeToTerminal } from "../../console/ConsoleCapture.js"
12
+ import { DEFAULT_FPS } from "../../constants.js"
13
+ import { requestExit } from "../../exit.js"
14
+ import * as Prof from "../../profiler.js"
15
+ import { flushSync, reconciler } from "../../reconciler/host-config.js"
16
+ import type { HostContext } from "../../reconciler/types.js"
17
+ // Extracted modules
18
+ import { FrameBuilder, RendererState } from "./core/index.js"
19
+ import { InputProcessor } from "./input/index.js"
20
+ import { EventBus, getRenderCache, registerProcessHandlers } from "./lifecycle/index.js"
21
+ import { createKeyboardCapabilityProbe, TerminalSetup } from "./terminal/index.js"
22
+ import { FullscreenRenderer, InlineRenderer, StaticContentRenderer } from "./modes/index.js"
23
+ import { RendererContext } from "./context.js"
24
+ import { startDevRuntime } from "../dev/runtime.js"
25
+ import type {
26
+ Container,
27
+ FrameStats,
28
+ PasteMsg,
29
+ RendererOptions,
30
+ TuiReadStream,
31
+ TuiRenderer,
32
+ TuiRendererInternal,
33
+ TuiWriteStream,
34
+ } from "./types.js"
35
+
36
+ export { RendererContext, useRenderer, useTerminalSize } from "./context.js"
37
+ // Re-export types and context for backwards compatibility
38
+ export type {
39
+ FrameStats,
40
+ PasteMsg,
41
+ RendererOptions,
42
+ TuiReadStream,
43
+ TuiRenderer,
44
+ TuiWriteStream,
45
+ } from "./types.js"
46
+
47
+ type HandledSignal = "SIGINT" | "SIGTERM"
48
+
49
+ export function createRenderer(options?: RendererOptions): TuiRenderer {
50
+ const fps = options?.fps ?? DEFAULT_FPS
51
+ const exitOnRenderError = options?.exitOnRenderError ?? true
52
+ // Use custom stdout if provided, otherwise use process.stdout with bypassed capture
53
+ let stdout: TuiWriteStream
54
+ if (options?.stdout) {
55
+ // Use custom stdout as-is (e.g., for testing with MockStdout)
56
+ stdout = options.stdout
57
+ } else {
58
+ // Create a proxy that bypasses console capture for writes
59
+ // This ensures Effect.log etc. don't corrupt the TUI
60
+ const terminalWrite = createTerminalWriter()
61
+ stdout = new Proxy(process.stdout as TuiWriteStream, {
62
+ get(target, prop) {
63
+ if (prop === "write") {
64
+ return terminalWrite
65
+ }
66
+ const value = (target as unknown as Record<string | symbol, unknown>)[prop]
67
+ // Bind methods to preserve `this` context (critical for .on(), .removeListener(), etc.)
68
+ if (typeof value === "function") {
69
+ return value.bind(target)
70
+ }
71
+ return value
72
+ },
73
+ })
74
+ }
75
+ const stdin: TuiReadStream = options?.stdin ?? process.stdin
76
+ const mode = options?.mode ?? "fullscreen"
77
+ const exitOnCtrlC = options?.exitOnCtrlC ?? true
78
+ const handleSignals = options?.handleSignals ?? true
79
+ const exitOnSignal = options?.exitOnSignal ?? true
80
+ const signalExitCodes: Record<HandledSignal, number> = {
81
+ SIGINT: 130,
82
+ SIGTERM: 143,
83
+ ...options?.signalExitCodes,
84
+ }
85
+ const manualMode = options?.manualMode ?? false
86
+ const enableDiff = options?.diff ?? !manualMode
87
+ const skipTerminalSetup = options?.skipTerminalSetup ?? false
88
+ const enablePaste = options?.enablePaste ?? true
89
+ const enableMouse = options?.enableMouse ?? mode === "fullscreen"
90
+ const enableKittyKeyboard = options?.enableKittyKeyboard
91
+ const debugHook = options?.debug?.onFrame
92
+ const emojiWidthOption = options?.emojiWidth
93
+ const envEmojiWidth =
94
+ process.env.TUI_EMOJI_WIDTH ?? process.env.EFFECT_TUI_EMOJI_WIDTH ?? process.env.EMOJI_WIDTH
95
+
96
+ const keyboardProbe =
97
+ !skipTerminalSetup && enableKittyKeyboard !== false
98
+ ? createKeyboardCapabilityProbe({
99
+ stdout,
100
+ onResolved: (supportsKitty) => {
101
+ if (supportsKitty) {
102
+ stdout.write(ANSI.modifyOtherKeys.disable)
103
+ stdout.write(ANSI.keyboard.enable(1))
104
+ } else {
105
+ stdout.write(ANSI.modifyOtherKeys.enable)
106
+ }
107
+ },
108
+ })
109
+ : null
110
+
111
+ if (emojiWidthOption === 1 || emojiWidthOption === 2) {
112
+ setEmojiWidth(emojiWidthOption)
113
+ } else if (envEmojiWidth === "1" || envEmojiWidth === "2") {
114
+ setEmojiWidth(envEmojiWidth === "1" ? 1 : 2)
115
+ }
116
+
117
+ // Initialize state
118
+ const state = new RendererState(stdout.columns || 80, stdout.rows || 24)
119
+ const events = new EventBus()
120
+ const frameBuilder = new FrameBuilder()
121
+
122
+ // Terminal setup/teardown
123
+ const terminal = new TerminalSetup(stdout, stdin, {
124
+ mode,
125
+ enablePaste,
126
+ enableMouse,
127
+ skipTerminalSetup,
128
+ })
129
+
130
+ // Render mode (fullscreen or inline)
131
+ const renderMode = mode === "fullscreen" ? new FullscreenRenderer() : new InlineRenderer()
132
+
133
+ // Static content renderer (inline mode only)
134
+ const staticRenderer = mode === "inline" ? new StaticContentRenderer(stdout, state.palette) : null
135
+
136
+ let renderer!: TuiRenderer
137
+ let cleanedUp = false
138
+ const cleanup = () => {
139
+ if (cleanedUp) return
140
+ cleanedUp = true
141
+ renderer.stop()
142
+ }
143
+
144
+ const handleRenderError = (err: unknown) => {
145
+ const error = err instanceof Error ? err : new Error(String(err))
146
+ cleanup()
147
+ writeToTerminal(`\n[effect-tui] Render error:\n${error.stack || error.message}\n`)
148
+ if (exitOnRenderError) process.exit(1)
149
+ }
150
+
151
+ const filterInput = keyboardProbe
152
+ ? (input: string) => {
153
+ let output = input
154
+ if (keyboardProbe) output = keyboardProbe.handleInput(output)
155
+ return output
156
+ }
157
+ : undefined
158
+
159
+ // Input processing
160
+ const inputProcessor = new InputProcessor({
161
+ exitOnCtrlC,
162
+ dispatchKey: (key) => {
163
+ events.dispatchKey(key)
164
+ return key.defaultPrevented ?? false
165
+ },
166
+ dispatchMouse: (mouse) => events.dispatchMouse(mouse),
167
+ dispatchPaste: (text) => events.dispatchPaste(text),
168
+ flushSync: (fn) => flushSync(fn) ?? (undefined as never),
169
+ onInputProcessed: () => {
170
+ if (!manualMode) renderFrame()
171
+ },
172
+ onQuit: () => {
173
+ // Clean up terminal state before exiting
174
+ renderer.stop()
175
+ requestExit(0)
176
+ },
177
+ filterInput,
178
+ })
179
+
180
+ const handleInlineFullRerender = (): string => {
181
+ if (mode !== "inline" || !staticRenderer) return ""
182
+ const inlineMode = renderMode as InlineRenderer
183
+ if (!inlineMode.needsFullRerender()) return ""
184
+
185
+ let output = ""
186
+
187
+ // Clear screen + scrollback + cursor home
188
+ output += ANSI.screen.clear + ANSI.screen.clearScrollback + ANSI.cursor.home
189
+
190
+ // Replay all cached static content
191
+ const cachedStatic = staticRenderer.getCachedOutput()
192
+ if (cachedStatic) {
193
+ output += cachedStatic
194
+ }
195
+
196
+ // Reset state
197
+ inlineMode.clearFullRerenderFlag()
198
+ state.invalidateBuffers()
199
+
200
+ return output
201
+ }
202
+
203
+ const flushInlineStatic = (container: Container | null, frameWidth: number) => {
204
+ if (mode !== "inline" || !container?.staticDirty || !container.staticRoot || !staticRenderer) {
205
+ return ""
206
+ }
207
+
208
+ const inlineMode = renderMode as InlineRenderer
209
+ const prevHeight = inlineMode.getPreviousHeight()
210
+ let output = ""
211
+
212
+ // Step 1: Clear the dynamic area (move up + clear to end of screen)
213
+ if (prevHeight > 0) {
214
+ output += ANSI.cursor.up(prevHeight) + ANSI.cursor.startOfLine + ANSI.screen.clearToEnd
215
+ }
216
+
217
+ // Step 2: Append static content (cursor ends at bottom of static)
218
+ output += staticRenderer.render(container.staticRoot, frameWidth)
219
+
220
+ // Step 3: Reset previousHeight to 0 (we cleared dynamic, starting fresh)
221
+ inlineMode.reset()
222
+ inlineMode.forceFullOutputOnce() // Force full output to resync cursor tracking after static
223
+ state.invalidateBuffers()
224
+ container.staticDirty = false
225
+
226
+ return output
227
+ }
228
+
229
+ // The render frame logic
230
+ const renderFrame = () => {
231
+ const frameStart = Prof.startFrame()
232
+ const frameStartMs = performance.now()
233
+ // Flush any pending React updates before measuring/layout.
234
+ // Ensures resize-driven state (useTerminalSize) is reflected in the host tree.
235
+ flushSync(() => {})
236
+ const frameWidth = state.width
237
+ const frameHeight = state.height
238
+ let contentH = frameHeight
239
+
240
+ const container = (renderer as TuiRendererInternal)._container
241
+ const root = container?.root ?? null
242
+
243
+ // Must render if dirty OR if static content needs flushing
244
+ if ((!state.dirty && !container?.staticDirty) || !root) return
245
+ state.dirty = false
246
+
247
+ try {
248
+ // Handle full rerender on resize (Ink-style: clear everything + replay static)
249
+ const fullRerenderOutput = handleInlineFullRerender()
250
+
251
+ // Handle static content: clear dynamic area, append static, then fresh dynamic render
252
+ // Note: IL (insert lines) won't work here because inline mode uses relative positioning
253
+ // and IL would desync the screen state from our buffer tracking.
254
+ const staticOutput = flushInlineStatic(container ?? null, frameWidth)
255
+
256
+ // For inline mode, measure content unconstrained to handle overflow
257
+ let actualContentHeight = frameHeight
258
+ if (mode === "inline") {
259
+ const size = root.measure(frameWidth, Number.MAX_SAFE_INTEGER)
260
+ actualContentHeight = size.h
261
+ }
262
+
263
+ // Buffer height: content height for inline (to capture all content), terminal height for fullscreen
264
+ const bufferHeight = mode === "inline" ? Math.max(actualContentHeight, frameHeight) : frameHeight
265
+
266
+ // Ensure buffers exist
267
+ state.ensureBuffers(frameWidth, bufferHeight)
268
+ if (!state.nextBuffer) return
269
+
270
+ // Build frame (clear, layout, render)
271
+ const timings = frameBuilder.build(root, state.nextBuffer, state.palette, frameWidth, bufferHeight)
272
+
273
+ // Generate output
274
+ const t = Prof.startPhase()
275
+ const diffStartMs = performance.now()
276
+
277
+ const { output: modeOutput, contentHeight } = renderMode.generateOutput({
278
+ nextBuffer: state.nextBuffer,
279
+ prevBuffer: state.prevBuffer,
280
+ palette: state.palette,
281
+ frameWidth,
282
+ frameHeight,
283
+ contentHeight: actualContentHeight,
284
+ enableDiff,
285
+ stdout,
286
+ })
287
+
288
+ // Combine all output for single atomic write
289
+ // fullRerenderOutput (clear + replay static) goes BEFORE sync block
290
+ // Sync block wraps only the dynamic content to prevent tearing
291
+ const output =
292
+ fullRerenderOutput +
293
+ ANSI.sync.begin +
294
+ staticOutput +
295
+ modeOutput +
296
+ state.palette.sgr(0) +
297
+ ANSI.sync.end
298
+ contentH = contentHeight
299
+ const diffAnsiMs = performance.now() - diffStartMs
300
+ Prof.endPhase("diff+ansi", t)
301
+
302
+ // Write output (single atomic write prevents visual glitches)
303
+ const writeT = Prof.startPhase()
304
+ const writeStart = performance.now()
305
+ stdout.write(output)
306
+ const writeMs = performance.now() - writeStart
307
+ Prof.endPhase("write", writeT)
308
+
309
+ Prof.endFrame(frameStart)
310
+ const frameMs = performance.now() - frameStartMs
311
+
312
+ // Swap buffers
313
+ state.swapBuffers()
314
+
315
+ // Build stats
316
+ const stats: FrameStats = {
317
+ mode,
318
+ width: state.width,
319
+ height: state.height,
320
+ contentHeight: contentH,
321
+ bytes: Buffer.byteLength(output, "utf8"),
322
+ frameMs,
323
+ phases: {
324
+ clear: timings.clear,
325
+ layout: timings.layout,
326
+ render: timings.render,
327
+ diffMs: diffAnsiMs,
328
+ write: writeMs,
329
+ },
330
+ timestamp: performance.now(),
331
+ }
332
+
333
+ if (debugHook) debugHook(stats)
334
+ if (events.hasFrameHandlers) events.dispatchFrame(stats)
335
+ } catch (err) {
336
+ handleRenderError(err)
337
+ }
338
+ }
339
+
340
+ const applyResize = (width: number, height: number, renderNow: boolean) => {
341
+ const prevWidth = state.lastWidth
342
+ renderMode.handleResize(width, height, prevWidth)
343
+ state.updateDimensions(width, height)
344
+ state.invalidateBuffers()
345
+ state.markDirty()
346
+ flushSync(() => {
347
+ events.dispatchResize(width, height)
348
+ })
349
+ if (renderNow) renderFrame()
350
+ }
351
+
352
+ let onExit: (() => void) | null = null
353
+ let onSignal: ((signal: NodeJS.Signals) => void) | null = null
354
+ let onUncaughtException: ((err: Error) => void) | null = null
355
+ let onUnhandledRejection: ((reason: unknown) => void) | null = null
356
+ let unregisterProcessHandlers: (() => void) | null = null
357
+
358
+ // Build renderer object
359
+ renderer = {
360
+ get width() {
361
+ return state.width
362
+ },
363
+ get height() {
364
+ return state.height
365
+ },
366
+ requestRender() {
367
+ state.markDirty()
368
+ },
369
+ onKey: (handler: (key: KeyMsg) => void) => events.onKey(handler),
370
+ onMouse: (handler: (mouse: MouseMsg) => void) => events.onMouse(handler),
371
+ onPaste: (handler: (paste: PasteMsg) => void) => events.onPaste(handler),
372
+ onResize: (handler: (width: number, height: number) => void) => events.onResize(handler),
373
+ onFrameStats: (handler: (stats: FrameStats) => void) => events.onFrameStats(handler),
374
+ stop() {
375
+ state.running = false
376
+ if (unregisterProcessHandlers) {
377
+ unregisterProcessHandlers()
378
+ unregisterProcessHandlers = null
379
+ onExit = null
380
+ onSignal = null
381
+ onUncaughtException = null
382
+ onUnhandledRejection = null
383
+ }
384
+ if (state.loop) {
385
+ clearInterval(state.loop)
386
+ state.loop = null
387
+ }
388
+ if (state.inputHandler) {
389
+ stdin.removeListener("data", state.inputHandler)
390
+ state.inputHandler = null
391
+ }
392
+ if (state.resizeHandler) {
393
+ stdout.removeListener("resize", state.resizeHandler)
394
+ state.resizeHandler = null
395
+ }
396
+ events.clear()
397
+ terminal.teardown()
398
+ },
399
+ renderNow() {
400
+ renderFrame()
401
+ },
402
+ getScreenshot() {
403
+ // Return the previous buffer as ANSI string (it has the last rendered frame)
404
+ if (state.prevBuffer) {
405
+ return bufferToString(state.prevBuffer, state.palette, state.width, state.height)
406
+ }
407
+ return ""
408
+ },
409
+ dispatchKey(key: KeyMsg) {
410
+ events.dispatchKey(key)
411
+ if (!manualMode) renderFrame()
412
+ },
413
+ dispatchPaste(text: string) {
414
+ events.dispatchPaste(text)
415
+ if (!manualMode) renderFrame()
416
+ },
417
+ dispatchResize(width: number, height: number) {
418
+ applyResize(width, height, !manualMode)
419
+ },
420
+ }
421
+
422
+ // Terminal setup
423
+ terminal.setup()
424
+
425
+ // Input handling
426
+ state.inputHandler = (data: Buffer) => inputProcessor.process(data)
427
+ stdin.on("data", state.inputHandler)
428
+
429
+ keyboardProbe?.start()
430
+ // No emoji width probing.
431
+ if (!keyboardProbe && !skipTerminalSetup) {
432
+ stdout.write(ANSI.modifyOtherKeys.enable)
433
+ }
434
+
435
+ // Resize handling
436
+ state.resizeHandler = () => {
437
+ const newWidth = stdout.columns || 80
438
+ const newHeight = stdout.rows || 24
439
+ applyResize(newWidth, newHeight, false)
440
+ }
441
+ stdout.on("resize", state.resizeHandler)
442
+
443
+ // Render loop
444
+ if (!manualMode) {
445
+ const frameMs = 1000 / fps
446
+ state.loop = setInterval(() => {
447
+ if (!state.running) {
448
+ if (state.loop) clearInterval(state.loop)
449
+ terminal.teardown()
450
+ return
451
+ }
452
+ renderFrame()
453
+ }, frameMs)
454
+ }
455
+
456
+ // Process exit handlers - ensure terminal is restored on any exit
457
+ // These handlers are critical for proper cleanup when process.exit() is called
458
+ if (handleSignals) {
459
+ // Handle normal process exit (synchronous - runs before exit completes)
460
+ onExit = () => cleanup()
461
+
462
+ // Handle SIGINT (Ctrl+C from shell, not from TUI input) and SIGTERM
463
+ onSignal = (signal: NodeJS.Signals) => {
464
+ cleanup()
465
+ if (!exitOnSignal) return
466
+ const code = signalExitCodes[signal as HandledSignal] ?? 0
467
+ requestExit(code)
468
+ }
469
+
470
+ // Handle uncaught exceptions - ensure error is visible before exit
471
+ onUncaughtException = (err: Error) => {
472
+ cleanup()
473
+ // Write directly to terminal, bypassing console capture
474
+ writeToTerminal(`\n[effect-tui] Uncaught exception:\n${err.stack || err.message}\n`)
475
+ process.exit(1)
476
+ }
477
+
478
+ // Handle unhandled promise rejections
479
+ onUnhandledRejection = (reason: unknown) => {
480
+ cleanup()
481
+ const message = reason instanceof Error ? reason.stack || reason.message : String(reason)
482
+ writeToTerminal(`\n[effect-tui] Unhandled rejection:\n${message}\n`)
483
+ process.exit(1)
484
+ }
485
+
486
+ unregisterProcessHandlers = registerProcessHandlers({
487
+ onExit,
488
+ onSignal,
489
+ onUncaughtException,
490
+ onUnhandledRejection,
491
+ })
492
+ }
493
+
494
+ ;(renderer as TuiRendererInternal)._container = null
495
+ return renderer
496
+ }
497
+
498
+ export interface Root {
499
+ render(element: ReactNode, sync?: boolean): void
500
+ unmount(): void
501
+ }
502
+
503
+ export function createRoot(renderer: TuiRenderer): Root {
504
+ const hostContext: HostContext = {
505
+ requestRender: () => renderer.requestRender(),
506
+ requestRenderNow: () => renderer.renderNow(),
507
+ }
508
+
509
+ const container: Container = {
510
+ root: null,
511
+ ctx: hostContext,
512
+ }
513
+
514
+ const fiberRoot = reconciler.createContainer(
515
+ container,
516
+ 0,
517
+ null,
518
+ false,
519
+ null,
520
+ "",
521
+ (err: Error) => console.error(err),
522
+ (err: Error) => console.error(err),
523
+ (err: Error) => console.error(err),
524
+ () => {},
525
+ null,
526
+ )
527
+
528
+ ;(renderer as TuiRendererInternal)._container = container
529
+
530
+ return {
531
+ render(element: ReactNode, sync = false) {
532
+ const wrapped = React.createElement(RendererContext.Provider, { value: renderer }, element)
533
+ if (sync) {
534
+ flushSync(() => {
535
+ reconciler.updateContainer(wrapped, fiberRoot, null, null)
536
+ })
537
+ renderer.requestRender()
538
+ } else {
539
+ reconciler.updateContainer(wrapped, fiberRoot, null, () => {
540
+ renderer.requestRender()
541
+ })
542
+ }
543
+ },
544
+ unmount() {
545
+ reconciler.updateContainer(null, fiberRoot, null, () => {
546
+ renderer.stop()
547
+ })
548
+ },
549
+ }
550
+ }
551
+
552
+ // High-level convenience API (Ink-style)
553
+ export interface RenderInstance {
554
+ renderer: TuiRenderer
555
+ root: Root
556
+ rerender(element: ReactNode): void
557
+ unmount(): void
558
+ waitUntilExit(): Promise<void>
559
+ /** Cleanly exit the application, restoring terminal state before process.exit() */
560
+ quit(code?: number): void
561
+ }
562
+
563
+ export type ImportMetaLike = {
564
+ url: string
565
+ main?: boolean
566
+ }
567
+
568
+ type RenderBaseOptions = {
569
+ /** Render mode: fullscreen (default) or inline. */
570
+ mode?: RendererOptions["mode"]
571
+ }
572
+
573
+ type RenderDevOptions = RenderBaseOptions & {
574
+ /** Enable dev runtime (HMR, console overlay, remote control). */
575
+ dev: true
576
+ /** Required in dev mode. */
577
+ importMeta: ImportMetaLike
578
+ }
579
+
580
+ type RenderRuntimeOptions = RenderBaseOptions & {
581
+ /** Dev mode disabled (default). */
582
+ dev?: false | undefined
583
+ }
584
+
585
+ export type RenderOptions = RenderDevOptions | RenderRuntimeOptions
586
+
587
+ const stripQuery = (url: string): string => url.split("?")[0]
588
+
589
+ const createRenderInstance = (
590
+ renderer: TuiRenderer,
591
+ root: Root,
592
+ element: ReactNode,
593
+ stop: () => void,
594
+ skipInitialRender = false,
595
+ ): RenderInstance => {
596
+ if (!skipInitialRender) {
597
+ root.render(element, true)
598
+ }
599
+
600
+ let resolved = false
601
+ let resolveExit: (() => void) | null = null
602
+ let unregisterExitHandler: (() => void) | null = null
603
+ const exitPromise = new Promise<void>((resolve) => {
604
+ resolveExit = () => {
605
+ if (resolved) return
606
+ resolved = true
607
+ resolve()
608
+ }
609
+ })
610
+
611
+ // Resolve the exit promise on process exit
612
+ // Note: Terminal cleanup is handled by createRenderer's exit handlers
613
+ const onExit = () => {
614
+ resolveExit?.()
615
+ }
616
+ unregisterExitHandler = registerProcessHandlers({ onExit })
617
+
618
+ const unmount = () => {
619
+ if (unregisterExitHandler) {
620
+ unregisterExitHandler()
621
+ unregisterExitHandler = null
622
+ }
623
+ if (!resolved) {
624
+ resolved = true
625
+ stop()
626
+ resolveExit?.()
627
+ }
628
+ }
629
+
630
+ const rerender = (next: ReactNode) => {
631
+ if (resolved) return
632
+ root.render(next)
633
+ }
634
+
635
+ // Clean quit function - stop() + process.exit()
636
+ // The createRenderer exit handler will also run, but it's idempotent
637
+ const quit = (code = 0) => {
638
+ stop()
639
+ requestExit(code)
640
+ }
641
+
642
+ return {
643
+ renderer,
644
+ root,
645
+ rerender,
646
+ unmount,
647
+ waitUntilExit: () => exitPromise,
648
+ quit,
649
+ }
650
+ }
651
+
652
+ export function render(element: ReactNode, options?: RenderOptions): RenderInstance {
653
+ if (options?.dev && !options.importMeta) {
654
+ throw new Error("[effect-tui] render(..., { dev: true, importMeta }) is required in dev mode")
655
+ }
656
+
657
+ if (!options?.dev) {
658
+ const renderer = createRenderer({ mode: options?.mode })
659
+ const root = createRoot(renderer)
660
+ return createRenderInstance(renderer, root, element, () => renderer.stop())
661
+ }
662
+
663
+ const importMeta = options.importMeta
664
+
665
+ const baseUrl = stripQuery(importMeta.url)
666
+ const cacheKey = `${baseUrl}::${options.mode ?? "fullscreen"}`
667
+ const renderCache = getRenderCache<RenderInstance>()
668
+ const cached = renderCache.get(cacheKey)
669
+ if (cached) return cached
670
+
671
+ const renderer = createRenderer({ mode: options.mode })
672
+ const root = createRoot(renderer)
673
+ const entryPath = fileURLToPath(new URL(baseUrl))
674
+
675
+ const devRuntime = startDevRuntime(entryPath, renderer, root, { mode: options.mode })
676
+
677
+ const instance = createRenderInstance(
678
+ renderer,
679
+ root,
680
+ element,
681
+ () => {
682
+ void devRuntime.stop()
683
+ },
684
+ true,
685
+ )
686
+
687
+ renderCache.set(cacheKey, instance)
688
+ return instance
689
+ }
@@ -1,5 +1,5 @@
1
1
  import { ANSI, decodeInput, type KeyMsg, type MouseMsg } from "@effect-tui/core"
2
- import { requestExit } from "../../exit.js"
2
+ import { requestExit } from "../../../exit.js"
3
3
 
4
4
  export interface InputProcessorConfig {
5
5
  exitOnCtrlC: boolean
@@ -9,6 +9,8 @@ export interface InputProcessorConfig {
9
9
  flushSync: <T>(fn: () => T) => T
10
10
  onInputProcessed: () => void
11
11
  onQuit?: () => void // Called instead of process.exit() for proper cleanup
12
+ /** Optional filter to strip capability responses from raw input */
13
+ filterInput?: (input: string) => string
12
14
  }
13
15
 
14
16
  /**
@@ -28,6 +30,13 @@ export class InputProcessor {
28
30
  process(data: Buffer): void {
29
31
  let chunk = this.pendingInput + data.toString("utf8")
30
32
  this.pendingInput = ""
33
+ if (this.config.filterInput) {
34
+ chunk = this.config.filterInput(chunk)
35
+ }
36
+ if (chunk.length === 0) {
37
+ this.config.onInputProcessed()
38
+ return
39
+ }
31
40
 
32
41
  while (chunk.length > 0) {
33
42
  if (this.pasteActive) {