termcast 1.3.10 → 1.3.18

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 (500) hide show
  1. package/dist/apis/toast.d.ts +1 -5
  2. package/dist/apis/toast.d.ts.map +1 -1
  3. package/dist/apis/toast.js +23 -17
  4. package/dist/apis/toast.js.map +1 -1
  5. package/dist/build.d.ts +2 -0
  6. package/dist/build.d.ts.map +1 -1
  7. package/dist/build.js +4 -3
  8. package/dist/build.js.map +1 -1
  9. package/dist/cli.js +42 -0
  10. package/dist/cli.js.map +1 -1
  11. package/dist/colors.d.ts +8 -1
  12. package/dist/colors.d.ts.map +1 -1
  13. package/dist/colors.js +12 -0
  14. package/dist/colors.js.map +1 -1
  15. package/dist/compile.d.ts +30 -0
  16. package/dist/compile.d.ts.map +1 -0
  17. package/dist/compile.js +156 -0
  18. package/dist/compile.js.map +1 -0
  19. package/dist/components/actions.d.ts +1 -0
  20. package/dist/components/actions.d.ts.map +1 -1
  21. package/dist/components/actions.js +5 -2
  22. package/dist/components/actions.js.map +1 -1
  23. package/dist/components/command-arguments.d.ts +9 -0
  24. package/dist/components/command-arguments.d.ts.map +1 -0
  25. package/dist/components/command-arguments.js +21 -0
  26. package/dist/components/command-arguments.js.map +1 -0
  27. package/dist/components/detail.d.ts +1 -1
  28. package/dist/components/detail.d.ts.map +1 -1
  29. package/dist/components/detail.js +17 -39
  30. package/dist/components/detail.js.map +1 -1
  31. package/dist/components/dropdown.d.ts +1 -0
  32. package/dist/components/dropdown.d.ts.map +1 -1
  33. package/dist/components/dropdown.js +77 -21
  34. package/dist/components/dropdown.js.map +1 -1
  35. package/dist/components/extension-preferences.d.ts.map +1 -1
  36. package/dist/components/extension-preferences.js +19 -29
  37. package/dist/components/extension-preferences.js.map +1 -1
  38. package/dist/components/form/checkbox.d.ts.map +1 -1
  39. package/dist/components/form/checkbox.js +9 -2
  40. package/dist/components/form/checkbox.js.map +1 -1
  41. package/dist/components/form/date-picker.d.ts.map +1 -1
  42. package/dist/components/form/date-picker.js +15 -28
  43. package/dist/components/form/date-picker.js.map +1 -1
  44. package/dist/components/form/description.d.ts +2 -0
  45. package/dist/components/form/description.d.ts.map +1 -1
  46. package/dist/components/form/description.js +20 -2
  47. package/dist/components/form/description.js.map +1 -1
  48. package/dist/components/form/dropdown.d.ts.map +1 -1
  49. package/dist/components/form/dropdown.js +13 -28
  50. package/dist/components/form/dropdown.js.map +1 -1
  51. package/dist/components/form/file-autocomplete.d.ts +7 -4
  52. package/dist/components/form/file-autocomplete.d.ts.map +1 -1
  53. package/dist/components/form/file-autocomplete.js +54 -46
  54. package/dist/components/form/file-autocomplete.js.map +1 -1
  55. package/dist/components/form/file-picker.d.ts +5 -0
  56. package/dist/components/form/file-picker.d.ts.map +1 -1
  57. package/dist/components/form/file-picker.js +46 -49
  58. package/dist/components/form/file-picker.js.map +1 -1
  59. package/dist/components/form/form-ref.d.ts +43 -0
  60. package/dist/components/form/form-ref.d.ts.map +1 -0
  61. package/dist/components/form/form-ref.js +53 -0
  62. package/dist/components/form/form-ref.js.map +1 -0
  63. package/dist/components/form/index.d.ts +16 -0
  64. package/dist/components/form/index.d.ts.map +1 -1
  65. package/dist/components/form/index.js +86 -23
  66. package/dist/components/form/index.js.map +1 -1
  67. package/dist/components/form/password-field.d.ts.map +1 -1
  68. package/dist/components/form/password-field.js +32 -16
  69. package/dist/components/form/password-field.js.map +1 -1
  70. package/dist/components/form/text-area.d.ts.map +1 -1
  71. package/dist/components/form/text-area.js +32 -15
  72. package/dist/components/form/text-area.js.map +1 -1
  73. package/dist/components/form/text-field.d.ts.map +1 -1
  74. package/dist/components/form/text-field.js +37 -17
  75. package/dist/components/form/text-field.js.map +1 -1
  76. package/dist/components/form/use-form-navigation.d.ts +4 -0
  77. package/dist/components/form/use-form-navigation.d.ts.map +1 -1
  78. package/dist/components/form/use-form-navigation.js +35 -18
  79. package/dist/components/form/use-form-navigation.js.map +1 -1
  80. package/dist/components/form/with-left-border.d.ts.map +1 -1
  81. package/dist/components/form/with-left-border.js +2 -2
  82. package/dist/components/form/with-left-border.js.map +1 -1
  83. package/dist/components/icon.d.ts +3 -1
  84. package/dist/components/icon.d.ts.map +1 -1
  85. package/dist/components/icon.js +494 -469
  86. package/dist/components/icon.js.map +1 -1
  87. package/dist/components/list.d.ts +11 -7
  88. package/dist/components/list.d.ts.map +1 -1
  89. package/dist/components/list.js +170 -38
  90. package/dist/components/list.js.map +1 -1
  91. package/dist/components/loading-bar.js +1 -1
  92. package/dist/components/loading-bar.js.map +1 -1
  93. package/dist/descendants.d.ts.map +1 -1
  94. package/dist/descendants.js +8 -5
  95. package/dist/descendants.js.map +1 -1
  96. package/dist/examples/action-show-in-finder.js +1 -1
  97. package/dist/examples/action-show-in-finder.js.map +1 -1
  98. package/dist/examples/environment-test.js +1 -1
  99. package/dist/examples/environment-test.js.map +1 -1
  100. package/dist/examples/error-boundary.js +1 -1
  101. package/dist/examples/error-boundary.js.map +1 -1
  102. package/dist/examples/form-basic.d.ts.map +1 -1
  103. package/dist/examples/form-basic.js +2 -2
  104. package/dist/examples/form-basic.js.map +1 -1
  105. package/dist/examples/form-dropdown.js +1 -1
  106. package/dist/examples/form-dropdown.js.map +1 -1
  107. package/dist/examples/form-scroll.d.ts +2 -0
  108. package/dist/examples/form-scroll.d.ts.map +1 -0
  109. package/dist/examples/form-scroll.js +8 -0
  110. package/dist/examples/form-scroll.js.map +1 -0
  111. package/dist/examples/form-tagpicker.js +1 -1
  112. package/dist/examples/form-tagpicker.js.map +1 -1
  113. package/dist/examples/internal/descendants-filtering.js +8 -3
  114. package/dist/examples/internal/descendants-filtering.js.map +1 -1
  115. package/dist/examples/internal/descendants.js +10 -3
  116. package/dist/examples/internal/descendants.js.map +1 -1
  117. package/dist/examples/internal/rhf-custom-ref.d.ts +2 -0
  118. package/dist/examples/internal/rhf-custom-ref.d.ts.map +1 -0
  119. package/dist/examples/internal/rhf-custom-ref.js +67 -0
  120. package/dist/examples/internal/rhf-custom-ref.js.map +1 -0
  121. package/dist/examples/internal/scrollbox-demo.js +3 -7
  122. package/dist/examples/internal/scrollbox-demo.js.map +1 -1
  123. package/dist/examples/internal/scrollbox-with-descendants.d.ts +2 -0
  124. package/dist/examples/internal/scrollbox-with-descendants.d.ts.map +1 -0
  125. package/dist/examples/internal/scrollbox-with-descendants.js +63 -0
  126. package/dist/examples/internal/scrollbox-with-descendants.js.map +1 -0
  127. package/dist/examples/internal/simple-dialog.js +1 -1
  128. package/dist/examples/internal/simple-dialog.js.map +1 -1
  129. package/dist/examples/internal/simple-scrollbox.js +2 -3
  130. package/dist/examples/internal/simple-scrollbox.js.map +1 -1
  131. package/dist/examples/internal/text-stacking.js +4 -2
  132. package/dist/examples/internal/text-stacking.js.map +1 -1
  133. package/dist/examples/list-dropdown-default.js +1 -1
  134. package/dist/examples/list-dropdown-default.js.map +1 -1
  135. package/dist/examples/list-fetch-data.js +1 -1
  136. package/dist/examples/list-fetch-data.js.map +1 -1
  137. package/dist/examples/list-scrollbox.d.ts +2 -0
  138. package/dist/examples/list-scrollbox.d.ts.map +1 -0
  139. package/dist/examples/list-scrollbox.js +29 -0
  140. package/dist/examples/list-scrollbox.js.map +1 -0
  141. package/dist/examples/list-with-detail.js +1 -1
  142. package/dist/examples/list-with-detail.js.map +1 -1
  143. package/dist/examples/list-with-dropdown.js +1 -1
  144. package/dist/examples/list-with-dropdown.js.map +1 -1
  145. package/dist/examples/list-with-sections.js +3 -3
  146. package/dist/examples/list-with-sections.js.map +1 -1
  147. package/dist/examples/miscellaneous.js +1 -1
  148. package/dist/examples/miscellaneous.js.map +1 -1
  149. package/dist/examples/nested-navigation.js +4 -2
  150. package/dist/examples/nested-navigation.js.map +1 -1
  151. package/dist/examples/preferences-test.js +1 -1
  152. package/dist/examples/preferences-test.js.map +1 -1
  153. package/dist/examples/simple-dropdown.js +1 -1
  154. package/dist/examples/simple-dropdown.js.map +1 -1
  155. package/dist/examples/simple-file-picker.js +1 -1
  156. package/dist/examples/simple-file-picker.js.map +1 -1
  157. package/dist/examples/simple-grid.js +1 -1
  158. package/dist/examples/simple-grid.js.map +1 -1
  159. package/dist/examples/simple-hud.js +1 -1
  160. package/dist/examples/simple-hud.js.map +1 -1
  161. package/dist/examples/simple-list-search.js +1 -1
  162. package/dist/examples/simple-list-search.js.map +1 -1
  163. package/dist/examples/simple-list.js +1 -1
  164. package/dist/examples/simple-list.js.map +1 -1
  165. package/dist/examples/simple-navigation.js +4 -2
  166. package/dist/examples/simple-navigation.js.map +1 -1
  167. package/dist/examples/store.js +1 -1
  168. package/dist/examples/store.js.map +1 -1
  169. package/dist/examples/submodule-diff.d.ts +2 -0
  170. package/dist/examples/submodule-diff.d.ts.map +1 -0
  171. package/dist/examples/submodule-diff.js +99 -0
  172. package/dist/examples/submodule-diff.js.map +1 -0
  173. package/dist/examples/tanstack-demo.js +1 -1
  174. package/dist/examples/tanstack-demo.js.map +1 -1
  175. package/dist/examples/use-promise-demo.js +1 -1
  176. package/dist/examples/use-promise-demo.js.map +1 -1
  177. package/dist/extensions/dev.d.ts +8 -0
  178. package/dist/extensions/dev.d.ts.map +1 -1
  179. package/dist/extensions/dev.js +53 -28
  180. package/dist/extensions/dev.js.map +1 -1
  181. package/dist/extensions/home.d.ts.map +1 -1
  182. package/dist/extensions/home.js +16 -91
  183. package/dist/extensions/home.js.map +1 -1
  184. package/dist/globals.js +1 -1
  185. package/dist/globals.js.map +1 -1
  186. package/dist/index.d.ts +3 -1
  187. package/dist/index.d.ts.map +1 -1
  188. package/dist/index.js +5 -1
  189. package/dist/index.js.map +1 -1
  190. package/dist/internal/date-picker-widget.d.ts.map +1 -1
  191. package/dist/internal/date-picker-widget.js +56 -48
  192. package/dist/internal/date-picker-widget.js.map +1 -1
  193. package/dist/internal/dialog.d.ts.map +1 -1
  194. package/dist/internal/dialog.js +7 -7
  195. package/dist/internal/dialog.js.map +1 -1
  196. package/dist/internal/scrollbox.d.ts +2 -1
  197. package/dist/internal/scrollbox.d.ts.map +1 -1
  198. package/dist/internal/scrollbox.js +13 -7
  199. package/dist/internal/scrollbox.js.map +1 -1
  200. package/dist/release.d.ts +11 -0
  201. package/dist/release.d.ts.map +1 -0
  202. package/dist/release.js +113 -0
  203. package/dist/release.js.map +1 -0
  204. package/dist/state.d.ts +1 -0
  205. package/dist/state.d.ts.map +1 -1
  206. package/dist/state.js +1 -0
  207. package/dist/state.js.map +1 -1
  208. package/dist/theme.d.ts +11 -8
  209. package/dist/theme.d.ts.map +1 -1
  210. package/dist/theme.js +31 -8
  211. package/dist/theme.js.map +1 -1
  212. package/dist/utils/run-command.d.ts +22 -0
  213. package/dist/utils/run-command.d.ts.map +1 -0
  214. package/dist/utils/run-command.js +127 -0
  215. package/dist/utils/run-command.js.map +1 -0
  216. package/dist/utils.d.ts +1 -1
  217. package/dist/utils.d.ts.map +1 -1
  218. package/dist/utils.js +5 -3
  219. package/dist/utils.js.map +1 -1
  220. package/package.json +10 -7
  221. package/src/apis/toast.tsx +38 -17
  222. package/src/build.tsx +5 -3
  223. package/src/cli.tsx +46 -0
  224. package/src/colors.tsx +22 -1
  225. package/src/compile.tsx +219 -0
  226. package/src/components/actions.tsx +7 -1
  227. package/src/components/command-arguments.tsx +81 -0
  228. package/src/components/detail.tsx +26 -58
  229. package/src/components/dropdown.tsx +108 -23
  230. package/src/components/extension-preferences.tsx +55 -35
  231. package/src/components/form/checkbox.tsx +13 -5
  232. package/src/components/form/date-picker.tsx +24 -29
  233. package/src/components/form/description.tsx +35 -7
  234. package/src/components/form/dropdown.tsx +16 -30
  235. package/src/components/form/file-autocomplete.tsx +87 -77
  236. package/src/components/form/file-picker.tsx +69 -57
  237. package/src/components/form/form-ref.tsx +68 -0
  238. package/src/components/form/index.tsx +152 -41
  239. package/src/components/form/password-field.tsx +35 -22
  240. package/src/components/form/text-area.tsx +78 -58
  241. package/src/components/form/text-field.tsx +82 -61
  242. package/src/components/form/use-form-navigation.tsx +43 -23
  243. package/src/components/form/with-left-border.tsx +2 -1
  244. package/src/components/icon.tsx +497 -469
  245. package/src/components/list.tsx +279 -112
  246. package/src/components/loading-bar.tsx +1 -1
  247. package/src/descendants.tsx +15 -5
  248. package/src/examples/action-show-in-finder.tsx +1 -1
  249. package/src/examples/environment-test.tsx +1 -1
  250. package/src/examples/error-boundary.tsx +1 -1
  251. package/src/examples/file-autocomplete.vitest.tsx +245 -0
  252. package/src/examples/form-basic.tsx +12 -12
  253. package/src/examples/form-basic.vitest.tsx +297 -671
  254. package/src/examples/form-dropdown.tsx +1 -1
  255. package/src/examples/form-dropdown.vitest.tsx +353 -221
  256. package/src/examples/form-scroll.tsx +56 -0
  257. package/src/examples/form-scroll.vitest.tsx +228 -0
  258. package/src/examples/form-tagpicker.tsx +1 -1
  259. package/src/examples/form-tagpicker.vitest.tsx +438 -193
  260. package/src/examples/internal/descendants-filtering.tsx +13 -5
  261. package/src/examples/internal/descendants.tsx +17 -5
  262. package/src/examples/internal/rhf-custom-ref.tsx +152 -0
  263. package/src/examples/internal/scrollbox-demo.tsx +15 -7
  264. package/src/examples/internal/scrollbox-with-descendants.tsx +94 -0
  265. package/src/examples/internal/simple-dialog.tsx +1 -1
  266. package/src/examples/internal/simple-scrollbox.tsx +8 -5
  267. package/src/examples/internal/simple-scrollbox.vitest.tsx +47 -37
  268. package/src/examples/internal/text-stacking.tsx +4 -2
  269. package/src/examples/list-dropdown-default.tsx +1 -1
  270. package/src/examples/list-dropdown-default.vitest.tsx +136 -71
  271. package/src/examples/list-fetch-data.tsx +1 -1
  272. package/src/examples/list-fetch-data.vitest.tsx +42 -33
  273. package/src/examples/list-scrollbox.tsx +46 -0
  274. package/src/examples/list-scrollbox.vitest.tsx +103 -0
  275. package/src/examples/list-with-detail.tsx +1 -1
  276. package/src/examples/list-with-detail.vitest.tsx +290 -294
  277. package/src/examples/list-with-dropdown.tsx +1 -1
  278. package/src/examples/list-with-dropdown.vitest.tsx +190 -150
  279. package/src/examples/list-with-sections.tsx +12 -1
  280. package/src/examples/list-with-sections.vitest.tsx +390 -218
  281. package/src/examples/miscellaneous.tsx +1 -1
  282. package/src/examples/nested-navigation.tsx +4 -2
  283. package/src/examples/preferences-test.tsx +1 -1
  284. package/src/examples/simple-dropdown.tsx +1 -1
  285. package/src/examples/simple-file-picker.tsx +1 -1
  286. package/src/examples/simple-file-picker.vitest.tsx +538 -132
  287. package/src/examples/simple-grid.tsx +1 -1
  288. package/src/examples/simple-grid.vitest.tsx +258 -234
  289. package/src/examples/simple-hud.tsx +1 -1
  290. package/src/examples/simple-list-search.tsx +1 -1
  291. package/src/examples/simple-list.tsx +1 -1
  292. package/src/examples/simple-navigation.tsx +4 -2
  293. package/src/examples/simple-navigation.vitest.tsx +434 -238
  294. package/src/examples/store.tsx +1 -1
  295. package/src/examples/store.vitest.tsx +31 -10
  296. package/src/examples/submodule-diff.tsx +153 -0
  297. package/src/examples/tanstack-demo.tsx +1 -1
  298. package/src/examples/use-promise-demo.tsx +1 -1
  299. package/src/extensions/dev.tsx +74 -36
  300. package/src/extensions/dev.vitest.tsx +220 -0
  301. package/src/extensions/home.tsx +17 -118
  302. package/src/globals.ts +1 -1
  303. package/src/index.tsx +7 -1
  304. package/src/internal/date-picker-widget.tsx +56 -46
  305. package/src/internal/dialog.tsx +6 -6
  306. package/src/internal/scrollbox.tsx +15 -5
  307. package/src/release.tsx +159 -0
  308. package/src/state.tsx +2 -0
  309. package/src/store-api/search.test.tsx +4 -13
  310. package/src/theme.tsx +33 -8
  311. package/src/utils/run-command.tsx +204 -0
  312. package/src/utils.tsx +5 -3
  313. package/dist/ai.d.ts +0 -104
  314. package/dist/ai.d.ts.map +0 -1
  315. package/dist/ai.js +0 -135
  316. package/dist/ai.js.map +0 -1
  317. package/dist/apis/cache.test.d.ts +0 -2
  318. package/dist/apis/cache.test.d.ts.map +0 -1
  319. package/dist/apis/cache.test.js +0 -246
  320. package/dist/apis/cache.test.js.map +0 -1
  321. package/dist/apis/localstorage.test.d.ts +0 -2
  322. package/dist/apis/localstorage.test.d.ts.map +0 -1
  323. package/dist/apis/localstorage.test.js +0 -131
  324. package/dist/apis/localstorage.test.js.map +0 -1
  325. package/dist/apis/toast.test.d.ts +0 -2
  326. package/dist/apis/toast.test.d.ts.map +0 -1
  327. package/dist/apis/toast.test.js +0 -67
  328. package/dist/apis/toast.test.js.map +0 -1
  329. package/dist/build.test.d.ts +0 -2
  330. package/dist/build.test.d.ts.map +0 -1
  331. package/dist/build.test.js +0 -73
  332. package/dist/build.test.js.map +0 -1
  333. package/dist/cache.d.ts +0 -32
  334. package/dist/cache.d.ts.map +0 -1
  335. package/dist/cache.js +0 -205
  336. package/dist/cache.js.map +0 -1
  337. package/dist/cache.test.d.ts +0 -2
  338. package/dist/cache.test.d.ts.map +0 -1
  339. package/dist/cache.test.js +0 -246
  340. package/dist/cache.test.js.map +0 -1
  341. package/dist/clipboard.d.ts +0 -36
  342. package/dist/clipboard.d.ts.map +0 -1
  343. package/dist/clipboard.js +0 -154
  344. package/dist/clipboard.js.map +0 -1
  345. package/dist/components/form/form-type-only.d.ts +0 -174
  346. package/dist/components/form/form-type-only.d.ts.map +0 -1
  347. package/dist/components/form/form-type-only.js +0 -2
  348. package/dist/components/form/form-type-only.js.map +0 -1
  349. package/dist/components/form/use-form-handling.d.ts +0 -4
  350. package/dist/components/form/use-form-handling.d.ts.map +0 -1
  351. package/dist/components/form/use-form-handling.js +0 -37
  352. package/dist/components/form/use-form-handling.js.map +0 -1
  353. package/dist/dev-ui.d.ts +0 -7
  354. package/dist/dev-ui.d.ts.map +0 -1
  355. package/dist/dev-ui.js +0 -118
  356. package/dist/dev-ui.js.map +0 -1
  357. package/dist/environment.d.ts +0 -63
  358. package/dist/environment.d.ts.map +0 -1
  359. package/dist/environment.js +0 -189
  360. package/dist/environment.js.map +0 -1
  361. package/dist/examples/datepicker.d.ts +0 -2
  362. package/dist/examples/datepicker.d.ts.map +0 -1
  363. package/dist/examples/datepicker.js +0 -344
  364. package/dist/examples/datepicker.js.map +0 -1
  365. package/dist/examples/form-basic-arrow-keys.vitest.d.ts +0 -2
  366. package/dist/examples/form-basic-arrow-keys.vitest.d.ts.map +0 -1
  367. package/dist/examples/form-basic-arrow-keys.vitest.js +0 -46
  368. package/dist/examples/form-basic-arrow-keys.vitest.js.map +0 -1
  369. package/dist/examples/form-basic.vitest.d.ts +0 -2
  370. package/dist/examples/form-basic.vitest.d.ts.map +0 -1
  371. package/dist/examples/form-basic.vitest.js +0 -995
  372. package/dist/examples/form-basic.vitest.js.map +0 -1
  373. package/dist/examples/form-dropdown-with-sections.d.ts +0 -2
  374. package/dist/examples/form-dropdown-with-sections.d.ts.map +0 -1
  375. package/dist/examples/form-dropdown-with-sections.js +0 -13
  376. package/dist/examples/form-dropdown-with-sections.js.map +0 -1
  377. package/dist/examples/form-dropdown-with-sections.vitest.d.ts +0 -2
  378. package/dist/examples/form-dropdown-with-sections.vitest.d.ts.map +0 -1
  379. package/dist/examples/form-dropdown-with-sections.vitest.js +0 -75
  380. package/dist/examples/form-dropdown-with-sections.vitest.js.map +0 -1
  381. package/dist/examples/form-dropdown.vitest.d.ts +0 -2
  382. package/dist/examples/form-dropdown.vitest.d.ts.map +0 -1
  383. package/dist/examples/form-dropdown.vitest.js +0 -722
  384. package/dist/examples/form-dropdown.vitest.js.map +0 -1
  385. package/dist/examples/form-multiselect-dropdown.d.ts +0 -2
  386. package/dist/examples/form-multiselect-dropdown.d.ts.map +0 -1
  387. package/dist/examples/form-multiselect-dropdown.js +0 -13
  388. package/dist/examples/form-multiselect-dropdown.js.map +0 -1
  389. package/dist/examples/form-tagpicker.vitest.d.ts +0 -2
  390. package/dist/examples/form-tagpicker.vitest.d.ts.map +0 -1
  391. package/dist/examples/form-tagpicker.vitest.js +0 -491
  392. package/dist/examples/form-tagpicker.vitest.js.map +0 -1
  393. package/dist/examples/internal/nested-boxes.d.ts +0 -2
  394. package/dist/examples/internal/nested-boxes.d.ts.map +0 -1
  395. package/dist/examples/internal/nested-boxes.js +0 -7
  396. package/dist/examples/internal/nested-boxes.js.map +0 -1
  397. package/dist/examples/internal/simple-scrollbox.vitest.d.ts +0 -2
  398. package/dist/examples/internal/simple-scrollbox.vitest.d.ts.map +0 -1
  399. package/dist/examples/internal/simple-scrollbox.vitest.js +0 -88
  400. package/dist/examples/internal/simple-scrollbox.vitest.js.map +0 -1
  401. package/dist/examples/internal/unicode-square-repro.d.ts +0 -2
  402. package/dist/examples/internal/unicode-square-repro.d.ts.map +0 -1
  403. package/dist/examples/internal/unicode-square-repro.js +0 -7
  404. package/dist/examples/internal/unicode-square-repro.js.map +0 -1
  405. package/dist/examples/list-dropdown-default.vitest.d.ts +0 -2
  406. package/dist/examples/list-dropdown-default.vitest.d.ts.map +0 -1
  407. package/dist/examples/list-dropdown-default.vitest.js +0 -164
  408. package/dist/examples/list-dropdown-default.vitest.js.map +0 -1
  409. package/dist/examples/list-fetch-data.vitest.d.ts +0 -2
  410. package/dist/examples/list-fetch-data.vitest.d.ts.map +0 -1
  411. package/dist/examples/list-fetch-data.vitest.js +0 -103
  412. package/dist/examples/list-fetch-data.vitest.js.map +0 -1
  413. package/dist/examples/list-filter-navigation.d.ts +0 -2
  414. package/dist/examples/list-filter-navigation.d.ts.map +0 -1
  415. package/dist/examples/list-filter-navigation.js +0 -8
  416. package/dist/examples/list-filter-navigation.js.map +0 -1
  417. package/dist/examples/list-with-detail.vitest.d.ts +0 -2
  418. package/dist/examples/list-with-detail.vitest.d.ts.map +0 -1
  419. package/dist/examples/list-with-detail.vitest.js +0 -438
  420. package/dist/examples/list-with-detail.vitest.js.map +0 -1
  421. package/dist/examples/list-with-dropdown.vitest.d.ts +0 -2
  422. package/dist/examples/list-with-dropdown.vitest.d.ts.map +0 -1
  423. package/dist/examples/list-with-dropdown.vitest.js +0 -297
  424. package/dist/examples/list-with-dropdown.vitest.js.map +0 -1
  425. package/dist/examples/list-with-sections.vitest.d.ts +0 -2
  426. package/dist/examples/list-with-sections.vitest.d.ts.map +0 -1
  427. package/dist/examples/list-with-sections.vitest.js +0 -441
  428. package/dist/examples/list-with-sections.vitest.js.map +0 -1
  429. package/dist/examples/simple-file-picker.vitest.d.ts +0 -2
  430. package/dist/examples/simple-file-picker.vitest.d.ts.map +0 -1
  431. package/dist/examples/simple-file-picker.vitest.js +0 -277
  432. package/dist/examples/simple-file-picker.vitest.js.map +0 -1
  433. package/dist/examples/simple-grid.vitest.d.ts +0 -2
  434. package/dist/examples/simple-grid.vitest.d.ts.map +0 -1
  435. package/dist/examples/simple-grid.vitest.js +0 -498
  436. package/dist/examples/simple-grid.vitest.js.map +0 -1
  437. package/dist/examples/simple-navigation.vitest.d.ts +0 -2
  438. package/dist/examples/simple-navigation.vitest.d.ts.map +0 -1
  439. package/dist/examples/simple-navigation.vitest.js +0 -522
  440. package/dist/examples/simple-navigation.vitest.js.map +0 -1
  441. package/dist/examples/store.vitest.d.ts +0 -2
  442. package/dist/examples/store.vitest.d.ts.map +0 -1
  443. package/dist/examples/store.vitest.js +0 -48
  444. package/dist/examples/store.vitest.js.map +0 -1
  445. package/dist/home-command.d.ts +0 -8
  446. package/dist/home-command.d.ts.map +0 -1
  447. package/dist/home-command.js +0 -181
  448. package/dist/home-command.js.map +0 -1
  449. package/dist/hooks/hooks.test.d.ts +0 -2
  450. package/dist/hooks/hooks.test.d.ts.map +0 -1
  451. package/dist/hooks/hooks.test.js +0 -37
  452. package/dist/hooks/hooks.test.js.map +0 -1
  453. package/dist/hover-repro.d.ts +0 -2
  454. package/dist/hover-repro.d.ts.map +0 -1
  455. package/dist/hover-repro.js +0 -20
  456. package/dist/hover-repro.js.map +0 -1
  457. package/dist/localstorage.d.ts +0 -13
  458. package/dist/localstorage.d.ts.map +0 -1
  459. package/dist/localstorage.js +0 -190
  460. package/dist/localstorage.js.map +0 -1
  461. package/dist/localstorage.test.d.ts +0 -2
  462. package/dist/localstorage.test.d.ts.map +0 -1
  463. package/dist/localstorage.test.js +0 -131
  464. package/dist/localstorage.test.js.map +0 -1
  465. package/dist/oauth.d.ts +0 -142
  466. package/dist/oauth.d.ts.map +0 -1
  467. package/dist/oauth.js +0 -551
  468. package/dist/oauth.js.map +0 -1
  469. package/dist/preferences.d.ts +0 -23
  470. package/dist/preferences.d.ts.map +0 -1
  471. package/dist/preferences.js +0 -105
  472. package/dist/preferences.js.map +0 -1
  473. package/dist/store-api/download.test.d.ts +0 -2
  474. package/dist/store-api/download.test.d.ts.map +0 -1
  475. package/dist/store-api/download.test.js +0 -36
  476. package/dist/store-api/download.test.js.map +0 -1
  477. package/dist/store-api/extension.test.d.ts +0 -2
  478. package/dist/store-api/extension.test.d.ts.map +0 -1
  479. package/dist/store-api/extension.test.js +0 -22
  480. package/dist/store-api/extension.test.js.map +0 -1
  481. package/dist/store-api/search.test.d.ts +0 -2
  482. package/dist/store-api/search.test.d.ts.map +0 -1
  483. package/dist/store-api/search.test.js +0 -45
  484. package/dist/store-api/search.test.js.map +0 -1
  485. package/dist/store.d.ts +0 -21
  486. package/dist/store.d.ts.map +0 -1
  487. package/dist/store.js +0 -84
  488. package/dist/store.js.map +0 -1
  489. package/dist/toast.d.ts +0 -44
  490. package/dist/toast.d.ts.map +0 -1
  491. package/dist/toast.js +0 -221
  492. package/dist/toast.js.map +0 -1
  493. package/dist/utils.test.d.ts +0 -2
  494. package/dist/utils.test.d.ts.map +0 -1
  495. package/dist/utils.test.js +0 -152
  496. package/dist/utils.test.js.map +0 -1
  497. package/dist/window.d.ts +0 -12
  498. package/dist/window.d.ts.map +0 -1
  499. package/dist/window.js +0 -48
  500. package/dist/window.js.map +0 -1
