@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,45 @@
1
+ import { defineComponent, h, useFocus, useInput, useTheme } from '@textui/core';
2
+ export const Slider = defineComponent('Slider', (props) => {
3
+ const theme = useTheme();
4
+ const { value, min = 0, max = 100, step = 1, label, onChange, trackWidth = 20, format, disabled, ...rest } = props;
5
+ const focus = useFocus({ disabled });
6
+ const clamp = (v) => Math.max(min, Math.min(max, v));
7
+ useInput((event) => {
8
+ if (disabled)
9
+ return false;
10
+ const big = Math.max(step, Math.round((max - min) / 10));
11
+ switch (event.name) {
12
+ case 'left':
13
+ onChange?.(clamp(value - step));
14
+ return true;
15
+ case 'right':
16
+ onChange?.(clamp(value + step));
17
+ return true;
18
+ case 'pagedown':
19
+ onChange?.(clamp(value - big));
20
+ return true;
21
+ case 'pageup':
22
+ onChange?.(clamp(value + big));
23
+ return true;
24
+ case 'home':
25
+ onChange?.(min);
26
+ return true;
27
+ case 'end':
28
+ onChange?.(max);
29
+ return true;
30
+ default: return false;
31
+ }
32
+ }, { focusId: focus.id });
33
+ const ratio = max === min ? 0 : (clamp(value) - min) / (max - min);
34
+ const filled = Math.round(ratio * (trackWidth - 1));
35
+ // Track, thumb and remainder all come from the theme, so an ascii terminal
36
+ // gets `--o--` rather than a row of question marks.
37
+ const thumb = theme.glyphs.bulletFilled;
38
+ const done = theme.glyphs.progressFull;
39
+ const todo = theme.glyphs.progressEmpty;
40
+ const track = Array.from({ length: trackWidth }, (_, i) => i === filled ? thumb : i < filled ? done : todo).join('');
41
+ return h('box', { id: focus.id, role: 'slider', label, direction: 'row', gap: 1, ...rest }, label ? h('text', { content: label, fg: 'muted' }) : null, h('text', {
42
+ content: track,
43
+ fg: disabled ? 'disabled' : focus.focused ? 'accent' : 'border',
44
+ }), h('text', { content: format ? format(value) : String(value), fg: 'muted' }));
45
+ });
@@ -0,0 +1,10 @@
1
+ import type { BoxProps } from '@textui/core';
2
+ export interface SwitchProps extends BoxProps {
3
+ label?: string;
4
+ value?: boolean;
5
+ onChange?(value: boolean): void;
6
+ /** Words either side, so the state reads without colour. */
7
+ labels?: [off: string, on: string];
8
+ }
9
+ export declare const Switch: (props: SwitchProps) => import("@textui/core").RenderOutput;
10
+ //# sourceMappingURL=switch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"switch.d.ts","sourceRoot":"","sources":["../../src/control/switch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAG7C,MAAM,WAAW,WAAY,SAAQ,QAAQ;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAChC,4DAA4D;IAC5D,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,eAAO,MAAM,MAAM,6DA2BjB,CAAC"}
@@ -0,0 +1,32 @@
1
+ import { defineComponent, h, useFocus, useInput } from '@textui/core';
2
+ export const Switch = defineComponent('Switch', (props) => {
3
+ const { label, value = false, onChange, labels = ['off', 'on'], disabled, ...rest } = props;
4
+ const focus = useFocus({ disabled });
5
+ useInput((event) => {
6
+ if (disabled)
7
+ return false;
8
+ if (event.name === 'space' || event.name === 'enter') {
9
+ onChange?.(!value);
10
+ return true;
11
+ }
12
+ if (event.name === 'left') {
13
+ onChange?.(false);
14
+ return true;
15
+ }
16
+ if (event.name === 'right') {
17
+ onChange?.(true);
18
+ return true;
19
+ }
20
+ return false;
21
+ }, { focusId: focus.id });
22
+ return h('box', {
23
+ id: focus.id, role: 'switch', label, selected: value,
24
+ direction: 'row', gap: 1, bold: focus.focused,
25
+ onClick: () => { if (!disabled)
26
+ onChange?.(!value); },
27
+ ...rest,
28
+ }, label ? h('text', { content: label }) : null, h('text', {
29
+ content: value ? `[${labels[1]}]` : `[${labels[0]}]`,
30
+ fg: disabled ? 'disabled' : value ? 'accent' : 'muted',
31
+ }));
32
+ });
@@ -0,0 +1,87 @@
1
+ import type { BoxProps, SemanticVariant } from '@textui/core';
2
+ export interface TextAreaProps extends BoxProps {
3
+ value: string;
4
+ onChange(value: string): void;
5
+ /**
6
+ * Enter. A newline is `ctrl+enter`, which is the one people reach for, with
7
+ * `alt+enter` as the one that cannot fail.
8
+ *
9
+ * A terminal has three ways to say `ctrl+enter` and `@textui/terminal`
10
+ * decodes all three: the kitty protocol's `CSI 13;5u`, xterm's
11
+ * `modifyOtherKeys` `CSI 27;5;13~`, and a bare LF - which is what most
12
+ * terminals send, and which is *not* the Return key, because in raw mode
13
+ * Return sends CR.
14
+ *
15
+ * `shift+enter` is not offered. There is no encoding in which it differs
16
+ * from enter, so a field that claimed it would be claiming a key that
17
+ * cannot arrive.
18
+ *
19
+ * Left off, enter inserts a newline like every other key does.
20
+ */
21
+ onSubmit?(value: string): void;
22
+ /** Escape, when there is nothing inside the field to cancel. */
23
+ onCancel?(): void;
24
+ /** Up at the top, down at the bottom: for walking a history. */
25
+ onOverflow?(direction: -1 | 1): void;
26
+ /**
27
+ * Left at the very start, right at the very end.
28
+ *
29
+ * The horizontal pair of `onOverflow`, and separate from it because they
30
+ * mean different things: up and down walk a history, and left off the front
31
+ * of the field is "I am done here" - which is how a composer hands the
32
+ * reader back to what is beside it without them reaching for escape.
33
+ */
34
+ onEdge?(edge: 'start' | 'end'): void;
35
+ placeholder?: string;
36
+ /** Rows before it stops growing and starts scrolling. */
37
+ maxRows?: number;
38
+ maxLength?: number;
39
+ autoFocus?: boolean;
40
+ focusId?: string;
41
+ /**
42
+ * The caret's colour. `cursor` by default, which is the theme's own.
43
+ *
44
+ * A composer usually wants `accent`: the caret is the one thing on the
45
+ * screen saying where typing goes, and the field it sits in is the point of
46
+ * the screen.
47
+ */
48
+ caretTone?: SemanticVariant;
49
+ /**
50
+ * What the caret looks like. An underline under the character it is on by
51
+ * default; `block` fills the cell instead.
52
+ *
53
+ * Both **mark a cell rather than occupying one**. The caret used to be a
54
+ * glyph pushed in between the text before it and the text after, so every
55
+ * character to its right sat one column off from where it would be once the
56
+ * caret moved on, and the row was a cell wider than its own text. On a
57
+ * wrapped row that extra cell is the one that does not fit.
58
+ */
59
+ caretStyle?: 'underline' | 'block';
60
+ /**
61
+ * Blink the caret while the field has the keyboard. On by default.
62
+ *
63
+ * Driven by the animation ticker, so it stops with every other animation -
64
+ * a still, a test and a terminal that has animation off all draw the caret
65
+ * solid rather than at whatever phase the clock happened to be in.
66
+ */
67
+ blink?: boolean;
68
+ }
69
+ /**
70
+ * A field that is a paragraph.
71
+ *
72
+ * `TextInput` is one line, and a message, a note or a commit body is not. It
73
+ * soft-wraps, grows to what has been typed and then scrolls, keeps the caret
74
+ * visible, and hands back every key it does not want - which is what lets the
75
+ * thing behind it keep its own shortcuts while this has the keyboard.
76
+ *
77
+ * Wrapping means a line is not a row: `maxRows` is a budget of rows, up and
78
+ * down move by row, and the inherited `wrap` prop picks how a line breaks.
79
+ * Any of its `truncate-*` values asks for one row per line instead, with an
80
+ * ellipsis where the text was cut.
81
+ *
82
+ * It takes a printable character before any keybinding sees it, because that
83
+ * is what typing is. That single fact is why an application with one of these
84
+ * cannot have single-letter global commands, and why it does not need to.
85
+ */
86
+ export declare const TextArea: (props: TextAreaProps) => import("@textui/core").RenderOutput;
87
+ //# sourceMappingURL=text-area.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-area.d.ts","sourceRoot":"","sources":["../../src/control/text-area.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAY,eAAe,EAAE,MAAM,cAAc,CAAC;AAcxE,MAAM,WAAW,aAAc,SAAQ,QAAQ;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,gEAAgE;IAChE,QAAQ,CAAC,IAAI,IAAI,CAAC;IAClB,gEAAgE;IAChE,UAAU,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;IACrC;;;;;;;OAOG;IACH,MAAM,CAAC,CAAC,IAAI,EAAE,OAAO,GAAG,KAAK,GAAG,IAAI,CAAC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B;;;;;;;;;OASG;IACH,UAAU,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;IACnC;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,QAAQ,+DA6OnB,CAAC"}
@@ -0,0 +1,299 @@
1
+ import { defineComponent, graphemes, h, stringWidth, useEffect, useFocus, useInput, useMeasure, useState, useTicker, } from '@textui/core';
2
+ /**
3
+ * A field that is a paragraph.
4
+ *
5
+ * `TextInput` is one line, and a message, a note or a commit body is not. It
6
+ * soft-wraps, grows to what has been typed and then scrolls, keeps the caret
7
+ * visible, and hands back every key it does not want - which is what lets the
8
+ * thing behind it keep its own shortcuts while this has the keyboard.
9
+ *
10
+ * Wrapping means a line is not a row: `maxRows` is a budget of rows, up and
11
+ * down move by row, and the inherited `wrap` prop picks how a line breaks.
12
+ * Any of its `truncate-*` values asks for one row per line instead, with an
13
+ * ellipsis where the text was cut.
14
+ *
15
+ * It takes a printable character before any keybinding sees it, because that
16
+ * is what typing is. That single fact is why an application with one of these
17
+ * cannot have single-letter global commands, and why it does not need to.
18
+ */
19
+ export const TextArea = defineComponent('TextArea', (props) => {
20
+ const { value, onChange, onSubmit, onCancel, onOverflow, onEdge, placeholder, maxRows = 6, maxLength, autoFocus, focusId, disabled, caretTone, blink = true, wrap = 'word', caretStyle = 'underline', ...rest } = props;
21
+ const focus = useFocus({
22
+ ...(focusId ? { id: focusId } : {}),
23
+ disabled,
24
+ ...(autoFocus ? { autoFocus } : {}),
25
+ });
26
+ const [caret, setCaret] = useState(value.length);
27
+ const [lit, setLit] = useState(true);
28
+ // Only while it has the keyboard, and solid the moment it loses it: a caret
29
+ // blinking in a field nobody is typing into is two carets on one screen.
30
+ useTicker(() => setLit((on) => !on), { fps: 2, enabled: blink && focus.focused });
31
+ useEffect(() => { setLit(true); }, [focus.focused]);
32
+ /**
33
+ * The caret, as a style laid over one cell.
34
+ *
35
+ * `inverse` for a block rather than a background colour, because it swaps
36
+ * whatever the cell already had - a caret over selected or coloured text
37
+ * stays legible instead of painting the theme's cursor colour over it.
38
+ */
39
+ const mark = caretStyle === 'block'
40
+ ? { inverse: true, fg: caretTone ?? 'cursor' }
41
+ : { underline: true, fg: caretTone ?? 'cursor' };
42
+ const chars = graphemes(value);
43
+ const position = Math.min(caret, chars.length);
44
+ const before = chars.slice(0, position).join('');
45
+ const lines = value === '' ? [''] : value.split('\n');
46
+ const caretLine = before.split('\n').length - 1;
47
+ const caretColumn = graphemes(before.split('\n').pop() ?? '').length;
48
+ /*
49
+ * Visual rows, which are not logical lines.
50
+ *
51
+ * Every row was drawn with `truncate: 'end'`, so a line wider than the field
52
+ * came out as its first few words and an ellipsis - text that had been typed
53
+ * and could not be read back. This is the component the docs call "the one
54
+ * that is a paragraph", and a paragraph wraps.
55
+ *
56
+ * So one line can occupy several rows, and everything below counts rows
57
+ * rather than lines: `maxRows`, the scroll offset, up and down. The
58
+ * inherited `wrap` prop chooses how, and any of the `truncate-*` values ask
59
+ * for the old single-row-per-line behaviour back.
60
+ *
61
+ * The width is the layout's, so it arrives a frame late. Until it does it is
62
+ * 0 and nothing wraps, which is precisely the behaviour above - and the pass
63
+ * that `useMeasure` schedules draws the wrapped version.
64
+ */
65
+ const width = useMeasure().width;
66
+ const mode = width > 0 && (wrap === 'word' || wrap === 'char') ? wrap : 'none';
67
+ const visual = [];
68
+ lines.forEach((line, index) => {
69
+ const cells = graphemes(line);
70
+ const starts = mode === 'none' ? [0] : breakColumns(cells, width, mode);
71
+ starts.forEach((start, i) => {
72
+ visual.push({ line: index, start, cells: cells.slice(start, starts[i + 1] ?? cells.length) });
73
+ });
74
+ // A caret at the end of a row that is already full has no cell of its own
75
+ // to mark and no room for a spare one, so it belongs on a new empty row -
76
+ // which is where the next character was going to land anyway.
77
+ if (mode !== 'none' && index === caretLine && caretColumn === cells.length) {
78
+ const last = visual[visual.length - 1];
79
+ if (last && rowWidth(last.cells) >= width) {
80
+ visual.push({ line: index, start: cells.length, cells: [] });
81
+ }
82
+ }
83
+ });
84
+ // The *last* row starting at or before the caret. With the caret exactly on
85
+ // a break that is the row it begins, not the one it just filled - which is
86
+ // where the caret goes when a word has just been pushed onto a new row.
87
+ let caretRow = 0;
88
+ visual.forEach((row, i) => {
89
+ if (row.line === caretLine && row.start <= caretColumn)
90
+ caretRow = i;
91
+ });
92
+ const rows = Math.min(maxRows, Math.max(1, visual.length));
93
+ const first = Math.max(0, Math.min(caretRow - rows + 1, visual.length - rows));
94
+ const lineStart = (index) => lines.slice(0, index).reduce((n, line) => n + graphemes(line).length + 1, 0);
95
+ /** The caret's column within its own row, carried onto another row. */
96
+ const rowCaret = (index) => {
97
+ const row = visual[index];
98
+ if (!row)
99
+ return position;
100
+ const column = Math.min(caretColumn - (visual[caretRow]?.start ?? 0), row.cells.length);
101
+ return lineStart(row.line) + row.start + column;
102
+ };
103
+ const replace = (next, at) => {
104
+ const capped = maxLength !== undefined ? next.slice(0, maxLength) : next;
105
+ onChange(capped);
106
+ setCaret(Math.max(0, Math.min(graphemes(capped).length, at)));
107
+ };
108
+ const insert = (text) => {
109
+ const inserted = graphemes(text);
110
+ replace([...chars.slice(0, position), ...inserted, ...chars.slice(position)].join(''), position + inserted.length);
111
+ };
112
+ useInput((event) => {
113
+ if (disabled)
114
+ return false;
115
+ switch (event.name) {
116
+ case 'left':
117
+ // Off the front of the field is the caller's key, the same way up at
118
+ // the top is - so "left, left, left" walks out of the composer.
119
+ if (position === 0) {
120
+ onEdge?.('start');
121
+ return true;
122
+ }
123
+ setCaret(position - 1);
124
+ return true;
125
+ case 'right':
126
+ if (position === chars.length) {
127
+ onEdge?.('end');
128
+ return true;
129
+ }
130
+ setCaret(position + 1);
131
+ return true;
132
+ case 'home':
133
+ setCaret(lineStart(caretLine));
134
+ return true;
135
+ case 'end':
136
+ setCaret(lineStart(caretLine) + graphemes(lines[caretLine] ?? '').length);
137
+ return true;
138
+ // By *row*, not by line. In a wrapped paragraph moving by line jumps
139
+ // the whole paragraph, and what is above the caret on the screen is
140
+ // the row above it.
141
+ case 'up': {
142
+ // At the top there is nowhere to go inside the field, so the caller
143
+ // gets the key - which is where "the last thing you sent" comes from.
144
+ if (caretRow === 0) {
145
+ onOverflow?.(-1);
146
+ return true;
147
+ }
148
+ setCaret(rowCaret(caretRow - 1));
149
+ return true;
150
+ }
151
+ case 'down': {
152
+ if (caretRow === visual.length - 1) {
153
+ onOverflow?.(1);
154
+ return true;
155
+ }
156
+ setCaret(rowCaret(caretRow + 1));
157
+ return true;
158
+ }
159
+ case 'backspace':
160
+ if (position > 0)
161
+ replace([...chars.slice(0, position - 1), ...chars.slice(position)].join(''), position - 1);
162
+ return true;
163
+ case 'delete':
164
+ if (position < chars.length)
165
+ replace([...chars.slice(0, position), ...chars.slice(position + 1)].join(''), position);
166
+ return true;
167
+ case 'enter':
168
+ // Alt or ctrl says "a line, not a message". `shift+enter` is not in
169
+ // this list on purpose: most terminals cannot tell it from enter, so
170
+ // binding it produces a key that works on one machine.
171
+ if (!onSubmit || event.alt || event.ctrl) {
172
+ insert('\n');
173
+ return true;
174
+ }
175
+ onSubmit(value);
176
+ setCaret(0);
177
+ return true;
178
+ case 'escape':
179
+ onCancel?.();
180
+ return true;
181
+ case 'paste':
182
+ insert(event.char ?? '');
183
+ return true;
184
+ default: break;
185
+ }
186
+ // ctrl+j, for the one case where it is a key of its own: with the kitty
187
+ // protocol on it arrives as `CSI 106;5u` and is named `j`. Without it
188
+ // ctrl+j is 0x0a, the same byte as ctrl+enter, and the case above has
189
+ // already taken it - there is no encoding in which those two differ.
190
+ if (event.name === 'j' && event.ctrl) {
191
+ insert('\n');
192
+ return true;
193
+ }
194
+ if (event.char && !event.ctrl && !event.alt && !event.meta) {
195
+ insert(event.char);
196
+ return true;
197
+ }
198
+ return false;
199
+ }, { focusId: focus.id, enabled: !disabled });
200
+ const shown = visual.slice(first, first + rows);
201
+ // The row count fixes the *content*, on an inner box, and the outer one
202
+ // sizes to it.
203
+ //
204
+ // `height: rows` on the outer box meant the rows and the border had to share
205
+ // one allowance: `<TextArea border="single"/>` came out as a top border and
206
+ // nothing else, because one row is what the field asked for and the border
207
+ // spent it. Nobody saw it because a field without a border looks the same
208
+ // either way, and `TextInput` fixes no height at all.
209
+ return h('box', {
210
+ id: focus.id,
211
+ role: 'textbox',
212
+ label: placeholder,
213
+ direction: 'column',
214
+ ...rest,
215
+ }, h('box', { direction: 'column', height: rows }, value === ''
216
+ // The placeholder stays while the field is focused and empty: it is the
217
+ // only thing on screen saying what enter will do, and hiding it when the
218
+ // caret arrives hides it exactly when it is read.
219
+ ? (() => {
220
+ // The caret sits on the placeholder's first cell rather than in front
221
+ // of it: a caret that pushed the placeholder along moved the one piece
222
+ // of text on the screen every time the field was focused.
223
+ const hint = graphemes(placeholder ?? '');
224
+ const head = hint.length > 0 ? hint[0] : ' ';
225
+ return h('box', { direction: 'row' }, h('text', {
226
+ content: head,
227
+ // The placeholder keeps its own colour and only takes the mark:
228
+ // recolouring it would make the first letter of the hint the one
229
+ // bright thing in an empty field.
230
+ ...(focus.focused && lit ? { ...mark, fg: 'subtle' } : { fg: 'subtle' }),
231
+ }), h('text', { content: hint.slice(1).join(''), fg: 'subtle', flex: 1, truncate: 'end' }));
232
+ })()
233
+ : shown.map((row, i) => {
234
+ const index = first + i;
235
+ const text = row.cells.join('');
236
+ if (index !== caretRow || !focus.focused) {
237
+ return h('text', { key: index, content: text === '' ? ' ' : text, wrap: 'none', truncate: 'end' });
238
+ }
239
+ // The caret's column inside this row. The row has already been broken
240
+ // to the width it will be drawn at, so the split lands where it looks.
241
+ //
242
+ // Three parts, and the middle one is the cell the caret is *on* - not
243
+ // an extra cell between them. Past the last character there is nothing
244
+ // to mark, so a space stands in; that is the one case where the caret
245
+ // adds a column, and it adds it at the end where nothing moves.
246
+ const at = caretColumn - row.start;
247
+ return h('box', { key: index, direction: 'row' }, h('text', { content: row.cells.slice(0, at).join('') }), h('text', { content: row.cells[at] ?? ' ', ...(lit ? mark : {}) }), h('text', { content: row.cells.slice(at + 1).join(''), flex: 1, truncate: 'end' }));
248
+ })));
249
+ });
250
+ /**
251
+ * Where a line breaks, as columns into its own grapheme array.
252
+ *
253
+ * One entry per visual row and always starting at 0, which is what makes a
254
+ * wrapped row addressable: a caret is a character offset, so a wrap that only
255
+ * returned strings could not say which offset a row begins at. `wrapText` in
256
+ * core returns strings and trims each one, and both of those lose the mapping.
257
+ *
258
+ * Nothing is trimmed here for the same reason. A trailing space is a character
259
+ * somebody typed and can delete, so the row it sits on has to contain it -
260
+ * otherwise the caret drifts one cell from the text for the rest of the line.
261
+ *
262
+ * Cells rather than characters throughout: a wide glyph is two columns.
263
+ */
264
+ function breakColumns(cells, width, mode) {
265
+ if (width <= 0 || cells.length === 0)
266
+ return [0];
267
+ const starts = [0];
268
+ let start = 0;
269
+ let used = 0;
270
+ // The last space on the row so far, and -1 once a break has consumed it.
271
+ let space = -1;
272
+ for (let i = 0; i < cells.length; i++) {
273
+ const cell = cells[i];
274
+ const cellWidth = stringWidth(cell);
275
+ if (used + cellWidth > width && i > start) {
276
+ // Break after the last space, or mid-word when the row has none - a word
277
+ // longer than the field has to break somewhere, and `space > start`
278
+ // rather than `>=` keeps a row that begins with a space from being one.
279
+ const at = mode === 'word' && space > start ? space + 1 : i;
280
+ starts.push(at);
281
+ start = at;
282
+ space = -1;
283
+ used = 0;
284
+ for (let j = at; j < i; j++)
285
+ used += stringWidth(cells[j]);
286
+ }
287
+ if (/\s/.test(cell))
288
+ space = i;
289
+ used += cellWidth;
290
+ }
291
+ return starts;
292
+ }
293
+ /** A row's width in cells, which is not its length in characters. */
294
+ function rowWidth(cells) {
295
+ let width = 0;
296
+ for (const cell of cells)
297
+ width += stringWidth(cell);
298
+ return width;
299
+ }
@@ -0,0 +1,10 @@
1
+ import type { TextInputProps } from './shared.js';
2
+ /**
3
+ * A single-line text field.
4
+ *
5
+ * The caret is a real cursor position published to the renderer when the
6
+ * terminal has a cursor, and a drawn glyph when it does not - so a text field
7
+ * still shows where typing lands over a link that swallowed cursor control.
8
+ */
9
+ export declare const TextInput: (props: TextInputProps) => import("@textui/core").RenderOutput;
10
+ //# sourceMappingURL=text-input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-input.d.ts","sourceRoot":"","sources":["../../src/control/text-input.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,gEA+HpB,CAAC"}
@@ -0,0 +1,129 @@
1
+ import { defineComponent, graphemes, h, sliceColumns, stringWidth, useFocus, useInput, useMeasure, useState, useTheme, } from '@textui/core';
2
+ /**
3
+ * A single-line text field.
4
+ *
5
+ * The caret is a real cursor position published to the renderer when the
6
+ * terminal has a cursor, and a drawn glyph when it does not - so a text field
7
+ * still shows where typing lands over a link that swallowed cursor control.
8
+ */
9
+ export const TextInput = defineComponent('TextInput', (props) => {
10
+ const theme = useTheme();
11
+ const { value, onChange, onSubmit, onEdge, placeholder, label, hideLabel, mask, maxLength, autoFocus, search, disabled, focusId, ...rest } = props;
12
+ const inlineLabel = label && !search && !hideLabel ? label : undefined;
13
+ const focus = useFocus({ ...(focusId ? { id: focusId } : {}), disabled, autoFocus });
14
+ const measured = useMeasure();
15
+ const [caret, setCaret] = useState(value.length);
16
+ const [scroll, setScroll] = useState(0);
17
+ const chars = graphemes(value);
18
+ const position = Math.min(caret, chars.length);
19
+ const shown = mask ? mask.repeat(chars.length) : value;
20
+ const empty = shown === '';
21
+ // Everything drawn before the value pushes the caret along with it. Counting
22
+ // the search glyph but not the label is what leaves the caret several cells
23
+ // to the left of where the typing lands.
24
+ const lead = (search ? stringWidth(theme.glyphs.search) + 1 : 0) +
25
+ (inlineLabel ? stringWidth(inlineLabel) + 1 : 0);
26
+ const total = stringWidth(shown);
27
+ const field = measured.width > 0
28
+ ? Math.max(1, measured.width - lead)
29
+ : Math.max(1, total);
30
+ // Cells, not characters: a wide glyph is two columns of caret travel.
31
+ const columnAt = (index) => stringWidth(chars.slice(0, index).join(''));
32
+ const caretColumn = columnAt(position);
33
+ const anchored = Math.max(0, Math.min(scroll, caretColumn, Math.max(0, total - field + 1)));
34
+ const start = caretColumn > anchored + field - 1 ? caretColumn - field + 1 : anchored;
35
+ /**
36
+ * Move the caret and scroll the field just enough to keep it in view.
37
+ *
38
+ * `limit` is the length the value is about to have: `chars` still holds the
39
+ * old one during the render that inserts a character, and clamping against
40
+ * that is what would pin the caret behind every keystroke.
41
+ */
42
+ const move = (next, limit = chars.length) => {
43
+ const clamped = Math.max(0, Math.min(limit, next));
44
+ setCaret(clamped);
45
+ const column = columnAt(clamped);
46
+ if (column < start)
47
+ setScroll(column);
48
+ else if (column > start + field - 1)
49
+ setScroll(column - field + 1);
50
+ };
51
+ useInput((event) => {
52
+ if (disabled)
53
+ return false;
54
+ switch (event.name) {
55
+ // Off either end is the caller's key, the same way it is in a
56
+ // `TextArea`. Reported rather than passed on, because the field is
57
+ // focused and a handler beside it would never see the event.
58
+ case 'left':
59
+ if (position === 0) {
60
+ onEdge?.('start');
61
+ return true;
62
+ }
63
+ move(position - 1);
64
+ return true;
65
+ case 'right':
66
+ if (position === chars.length) {
67
+ onEdge?.('end');
68
+ return true;
69
+ }
70
+ move(position + 1);
71
+ return true;
72
+ case 'home':
73
+ move(0);
74
+ return true;
75
+ case 'end':
76
+ move(chars.length);
77
+ return true;
78
+ case 'backspace':
79
+ if (position > 0) {
80
+ onChange?.([...chars.slice(0, position - 1), ...chars.slice(position)].join(''));
81
+ move(position - 1);
82
+ }
83
+ return true;
84
+ case 'delete':
85
+ if (position < chars.length) {
86
+ onChange?.([...chars.slice(0, position), ...chars.slice(position + 1)].join(''));
87
+ }
88
+ return true;
89
+ case 'enter':
90
+ onSubmit?.(value);
91
+ return true;
92
+ case 'paste': {
93
+ const text = event.char ?? '';
94
+ const inserted = graphemes(text).length;
95
+ const next = [...chars.slice(0, position), ...graphemes(text), ...chars.slice(position)].join('');
96
+ onChange?.(maxLength ? next.slice(0, maxLength) : next);
97
+ move(position + inserted, chars.length + inserted);
98
+ return true;
99
+ }
100
+ default: break;
101
+ }
102
+ // A printable character. Modifiers mean it was a shortcut, not typing.
103
+ if (event.char && !event.ctrl && !event.alt && !event.meta) {
104
+ if (maxLength !== undefined && chars.length >= maxLength)
105
+ return true;
106
+ onChange?.([...chars.slice(0, position), event.char, ...chars.slice(position)].join(''));
107
+ move(position + 1, chars.length + 1);
108
+ return true;
109
+ }
110
+ return false;
111
+ }, { focusId: focus.id });
112
+ return h('box', {
113
+ id: focus.id,
114
+ role: search ? 'searchbox' : 'textbox',
115
+ label,
116
+ direction: 'row',
117
+ gap: 1,
118
+ border: { style: theme.border, color: focus.focused ? 'focus' : 'border' },
119
+ padding: [0, 1],
120
+ cursor: focus.focused ? lead + caretColumn - start : undefined,
121
+ ...rest,
122
+ }, search ? h('text', { content: theme.glyphs.search, fg: 'muted' }) : null, inlineLabel ? h('text', { content: inlineLabel, fg: 'muted' }) : null, h('text', {
123
+ content: empty
124
+ ? sliceColumns(placeholder ?? '', 0, field)
125
+ : sliceColumns(shown, start, field),
126
+ fg: empty ? 'subtle' : disabled ? 'disabled' : undefined,
127
+ flex: 1,
128
+ }));
129
+ });