@textui/widgets 0.1.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 (423) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +69 -0
  3. package/dist/chart/area-chart.d.ts +4 -0
  4. package/dist/chart/area-chart.d.ts.map +1 -0
  5. package/dist/chart/area-chart.js +3 -0
  6. package/dist/chart/bar-chart.d.ts +32 -0
  7. package/dist/chart/bar-chart.d.ts.map +1 -0
  8. package/dist/chart/bar-chart.js +32 -0
  9. package/dist/chart/gauge.d.ts +26 -0
  10. package/dist/chart/gauge.d.ts.map +1 -0
  11. package/dist/chart/gauge.js +26 -0
  12. package/dist/chart/heatmap.d.ts +13 -0
  13. package/dist/chart/heatmap.d.ts.map +1 -0
  14. package/dist/chart/heatmap.js +27 -0
  15. package/dist/chart/histogram.d.ts +12 -0
  16. package/dist/chart/histogram.d.ts.map +1 -0
  17. package/dist/chart/histogram.js +22 -0
  18. package/dist/chart/index.d.ts +20 -0
  19. package/dist/chart/index.d.ts.map +1 -0
  20. package/dist/chart/index.js +32 -0
  21. package/dist/chart/line-chart.d.ts +7 -0
  22. package/dist/chart/line-chart.d.ts.map +1 -0
  23. package/dist/chart/line-chart.js +75 -0
  24. package/dist/chart/shared.d.ts +34 -0
  25. package/dist/chart/shared.d.ts.map +1 -0
  26. package/dist/chart/shared.js +78 -0
  27. package/dist/chart/sparkline.d.ts +15 -0
  28. package/dist/chart/sparkline.d.ts.map +1 -0
  29. package/dist/chart/sparkline.js +22 -0
  30. package/dist/control/button.d.ts +27 -0
  31. package/dist/control/button.d.ts.map +1 -0
  32. package/dist/control/button.js +130 -0
  33. package/dist/control/checkbox.d.ts +10 -0
  34. package/dist/control/checkbox.d.ts.map +1 -0
  35. package/dist/control/checkbox.js +31 -0
  36. package/dist/control/index.d.ts +20 -0
  37. package/dist/control/index.d.ts.map +1 -0
  38. package/dist/control/index.js +36 -0
  39. package/dist/control/radio-group.d.ts +17 -0
  40. package/dist/control/radio-group.d.ts.map +1 -0
  41. package/dist/control/radio-group.js +32 -0
  42. package/dist/control/search-box.d.ts +7 -0
  43. package/dist/control/search-box.d.ts.map +1 -0
  44. package/dist/control/search-box.js +3 -0
  45. package/dist/control/select.d.ts +42 -0
  46. package/dist/control/select.d.ts.map +1 -0
  47. package/dist/control/select.js +166 -0
  48. package/dist/control/shared.d.ts +41 -0
  49. package/dist/control/shared.d.ts.map +1 -0
  50. package/dist/control/shared.js +1 -0
  51. package/dist/control/slider.d.ts +14 -0
  52. package/dist/control/slider.d.ts.map +1 -0
  53. package/dist/control/slider.js +45 -0
  54. package/dist/control/switch.d.ts +10 -0
  55. package/dist/control/switch.d.ts.map +1 -0
  56. package/dist/control/switch.js +32 -0
  57. package/dist/control/text-area.d.ts +87 -0
  58. package/dist/control/text-area.d.ts.map +1 -0
  59. package/dist/control/text-area.js +299 -0
  60. package/dist/control/text-input.d.ts +10 -0
  61. package/dist/control/text-input.d.ts.map +1 -0
  62. package/dist/control/text-input.js +129 -0
  63. package/dist/data/code-viewer.d.ts +71 -0
  64. package/dist/data/code-viewer.d.ts.map +1 -0
  65. package/dist/data/code-viewer.js +232 -0
  66. package/dist/data/feed.d.ts +43 -0
  67. package/dist/data/feed.d.ts.map +1 -0
  68. package/dist/data/feed.js +178 -0
  69. package/dist/data/index.d.ts +54 -0
  70. package/dist/data/index.d.ts.map +1 -0
  71. package/dist/data/index.js +187 -0
  72. package/dist/data/list.d.ts +31 -0
  73. package/dist/data/list.d.ts.map +1 -0
  74. package/dist/data/list.js +104 -0
  75. package/dist/data/log-viewer.d.ts +26 -0
  76. package/dist/data/log-viewer.d.ts.map +1 -0
  77. package/dist/data/log-viewer.js +69 -0
  78. package/dist/data/markdown-view.d.ts +31 -0
  79. package/dist/data/markdown-view.d.ts.map +1 -0
  80. package/dist/data/markdown-view.js +139 -0
  81. package/dist/data/pagination.d.ts +9 -0
  82. package/dist/data/pagination.d.ts.map +1 -0
  83. package/dist/data/pagination.js +18 -0
  84. package/dist/data/scroll-thumb.d.ts +15 -0
  85. package/dist/data/scroll-thumb.d.ts.map +1 -0
  86. package/dist/data/scroll-thumb.js +32 -0
  87. package/dist/data/shared.d.ts +12 -0
  88. package/dist/data/shared.d.ts.map +1 -0
  89. package/dist/data/shared.js +17 -0
  90. package/dist/data/tree.d.ts +35 -0
  91. package/dist/data/tree.d.ts.map +1 -0
  92. package/dist/data/tree.js +158 -0
  93. package/dist/decorations.d.ts +88 -0
  94. package/dist/decorations.d.ts.map +1 -0
  95. package/dist/decorations.js +135 -0
  96. package/dist/display/alert.d.ts +8 -0
  97. package/dist/display/alert.d.ts.map +1 -0
  98. package/dist/display/alert.js +12 -0
  99. package/dist/display/badge.d.ts +10 -0
  100. package/dist/display/badge.d.ts.map +1 -0
  101. package/dist/display/badge.js +21 -0
  102. package/dist/display/card.d.ts +8 -0
  103. package/dist/display/card.d.ts.map +1 -0
  104. package/dist/display/card.js +12 -0
  105. package/dist/display/empty-state.d.ts +10 -0
  106. package/dist/display/empty-state.d.ts.map +1 -0
  107. package/dist/display/empty-state.js +5 -0
  108. package/dist/display/error-state.d.ts +9 -0
  109. package/dist/display/error-state.d.ts.map +1 -0
  110. package/dist/display/error-state.js +7 -0
  111. package/dist/display/heading.d.ts +6 -0
  112. package/dist/display/heading.d.ts.map +1 -0
  113. package/dist/display/heading.js +8 -0
  114. package/dist/display/index.d.ts +25 -0
  115. package/dist/display/index.d.ts.map +1 -0
  116. package/dist/display/index.js +52 -0
  117. package/dist/display/key-value.d.ts +14 -0
  118. package/dist/display/key-value.d.ts.map +1 -0
  119. package/dist/display/key-value.js +11 -0
  120. package/dist/display/label.d.ts +6 -0
  121. package/dist/display/label.d.ts.map +1 -0
  122. package/dist/display/label.js +3 -0
  123. package/dist/display/marquee.d.ts +36 -0
  124. package/dist/display/marquee.d.ts.map +1 -0
  125. package/dist/display/marquee.js +55 -0
  126. package/dist/display/progress.d.ts +39 -0
  127. package/dist/display/progress.d.ts.map +1 -0
  128. package/dist/display/progress.js +46 -0
  129. package/dist/display/skeleton.d.ts +20 -0
  130. package/dist/display/skeleton.d.ts.map +1 -0
  131. package/dist/display/skeleton.js +20 -0
  132. package/dist/display/spinner.d.ts +7 -0
  133. package/dist/display/spinner.d.ts.map +1 -0
  134. package/dist/display/spinner.js +9 -0
  135. package/dist/display/status-dot.d.ts +8 -0
  136. package/dist/display/status-dot.d.ts.map +1 -0
  137. package/dist/display/status-dot.js +13 -0
  138. package/dist/display/timeline.d.ts +12 -0
  139. package/dist/display/timeline.d.ts.map +1 -0
  140. package/dist/display/timeline.js +22 -0
  141. package/dist/find.d.ts +74 -0
  142. package/dist/find.d.ts.map +1 -0
  143. package/dist/find.js +102 -0
  144. package/dist/form/danger-zone.d.ts +11 -0
  145. package/dist/form/danger-zone.d.ts.map +1 -0
  146. package/dist/form/danger-zone.js +15 -0
  147. package/dist/form/field.d.ts +19 -0
  148. package/dist/form/field.d.ts.map +1 -0
  149. package/dist/form/field.js +35 -0
  150. package/dist/form/form-actions.d.ts +11 -0
  151. package/dist/form/form-actions.d.ts.map +1 -0
  152. package/dist/form/form-actions.js +16 -0
  153. package/dist/form/form-section.d.ts +7 -0
  154. package/dist/form/form-section.d.ts.map +1 -0
  155. package/dist/form/form-section.js +5 -0
  156. package/dist/form/form.d.ts +7 -0
  157. package/dist/form/form.d.ts.map +1 -0
  158. package/dist/form/form.js +3 -0
  159. package/dist/form/index.d.ts +42 -0
  160. package/dist/form/index.d.ts.map +1 -0
  161. package/dist/form/index.js +140 -0
  162. package/dist/form/shared.d.ts +25 -0
  163. package/dist/form/shared.d.ts.map +1 -0
  164. package/dist/form/shared.js +9 -0
  165. package/dist/index.d.ts +26 -0
  166. package/dist/index.d.ts.map +1 -0
  167. package/dist/index.js +62 -0
  168. package/dist/layout/center.d.ts +7 -0
  169. package/dist/layout/center.d.ts.map +1 -0
  170. package/dist/layout/center.js +8 -0
  171. package/dist/layout/column.d.ts +3 -0
  172. package/dist/layout/column.d.ts.map +1 -0
  173. package/dist/layout/column.js +2 -0
  174. package/dist/layout/divider.d.ts +11 -0
  175. package/dist/layout/divider.d.ts.map +1 -0
  176. package/dist/layout/divider.js +13 -0
  177. package/dist/layout/grid.d.ts +10 -0
  178. package/dist/layout/grid.d.ts.map +1 -0
  179. package/dist/layout/grid.js +22 -0
  180. package/dist/layout/index.d.ts +19 -0
  181. package/dist/layout/index.d.ts.map +1 -0
  182. package/dist/layout/index.js +36 -0
  183. package/dist/layout/panel.d.ts +37 -0
  184. package/dist/layout/panel.d.ts.map +1 -0
  185. package/dist/layout/panel.js +71 -0
  186. package/dist/layout/row.d.ts +7 -0
  187. package/dist/layout/row.d.ts.map +1 -0
  188. package/dist/layout/row.js +2 -0
  189. package/dist/layout/scroll-view.d.ts +27 -0
  190. package/dist/layout/scroll-view.d.ts.map +1 -0
  191. package/dist/layout/scroll-view.js +93 -0
  192. package/dist/layout/splitter.d.ts +10 -0
  193. package/dist/layout/splitter.d.ts.map +1 -0
  194. package/dist/layout/splitter.js +9 -0
  195. package/dist/layout/stack.d.ts +7 -0
  196. package/dist/layout/stack.d.ts.map +1 -0
  197. package/dist/layout/stack.js +5 -0
  198. package/dist/navigation/breadcrumb.d.ts +14 -0
  199. package/dist/navigation/breadcrumb.d.ts.map +1 -0
  200. package/dist/navigation/breadcrumb.js +24 -0
  201. package/dist/navigation/index.d.ts +17 -0
  202. package/dist/navigation/index.d.ts.map +1 -0
  203. package/dist/navigation/index.js +30 -0
  204. package/dist/navigation/key-hints.d.ts +11 -0
  205. package/dist/navigation/key-hints.d.ts.map +1 -0
  206. package/dist/navigation/key-hints.js +13 -0
  207. package/dist/navigation/menu.d.ts +35 -0
  208. package/dist/navigation/menu.d.ts.map +1 -0
  209. package/dist/navigation/menu.js +88 -0
  210. package/dist/navigation/status-bar.d.ts +18 -0
  211. package/dist/navigation/status-bar.d.ts.map +1 -0
  212. package/dist/navigation/status-bar.js +12 -0
  213. package/dist/navigation/tabs.d.ts +20 -0
  214. package/dist/navigation/tabs.d.ts.map +1 -0
  215. package/dist/navigation/tabs.js +41 -0
  216. package/dist/navigation/toolbar.d.ts +14 -0
  217. package/dist/navigation/toolbar.d.ts.map +1 -0
  218. package/dist/navigation/toolbar.js +10 -0
  219. package/dist/navigation/wizard.d.ts +15 -0
  220. package/dist/navigation/wizard.d.ts.map +1 -0
  221. package/dist/navigation/wizard.js +17 -0
  222. package/dist/overlay/command-palette.d.ts +63 -0
  223. package/dist/overlay/command-palette.d.ts.map +1 -0
  224. package/dist/overlay/command-palette.js +415 -0
  225. package/dist/overlay/dialog.d.ts +16 -0
  226. package/dist/overlay/dialog.d.ts.map +1 -0
  227. package/dist/overlay/dialog.js +44 -0
  228. package/dist/overlay/index.d.ts +84 -0
  229. package/dist/overlay/index.d.ts.map +1 -0
  230. package/dist/overlay/index.js +157 -0
  231. package/dist/overlay/layer-scope.d.ts +21 -0
  232. package/dist/overlay/layer-scope.d.ts.map +1 -0
  233. package/dist/overlay/layer-scope.js +5 -0
  234. package/dist/overlay/path-picker.d.ts +14 -0
  235. package/dist/overlay/path-picker.d.ts.map +1 -0
  236. package/dist/overlay/path-picker.js +200 -0
  237. package/dist/overlay/prompt-dialog.d.ts +12 -0
  238. package/dist/overlay/prompt-dialog.d.ts.map +1 -0
  239. package/dist/overlay/prompt-dialog.js +29 -0
  240. package/dist/overlay/shared.d.ts +17 -0
  241. package/dist/overlay/shared.d.ts.map +1 -0
  242. package/dist/overlay/shared.js +17 -0
  243. package/dist/overlay/toast-host.d.ts +8 -0
  244. package/dist/overlay/toast-host.d.ts.map +1 -0
  245. package/dist/overlay/toast-host.js +19 -0
  246. package/dist/overlay/toast.d.ts +9 -0
  247. package/dist/overlay/toast.d.ts.map +1 -0
  248. package/dist/overlay/toast.js +20 -0
  249. package/dist/overlay/tooltip.d.ts +6 -0
  250. package/dist/overlay/tooltip.d.ts.map +1 -0
  251. package/dist/overlay/tooltip.js +11 -0
  252. package/dist/panel/index.d.ts +79 -0
  253. package/dist/panel/index.d.ts.map +1 -0
  254. package/dist/panel/index.js +256 -0
  255. package/dist/panel/resource-panel.d.ts +93 -0
  256. package/dist/panel/resource-panel.d.ts.map +1 -0
  257. package/dist/panel/resource-panel.js +213 -0
  258. package/dist/panel/shared.d.ts +6 -0
  259. package/dist/panel/shared.d.ts.map +1 -0
  260. package/dist/panel/shared.js +4 -0
  261. package/dist/shells/console-shell.d.ts +8 -0
  262. package/dist/shells/console-shell.d.ts.map +1 -0
  263. package/dist/shells/console-shell.js +30 -0
  264. package/dist/shells/index.d.ts +20 -0
  265. package/dist/shells/index.d.ts.map +1 -0
  266. package/dist/shells/index.js +61 -0
  267. package/dist/shells/paper-shell.d.ts +9 -0
  268. package/dist/shells/paper-shell.d.ts.map +1 -0
  269. package/dist/shells/paper-shell.js +23 -0
  270. package/dist/shells/plain-shell.d.ts +3 -0
  271. package/dist/shells/plain-shell.d.ts.map +1 -0
  272. package/dist/shells/plain-shell.js +11 -0
  273. package/dist/shells/shared.d.ts +12 -0
  274. package/dist/shells/shared.d.ts.map +1 -0
  275. package/dist/shells/shared.js +21 -0
  276. package/dist/shells/workbench-shell.d.ts +9 -0
  277. package/dist/shells/workbench-shell.d.ts.map +1 -0
  278. package/dist/shells/workbench-shell.js +43 -0
  279. package/dist/surface/bar-layout.d.ts +4 -0
  280. package/dist/surface/bar-layout.d.ts.map +1 -0
  281. package/dist/surface/bar-layout.js +7 -0
  282. package/dist/surface/index.d.ts +58 -0
  283. package/dist/surface/index.d.ts.map +1 -0
  284. package/dist/surface/index.js +48 -0
  285. package/dist/surface/inline-layout.d.ts +4 -0
  286. package/dist/surface/inline-layout.d.ts.map +1 -0
  287. package/dist/surface/inline-layout.js +7 -0
  288. package/dist/surface/mount-view.d.ts +7 -0
  289. package/dist/surface/mount-view.d.ts.map +1 -0
  290. package/dist/surface/mount-view.js +19 -0
  291. package/dist/surface/rail-layout.d.ts +4 -0
  292. package/dist/surface/rail-layout.d.ts.map +1 -0
  293. package/dist/surface/rail-layout.js +19 -0
  294. package/dist/surface/shared.d.ts +7 -0
  295. package/dist/surface/shared.d.ts.map +1 -0
  296. package/dist/surface/shared.js +1 -0
  297. package/dist/surface/single-layout.d.ts +4 -0
  298. package/dist/surface/single-layout.d.ts.map +1 -0
  299. package/dist/surface/single-layout.js +18 -0
  300. package/dist/surface/split-layout.d.ts +4 -0
  301. package/dist/surface/split-layout.d.ts.map +1 -0
  302. package/dist/surface/split-layout.js +7 -0
  303. package/dist/surface/stack-layout.d.ts +4 -0
  304. package/dist/surface/stack-layout.d.ts.map +1 -0
  305. package/dist/surface/stack-layout.js +22 -0
  306. package/dist/surface/surface-area.d.ts +16 -0
  307. package/dist/surface/surface-area.d.ts.map +1 -0
  308. package/dist/surface/surface-area.js +32 -0
  309. package/dist/surface/tabs-layout.d.ts +4 -0
  310. package/dist/surface/tabs-layout.d.ts.map +1 -0
  311. package/dist/surface/tabs-layout.js +24 -0
  312. package/dist/tone.d.ts +16 -0
  313. package/dist/tone.d.ts.map +1 -0
  314. package/dist/tone.js +45 -0
  315. package/dist/viewport.d.ts +30 -0
  316. package/dist/viewport.d.ts.map +1 -0
  317. package/dist/viewport.js +17 -0
  318. package/package.json +54 -0
  319. package/src/chart/area-chart.ts +9 -0
  320. package/src/chart/bar-chart.ts +74 -0
  321. package/src/chart/gauge.ts +60 -0
  322. package/src/chart/heatmap.ts +49 -0
  323. package/src/chart/histogram.ts +36 -0
  324. package/src/chart/index.ts +36 -0
  325. package/src/chart/line-chart.ts +100 -0
  326. package/src/chart/shared.ts +112 -0
  327. package/src/chart/sparkline.ts +46 -0
  328. package/src/control/button.ts +184 -0
  329. package/src/control/checkbox.ts +48 -0
  330. package/src/control/index.ts +40 -0
  331. package/src/control/radio-group.ts +60 -0
  332. package/src/control/search-box.ts +15 -0
  333. package/src/control/select.ts +230 -0
  334. package/src/control/shared.ts +41 -0
  335. package/src/control/slider.ts +63 -0
  336. package/src/control/switch.ts +39 -0
  337. package/src/control/text-area.ts +390 -0
  338. package/src/control/text-input.ts +150 -0
  339. package/src/data/code-viewer.ts +337 -0
  340. package/src/data/feed.ts +204 -0
  341. package/src/data/index.ts +243 -0
  342. package/src/data/list.ts +149 -0
  343. package/src/data/log-viewer.ts +105 -0
  344. package/src/data/markdown-view.ts +182 -0
  345. package/src/data/pagination.ts +31 -0
  346. package/src/data/scroll-thumb.ts +40 -0
  347. package/src/data/shared.ts +23 -0
  348. package/src/data/tree.ts +215 -0
  349. package/src/decorations.ts +176 -0
  350. package/src/display/alert.ts +28 -0
  351. package/src/display/badge.ts +40 -0
  352. package/src/display/card.ts +23 -0
  353. package/src/display/empty-state.ts +21 -0
  354. package/src/display/error-state.ts +23 -0
  355. package/src/display/heading.ts +17 -0
  356. package/src/display/index.ts +55 -0
  357. package/src/display/key-value.ts +32 -0
  358. package/src/display/label.ts +11 -0
  359. package/src/display/marquee.ts +88 -0
  360. package/src/display/progress.ts +92 -0
  361. package/src/display/skeleton.ts +34 -0
  362. package/src/display/spinner.ts +20 -0
  363. package/src/display/status-dot.ts +24 -0
  364. package/src/display/timeline.ts +47 -0
  365. package/src/find.ts +138 -0
  366. package/src/form/danger-zone.ts +34 -0
  367. package/src/form/field.ts +61 -0
  368. package/src/form/form-actions.ts +35 -0
  369. package/src/form/form-section.ts +16 -0
  370. package/src/form/form.ts +13 -0
  371. package/src/form/index.ts +191 -0
  372. package/src/form/shared.ts +36 -0
  373. package/src/index.ts +63 -0
  374. package/src/layout/center.ts +17 -0
  375. package/src/layout/column.ts +6 -0
  376. package/src/layout/divider.ts +31 -0
  377. package/src/layout/grid.ts +34 -0
  378. package/src/layout/index.ts +39 -0
  379. package/src/layout/panel.ts +114 -0
  380. package/src/layout/row.ts +11 -0
  381. package/src/layout/scroll-view.ts +115 -0
  382. package/src/layout/splitter.ts +24 -0
  383. package/src/layout/stack.ts +12 -0
  384. package/src/navigation/breadcrumb.ts +43 -0
  385. package/src/navigation/index.ts +33 -0
  386. package/src/navigation/key-hints.ts +25 -0
  387. package/src/navigation/menu.ts +134 -0
  388. package/src/navigation/status-bar.ts +43 -0
  389. package/src/navigation/tabs.ts +73 -0
  390. package/src/navigation/toolbar.ts +25 -0
  391. package/src/navigation/wizard.ts +43 -0
  392. package/src/overlay/command-palette.ts +485 -0
  393. package/src/overlay/dialog.ts +65 -0
  394. package/src/overlay/index.ts +232 -0
  395. package/src/overlay/layer-scope.ts +26 -0
  396. package/src/overlay/path-picker.ts +215 -0
  397. package/src/overlay/prompt-dialog.ts +45 -0
  398. package/src/overlay/shared.ts +19 -0
  399. package/src/overlay/toast-host.ts +30 -0
  400. package/src/overlay/toast.ts +35 -0
  401. package/src/overlay/tooltip.ts +17 -0
  402. package/src/panel/index.ts +317 -0
  403. package/src/panel/resource-panel.ts +304 -0
  404. package/src/panel/shared.ts +8 -0
  405. package/src/shells/console-shell.ts +47 -0
  406. package/src/shells/index.ts +66 -0
  407. package/src/shells/paper-shell.ts +31 -0
  408. package/src/shells/plain-shell.ts +19 -0
  409. package/src/shells/shared.ts +24 -0
  410. package/src/shells/workbench-shell.ts +62 -0
  411. package/src/surface/bar-layout.ts +11 -0
  412. package/src/surface/index.ts +53 -0
  413. package/src/surface/inline-layout.ts +11 -0
  414. package/src/surface/mount-view.ts +30 -0
  415. package/src/surface/rail-layout.ts +27 -0
  416. package/src/surface/shared.ts +7 -0
  417. package/src/surface/single-layout.ts +25 -0
  418. package/src/surface/split-layout.ts +11 -0
  419. package/src/surface/stack-layout.ts +32 -0
  420. package/src/surface/surface-area.ts +46 -0
  421. package/src/surface/tabs-layout.ts +30 -0
  422. package/src/tone.ts +48 -0
  423. package/src/viewport.ts +45 -0