@@ -1,12 +1,14 @@
1
- import React from 'react'
1
+ import React, { useRef } from 'react'
2
2
  import { Theme } from 'termcast/src/theme'
3
+ import { BoxRenderable, TextareaRenderable } from '@opentui/core'
3
4
  import { WithLeftBorder } from './with-left-border'
4
5
  import { FormItemProps, FormItemRef } from './types'
5
6
  import { useFormContext, Controller } from 'react-hook-form'
6
- import { useFocusContext } from './index'
7
+ import { useFocusContext, useFormFieldDescendant } from './index'
7
8
  import { useKeyboard } from '@opentui/react'
8
9
  import { useIsInFocus } from 'termcast/src/internal/focus-context'
9
10
  import { FileAutocomplete } from './file-autocomplete'
11
+ import { useFormNavigationHelpers } from './use-form-navigation'
10
12
 
11
13
  export interface FilePickerProps extends FormItemProps<string[]> {
12
14
  /**
@@ -34,6 +36,11 @@ export interface FilePickerProps extends FormItemProps<string[]> {
34
36
  * Placeholder text for the input field
35
37
  */
36
38
  placeholder?: string
39
+ /**
40
+ * The initial directory to start browsing from.
41
+ * @defaultValue current working directory
42
+ */
43
+ initialDirectory?: string
37
44
  }
