@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,485 @@
1
+ import type { ArgChoice, ArgSpec, BoxProps, CommandDefinition, TextUIApp } from '@textui/core';
2
+ import {
3
+ defineComponent,
4
+ h,
5
+ useEffect,
6
+ useFocusScope,
7
+ useInput,
8
+ useRuntime,
9
+ useState,
10
+ useTheme,
11
+ } from '@textui/core';
12
+ import { TextInput } from '../control/index.js';
13
+ import type { MenuItem } from '../navigation/index.js';
14
+ import { Menu } from '../navigation/index.js';
15
+ import { hint } from './shared.js';
16
+
17
+ export interface CommandPaletteProps extends BoxProps {
18
+ /**
19
+ * Rows to search. Defaults to every enabled command in the `palette` slot.
20
+ *
21
+ * A function is re-read every render, which is what a list of switches
22
+ * needs: after one is flipped the row has to show its new state, and a
23
+ * snapshot taken when the palette opened cannot.
24
+ */
25
+ commands?: CommandDefinition[] | (() => CommandDefinition[]);
26
+ placeholder?: string;
27
+ /** Notified after a command runs. The palette runs it itself. */
28
+ onRun?(id: string, args?: Record<string, unknown>): void;
29
+ onClose?(): void;
30
+ /** Off makes this a picker: it reports the choice and runs nothing. */
31
+ execute?: boolean;
32
+ /** Group the list by `category`, with a rule between groups. */
33
+ grouped?: boolean;
34
+ visibleRows?: number;
35
+ width?: number;
36
+ /**
37
+ * Open already drilled into this command's choices.
38
+ *
39
+ * For a caller that has decided *which* question is being asked and only
40
+ * wants the palette to ask it - a menu item for "Theme" should offer the
41
+ * themes, not the whole command list with "Theme" typed into the search box.
42
+ */
43
+ openAt?: string;
44
+ }
45
+
46
+ /**
47
+ * The command palette.
48
+ *
49
+ * It searches the command registry rather than a list someone maintained, so
50
+ * a command registered anywhere is reachable here the moment it exists - which
51
+ * is the payoff for insisting actions are commands. It also *runs* what it
52
+ * finds, so choosing "Open dialog" here and pressing the button that opens a
53
+ * dialog are the same act, reaching the same code.
54
+ *
55
+ * A command that declares an argument with `choices` gets a second level:
56
+ * choosing it lists the choices, and picking one runs the command with it.
57
+ * That is where sub-items come from - the command says what it needs and the
58
+ * palette asks, rather than every caller inventing its own submenu.
59
+ */
60
+ export const CommandPalette = defineComponent<CommandPaletteProps>('CommandPalette', (props) => {
61
+ const theme = useTheme();
62
+ const runtime = useRuntime();
63
+ const {
64
+ commands, placeholder, onRun, onClose, execute = true,
65
+ grouped = true, visibleRows = 8, width = 60, openAt, ...rest
66
+ } = props;
67
+
68
+ const [query, setQuery] = useState('');
69
+ const [highlight, setHighlight] = useState(0);
70
+ /** The command being asked about, when the palette has drilled in. */
71
+ const [pending, setPending] = useState<{
72
+ command: CommandDefinition;
73
+ arg: ArgSpec;
74
+ /** Answers given so far, for a command that asks for more than one. */
75
+ collected: Record<string, unknown>;
76
+ } | null>(null);
77
+ const [choices, setChoices] = useState<ArgChoice[]>([]);
78
+ /**
79
+ * Whether the answer to "what may I choose" is still on its way.
80
+ *
81
+ * An empty list is a real answer - a host advertises a harness and no models
82
+ * until somebody has signed into it - and it is not the same answer as "the
83
+ * request is in flight". Both draw an empty menu, so without this the panel
84
+ * that will never have anything in it and the one that is about to look
85
+ * identical, and neither of them says so.
86
+ */
87
+ const [asking, setAsking] = useState(false);
88
+ /** Bumped after a row that stays, so a `commands` function is read again. */
89
+ const [, setRefresh] = useState(0);
90
+ // The search field asks for focus itself. See `Dialog`.
91
+ useFocusScope({ trap: true, restore: true });
92
+
93
+ const app = runtime.app();
94
+ const given = typeof commands === 'function' ? commands() : commands;
95
+ const all = given ?? app?.commands.list({ slot: 'palette', enabledOnly: true }) ?? [];
96
+
97
+ const matches = pending ? [] : filterCommands(all, query);
98
+ const offered = pending ? filterChoices(choices, query) : [];
99
+ const rows = pending ? offered.map((choice) => choice.value) : matches.map((c) => c.id);
100
+ const index = Math.max(0, Math.min(highlight, rows.length - 1));
101
+
102
+ const items: MenuItem[] = pending
103
+ ? (offered.length > 0
104
+ ? offered.map((choice) => ({
105
+ id: choice.value,
106
+ label: choice.label ?? choice.value,
107
+ ...(choice.icon ? { icon: choice.icon } : {}),
108
+ // What it means, when the choice says; "default" otherwise, which is
109
+ // the only thing the palette itself knows about a value.
110
+ ...(choice.description
111
+ ? { description: choice.description }
112
+ : pending.arg.default === choice.value ? { description: 'default' } : {}),
113
+ }))
114
+ // One row, saying which kind of nothing this is. A disabled item rather
115
+ // than no items: the panel keeps its height, so the answer arriving does
116
+ // not move everything under it.
117
+ : [{
118
+ id: '',
119
+ label: asking
120
+ ? `Asking${theme.glyphs.ellipsis}`
121
+ : (query === '' ? 'Nothing to choose' : 'No match'),
122
+ disabled: true,
123
+ }])
124
+ : matches.map((command, i) => ({
125
+ id: command.id,
126
+ label: command.title,
127
+ // `badge` when the row has state to report, the category otherwise.
128
+ // The icon stays put either way: it is what the row *is*.
129
+ description: command.badge ?? command.category,
130
+ icon: command.icon,
131
+ // A row may stand for a command registered under another id, and the
132
+ // key a person would press belongs to that one.
133
+ shortcut: command.shortcut ?? app?.keybindings.forCommand(command.id)[0],
134
+ // A chevron, from `Menu`, for anything that will ask a question.
135
+ children: argumentOf(command) ? [] : undefined,
136
+ separatorBefore:
137
+ grouped && i > 0 && (matches[i - 1] as CommandDefinition).category !== command.category,
138
+ }));
139
+
140
+ const back = (): void => {
141
+ // `null` is "never mind": whatever the preview did gets undone by whoever
142
+ // did it, because only the command knows what it changed.
143
+ pending?.arg.preview?.(null);
144
+ setPending(null);
145
+ setChoices([]);
146
+ setQuery('');
147
+ setHighlight(0);
148
+ };
149
+
150
+ const finish = (id: string, args?: Record<string, unknown>): void => {
151
+ // A row that only flips something stays: closing after every switch means
152
+ // reopening the list to reach the next one, which is the wrong answer for
153
+ // a list of switches and the right one for a list of actions. The command
154
+ // says which it is.
155
+ const definition = given?.find((c) => c.id === id) ?? app?.commands.get(id);
156
+ const dismiss = definition?.keepOpen !== true;
157
+
158
+ // Close first: the command may open a layer of its own, and the palette
159
+ // should be gone by the time it does rather than sitting underneath it.
160
+ if (dismiss) onClose?.();
161
+
162
+ if (execute) {
163
+ // A caller may hand the palette rows the registry has never seen - a
164
+ // list built for one screen, a switch per surface the running shell
165
+ // happens to have. Running those through `execute` would find nothing
166
+ // and silently do nothing, so a definition that came in through `commands`
167
+ // and is not registered is run directly.
168
+ const local = given?.find((c) => c.id === id);
169
+ if (local && !app?.commands.get(id)) {
170
+ void local.run?.(args ?? {}, {
171
+ app: app as TextUIApp,
172
+ store: runtime.store,
173
+ args: args ?? {},
174
+ } as never);
175
+ } else {
176
+ void app?.execute(id, args, 'palette');
177
+ }
178
+ }
179
+ // A row that stayed just changed something the list is describing, so the
180
+ // list has to be asked again. Nothing else will invalidate this component:
181
+ // it is not subscribed to whatever the row touched, and should not be.
182
+ if (!dismiss) setRefresh((n) => n + 1);
183
+
184
+ onRun?.(id, args);
185
+ };
186
+
187
+ /** Ask about one command. `choices` may be a function, and may be async. */
188
+ const drillInto = (
189
+ command: CommandDefinition,
190
+ arg: ArgSpec,
191
+ collected: Record<string, unknown> = {},
192
+ ): void => {
193
+ const resolved = typeof arg.choices === 'function' ? arg.choices() : arg.choices ?? [];
194
+ setPending({ command, arg, collected });
195
+ setQuery('');
196
+ setHighlight(0);
197
+ if (Array.isArray(resolved)) {
198
+ setAsking(false);
199
+ setChoices(resolved.map(asChoice));
200
+ return;
201
+ }
202
+ setChoices([]);
203
+ setAsking(true);
204
+ // Answered either way: a `choices` function that rejects leaves the panel
205
+ // saying "nothing to choose", which is true of what it can offer.
206
+ void resolved
207
+ .then((list) => setChoices(list.map(asChoice)))
208
+ .catch(() => setChoices([]))
209
+ .finally(() => setAsking(false));
210
+ };
211
+
212
+ /**
213
+ * Answer one argument, then ask about the next one or run it.
214
+ *
215
+ * A command with two things to ask about used to have the first one
216
+ * collected and the second one skipped, which meant running it with an
217
+ * argument it said was required - and `execute` refuses that, loudly.
218
+ */
219
+ const answer = (value: unknown): void => {
220
+ if (!pending) return;
221
+ const collected = { ...pending.collected, [pending.arg.name]: value };
222
+ const next = argumentOf(pending.command, collected);
223
+ if (next) {
224
+ drillInto(pending.command, next, collected);
225
+ return;
226
+ }
227
+ finish(pending.command.id, collected);
228
+ };
229
+
230
+ const choose = (): void => {
231
+ if (pending) {
232
+ // An argument with choices is answered by picking one; an argument
233
+ // without any is answered by typing it. The palette already has a field
234
+ // and a list, so it is the same overlay either way - and without this a
235
+ // command that asks for a name could be *opened* and never *answered*.
236
+ if (pending.arg.choices === undefined) {
237
+ const typed = query.trim();
238
+ // Nothing typed is not an answer. Running anyway hands the command an
239
+ // empty required argument, which is the error it exists to prevent.
240
+ if (typed === '') return;
241
+ answer(typed);
242
+ return;
243
+ }
244
+ const value = rows[index];
245
+ if (value === undefined) return;
246
+ answer(value);
247
+ return;
248
+ }
249
+
250
+ const command = matches[index];
251
+ if (!command) return;
252
+
253
+ const arg = argumentOf(command);
254
+ if (!arg) {
255
+ finish(command.id);
256
+ return;
257
+ }
258
+
259
+ drillInto(command, arg);
260
+ };
261
+
262
+ // Opening at a command is the same act as choosing it, minus the choosing.
263
+ useEffect(() => {
264
+ if (!openAt) return;
265
+ const command = all.find((c) => c.id === openAt) ?? app?.commands.get(openAt);
266
+ const arg = command ? argumentOf(command) : undefined;
267
+ if (command && arg) drillInto(command, arg);
268
+ }, [openAt]);
269
+
270
+ // The field owns typing; the list owns up, down and enter. Without this
271
+ // split, Enter submits the search box and the highlighted command is
272
+ // never the thing that runs.
273
+ useInput(
274
+ (event) => {
275
+ if (event.name === 'escape') {
276
+ // Opened at a command, there is nothing behind the question that
277
+ // anybody chose to be at: the caller drilled in on their behalf, and
278
+ // the level underneath is a list of one. Backing out to it reads as
279
+ // "escape did nothing", and the second escape - the one that would
280
+ // close it - is spent leaving a screen nobody asked to see.
281
+ if (pending && !openAt) { back(); return true; }
282
+ // Whatever a highlighted choice previewed has to be put back on the
283
+ // way out, and `null` is how the command is told to undo it - only it
284
+ // knows what it changed. `back()` did that, so closing instead of
285
+ // backing has to do it too, or escaping a theme picker leaves the
286
+ // theme it was merely showing you.
287
+ pending?.arg.preview?.(null);
288
+ onClose?.();
289
+ return true;
290
+ }
291
+ if (event.name === 'left' && pending && query === '' && !openAt) { back(); return true; }
292
+ // Wrapping, both ways. A list you can walk off the end of makes you
293
+ // check where you are before every press; one that comes round means the
294
+ // last item is one key from the first.
295
+ const wrap = (n: number): number =>
296
+ rows.length === 0 ? 0 : (n + rows.length) % rows.length;
297
+ const move = (to: number): true => {
298
+ setHighlight(to);
299
+ // Show what the choice would do while it is merely highlighted. The
300
+ // command put the `preview` there; the palette only reports movement.
301
+ if (pending) pending.arg.preview?.(rows[to] ?? null);
302
+ return true;
303
+ };
304
+ if (event.name === 'up') return move(wrap(index - 1));
305
+ if (event.name === 'down') return move(wrap(index + 1));
306
+ return false;
307
+ },
308
+ { global: true },
309
+ );
310
+
311
+ const highlighted = pending ? undefined : matches[index];
312
+ const move = `${theme.glyphs.arrowUp}${theme.glyphs.arrowDown} move`;
313
+ // What the highlighted row is, in full.
314
+ //
315
+ // The rows themselves have to fit, so a choice's own sentence is truncated
316
+ // there - and a sentence about what a mode does is exactly the thing a
317
+ // person needs whole. So the line under the list follows the highlight: the
318
+ // choice's description while there is one, the question's otherwise.
319
+ const chosen = pending ? offered[index] : undefined;
320
+ const detail = pending
321
+ ? chosen?.description
322
+ ?? pending.arg.description ?? `${pending.command.title} needs a ${pending.arg.name}`
323
+ : highlighted?.description ?? highlighted?.id ?? '';
324
+
325
+ return h('box', {
326
+ role: 'dialog',
327
+ label: 'Commands',
328
+ border: theme.border,
329
+ bg: 'overlay',
330
+ width,
331
+ direction: 'column',
332
+ // A border is a gutter as well as a line. Without one - `paper` sets
333
+ // `border: 'none'` - the rows run flush to the panel edge and the last
334
+ // character sits against whatever is behind it.
335
+ ...(theme.border === 'none' ? { padding: { left: 1, right: 1 } } : {}),
336
+ // The crumb names the command, not its argument. "commands › Theme" is
337
+ // where you are; "commands › id" is what the parameter happens to be
338
+ // called, which is the author's business rather than the reader's.
339
+ title: pending
340
+ ? ` commands ${theme.glyphs.breadcrumb} ${pending.command.title} `
341
+ : ' commands ',
342
+ ...rest,
343
+ },
344
+ h(TextInput, {
345
+ value: query,
346
+ onChange: (next: string) => {
347
+ setQuery(next);
348
+ setHighlight(0);
349
+ },
350
+ onSubmit: choose,
351
+ /*
352
+ * `right` drills into a command's choices, which the hint row has been
353
+ * advertising as `> sub-items` all along.
354
+ *
355
+ * It used to be a case in the handler above, and that handler is global
356
+ * - so it ran only if the focused field declined the key first, and a
357
+ * single-line field never declines `right`. The key did nothing and the
358
+ * footer said it did something. `onEdge` is how a focused field hands
359
+ * a key back.
360
+ */
361
+ onEdge: (edge: 'start' | 'end') => {
362
+ if (edge !== 'end' || pending) return;
363
+ const command = matches[index];
364
+ if (command && argumentOf(command)) choose();
365
+ },
366
+ placeholder: pending
367
+ ? (pending.arg.description ?? (pending.arg.choices === undefined
368
+ ? `${pending.command.title}${theme.glyphs.ellipsis}`
369
+ : `Choose ${pending.command.title.toLowerCase()}${theme.glyphs.ellipsis}`))
370
+ : (placeholder ?? `Type a command${theme.glyphs.ellipsis}`),
371
+ search: true,
372
+ autoFocus: true,
373
+ border: 'none',
374
+ }),
375
+ h('box', { height: 1, fill: theme.borderChars().top, fg: 'borderSubtle' }),
376
+ h(Menu, {
377
+ items,
378
+ visibleRows,
379
+ interactive: false,
380
+ activeId: rows[index],
381
+ onSelect: (id: string) => {
382
+ const at = rows.indexOf(id);
383
+ if (at >= 0) setHighlight(at);
384
+ choose();
385
+ },
386
+ }),
387
+ h('box', { height: 1, fill: theme.borderChars().top, fg: 'borderSubtle' }),
388
+ // What the highlighted row actually is, and how to move around. A palette
389
+ // that shows only titles makes you run something to find out what it does.
390
+ h('box', { direction: 'row', gap: 1 },
391
+ h('text', { content: detail, fg: 'muted', flex: 1, truncate: 'end' }),
392
+ h('text', { content: `${rows.length}`, fg: 'subtle' })),
393
+ h('box', { direction: 'row', gap: 1 },
394
+ h('text', {
395
+ content: pending
396
+ ? (pending.arg.choices === undefined
397
+ ? hint(theme, ['type it', 'enter confirm', 'esc back'])
398
+ // Nothing to choose is nothing to press enter on, and offering it
399
+ // is how a panel reads as broken rather than as empty.
400
+ : hint(theme, rows.length > 0 ? [move, 'enter choose', 'esc back'] : ['esc back']))
401
+ : hint(theme, [move, 'enter run', `${theme.glyphs.chevronRight} sub-items`, 'esc close']),
402
+ fg: 'subtle',
403
+ truncate: 'end',
404
+ })),
405
+ );
406
+ });
407
+
408
+ /**
409
+ * The argument the palette should ask about, if there is one.
410
+ *
411
+ * A fixed set of choices, or something required that nothing has supplied.
412
+ * Only the first kind used to count, which meant a command declaring "I need a
413
+ * title" ran with no title and did nothing - the one failure mode that looks
414
+ * exactly like a broken key.
415
+ *
416
+ * Exported because "will this command ask something?" is a question other
417
+ * chrome has to answer too - a menu row wants a chevron and wants to hand off
418
+ * rather than run - and the menu bar had written its own version that
419
+ * *called* the `choices` function to find out. A function is allowed to be
420
+ * async, so that answer was sometimes a promise, and the copy went on to call
421
+ * `.map` on it. Whether a command will ask is a property of its declaration;
422
+ * nothing has to be resolved to know it.
423
+ */
424
+ export function argumentOf(
425
+ command: CommandDefinition,
426
+ collected: Record<string, unknown> = {},
427
+ ): ArgSpec | undefined {
428
+ return (command.args ?? []).find(
429
+ (arg) => collected[arg.name] === undefined &&
430
+ (arg.choices !== undefined || (arg.required === true && arg.default === undefined)),
431
+ );
432
+ }
433
+
434
+ /** The short form and the long one, as one shape. */
435
+ function asChoice(choice: string | ArgChoice): ArgChoice {
436
+ return typeof choice === 'string' ? { value: choice } : choice;
437
+ }
438
+
439
+ /**
440
+ * Filter on everything a person can see.
441
+ *
442
+ * The label, because that is what they are reading; the value, because a host
443
+ * id is often what somebody types from memory; and the description, because
444
+ * "asks before editing" is how you find a mode whose label is "Default".
445
+ */
446
+ function filterChoices(choices: ArgChoice[], query: string): ArgChoice[] {
447
+ const q = query.trim().toLowerCase();
448
+ if (q === '') return choices;
449
+ return choices.filter((choice) => `${choice.label ?? ''} ${choice.value} ${choice.description ?? ''}`
450
+ .toLowerCase().includes(q));
451
+ }
452
+
453
+ export function filterCommands(commands: CommandDefinition[], query: string): CommandDefinition[] {
454
+ const q = query.trim().toLowerCase();
455
+ if (q === '') return commands;
456
+
457
+ const scored: { command: CommandDefinition; score: number }[] = [];
458
+ for (const command of commands) {
459
+ const haystacks = [command.title, command.id, ...(command.keywords ?? [])];
460
+ let best = -1;
461
+ for (const haystack of haystacks) {
462
+ const score = subsequenceScore(haystack.toLowerCase(), q);
463
+ if (score > best) best = score;
464
+ }
465
+ if (best >= 0) scored.push({ command, score: best });
466
+ }
467
+ return scored.sort((a, b) => b.score - a.score).map((s) => s.command);
468
+ }
469
+
470
+ function subsequenceScore(haystack: string, needle: string): number {
471
+ if (haystack.startsWith(needle)) return 1000 - haystack.length;
472
+ if (haystack.includes(needle)) return 500 - haystack.indexOf(needle);
473
+
474
+ let hi = 0;
475
+ let score = 0;
476
+ let streak = 0;
477
+ for (const char of needle) {
478
+ const found = haystack.indexOf(char, hi);
479
+ if (found === -1) return -1;
480
+ streak = found === hi ? streak + 1 : 0;
481
+ score += 10 + streak * 5 - (found - hi);
482
+ hi = found + 1;
483
+ }
484
+ return score;
485
+ }
@@ -0,0 +1,65 @@
1
+ import type { BoxProps, SemanticVariant } from '@textui/core';
2
+ import { defineComponent, h, useFocusScope, useInput, useTheme } from '@textui/core';
3
+ import { Button } from '../control/index.js';
4
+
5
+ export interface DialogProps extends BoxProps {
6
+ title?: string;
7
+ /** Buttons along the bottom. The first is the default action. */
8
+ actions?: { id: string; label: string; tone?: SemanticVariant; onPress?(): void }[];
9
+ onClose?(): void;
10
+ /** Cells wide. The dialog centres itself in whatever is left. */
11
+ width?: number;
12
+ }
13
+
14
+ export const Dialog = defineComponent<DialogProps>('Dialog', (props) => {
15
+ const theme = useTheme();
16
+ const { title, actions = [], onClose, children, width = 50, ...rest } = props;
17
+
18
+ // No `autoFocus` on the scope: a dialog's own controls say which of them
19
+ // wants focus - the first action, or the field in a prompt - and the scope
20
+ // taking it first would hand it to whichever box happened to register
21
+ // earliest. The flag never fired before, because a scope is empty when it
22
+ // is activated; now that it works, it has to mean what it says.
23
+ useFocusScope({ trap: true, restore: true });
24
+
25
+ // Only consume escape when there is something to do with it. Consuming it
26
+ // regardless would stop the layer manager dismissing a dialog that was
27
+ // opened without an `onClose` - which is most of them.
28
+ useInput(
29
+ (event) => {
30
+ if (event.name !== 'escape' || !onClose) return false;
31
+ onClose();
32
+ return true;
33
+ },
34
+ { global: true },
35
+ );
36
+
37
+ return h('box', {
38
+ role: 'dialog',
39
+ label: title,
40
+ border: theme.border,
41
+ bg: 'overlay',
42
+ padding: [0, 1],
43
+ width,
44
+ direction: 'column',
45
+ title,
46
+ ...rest,
47
+ },
48
+ h('box', { direction: 'column', flex: 1 }, children),
49
+ actions.length > 0
50
+ ? h('box', { direction: 'row', gap: 1, justify: 'end' },
51
+ ...actions.map((action, i) =>
52
+ h(Button, {
53
+ key: action.id,
54
+ label: action.label,
55
+ tone: action.tone,
56
+ // Every action is a line at rest; the focused one fills. Making
57
+ // the default action solid as well meant two filled buttons the
58
+ // moment focus moved, and no way to tell which one Enter meant.
59
+ variant: 'outline',
60
+ autoFocus: i === 0,
61
+ onPress: action.onPress,
62
+ })))
63
+ : null,
64
+ );
65
+ });