@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,337 @@
1
+ import type { BoxProps, RenderOutput, ResolvedTheme, SyntaxToken } from '@textui/core';
2
+ import {
3
+ chorded,
4
+ defineComponent,
5
+ expandTabs,
6
+ h,
7
+ nameOf,
8
+ sliceColumns,
9
+ stringWidth,
10
+ useEffect,
11
+ useFocus,
12
+ useHighlight,
13
+ useInput,
14
+ useMeasure,
15
+ useMemo,
16
+ useTheme,
17
+ } from '@textui/core';
18
+ import type { LineMark } from '../decorations.js';
19
+ import { MARK_GLYPH, MARK_TONE, useLineMarks } from '../decorations.js';
20
+ import { usePanelState, usePanelStatus } from '../panel/index.js';
21
+ import { viewportRows } from '../viewport.js';
22
+ import { ScrollThumb } from './scroll-thumb.js';
23
+
24
+ export interface CodeViewerProps extends BoxProps {
25
+ content: string;
26
+ /** Show line numbers in a gutter. */
27
+ lineNumbers?: boolean;
28
+ startLine?: number;
29
+ /**
30
+ * Fix the number of rows. Left off, the viewer renders exactly as many rows
31
+ * as it was laid out into - which is what stops a long file from resizing
32
+ * the pane it is shown in.
33
+ */
34
+ visibleRows?: number;
35
+ /** Rows to mark, 1-based. */
36
+ highlight?: number[];
37
+ /** Pre-tokenised content, when the caller has already highlighted it. */
38
+ tokens?: SyntaxToken[][];
39
+ /** Ask the registry for a highlighter by id... */
40
+ language?: string;
41
+ /** ...or by resource kind... */
42
+ kind?: string;
43
+ /** ...or by filename. */
44
+ uri?: string;
45
+ /** Caret line, 1-based. Controlled when passed. */
46
+ line?: number;
47
+ onLineChange?(line: number): void;
48
+ /** Called whenever the caret or the viewport moves. */
49
+ onPosition?(position: CodeViewerPosition): void;
50
+ /** Draw a scrollbar when the content is taller than the view. */
51
+ scrollbar?: boolean;
52
+ /** Mark the caret line. Off for a plain excerpt. */
53
+ showCaret?: boolean;
54
+ tabWidth?: number;
55
+ /**
56
+ * Take the keyboard on mount.
57
+ *
58
+ * Wanted by a viewer that is the only thing in a dialog: a modal traps focus
59
+ * but does not hand it to anything, so a scrollable body nobody focused is a
60
+ * body nobody can scroll.
61
+ */
62
+ autoFocus?: boolean;
63
+ }
64
+
65
+ export interface CodeViewerPosition {
66
+ /** Caret line, 1-based. */
67
+ line: number;
68
+ /** Leftmost visible column, 0-based. */
69
+ column: number;
70
+ /** First visible line, 1-based. */
71
+ top: number;
72
+ rows: number;
73
+ lines: number;
74
+ /** Longest line, in cells. */
75
+ width: number;
76
+ }
77
+
78
+ /**
79
+ * How far one press of `left` or `right` pans a viewer that cannot fit its
80
+ * widest line. Exported because every viewer that scrolls sideways has to
81
+ * agree - a diff that panned by one column beside a code view that panned by
82
+ * four would be two different keys wearing the same arrow.
83
+ */
84
+ export const HORIZONTAL_STEP = 4;
85
+
86
+ /**
87
+ * A file viewer.
88
+ *
89
+ * Two things make this different from a column of `text` nodes. It renders
90
+ * only the rows it was given room for, so opening a ten-thousand-line file
91
+ * costs the same as opening a ten-line one and neither of them moves the panes
92
+ * around it. And it colours by asking the registry for a highlighter, so a new
93
+ * file type arrives coloured without this component learning what it is.
94
+ */
95
+ export const CodeViewer = defineComponent<CodeViewerProps>('CodeViewer', (props) => {
96
+ const {
97
+ content, lineNumbers = true, startLine = 1, visibleRows, highlight = [],
98
+ tokens, language, kind, uri, line, onLineChange, onPosition,
99
+ scrollbar = true, showCaret = true, tabWidth = 4, disabled, autoFocus, ...rest
100
+ } = props;
101
+
102
+ const theme = useTheme();
103
+ const focus = useFocus({ disabled, ...(autoFocus ? { autoFocus } : {}) });
104
+ const measured = useMeasure();
105
+
106
+ const text = useMemo(() => expandTabs(content, tabWidth), [content, tabWidth]);
107
+ const lines = useMemo(() => text.split('\n'), [text]);
108
+ const auto = useHighlight(tokens ? '' : text, { language, kind, uri });
109
+ const lineTokens = tokens ?? auto;
110
+
111
+ /*
112
+ * Where this viewer is looking belongs to the panel, not to this component.
113
+ *
114
+ * Switching tabs and coming back, or swapping this viewer for an editor on
115
+ * the same file, both unmount it - and both are the moments a reader most
116
+ * expects to land where they left. `line`, `column`, `top` and `left` are
117
+ * the shared vocabulary for a renderer that measures in source lines, so an
118
+ * editor picks up the caret this viewer left behind. Outside a panel the
119
+ * hook is ordinary component state, which is what a viewer in a dialog gets.
120
+ */
121
+ const [view, setView] = usePanelState({ top: 0, left: 0, line: 0 });
122
+ const top = view.top;
123
+ const setTop = (next: number): void => setView({ top: next });
124
+ const left = view.left;
125
+ const setLeft = (next: number): void => setView({ left: next });
126
+ // The record counts from zero, like an index; this component's `line` prop
127
+ // counts from one, like a gutter. The conversion lives here, once.
128
+ const internalLine = view.line + 1;
129
+ const setInternalLine = (next: number): void => setView({ line: next - 1 });
130
+
131
+ const rows = viewportRows(props, measured, lines.length, { requested: visibleRows });
132
+
133
+ /*
134
+ * The same marks the editor draws, for the same reason.
135
+ *
136
+ * "This line changed" is true of the file, not of whether you happen to be
137
+ * editing it - and it lived only in the editor, so turning the setting on
138
+ * while reading did nothing at all and said nothing about why.
139
+ *
140
+ * The column costs a cell only when somebody has actually marked something,
141
+ * so a viewer nobody has decorated is the width it always was.
142
+ */
143
+ const marks = useLineMarks(uri ?? null);
144
+ const markWidth = Object.keys(marks).length > 0 ? 1 : 0;
145
+
146
+ const gutter = lineNumbers ? String(startLine + lines.length - 1).length : 0;
147
+ const bars = scrollbar && lines.length > rows ? 1 : 0;
148
+ const textWidth = Math.max(
149
+ 1,
150
+ (measured.width > 0 ? measured.width : 80) - (lineNumbers ? gutter + 1 : 0) - markWidth - bars,
151
+ );
152
+
153
+ const longest = useMemo(
154
+ () => lines.reduce((max, l) => Math.max(max, stringWidth(l)), 0),
155
+ [lines],
156
+ );
157
+
158
+ const maxTop = Math.max(0, lines.length - rows);
159
+ const maxLeft = Math.max(0, longest - textWidth);
160
+ const caret = clampLine(line ?? internalLine, lines.length);
161
+
162
+ // The caret leads and the viewport follows, so arrowing past the last row
163
+ // scrolls rather than parking the caret out of sight.
164
+ const firstVisible = Math.min(
165
+ Math.max(0, Math.min(top, maxTop)),
166
+ Math.max(0, Math.min(caret - 1, maxTop)),
167
+ );
168
+ const offset = Math.max(firstVisible, Math.min(caret - rows, maxTop));
169
+ const leftColumn = Math.max(0, Math.min(left, maxLeft));
170
+
171
+ const moveTo = (next: number): void => {
172
+ const clamped = clampLine(next, lines.length);
173
+ if (line === undefined) setInternalLine(clamped);
174
+ onLineChange?.(clamped);
175
+ if (clamped - 1 < offset) setTop(clamped - 1);
176
+ else if (clamped > offset + rows) setTop(clamped - rows);
177
+ };
178
+
179
+ const scrollBy = (delta: number): void => {
180
+ const next = Math.max(0, Math.min(maxTop, offset + delta));
181
+ setTop(next);
182
+ // Keep the caret inside the view, the way a pager does.
183
+ const clamped = clampLine(Math.min(Math.max(caret, next + 1), next + rows), lines.length);
184
+ if (line === undefined) setInternalLine(clamped);
185
+ else onLineChange?.(clamped);
186
+ };
187
+
188
+ useInput(
189
+ (event) => {
190
+ if (disabled || chorded(event)) return false;
191
+ switch (event.name) {
192
+ case 'up': moveTo(caret - 1); return true;
193
+ case 'down': moveTo(caret + 1); return true;
194
+ case 'pageup': moveTo(caret - rows); return true;
195
+ case 'pagedown': moveTo(caret + rows); return true;
196
+ case 'home': moveTo(1); return true;
197
+ case 'end': moveTo(lines.length); return true;
198
+ case 'left': setLeft(Math.max(0, leftColumn - HORIZONTAL_STEP)); return true;
199
+ case 'right': setLeft(Math.min(maxLeft, leftColumn + HORIZONTAL_STEP)); return true;
200
+ default: return false;
201
+ }
202
+ },
203
+ { focusId: focus.id },
204
+ );
205
+
206
+ useEffect(() => {
207
+ onPosition?.({
208
+ line: caret,
209
+ column: leftColumn,
210
+ top: offset + 1,
211
+ rows,
212
+ lines: lines.length,
213
+ width: longest,
214
+ });
215
+ }, [caret, leftColumn, offset, rows, lines.length, longest]);
216
+
217
+ /*
218
+ * What this pane says about itself: which file it is showing.
219
+ *
220
+ * A viewer is not an editor and should not pretend to be one - `Ln 12,
221
+ * Col 4` is what the *editor* says, because a caret is a thing you put
222
+ * somewhere and a reader has not put one anywhere. The name is what a
223
+ * reader wants confirmed, particularly in a split where two panes are
224
+ * showing two files and only one of them is focused.
225
+ */
226
+ usePanelStatus(uri !== null && uri !== undefined ? nameOf(uri) : null);
227
+
228
+ const body: RenderOutput[] = [];
229
+ for (let i = 0; i < rows; i++) {
230
+ const index = offset + i;
231
+ if (index >= lines.length) break;
232
+
233
+ const number = startLine + index;
234
+ const marked = highlight.includes(number);
235
+ const onCaret = showCaret && focus.focused && number === caret + startLine - 1;
236
+
237
+ // No `gap` on this row: the gap would land between every token, not only
238
+ // between the gutter and the code, and space out the source by a cell per
239
+ // token. The gutter carries its own trailing space instead.
240
+ body.push(
241
+ h('box', {
242
+ key: number,
243
+ direction: 'row',
244
+ height: 1,
245
+ bg: marked ? 'active' : onCaret ? 'hover' : undefined,
246
+ },
247
+ lineNumbers
248
+ ? h('text', {
249
+ content: `${String(number).padStart(gutter)} `,
250
+ fg: onCaret || marked ? 'text' : 'subtle',
251
+ width: gutter + 1,
252
+ })
253
+ : null,
254
+ // Between the number and the code, which is where the editor puts it
255
+ // too - so swapping a viewer for an editor on the same file does not
256
+ // move the text sideways.
257
+ markWidth > 0
258
+ ? h('text', {
259
+ content: MARK_GLYPH[marks[index] as LineMark] ?? ' ',
260
+ fg: MARK_TONE[marks[index] as LineMark] ?? 'border',
261
+ width: 1,
262
+ })
263
+ : null,
264
+ ...spansFor(
265
+ lineTokens[index] ?? [{ text: lines[index] ?? '', scope: 'plain' }],
266
+ leftColumn,
267
+ textWidth,
268
+ theme,
269
+ ),
270
+ ),
271
+ );
272
+ }
273
+
274
+ return h('box', {
275
+ id: focus.id,
276
+ role: 'document',
277
+ direction: 'row',
278
+ onMouse: (event: { action: string; wheel?: number }) => {
279
+ if (event.action !== 'wheel') return false;
280
+ scrollBy((event.wheel ?? 0) * 3);
281
+ return true;
282
+ },
283
+ ...rest,
284
+ },
285
+ h('box', { direction: 'column', flex: 1 }, ...body),
286
+ bars
287
+ ? h(ScrollThumb, { total: lines.length, rows, offset, focused: focus.focused })
288
+ : null,
289
+ );
290
+ });
291
+
292
+ function clampLine(line: number, total: number): number {
293
+ return Math.max(1, Math.min(total === 0 ? 1 : total, Math.round(line)));
294
+ }
295
+
296
+ /**
297
+ * One `text` node per token, sliced to the visible columns.
298
+ *
299
+ * Slicing here rather than truncating in the layout is what keeps the viewer
300
+ * from claiming the width of its longest line - which is the horizontal half
301
+ * of the same bug that makes a pane resize when a file is opened.
302
+ */
303
+ function spansFor(
304
+ tokens: SyntaxToken[],
305
+ left: number,
306
+ width: number,
307
+ theme: ResolvedTheme,
308
+ ): RenderOutput[] {
309
+ const out: RenderOutput[] = [];
310
+ let column = 0;
311
+ let drawn = 0;
312
+
313
+ for (let i = 0; i < tokens.length && drawn < width; i++) {
314
+ const token = tokens[i] as SyntaxToken;
315
+ const tokenWidth = stringWidth(token.text);
316
+ const end = column + tokenWidth;
317
+
318
+ if (end > left) {
319
+ const from = Math.max(0, left - column);
320
+ const slice = sliceColumns(token.text, from, width - drawn);
321
+ if (slice !== '') {
322
+ out.push(h('text', {
323
+ key: i,
324
+ content: slice,
325
+ fg: token.scope === 'plain' ? undefined : theme.syntax[token.scope],
326
+ }));
327
+ drawn += stringWidth(slice);
328
+ }
329
+ }
330
+ column = end;
331
+ }
332
+
333
+ // The row must still fill its width, or the caret background stops short of
334
+ // the right edge and the highlight looks ragged.
335
+ if (drawn < width) out.push(h('box', { flex: 1 }));
336
+ return out;
337
+ }
@@ -0,0 +1,204 @@
1
+ import type { BoxProps } from '@textui/core';
2
+ import {
3
+ chorded,
4
+ defineComponent,
5
+ h,
6
+ useFocus,
7
+ useInput,
8
+ useMeasure,
9
+ useRef,
10
+ useScrollExtent,
11
+ useState,
12
+ } from '@textui/core';
13
+ import { sizedByLayout } from '../viewport.js';
14
+ import { FeedEntry, FeedScrollbar } from './shared.js';
15
+
16
+ // ---------------------------------------------------------------------- feed
17
+
18
+ export interface FeedProps extends BoxProps {
19
+ /** The entries. Any height each - that is the whole point of this one. */
20
+ children?: unknown;
21
+ /**
22
+ * Stick to the newest entry. Turned off when the reader scrolls up, and back
23
+ * on at the bottom - a feed that yanks itself away is one you cannot read.
24
+ */
25
+ follow?: boolean;
26
+ onFollowChange?(follow: boolean): void;
27
+ /**
28
+ * The cursor, by index. Passed, the caller owns it; omitted, the arrows
29
+ * scroll by line instead, which is what a feed with nothing to activate
30
+ * wants.
31
+ */
32
+ selectedIndex?: number;
33
+ onSelect?(index: number): void;
34
+ onActivate?(index: number): void;
35
+ scrollbar?: boolean;
36
+ focusable?: boolean;
37
+ autoFocus?: boolean;
38
+ /** So a command can send the reader here by name. */
39
+ focusId?: string;
40
+ }
41
+
42
+ /**
43
+ * A viewport over entries of any height.
44
+ *
45
+ * The one between `List` and `ScrollView`, and it is not either of them:
46
+ * `List` is fixed-height rows with a selection, `ScrollView` is a dumb
47
+ * viewport, and a feed is entries whose height is whatever their text wrapped
48
+ * to, with a cursor that moves between them and a tail it follows.
49
+ *
50
+ * Heights are **measured, not computed**. What a paragraph wraps to is decided
51
+ * by the layout, so each entry reports its height after it is laid out and the
52
+ * feed scrolls by summing them. That is one frame behind, which is invisible,
53
+ * and it is the only answer that is not a guess.
54
+ *
55
+ * A transcript, an activity stream, search results with snippets, a diff whose
56
+ * files expand - all the same component, because all of them are "an ordered
57
+ * list of things that are not one line tall".
58
+ */
59
+ export const Feed = defineComponent<FeedProps>('Feed', (props) => {
60
+ const {
61
+ children, follow: followProp, onFollowChange, selectedIndex, onSelect, onActivate,
62
+ scrollbar = true, focusable = true, autoFocus, focusId, id, ...rest
63
+ } = props;
64
+
65
+ const focus = useFocus({
66
+ ...(focusId ? { id: focusId } : {}),
67
+ disabled: !focusable,
68
+ ...(autoFocus ? { autoFocus } : {}),
69
+ });
70
+ const measured = useMeasure();
71
+ const extent = useScrollExtent();
72
+ const heights = useRef<number[]>([]);
73
+
74
+ // The catalog's rule, and this has to follow it like every other viewport:
75
+ // given `flex`, a `height`, a `maxHeight` or a `basis`, the layout decided
76
+ // the size and the feed fills it and scrolls; given none of those, the
77
+ // content decides and it draws everything. Clamping to a measurement in the
78
+ // second case is clamping to how tall it happened to be last frame, which
79
+ // for a box that is sized *by* this content is nothing at all - the entries
80
+ // vanish and the panel is empty.
81
+ const fills = sizedByLayout(props) && measured.height > 0;
82
+
83
+ // Null is "at the tail", rather than a boolean beside a number: the two can
84
+ // disagree, and a follow flag that says yes while the offset says otherwise
85
+ // is how a log ends up frozen three lines from the bottom.
86
+ const [internalTop, setInternalTop] = useState<number | null>(null);
87
+ const [internalIndex, setInternalIndex] = useState(0);
88
+
89
+ const entries = (Array.isArray(children) ? children : [children]).filter((c) => c != null);
90
+ const count = entries.length;
91
+ const selects = onSelect !== undefined || selectedIndex !== undefined;
92
+ const index = Math.max(0, Math.min(count - 1, selectedIndex ?? internalIndex));
93
+
94
+ const total = extent?.height ?? 0;
95
+ const limit = Math.max(0, total - measured.height);
96
+ const following = followProp ?? (internalTop === null);
97
+ const top = following ? limit : Math.min(internalTop ?? 0, limit);
98
+
99
+ const scrollTo = (next: number | null): void => {
100
+ if (next === null) {
101
+ setInternalTop(null);
102
+ onFollowChange?.(true);
103
+ return;
104
+ }
105
+ const clamped = Math.max(0, Math.min(next, limit));
106
+ setInternalTop(clamped);
107
+ // Scrolled back to the bottom is following again, without a second key.
108
+ onFollowChange?.(clamped >= limit);
109
+ };
110
+
111
+ /** Where an entry starts, from what the layout measured last frame. */
112
+ const startOf = (target: number): number => {
113
+ let y = 0;
114
+ for (let i = 0; i < target && i < heights.current.length; i++) y += (heights.current[i] ?? 1);
115
+ return y;
116
+ };
117
+
118
+ const reveal = (target: number): void => {
119
+ const start = startOf(target);
120
+ const height = heights.current[target] ?? 1;
121
+ const view = Math.max(1, measured.height);
122
+ if (start < top) scrollTo(start);
123
+ else if (start + height > top + view) scrollTo(start + height - view);
124
+ else scrollTo(top);
125
+ };
126
+
127
+ const move = (delta: number): void => {
128
+ if (count === 0) return;
129
+ const next = Math.max(0, Math.min(count - 1, index + delta));
130
+ if (selectedIndex === undefined) setInternalIndex(next);
131
+ onSelect?.(next);
132
+ reveal(next);
133
+ };
134
+
135
+ useInput(
136
+ (event) => {
137
+ const page = Math.max(1, measured.height - 2);
138
+ if (chorded(event)) return false;
139
+ switch (event.name) {
140
+ case 'up': case 'k':
141
+ if (selects) move(-1); else scrollTo(top - 1);
142
+ return true;
143
+ case 'down': case 'j':
144
+ if (selects) move(1); else scrollTo(top + 1);
145
+ return true;
146
+ case 'pageup': scrollTo(top - page); return true;
147
+ case 'pagedown': scrollTo(top + page); return true;
148
+ case 'home': case 'g':
149
+ scrollTo(0);
150
+ if (selects) { if (selectedIndex === undefined) setInternalIndex(0); onSelect?.(0); }
151
+ return true;
152
+ case 'end': case 'G':
153
+ scrollTo(null);
154
+ if (selects && count > 0) {
155
+ if (selectedIndex === undefined) setInternalIndex(count - 1);
156
+ onSelect?.(count - 1);
157
+ }
158
+ return true;
159
+ case 'f': scrollTo(following ? top : null); return true;
160
+ case 'enter': case 'space':
161
+ if (onActivate && count > 0) { onActivate(index); return true; }
162
+ return false;
163
+ default: return false;
164
+ }
165
+ },
166
+ { focusId: focus.id, enabled: focusable },
167
+ );
168
+
169
+ const drawn = entries.map((entry, i) => h(FeedEntry, {
170
+ key: i,
171
+ onHeight: (height: number) => { heights.current[i] = height; },
172
+ }, entry));
173
+
174
+ // Content-sized: draw everything and let the box grow. Clamping to a
175
+ // measurement here would clamp to how tall this happened to be last frame,
176
+ // which for a box sized *by* its own content is nothing at all - the entries
177
+ // vanish and the panel is empty.
178
+ if (!fills) {
179
+ return h('box', {
180
+ id: id ?? focus.id,
181
+ role: 'list',
182
+ ...rest,
183
+ direction: 'column',
184
+ }, ...drawn);
185
+ }
186
+
187
+ return h('box', {
188
+ id: id ?? focus.id,
189
+ role: 'list',
190
+ ...rest,
191
+ direction: 'row',
192
+ overflow: 'hidden',
193
+ onMouse: (event: { action: string; wheel?: number }) => {
194
+ if (event.action !== 'wheel') return false;
195
+ scrollTo(top + (event.wheel ?? 0) * 3);
196
+ return true;
197
+ },
198
+ },
199
+ h('box', { flex: 1, direction: 'column', scrollTop: top, overflow: 'scroll' }, ...drawn),
200
+ // Only when there is somewhere to scroll. A track down the side of a feed
201
+ // that fits is chrome that states something untrue.
202
+ scrollbar && limit > 0 ? h(FeedScrollbar, {}) : null,
203
+ );
204
+ });