38
45
 
39
46
  export type FilePickerRef = FormItemRef
@@ -54,28 +61,34 @@ const FilePickerField = ({
54
61
  setFocusedField: (id: string) => void
55
62
  }): any => {
56
63
  const isInFocus = useIsInFocus()
57
- const [inputValue, setInputValue] = React.useState('')
58
64
  const [showAutocomplete, setShowAutocomplete] = React.useState(false)
59
- const inputRef = React.useRef<any>(null)
65
+ const [searchTrigger, setSearchTrigger] = React.useState(0)
66
+ const inputRef = React.useRef<TextareaRenderable>(null)
60
67
  const anchorRef = React.useRef<any>(null)
61
68
 
62
- // Show autocomplete when typing
63
- React.useEffect(() => {
64
- if (inputValue && isFocused) {
65
- setShowAutocomplete(true)
66
- } else if (!inputValue) {
67
- setShowAutocomplete(false)
68
- }
69
- }, [inputValue, isFocused])
70
-
71
- // Handle Enter key
69
+ // Handle Enter key and left arrow for removing last file
72
70
  useKeyboard((evt) => {
73
71
  if (!isFocused || !isInFocus) return
74
72
 
73
+ // Left arrow removes last selected file when input is empty
74
+ if (evt.name === 'left') {
75
+ const inputValue = inputRef.current?.plainText || ''
76
+ if (!inputValue && selectedFiles.length > 0) {
77
+ const newFiles = selectedFiles.slice(0, -1)
78
+ field.onChange(newFiles)
79
+ if (props.onChange) {
80
+ props.onChange(newFiles)
81
+ }
82
+ }
83
+ return
84
+ }
85
+
75
86
  if (evt.name === 'return') {
87
+ const inputValue = inputRef.current?.plainText || ''
76
88
  // If input is empty, show files in current directory
77
89
  if (!inputValue && !showAutocomplete) {
78
90
  setShowAutocomplete(true)
91
+ setSearchTrigger((n) => n + 1)
79
92
  }
80
93
  // If autocomplete is not visible and input has value, add the path
81
94
  else if (inputValue.trim() && !showAutocomplete) {
@@ -88,7 +101,7 @@ const FilePickerField = ({
88
101
  if (props.onChange) {
89
102
  props.onChange(newFiles)
90
103
  }
91
- setInputValue('')
104
+ inputRef.current?.setText('')
92
105
  }
93
106
  }
94
107
  })
@@ -101,7 +114,7 @@ const FilePickerField = ({
101
114
  if (props.onChange) {
102
115
  props.onChange(newFiles)
103
116
  }
104
- setInputValue('')
117
+ inputRef.current?.setText('')
105
118
  setShowAutocomplete(false)
106
119
  }
107
120
 
@@ -111,7 +124,7 @@ const FilePickerField = ({
111
124
  <box flexDirection='column'>
112
125
  <WithLeftBorder withDiamond isFocused={isFocused}>
113
126
  <text
114
- fg={Theme.text}
127
+ fg={isFocused ? Theme.primary : Theme.text}
115
128
  onMouseDown={() => {
116
129
  setFocusedField(props.id)
117
130
  }}
@@ -121,13 +134,26 @@ const FilePickerField = ({
121
134
  </WithLeftBorder>
122
135
  <WithLeftBorder isFocused={isFocused}>
123
136
  <box flexDirection='column' ref={anchorRef}>
124
- <input
137
+ <textarea
125
138
  ref={inputRef}
126
- value={inputValue}
139
+ height={1}
140
+ keyBindings={[
141
+ { name: 'return', action: 'submit' },
142
+ { name: 'linefeed', action: 'submit' },
143
+ ]}
144
+ initialValue=""
127
145
  placeholder={props.placeholder || 'Enter file path...'}
128
146
  focused={isFocused}
129
147
  onMouseDown={() => setFocusedField(props.id)}
130
- onInput={(value: string) => setInputValue(value)}
148
+ onContentChange={() => {
149
+ const value = inputRef.current?.plainText || ''
150
+ if (value && isFocused) {
151
+ setShowAutocomplete(true)
152
+ setSearchTrigger((n) => n + 1)
153
+ } else if (!value) {
154
+ setShowAutocomplete(false)
155
+ }
156
+ }}
131
157
  />
132
158
  {selectedFiles.length > 0 && (
133
159
  <box flexDirection='column' marginTop={1}>
@@ -154,51 +180,35 @@ const FilePickerField = ({
154
180
  </WithLeftBorder>
155
181
  )}
156
182
  <FileAutocomplete
157
- value={inputValue}
158
- onChange={(newValue) => {
159
- setInputValue(newValue)
160
- // Move cursor to end when value changes
161
- if (inputRef.current) {
162
- inputRef.current.cursorPosition = newValue.length
163
- }
164
- }}
165
183
  onSelect={handleSelectFile}
166
184
  visible={showAutocomplete}
167
185
  onVisibilityChange={setShowAutocomplete}
168
186
  inputRef={inputRef}
169
187
  anchorRef={anchorRef}
188
+ searchTrigger={searchTrigger}
189
+ canChooseFiles={props.canChooseFiles}
190
+ canChooseDirectories={props.canChooseDirectories}
191
+ initialDirectory={props.initialDirectory}
170
192
  />
171
193
  </box>
172
194
  ) as React.ReactElement
173
195
  }
174
196
 
175
197
  export const FilePicker = (props: FilePickerProps): any => {
176
- const { control, getValues } = useFormContext()
198
+ const { control } = useFormContext()
177
199
  const { focusedField, setFocusedField } = useFocusContext()
178
200
  const isFocused = focusedField === props.id
179
201
  const isInFocus = useIsInFocus()
180
202
 
181
- const handleNavigateUp = () => {
182
- // Find previous field and focus it
183
- const fieldNames = Object.keys(getValues())
184
- const currentIndex = fieldNames.indexOf(props.id)
185
- if (currentIndex > 0) {
186
- setFocusedField(fieldNames[currentIndex - 1])
187
- } else {
188
- setFocusedField(fieldNames[fieldNames.length - 1])
189
- }
190
- }
203
+ const elementRef = useRef<BoxRenderable>(null)
191
204
 
192
- const handleNavigateDown = () => {
193
- // Find next field and focus it
194
- const fieldNames = Object.keys(getValues())
195
- const currentIndex = fieldNames.indexOf(props.id)
196
- if (currentIndex < fieldNames.length - 1) {
197
- setFocusedField(fieldNames[currentIndex + 1])
198
- } else {
199
- setFocusedField(fieldNames[0])
200
- }
201
- }
205
+ // Register as form field descendant for scroll support
206
+ useFormFieldDescendant({
207
+ id: props.id,
208
+ elementRef: elementRef.current,
209
+ })
210
+
211
+ const { navigateToPrevious, navigateToNext } = useFormNavigationHelpers(props.id)
202
212
 
203
213
  // Handle keyboard navigation
204
214
  useKeyboard((evt) => {
@@ -206,9 +216,9 @@ export const FilePicker = (props: FilePickerProps): any => {
206
216
 
207
217
  if (evt.name === 'tab') {
208
218
  if (evt.shift) {
209
- handleNavigateUp()
219
+ navigateToPrevious()
210
220
  } else {
211
- handleNavigateDown()
221
+ navigateToNext()
212
222
  }
213
223
  }
214
224
  })
@@ -220,12 +230,14 @@ export const FilePicker = (props: FilePickerProps): any => {
220
230
  defaultValue={props.defaultValue || props.value || []}
221
231
  render={(renderProps) => {
222
232
  return (
223
- <FilePickerField
224
- {...renderProps}
225
- props={props}
226
- isFocused={isFocused}
227
- setFocusedField={setFocusedField}
228
- />
233
+ <box ref={elementRef} flexDirection='column'>
234
+ <FilePickerField
235
+ {...renderProps}
236
+ props={props}
237
+ isFocused={isFocused}
238
+ setFocusedField={setFocusedField}
239
+ />
240
+ </box>
229
241
  ) as React.ReactElement
230
242
  }}
231
243
  />
@@ -0,0 +1,68 @@
1
+ import React from 'react'
2
+ import { TextareaRenderable } from '@opentui/core'
3
+ import { UseFormRegisterReturn } from 'react-hook-form'
4
+
5
+ /**
6
+ * Creates a fake ref object that satisfies react-hook-form's register() requirements.
7
+ * RHF expects a ref with:
8
+ * - `name`: field name
9
+ * - `value`: getter/setter for reading/writing the field value
10
+ * - `focus()`: optional, for focusing on validation errors
11
+ *
12
+ * This allows using register() directly with non-HTML inputs like opentui's textarea.
13
+ */
14
+ export function createReactHookFormRef(options: {
15
+ name: string
16
+ getValue: () => string
17
+ setValue: (value: string) => void
18
+ focus?: () => void
19
+ }) {
20
+ return {
21
+ name: options.name,
22
+ get value() {
23
+ return options.getValue()
24
+ },
25
+ set value(v: string) {
26
+ options.setValue(v)
27
+ },
28
+ focus: options.focus || (() => {}),
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Creates a react-hook-form adapter for opentui's TextareaRenderable.
34
+ *
35
+ * Returns:
36
+ * - `formRef`: A fake ref object that RHF can use to get/set values
37
+ * - `onContentChange`: Handler to trigger RHF's onChange
38
+ * - `refCallback`: A memoized ref callback to pass to the textarea's ref prop
39
+ *
40
+ * IMPORTANT: The refCallback must be memoized (via useCallback) at the call site
41
+ * because register() returns a new object on every render. Passing register().ref
42
+ * directly to the textarea ref prop causes unnecessary re-renders.
43
+ */
44
+ export function createTextareaFormRef(
45
+ name: string,
46
+ textareaRef: React.RefObject<TextareaRenderable | null>,
47
+ registration: UseFormRegisterReturn<string>,
48
+ ) {
49
+ const formRef = createReactHookFormRef({
50
+ name,
51
+ getValue: () => textareaRef.current?.plainText || '',
52
+ setValue: (v) => {
53
+ textareaRef.current?.setText(v)
54
+ },
55
+ })
56
+
57
+ const onContentChange = () => {
58
+ registration.onChange({
59
+ target: formRef,
60
+ type: 'change',
61
+ })
62
+ }
63
+
64
+ return {
65
+ formRef,
66
+ onContentChange,
67
+ }
68
+ }
@@ -1,4 +1,10 @@
1
- import React, { useState, createContext, useContext, useEffect } from 'react'
1
+ import React, {
2
+ useState,
3
+ createContext,
4
+ useContext,
5
+ useLayoutEffect,
6
+ useRef,
7
+ } from 'react'
2
8
  import { useKeyboard } from '@opentui/react'
3
9
  import { useForm, FormProvider } from 'react-hook-form'
4
10
  import { ActionPanel } from 'termcast/src/components/actions'
@@ -6,8 +12,16 @@ import { logger } from 'termcast/src/logger'
6
12
  import { InFocus, useIsInFocus } from 'termcast/src/internal/focus-context'
7
13
  import { useDialog } from 'termcast/src/internal/dialog'
8
14
  import { Theme } from 'termcast/src/theme'
9
- import { TextAttributes } from '@opentui/core'
10
- import { useStore } from 'termcast/src/state'
15
+ import {
16
+ TextAttributes,
17
+ ScrollBoxRenderable,
18
+ BoxRenderable,
19
+ } from '@opentui/core'
20
+
21
+ import {
22
+ createDescendants,
23
+ DescendantContextType,
24
+ } from 'termcast/src/descendants'
11
25
  import {
12
26
  FormValues,
13
27
  FormProps,
@@ -20,10 +34,39 @@ import {
20
34
  FormProps_2,
21
35
  FormItemProps_2,
22
36
  } from './types'
37
+ import { FORM_MAX_WIDTH } from './description'
38
+ import { ScrollBox } from 'termcast/src/internal/scrollbox'
23
39
 
24
40
  export * from './types'
25
41
  export { useFormContext } from 'react-hook-form'
26
42
 
43
+ // Form field descendant type - stores element ref for scrolling
44
+ interface FormFieldDescendant {
45
+ id: string
46
+ elementRef?: BoxRenderable | null
47
+ }
48
+
49
+ // Create descendants for form fields
50
+ const {
51
+ DescendantsProvider: FormFieldDescendantsProvider,
52
+ useDescendants: useFormFieldDescendants,
53
+ useDescendant: useFormFieldDescendant,
54
+ } = createDescendants<FormFieldDescendant>()
55
+
56
+ export { useFormFieldDescendant }
57
+
58
+ // Context to provide scrollbox ref and descendants to form fields
59
+ interface FormScrollContextValue {
60
+ scrollBoxRef: React.RefObject<ScrollBoxRenderable | null>
61
+ descendantsContext: DescendantContextType<FormFieldDescendant>
62
+ }
63
+
64
+ const FormScrollContext = createContext<FormScrollContextValue | null>(null)
65
+
66
+ export const useFormScrollContext = () => {
67
+ return useContext(FormScrollContext)
68
+ }
69
+
27
70
  // Context for managing focused field
28
71
  interface FocusContextValue {
29
72
  focusedField: string | null
@@ -52,26 +95,7 @@ export const useFormSubmit = () => {
52
95
  return context // Can be null if not in a form
53
96
  }
54
97
 
55
- // Footer component to show keyboard shortcuts
56
98
  function FormFooter(): any {
57
- const toast = useStore((state) => state.toast)
58
-
59
- if (toast) {
60
- return (
61
- <box
62
- border={false}
63
- style={{
64
- paddingLeft: 1,
65
- paddingRight: 1,
66
- paddingTop: 1,
67
- marginTop: 1,
68
- }}
69
- >
70
- {toast}
71
- </box>
72
- )
73
- }
74
-
75
99
  return (
76
100
  <box
77
101
  border={false}
@@ -83,16 +107,14 @@ function FormFooter(): any {
83
107
  flexDirection: 'row',
84
108
  }}
85
109
  >
86
- <text fg={Theme.text} attributes={TextAttributes.BOLD}>
87
-
88
- </text>
110
+ <text fg={Theme.text} attributes={TextAttributes.BOLD}>ctrl ↵</text>
89
111
  <text fg={Theme.textMuted}> submit</text>
90
112
  <text fg={Theme.text} attributes={TextAttributes.BOLD}>
91
113
  {' '}↑↓
92
114
  </text>
93
115
  <text fg={Theme.textMuted}> navigate</text>
94
116
  <text fg={Theme.text} attributes={TextAttributes.BOLD}>
95
- {' '}^k
117
+ {' '}^k
96
118
  </text>
97
119
  <text fg={Theme.textMuted}> actions</text>
98
120
  </box>
@@ -157,14 +179,54 @@ export const Form: FormType = ((props) => {
157
179
  // mode: 'onChange',
158
180
  })
159
181
 
160
- const [focusedField, setFocusedField] = useState<string | null>(null)
182
+ const [focusedField, setFocusedFieldRaw] = useState<string | null>(null)
183
+
184
+ const scrollBoxRef = useRef<ScrollBoxRenderable>(null)
185
+ const descendantsContext = useFormFieldDescendants()
186
+
187
+ const scrollToField = (fieldId: string) => {
188
+ const scrollBox = scrollBoxRef.current
189
+ if (!scrollBox) return
190
+
191
+ // Find field in descendants map by matching props.id
192
+ const field = Object.values(descendantsContext.map.current).find(
193
+ (item) => item.props?.id === fieldId,
194
+ )
195
+ const elementRef = field?.props?.elementRef
196
+ if (!elementRef) return
161
197
 
162
- // Auto-focus first field on mount
163
- useEffect(() => {
164
- const fieldNames = Object.keys(methods.getValues())
165
- if (fieldNames.length > 0) {
166
- logger.log(`focusing `, fieldNames[0])
167
- setFocusedField(fieldNames[0])
198
+ const contentY = scrollBox.content?.y || 0
199
+ const viewportHeight = scrollBox.viewport?.height || 10
200
+ const currentScrollTop = scrollBox.scrollTop || 0
201
+
202
+ // Access current position from the BoxRenderable ref
203
+ const itemTop = elementRef.y - contentY
204
+ const itemBottom = itemTop + elementRef.height
205
+
206
+ if (itemTop < currentScrollTop) {
207
+ scrollBox.scrollTo(itemTop)
208
+ } else if (itemBottom > currentScrollTop + viewportHeight) {
209
+ scrollBox.scrollTo(itemBottom - viewportHeight)
210
+ }
211
+ }
212
+
213
+ const setFocusedField = (id: string | null) => {
214
+ setFocusedFieldRaw(id)
215
+ if (id) {
216
+ scrollToField(id)
217
+ }
218
+ }
219
+
220
+ // Auto-focus first field after children have registered as descendants
221
+ useLayoutEffect(() => {
222
+ const descendants = Object.values(descendantsContext.map.current)
223
+ .filter((item) => item.index !== -1 && item.props?.id)
224
+ .sort((a, b) => a.index - b.index)
225
+
226
+ if (descendants.length > 0) {
227
+ const firstId = descendants[0].props!.id
228
+ logger.log(`focusing `, firstId)
229
+ setFocusedFieldRaw(firstId)
168
230
  } else {
169
231
  logger.log(`no fields to focus in form`)
170
232
  }
@@ -174,11 +236,28 @@ export const Form: FormType = ((props) => {
174
236
  const inFocus = useIsInFocus()
175
237
  const dialog = useDialog()
176
238
 
177
- // Handle action key navigation only
239
+ // Handle action keys and page scrolling
178
240
  useKeyboard((evt) => {
179
241
  // Only handle keyboard events when form is in focus
180
242
  if (!inFocus) return
181
243
 
244
+ // Page up/down scrolling
245
+ if (evt.name === 'pageup' || evt.name === 'pagedown') {
246
+ const scrollBox = scrollBoxRef.current
247
+ if (!scrollBox) return
248
+
249
+ const viewportHeight = scrollBox.viewport?.height || 10
250
+ const currentScrollTop = scrollBox.scrollTop || 0
251
+ const scrollAmount = viewportHeight - 2 // Leave some overlap
252
+
253
+ if (evt.name === 'pageup') {
254
+ scrollBox.scrollTo(Math.max(0, currentScrollTop - scrollAmount))
255
+ } else {
256
+ scrollBox.scrollTo(currentScrollTop + scrollAmount)
257
+ }
258
+ return
259
+ }
260
+
182
261
  if (evt.name === 'k' && evt.ctrl && props.actions) {
183
262
  // Ctrl+K shows actions
184
263
  dialog.push(
@@ -187,6 +266,14 @@ export const Form: FormType = ((props) => {
187
266
  </FormSubmitContext.Provider>,
188
267
  'bottom-right',
189
268
  )
269
+ } else if (evt.name === 'return' && evt.ctrl && props.actions) {
270
+ // Ctrl+Return shows actions (form submit shortcut)
271
+ dialog.push(
272
+ <FormSubmitContext.Provider value={submitContextValue}>
273
+ {props.actions}
274
+ </FormSubmitContext.Provider>,
275
+ 'bottom-right',
276
+ )
190
277
  } else if (evt.name === 'return' && evt.meta && props.actions) {
191
278
  // Cmd+Return also shows actions (consistent with List)
192
279
  dialog.push(
@@ -202,16 +289,40 @@ export const Form: FormType = ((props) => {
202
289
  getFormValues: () => methods.getValues(),
203
290
  }
204
291
 
292
+ const scrollContextValue: FormScrollContextValue = {
293
+ scrollBoxRef,
294
+ descendantsContext,
295
+ }
296
+
205
297
  return (
206
298
  <FormProvider {...methods}>
207
299
  <FormSubmitContext.Provider value={submitContextValue}>
208
- <FocusContext.Provider value={{ focusedField, setFocusedField }}>
209
- <box flexDirection='column'>
210
- {props.children}
211
- <FormEnd />
212
- <FormFooter />
213
- </box>
214
- </FocusContext.Provider>
300
+ <FormScrollContext.Provider value={scrollContextValue}>
301
+ <FocusContext.Provider value={{ focusedField, setFocusedField }}>
302
+ <box flexDirection='row' flexGrow={1} justifyContent='center'>
303
+ <box flexDirection='column'>
304
+ <ScrollBox
305
+ ref={scrollBoxRef}
306
+ flexGrow={1}
307
+ style={{
308
+ rootOptions: {
309
+ maxWidth: FORM_MAX_WIDTH,
310
+ },
311
+ contentOptions: {
312
+ justifyContent: 'center',
313
+ },
314
+ }}
315
+ >
316
+ <FormFieldDescendantsProvider value={descendantsContext}>
317
+ {props.children}
318
+ <FormEnd />
319
+ </FormFieldDescendantsProvider>
320
+ </ScrollBox>
321
+ <FormFooter />
322
+ </box>
323
+ </box>
324
+ </FocusContext.Provider>
325
+ </FormScrollContext.Provider>
215
326
  </FormSubmitContext.Provider>
216
327
  </FormProvider>
217
328
  )
@@ -1,9 +1,8 @@
1
- import React, { useState } from 'react'
2
- import { TextAttributes } from '@opentui/core'
1
+ import React, { useRef, useState } from 'react'
2
+ import { BoxRenderable } from '@opentui/core'
3
3
  import { useFormContext, Controller } from 'react-hook-form'
4
- import { useFocusContext } from './index'
4
+ import { useFocusContext, useFormFieldDescendant } from './index'
5
5
  import { FormItemProps, FormItemRef } from './types'
6
- import { logger } from 'termcast/src/logger'
7
6
  import { Theme } from 'termcast/src/theme'
8
7
  import { WithLeftBorder } from './with-left-border'
9
8
  import { useFormNavigation } from './use-form-navigation'
@@ -18,8 +17,15 @@ export const PasswordField = (props: PasswordFieldProps): any => {
18
17
  const { control } = useFormContext()
19
18
  const { focusedField, setFocusedField } = useFocusContext()
20
19
  const isFocused = focusedField === props.id
20
+ const elementRef = useRef<BoxRenderable>(null)
21
+ const realValueRef = useRef(props.defaultValue || props.value || '')
22
+ const [displayLength, setDisplayLength] = useState(realValueRef.current.length)
23
+
24
+ useFormFieldDescendant({
25
+ id: props.id,
26
+ elementRef: elementRef.current,
27
+ })
21
28
 
22
- // Use form navigation hook
23
29
  useFormNavigation(props.id)
24
30
 
25
31
  return (
@@ -27,17 +33,14 @@ export const PasswordField = (props: PasswordFieldProps): any => {
27
33
  name={props.id}
28
34
  control={control}
29
35
  defaultValue={props.defaultValue || props.value || ''}
30
- render={({ field, fieldState, formState }) => {
31
- // Always show masked value when not focused
32
- const displayValue = isFocused
33
- ? field.value
34
- : '*'.repeat(field.value.length)
36
+ render={({ field, fieldState }) => {
37
+ const displayValue = '*'.repeat(displayLength)
35
38
 
36
39
  return (
37
- <box flexDirection='column'>
40
+ <box ref={elementRef} flexDirection="column">
38
41
  <WithLeftBorder withDiamond isFocused={isFocused}>
39
42
  <text
40
- fg={Theme.text}
43
+ fg={isFocused ? Theme.primary : Theme.text}
41
44
  onMouseDown={() => {
42
45
  setFocusedField(props.id)
43
46
  }}
@@ -48,17 +51,27 @@ export const PasswordField = (props: PasswordFieldProps): any => {
48
51
  <WithLeftBorder isFocused={isFocused}>
49
52
  <input
50
53
  value={displayValue}
51
- onInput={(value: string) => {
52
- // Ignore masked input (all asterisks) when not focused
53
- if (
54
- isFocused &&
55
- !(/^\*+$/.test(value) && !field.value.startsWith('*'))
56
- ) {
57
- field.onChange(value)
58
- if (props.onChange) {
59
- props.onChange(value)
60
- }
54
+ onInput={(newDisplay: string) => {
55
+ if (!isFocused) return
56
+
57
+ const currentValue = realValueRef.current
58
+ const oldLen = currentValue.length
59
+ const newLen = newDisplay.length
60
+
61
+ let newValue: string
62
+ if (newLen > oldLen) {
63
+ const addedChars = newDisplay.replace(/\*/g, '')
64
+ newValue = currentValue + addedChars
65
+ } else if (newLen < oldLen) {
66
+ newValue = currentValue.slice(0, newLen)
67
+ } else {
68
+ return
61
69
  }
70
+
71
+ realValueRef.current = newValue
72
+ setDisplayLength(newValue.length)
73
+ field.onChange(newValue)
74
+ props.onChange?.(newValue)
62
75
  }}
63
76
  placeholder={props.placeholder}
64
77
  focused={isFocused}