@@ -0,0 +1,390 @@
1
+ import type { BoxProps, KeyEvent, SemanticVariant } from '@textui/core';
2
+ import {
3
+ defineComponent,
4
+ graphemes,
5
+ h,
6
+ stringWidth,
7
+ useEffect,
8
+ useFocus,
9
+ useInput,
10
+ useMeasure,
11
+ useState,
12
+ useTicker,
13
+ } from '@textui/core';
14
+
15
+ export interface TextAreaProps extends BoxProps {
16
+ value: string;
17
+ onChange(value: string): void;
18
+ /**
19
+ * Enter. A newline is `ctrl+enter`, which is the one people reach for, with
20
+ * `alt+enter` as the one that cannot fail.
21
+ *
22
+ * A terminal has three ways to say `ctrl+enter` and `@textui/terminal`
23
+ * decodes all three: the kitty protocol's `CSI 13;5u`, xterm's
24
+ * `modifyOtherKeys` `CSI 27;5;13~`, and a bare LF - which is what most
25
+ * terminals send, and which is *not* the Return key, because in raw mode
26
+ * Return sends CR.
27
+ *
28
+ * `shift+enter` is not offered. There is no encoding in which it differs
29
+ * from enter, so a field that claimed it would be claiming a key that
30
+ * cannot arrive.
31
+ *
32
+ * Left off, enter inserts a newline like every other key does.
33
+ */
34
+ onSubmit?(value: string): void;
35
+ /** Escape, when there is nothing inside the field to cancel. */
36
+ onCancel?(): void;
37
+ /** Up at the top, down at the bottom: for walking a history. */
38
+ onOverflow?(direction: -1 | 1): void;
39
+ /**
40
+ * Left at the very start, right at the very end.
41
+ *
42
+ * The horizontal pair of `onOverflow`, and separate from it because they
43
+ * mean different things: up and down walk a history, and left off the front
44
+ * of the field is "I am done here" - which is how a composer hands the
45
+ * reader back to what is beside it without them reaching for escape.
46
+ */
47
+ onEdge?(edge: 'start' | 'end'): void;
48
+ placeholder?: string;
49
+ /** Rows before it stops growing and starts scrolling. */
50
+ maxRows?: number;
51
+ maxLength?: number;
52
+ autoFocus?: boolean;
53
+ focusId?: string;
54
+ /**
55
+ * The caret's colour. `cursor` by default, which is the theme's own.
56
+ *
57
+ * A composer usually wants `accent`: the caret is the one thing on the
58
+ * screen saying where typing goes, and the field it sits in is the point of
59
+ * the screen.
60
+ */
61
+ caretTone?: SemanticVariant;
62
+ /**
63
+ * What the caret looks like. An underline under the character it is on by
64
+ * default; `block` fills the cell instead.
65
+ *
66
+ * Both **mark a cell rather than occupying one**. The caret used to be a
67
+ * glyph pushed in between the text before it and the text after, so every
68
+ * character to its right sat one column off from where it would be once the
69
+ * caret moved on, and the row was a cell wider than its own text. On a
70
+ * wrapped row that extra cell is the one that does not fit.
71
+ */
72
+ caretStyle?: 'underline' | 'block';
73
+ /**
74
+ * Blink the caret while the field has the keyboard. On by default.
75
+ *
76
+ * Driven by the animation ticker, so it stops with every other animation -
77
+ * a still, a test and a terminal that has animation off all draw the caret
78
+ * solid rather than at whatever phase the clock happened to be in.
79
+ */
80
+ blink?: boolean;
81
+ }
82
+
83
+ /**
84
+ * A field that is a paragraph.
85
+ *
86
+ * `TextInput` is one line, and a message, a note or a commit body is not. It
87
+ * soft-wraps, grows to what has been typed and then scrolls, keeps the caret
88
+ * visible, and hands back every key it does not want - which is what lets the
89
+ * thing behind it keep its own shortcuts while this has the keyboard.
90
+ *
91
+ * Wrapping means a line is not a row: `maxRows` is a budget of rows, up and
92
+ * down move by row, and the inherited `wrap` prop picks how a line breaks.
93
+ * Any of its `truncate-*` values asks for one row per line instead, with an
94
+ * ellipsis where the text was cut.
95
+ *
96
+ * It takes a printable character before any keybinding sees it, because that
97
+ * is what typing is. That single fact is why an application with one of these
98
+ * cannot have single-letter global commands, and why it does not need to.
99
+ */
100
+ export const TextArea = defineComponent<TextAreaProps>('TextArea', (props) => {
101
+ const {
102
+ value, onChange, onSubmit, onCancel, onOverflow, onEdge, placeholder, maxRows = 6,
103
+ maxLength, autoFocus, focusId, disabled, caretTone, blink = true, wrap = 'word',
104
+ caretStyle = 'underline', ...rest
105
+ } = props;
106
+
107
+ const focus = useFocus({
108
+ ...(focusId ? { id: focusId } : {}),
109
+ disabled,
110
+ ...(autoFocus ? { autoFocus } : {}),
111
+ });
112
+ const [caret, setCaret] = useState(value.length);
113
+ const [lit, setLit] = useState(true);
114
+
115
+ // Only while it has the keyboard, and solid the moment it loses it: a caret
116
+ // blinking in a field nobody is typing into is two carets on one screen.
117
+ useTicker(() => setLit((on) => !on), { fps: 2, enabled: blink && focus.focused });
118
+ useEffect(() => { setLit(true); }, [focus.focused]);
119
+
120
+ /**
121
+ * The caret, as a style laid over one cell.
122
+ *
123
+ * `inverse` for a block rather than a background colour, because it swaps
124
+ * whatever the cell already had - a caret over selected or coloured text
125
+ * stays legible instead of painting the theme's cursor colour over it.
126
+ */
127
+ const mark = caretStyle === 'block'
128
+ ? { inverse: true, fg: caretTone ?? 'cursor' }
129
+ : { underline: true, fg: caretTone ?? 'cursor' };
130
+
131
+ const chars = graphemes(value);
132
+ const position = Math.min(caret, chars.length);
133
+ const before = chars.slice(0, position).join('');
134
+ const lines = value === '' ? [''] : value.split('\n');
135
+ const caretLine = before.split('\n').length - 1;
136
+ const caretColumn = graphemes(before.split('\n').pop() ?? '').length;
137
+
138
+ /*
139
+ * Visual rows, which are not logical lines.
140
+ *
141
+ * Every row was drawn with `truncate: 'end'`, so a line wider than the field
142
+ * came out as its first few words and an ellipsis - text that had been typed
143
+ * and could not be read back. This is the component the docs call "the one
144
+ * that is a paragraph", and a paragraph wraps.
145
+ *
146
+ * So one line can occupy several rows, and everything below counts rows
147
+ * rather than lines: `maxRows`, the scroll offset, up and down. The
148
+ * inherited `wrap` prop chooses how, and any of the `truncate-*` values ask
149
+ * for the old single-row-per-line behaviour back.
150
+ *
151
+ * The width is the layout's, so it arrives a frame late. Until it does it is
152
+ * 0 and nothing wraps, which is precisely the behaviour above - and the pass
153
+ * that `useMeasure` schedules draws the wrapped version.
154
+ */
155
+ const width = useMeasure().width;
156
+ const mode = width > 0 && (wrap === 'word' || wrap === 'char') ? wrap : 'none';
157
+
158
+ const visual: { line: number; start: number; cells: string[] }[] = [];
159
+ lines.forEach((line, index) => {
160
+ const cells = graphemes(line);
161
+ const starts = mode === 'none' ? [0] : breakColumns(cells, width, mode);
162
+ starts.forEach((start, i) => {
163
+ visual.push({ line: index, start, cells: cells.slice(start, starts[i + 1] ?? cells.length) });
164
+ });
165
+
166
+ // A caret at the end of a row that is already full has no cell of its own
167
+ // to mark and no room for a spare one, so it belongs on a new empty row -
168
+ // which is where the next character was going to land anyway.
169
+ if (mode !== 'none' && index === caretLine && caretColumn === cells.length) {
170
+ const last = visual[visual.length - 1];
171
+ if (last && rowWidth(last.cells) >= width) {
172
+ visual.push({ line: index, start: cells.length, cells: [] });
173
+ }
174
+ }
175
+ });
176
+
177
+ // The *last* row starting at or before the caret. With the caret exactly on
178
+ // a break that is the row it begins, not the one it just filled - which is
179
+ // where the caret goes when a word has just been pushed onto a new row.
180
+ let caretRow = 0;
181
+ visual.forEach((row, i) => {
182
+ if (row.line === caretLine && row.start <= caretColumn) caretRow = i;
183
+ });
184
+
185
+ const rows = Math.min(maxRows, Math.max(1, visual.length));
186
+ const first = Math.max(0, Math.min(caretRow - rows + 1, visual.length - rows));
187
+
188
+ const lineStart = (index: number): number =>
189
+ lines.slice(0, index).reduce((n, line) => n + graphemes(line).length + 1, 0);
190
+
191
+ /** The caret's column within its own row, carried onto another row. */
192
+ const rowCaret = (index: number): number => {
193
+ const row = visual[index];
194
+ if (!row) return position;
195
+ const column = Math.min(caretColumn - (visual[caretRow]?.start ?? 0), row.cells.length);
196
+ return lineStart(row.line) + row.start + column;
197
+ };
198
+
199
+ const replace = (next: string, at: number): void => {
200
+ const capped = maxLength !== undefined ? next.slice(0, maxLength) : next;
201
+ onChange(capped);
202
+ setCaret(Math.max(0, Math.min(graphemes(capped).length, at)));
203
+ };
204
+
205
+ const insert = (text: string): void => {
206
+ const inserted = graphemes(text);
207
+ replace([...chars.slice(0, position), ...inserted, ...chars.slice(position)].join(''), position + inserted.length);
208
+ };
209
+
210
+ useInput(
211
+ (event: KeyEvent) => {
212
+ if (disabled) return false;
213
+
214
+ switch (event.name) {
215
+ case 'left':
216
+ // Off the front of the field is the caller's key, the same way up at
217
+ // the top is - so "left, left, left" walks out of the composer.
218
+ if (position === 0) { onEdge?.('start'); return true; }
219
+ setCaret(position - 1);
220
+ return true;
221
+ case 'right':
222
+ if (position === chars.length) { onEdge?.('end'); return true; }
223
+ setCaret(position + 1);
224
+ return true;
225
+ case 'home': setCaret(lineStart(caretLine)); return true;
226
+ case 'end': setCaret(lineStart(caretLine) + graphemes(lines[caretLine] ?? '').length); return true;
227
+
228
+ // By *row*, not by line. In a wrapped paragraph moving by line jumps
229
+ // the whole paragraph, and what is above the caret on the screen is
230
+ // the row above it.
231
+ case 'up': {
232
+ // At the top there is nowhere to go inside the field, so the caller
233
+ // gets the key - which is where "the last thing you sent" comes from.
234
+ if (caretRow === 0) { onOverflow?.(-1); return true; }
235
+ setCaret(rowCaret(caretRow - 1));
236
+ return true;
237
+ }
238
+ case 'down': {
239
+ if (caretRow === visual.length - 1) { onOverflow?.(1); return true; }
240
+ setCaret(rowCaret(caretRow + 1));
241
+ return true;
242
+ }
243
+
244
+ case 'backspace':
245
+ if (position > 0) replace([...chars.slice(0, position - 1), ...chars.slice(position)].join(''), position - 1);
246
+ return true;
247
+ case 'delete':
248
+ if (position < chars.length) replace([...chars.slice(0, position), ...chars.slice(position + 1)].join(''), position);
249
+ return true;
250
+
251
+ case 'enter':
252
+ // Alt or ctrl says "a line, not a message". `shift+enter` is not in
253
+ // this list on purpose: most terminals cannot tell it from enter, so
254
+ // binding it produces a key that works on one machine.
255
+ if (!onSubmit || event.alt || event.ctrl) { insert('\n'); return true; }
256
+ onSubmit(value);
257
+ setCaret(0);
258
+ return true;
259
+
260
+ case 'escape': onCancel?.(); return true;
261
+ case 'paste': insert(event.char ?? ''); return true;
262
+ default: break;
263
+ }
264
+
265
+ // ctrl+j, for the one case where it is a key of its own: with the kitty
266
+ // protocol on it arrives as `CSI 106;5u` and is named `j`. Without it
267
+ // ctrl+j is 0x0a, the same byte as ctrl+enter, and the case above has
268
+ // already taken it - there is no encoding in which those two differ.
269
+ if (event.name === 'j' && event.ctrl) { insert('\n'); return true; }
270
+
271
+ if (event.char && !event.ctrl && !event.alt && !event.meta) {
272
+ insert(event.char);
273
+ return true;
274
+ }
275
+ return false;
276
+ },
277
+ { focusId: focus.id, enabled: !disabled },
278
+ );
279
+
280
+ const shown = visual.slice(first, first + rows);
281
+
282
+ // The row count fixes the *content*, on an inner box, and the outer one
283
+ // sizes to it.
284
+ //
285
+ // `height: rows` on the outer box meant the rows and the border had to share
286
+ // one allowance: `<TextArea border="single"/>` came out as a top border and
287
+ // nothing else, because one row is what the field asked for and the border
288
+ // spent it. Nobody saw it because a field without a border looks the same
289
+ // either way, and `TextInput` fixes no height at all.
290
+ return h('box', {
291
+ id: focus.id,
292
+ role: 'textbox',
293
+ label: placeholder,
294
+ direction: 'column',
295
+ ...rest,
296
+ }, h('box', { direction: 'column', height: rows },
297
+ value === ''
298
+ // The placeholder stays while the field is focused and empty: it is the
299
+ // only thing on screen saying what enter will do, and hiding it when the
300
+ // caret arrives hides it exactly when it is read.
301
+ ? (() => {
302
+ // The caret sits on the placeholder's first cell rather than in front
303
+ // of it: a caret that pushed the placeholder along moved the one piece
304
+ // of text on the screen every time the field was focused.
305
+ const hint = graphemes(placeholder ?? '');
306
+ const head = hint.length > 0 ? (hint[0] as string) : ' ';
307
+ return h('box', { direction: 'row' },
308
+ h('text', {
309
+ content: head,
310
+ // The placeholder keeps its own colour and only takes the mark:
311
+ // recolouring it would make the first letter of the hint the one
312
+ // bright thing in an empty field.
313
+ ...(focus.focused && lit ? { ...mark, fg: 'subtle' as const } : { fg: 'subtle' as const }),
314
+ }),
315
+ h('text', { content: hint.slice(1).join(''), fg: 'subtle', flex: 1, truncate: 'end' }));
316
+ })()
317
+ : shown.map((row, i) => {
318
+ const index = first + i;
319
+ const text = row.cells.join('');
320
+ if (index !== caretRow || !focus.focused) {
321
+ return h('text', { key: index, content: text === '' ? ' ' : text, wrap: 'none', truncate: 'end' });
322
+ }
323
+ // The caret's column inside this row. The row has already been broken
324
+ // to the width it will be drawn at, so the split lands where it looks.
325
+ //
326
+ // Three parts, and the middle one is the cell the caret is *on* - not
327
+ // an extra cell between them. Past the last character there is nothing
328
+ // to mark, so a space stands in; that is the one case where the caret
329
+ // adds a column, and it adds it at the end where nothing moves.
330
+ const at = caretColumn - row.start;
331
+ return h('box', { key: index, direction: 'row' },
332
+ h('text', { content: row.cells.slice(0, at).join('') }),
333
+ h('text', { content: row.cells[at] ?? ' ', ...(lit ? mark : {}) }),
334
+ h('text', { content: row.cells.slice(at + 1).join(''), flex: 1, truncate: 'end' }));
335
+ }),
336
+ ));
337
+ });
338
+
339
+ /**
340
+ * Where a line breaks, as columns into its own grapheme array.
341
+ *
342
+ * One entry per visual row and always starting at 0, which is what makes a
343
+ * wrapped row addressable: a caret is a character offset, so a wrap that only
344
+ * returned strings could not say which offset a row begins at. `wrapText` in
345
+ * core returns strings and trims each one, and both of those lose the mapping.
346
+ *
347
+ * Nothing is trimmed here for the same reason. A trailing space is a character
348
+ * somebody typed and can delete, so the row it sits on has to contain it -
349
+ * otherwise the caret drifts one cell from the text for the rest of the line.
350
+ *
351
+ * Cells rather than characters throughout: a wide glyph is two columns.
352
+ */
353
+ function breakColumns(cells: string[], width: number, mode: 'word' | 'char'): number[] {
354
+ if (width <= 0 || cells.length === 0) return [0];
355
+
356
+ const starts = [0];
357
+ let start = 0;
358
+ let used = 0;
359
+ // The last space on the row so far, and -1 once a break has consumed it.
360
+ let space = -1;
361
+
362
+ for (let i = 0; i < cells.length; i++) {
363
+ const cell = cells[i] as string;
364
+ const cellWidth = stringWidth(cell);
365
+
366
+ if (used + cellWidth > width && i > start) {
367
+ // Break after the last space, or mid-word when the row has none - a word
368
+ // longer than the field has to break somewhere, and `space > start`
369
+ // rather than `>=` keeps a row that begins with a space from being one.
370
+ const at = mode === 'word' && space > start ? space + 1 : i;
371
+ starts.push(at);
372
+ start = at;
373
+ space = -1;
374
+ used = 0;
375
+ for (let j = at; j < i; j++) used += stringWidth(cells[j] as string);
376
+ }
377
+
378
+ if (/\s/.test(cell)) space = i;
379
+ used += cellWidth;
380
+ }
381
+
382
+ return starts;
383
+ }
384
+
385
+ /** A row's width in cells, which is not its length in characters. */
386
+ function rowWidth(cells: string[]): number {
387
+ let width = 0;
388
+ for (const cell of cells) width += stringWidth(cell);
389
+ return width;
390
+ }
@@ -0,0 +1,150 @@
1
+ import type { KeyEvent } from '@textui/core';
2
+ import {
3
+ defineComponent,
4
+ graphemes,
5
+ h,
6
+ sliceColumns,
7
+ stringWidth,
8
+ useFocus,
9
+ useInput,
10
+ useMeasure,
11
+ useState,
12
+ useTheme,
13
+ } from '@textui/core';
14
+ import type { TextInputProps } from './shared.js';
15
+
16
+ /**
17
+ * A single-line text field.
18
+ *
19
+ * The caret is a real cursor position published to the renderer when the
20
+ * terminal has a cursor, and a drawn glyph when it does not - so a text field
21
+ * still shows where typing lands over a link that swallowed cursor control.
22
+ */
23
+ export const TextInput = defineComponent<TextInputProps>('TextInput', (props) => {
24
+ const theme = useTheme();
25
+ const {
26
+ value, onChange, onSubmit, onEdge, placeholder, label, hideLabel, mask, maxLength,
27
+ autoFocus, search, disabled, focusId, ...rest
28
+ } = props;
29
+ const inlineLabel = label && !search && !hideLabel ? label : undefined;
30
+
31
+ const focus = useFocus({ ...(focusId ? { id: focusId } : {}), disabled, autoFocus });
32
+ const measured = useMeasure();
33
+ const [caret, setCaret] = useState(value.length);
34
+ const [scroll, setScroll] = useState(0);
35
+
36
+ const chars = graphemes(value);
37
+ const position = Math.min(caret, chars.length);
38
+ const shown = mask ? mask.repeat(chars.length) : value;
39
+ const empty = shown === '';
40
+
41
+ // Everything drawn before the value pushes the caret along with it. Counting
42
+ // the search glyph but not the label is what leaves the caret several cells
43
+ // to the left of where the typing lands.
44
+ const lead =
45
+ (search ? stringWidth(theme.glyphs.search) + 1 : 0) +
46
+ (inlineLabel ? stringWidth(inlineLabel) + 1 : 0);
47
+
48
+ const total = stringWidth(shown);
49
+ const field = measured.width > 0
50
+ ? Math.max(1, measured.width - lead)
51
+ : Math.max(1, total);
52
+
53
+ // Cells, not characters: a wide glyph is two columns of caret travel.
54
+ const columnAt = (index: number): number => stringWidth(chars.slice(0, index).join(''));
55
+ const caretColumn = columnAt(position);
56
+ const anchored = Math.max(0, Math.min(scroll, caretColumn, Math.max(0, total - field + 1)));
57
+ const start = caretColumn > anchored + field - 1 ? caretColumn - field + 1 : anchored;
58
+
59
+ /**
60
+ * Move the caret and scroll the field just enough to keep it in view.
61
+ *
62
+ * `limit` is the length the value is about to have: `chars` still holds the
63
+ * old one during the render that inserts a character, and clamping against
64
+ * that is what would pin the caret behind every keystroke.
65
+ */
66
+ const move = (next: number, limit = chars.length): void => {
67
+ const clamped = Math.max(0, Math.min(limit, next));
68
+ setCaret(clamped);
69
+ const column = columnAt(clamped);
70
+ if (column < start) setScroll(column);
71
+ else if (column > start + field - 1) setScroll(column - field + 1);
72
+ };
73
+
74
+ useInput(
75
+ (event: KeyEvent) => {
76
+ if (disabled) return false;
77
+
78
+ switch (event.name) {
79
+ // Off either end is the caller's key, the same way it is in a
80
+ // `TextArea`. Reported rather than passed on, because the field is
81
+ // focused and a handler beside it would never see the event.
82
+ case 'left':
83
+ if (position === 0) { onEdge?.('start'); return true; }
84
+ move(position - 1);
85
+ return true;
86
+ case 'right':
87
+ if (position === chars.length) { onEdge?.('end'); return true; }
88
+ move(position + 1);
89
+ return true;
90
+ case 'home': move(0); return true;
91
+ case 'end': move(chars.length); return true;
92
+ case 'backspace':
93
+ if (position > 0) {
94
+ onChange?.([...chars.slice(0, position - 1), ...chars.slice(position)].join(''));
95
+ move(position - 1);
96
+ }
97
+ return true;
98
+ case 'delete':
99
+ if (position < chars.length) {
100
+ onChange?.([...chars.slice(0, position), ...chars.slice(position + 1)].join(''));
101
+ }
102
+ return true;
103
+ case 'enter':
104
+ onSubmit?.(value);
105
+ return true;
106
+ case 'paste': {
107
+ const text = event.char ?? '';
108
+ const inserted = graphemes(text).length;
109
+ const next = [...chars.slice(0, position), ...graphemes(text), ...chars.slice(position)].join('');
110
+ onChange?.(maxLength ? next.slice(0, maxLength) : next);
111
+ move(position + inserted, chars.length + inserted);
112
+ return true;
113
+ }
114
+ default: break;
115
+ }
116
+
117
+ // A printable character. Modifiers mean it was a shortcut, not typing.
118
+ if (event.char && !event.ctrl && !event.alt && !event.meta) {
119
+ if (maxLength !== undefined && chars.length >= maxLength) return true;
120
+ onChange?.([...chars.slice(0, position), event.char, ...chars.slice(position)].join(''));
121
+ move(position + 1, chars.length + 1);
122
+ return true;
123
+ }
124
+ return false;
125
+ },
126
+ { focusId: focus.id },
127
+ );
128
+
129
+ return h('box', {
130
+ id: focus.id,
131
+ role: search ? 'searchbox' : 'textbox',
132
+ label,
133
+ direction: 'row',
134
+ gap: 1,
135
+ border: { style: theme.border, color: focus.focused ? 'focus' : 'border' },
136
+ padding: [0, 1],
137
+ cursor: focus.focused ? lead + caretColumn - start : undefined,
138
+ ...rest,
139
+ },
140
+ search ? h('text', { content: theme.glyphs.search, fg: 'muted' }) : null,
141
+ inlineLabel ? h('text', { content: inlineLabel, fg: 'muted' }) : null,
142
+ h('text', {
143
+ content: empty
144
+ ? sliceColumns(placeholder ?? '', 0, field)
145
+ : sliceColumns(shown, start, field),
146
+ fg: empty ? 'subtle' : disabled ? 'disabled' : undefined,
147
+ flex: 1,
148
+ }),
149
+ );
150
+ });