termcast 1.3.21 → 1.3.24

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 (412) hide show
  1. package/dist/ai.d.ts +104 -0
  2. package/dist/ai.d.ts.map +1 -0
  3. package/dist/ai.js +135 -0
  4. package/dist/ai.js.map +1 -0
  5. package/dist/apis/browser-extension.d.ts +18 -0
  6. package/dist/apis/browser-extension.d.ts.map +1 -0
  7. package/dist/apis/browser-extension.js +14 -0
  8. package/dist/apis/browser-extension.js.map +1 -0
  9. package/dist/apis/localstorage.d.ts.map +1 -1
  10. package/dist/apis/localstorage.js +4 -7
  11. package/dist/apis/localstorage.js.map +1 -1
  12. package/dist/apis/oauth.d.ts.map +1 -1
  13. package/dist/apis/oauth.js +5 -1
  14. package/dist/apis/oauth.js.map +1 -1
  15. package/dist/apis/preferences.d.ts.map +1 -1
  16. package/dist/apis/preferences.js +38 -19
  17. package/dist/apis/preferences.js.map +1 -1
  18. package/dist/build.d.ts.map +1 -1
  19. package/dist/build.js +2 -1
  20. package/dist/build.js.map +1 -1
  21. package/dist/cache.d.ts +32 -0
  22. package/dist/cache.d.ts.map +1 -0
  23. package/dist/cache.js +205 -0
  24. package/dist/cache.js.map +1 -0
  25. package/dist/cli.d.ts.map +1 -1
  26. package/dist/cli.js +67 -31
  27. package/dist/cli.js.map +1 -1
  28. package/dist/clipboard.d.ts +36 -0
  29. package/dist/clipboard.d.ts.map +1 -0
  30. package/dist/clipboard.js +154 -0
  31. package/dist/clipboard.js.map +1 -0
  32. package/dist/compile.d.ts.map +1 -1
  33. package/dist/compile.js +22 -5
  34. package/dist/compile.js.map +1 -1
  35. package/dist/components/actions.d.ts.map +1 -1
  36. package/dist/components/actions.js +56 -30
  37. package/dist/components/actions.js.map +1 -1
  38. package/dist/components/detail.d.ts.map +1 -1
  39. package/dist/components/detail.js +4 -0
  40. package/dist/components/detail.js.map +1 -1
  41. package/dist/components/dropdown.d.ts.map +1 -1
  42. package/dist/components/dropdown.js +38 -15
  43. package/dist/components/dropdown.js.map +1 -1
  44. package/dist/components/extension-preferences.d.ts.map +1 -1
  45. package/dist/components/extension-preferences.js +40 -13
  46. package/dist/components/extension-preferences.js.map +1 -1
  47. package/dist/components/form/checkbox.d.ts.map +1 -1
  48. package/dist/components/form/checkbox.js +5 -3
  49. package/dist/components/form/checkbox.js.map +1 -1
  50. package/dist/components/form/date-picker.d.ts.map +1 -1
  51. package/dist/components/form/date-picker.js +5 -3
  52. package/dist/components/form/date-picker.js.map +1 -1
  53. package/dist/components/form/description.d.ts.map +1 -1
  54. package/dist/components/form/description.js +2 -2
  55. package/dist/components/form/description.js.map +1 -1
  56. package/dist/components/form/dropdown.d.ts.map +1 -1
  57. package/dist/components/form/dropdown.js +84 -80
  58. package/dist/components/form/dropdown.js.map +1 -1
  59. package/dist/components/form/file-autocomplete.d.ts +3 -6
  60. package/dist/components/form/file-autocomplete.d.ts.map +1 -1
  61. package/dist/components/form/file-autocomplete.js +61 -66
  62. package/dist/components/form/file-autocomplete.js.map +1 -1
  63. package/dist/components/form/file-picker.d.ts.map +1 -1
  64. package/dist/components/form/file-picker.js +33 -30
  65. package/dist/components/form/file-picker.js.map +1 -1
  66. package/dist/components/form/form-end.d.ts.map +1 -1
  67. package/dist/components/form/form-end.js +21 -1
  68. package/dist/components/form/form-end.js.map +1 -1
  69. package/dist/components/form/form-type-only.d.ts +174 -0
  70. package/dist/components/form/form-type-only.d.ts.map +1 -0
  71. package/dist/components/form/form-type-only.js +2 -0
  72. package/dist/components/form/form-type-only.js.map +1 -0
  73. package/dist/components/form/index.d.ts +3 -1
  74. package/dist/components/form/index.d.ts.map +1 -1
  75. package/dist/components/form/index.js +100 -28
  76. package/dist/components/form/index.js.map +1 -1
  77. package/dist/components/form/password-field.d.ts.map +1 -1
  78. package/dist/components/form/password-field.js +5 -3
  79. package/dist/components/form/password-field.js.map +1 -1
  80. package/dist/components/form/text-area.d.ts.map +1 -1
  81. package/dist/components/form/text-area.js +5 -3
  82. package/dist/components/form/text-area.js.map +1 -1
  83. package/dist/components/form/text-field.d.ts.map +1 -1
  84. package/dist/components/form/text-field.js +6 -4
  85. package/dist/components/form/text-field.js.map +1 -1
  86. package/dist/components/form/types.d.ts +5 -0
  87. package/dist/components/form/types.d.ts.map +1 -1
  88. package/dist/components/form/use-form-handling.d.ts +4 -0
  89. package/dist/components/form/use-form-handling.d.ts.map +1 -0
  90. package/dist/components/form/use-form-handling.js +37 -0
  91. package/dist/components/form/use-form-handling.js.map +1 -0
  92. package/dist/components/form/with-left-border.d.ts +2 -1
  93. package/dist/components/form/with-left-border.d.ts.map +1 -1
  94. package/dist/components/form/with-left-border.js +27 -3
  95. package/dist/components/form/with-left-border.js.map +1 -1
  96. package/dist/components/icon.d.ts +1 -0
  97. package/dist/components/icon.d.ts.map +1 -1
  98. package/dist/components/icon.js +24 -8
  99. package/dist/components/icon.js.map +1 -1
  100. package/dist/components/list.d.ts +2 -2
  101. package/dist/components/list.d.ts.map +1 -1
  102. package/dist/components/list.js +140 -62
  103. package/dist/components/list.js.map +1 -1
  104. package/dist/components/loading-bar.d.ts.map +1 -1
  105. package/dist/components/loading-bar.js +2 -2
  106. package/dist/components/loading-bar.js.map +1 -1
  107. package/dist/components/loading-text.d.ts +8 -0
  108. package/dist/components/loading-text.d.ts.map +1 -0
  109. package/dist/components/loading-text.js +58 -0
  110. package/dist/components/loading-text.js.map +1 -0
  111. package/dist/descendants.js +1 -1
  112. package/dist/descendants.js.map +1 -1
  113. package/dist/dev-ui.d.ts +7 -0
  114. package/dist/dev-ui.d.ts.map +1 -0
  115. package/dist/dev-ui.js +118 -0
  116. package/dist/dev-ui.js.map +1 -0
  117. package/dist/environment.d.ts +63 -0
  118. package/dist/environment.d.ts.map +1 -0
  119. package/dist/environment.js +189 -0
  120. package/dist/environment.js.map +1 -0
  121. package/dist/examples/datepicker.d.ts +2 -0
  122. package/dist/examples/datepicker.d.ts.map +1 -0
  123. package/dist/examples/datepicker.js +344 -0
  124. package/dist/examples/datepicker.js.map +1 -0
  125. package/dist/examples/file-autocomplete.vitest.d.ts +2 -0
  126. package/dist/examples/file-autocomplete.vitest.d.ts.map +1 -0
  127. package/dist/examples/file-autocomplete.vitest.js +223 -0
  128. package/dist/examples/file-autocomplete.vitest.js.map +1 -0
  129. package/dist/examples/form-basic-arrow-keys.vitest.d.ts +2 -0
  130. package/dist/examples/form-basic-arrow-keys.vitest.d.ts.map +1 -0
  131. package/dist/examples/form-basic-arrow-keys.vitest.js +46 -0
  132. package/dist/examples/form-basic-arrow-keys.vitest.js.map +1 -0
  133. package/dist/examples/form-basic.vitest.d.ts +2 -0
  134. package/dist/examples/form-basic.vitest.d.ts.map +1 -0
  135. package/dist/examples/form-basic.vitest.js +630 -0
  136. package/dist/examples/form-basic.vitest.js.map +1 -0
  137. package/dist/examples/form-dropdown-with-sections.d.ts +2 -0
  138. package/dist/examples/form-dropdown-with-sections.d.ts.map +1 -0
  139. package/dist/examples/form-dropdown-with-sections.js +13 -0
  140. package/dist/examples/form-dropdown-with-sections.js.map +1 -0
  141. package/dist/examples/form-dropdown-with-sections.vitest.d.ts +2 -0
  142. package/dist/examples/form-dropdown-with-sections.vitest.d.ts.map +1 -0
  143. package/dist/examples/form-dropdown-with-sections.vitest.js +75 -0
  144. package/dist/examples/form-dropdown-with-sections.vitest.js.map +1 -0
  145. package/dist/examples/form-dropdown.vitest.d.ts +2 -0
  146. package/dist/examples/form-dropdown.vitest.d.ts.map +1 -0
  147. package/dist/examples/form-dropdown.vitest.js +854 -0
  148. package/dist/examples/form-dropdown.vitest.js.map +1 -0
  149. package/dist/examples/form-multiselect-dropdown.d.ts +2 -0
  150. package/dist/examples/form-multiselect-dropdown.d.ts.map +1 -0
  151. package/dist/examples/form-multiselect-dropdown.js +13 -0
  152. package/dist/examples/form-multiselect-dropdown.js.map +1 -0
  153. package/dist/examples/form-scroll.d.ts.map +1 -1
  154. package/dist/examples/form-scroll.js +7 -1
  155. package/dist/examples/form-scroll.js.map +1 -1
  156. package/dist/examples/form-scroll.vitest.d.ts +2 -0
  157. package/dist/examples/form-scroll.vitest.d.ts.map +1 -0
  158. package/dist/examples/form-scroll.vitest.js +211 -0
  159. package/dist/examples/form-scroll.vitest.js.map +1 -0
  160. package/dist/examples/form-tagpicker.vitest.d.ts +2 -0
  161. package/dist/examples/form-tagpicker.vitest.d.ts.map +1 -0
  162. package/dist/examples/form-tagpicker.vitest.js +736 -0
  163. package/dist/examples/form-tagpicker.vitest.js.map +1 -0
  164. package/dist/examples/internal/descendants-filtering.js +1 -1
  165. package/dist/examples/internal/descendants-filtering.js.map +1 -1
  166. package/dist/examples/internal/descendants.js +1 -1
  167. package/dist/examples/internal/descendants.js.map +1 -1
  168. package/dist/examples/internal/nested-boxes.d.ts +2 -0
  169. package/dist/examples/internal/nested-boxes.d.ts.map +1 -0
  170. package/dist/examples/internal/nested-boxes.js +7 -0
  171. package/dist/examples/internal/nested-boxes.js.map +1 -0
  172. package/dist/examples/internal/rhf-custom-ref.js +2 -2
  173. package/dist/examples/internal/rhf-custom-ref.js.map +1 -1
  174. package/dist/examples/internal/scrollbox-demo.js +3 -22
  175. package/dist/examples/internal/scrollbox-demo.js.map +1 -1
  176. package/dist/examples/internal/scrollbox-descendants.d.ts +2 -0
  177. package/dist/examples/internal/scrollbox-descendants.d.ts.map +1 -0
  178. package/dist/examples/internal/scrollbox-descendants.js +83 -0
  179. package/dist/examples/internal/scrollbox-descendants.js.map +1 -0
  180. package/dist/examples/internal/scrollbox-with-descendants.js +4 -8
  181. package/dist/examples/internal/scrollbox-with-descendants.js.map +1 -1
  182. package/dist/examples/internal/simple-scrollbox.vitest.d.ts +2 -0
  183. package/dist/examples/internal/simple-scrollbox.vitest.d.ts.map +1 -0
  184. package/dist/examples/internal/simple-scrollbox.vitest.js +96 -0
  185. package/dist/examples/internal/simple-scrollbox.vitest.js.map +1 -0
  186. package/dist/examples/internal/unicode-square-repro.d.ts +2 -0
  187. package/dist/examples/internal/unicode-square-repro.d.ts.map +1 -0
  188. package/dist/examples/internal/unicode-square-repro.js +7 -0
  189. package/dist/examples/internal/unicode-square-repro.js.map +1 -0
  190. package/dist/examples/list-detail-metadata.d.ts +2 -0
  191. package/dist/examples/list-detail-metadata.d.ts.map +1 -0
  192. package/dist/examples/list-detail-metadata.js +8 -0
  193. package/dist/examples/list-detail-metadata.js.map +1 -0
  194. package/dist/examples/list-dropdown-default.vitest.d.ts +2 -0
  195. package/dist/examples/list-dropdown-default.vitest.d.ts.map +1 -0
  196. package/dist/examples/list-dropdown-default.vitest.js +234 -0
  197. package/dist/examples/list-dropdown-default.vitest.js.map +1 -0
  198. package/dist/examples/list-fetch-data.vitest.d.ts +2 -0
  199. package/dist/examples/list-fetch-data.vitest.d.ts.map +1 -0
  200. package/dist/examples/list-fetch-data.vitest.js +111 -0
  201. package/dist/examples/list-fetch-data.vitest.js.map +1 -0
  202. package/dist/examples/list-filter-navigation.d.ts +2 -0
  203. package/dist/examples/list-filter-navigation.d.ts.map +1 -0
  204. package/dist/examples/list-filter-navigation.js +8 -0
  205. package/dist/examples/list-filter-navigation.js.map +1 -0
  206. package/dist/examples/list-scrollbox.vitest.d.ts +2 -0
  207. package/dist/examples/list-scrollbox.vitest.d.ts.map +1 -0
  208. package/dist/examples/list-scrollbox.vitest.js +93 -0
  209. package/dist/examples/list-scrollbox.vitest.js.map +1 -0
  210. package/dist/examples/list-with-detail-long.d.ts +2 -0
  211. package/dist/examples/list-with-detail-long.d.ts.map +1 -0
  212. package/dist/examples/list-with-detail-long.js +53 -0
  213. package/dist/examples/list-with-detail-long.js.map +1 -0
  214. package/dist/examples/list-with-detail.vitest.d.ts +2 -0
  215. package/dist/examples/list-with-detail.vitest.d.ts.map +1 -0
  216. package/dist/examples/list-with-detail.vitest.js +434 -0
  217. package/dist/examples/list-with-detail.vitest.js.map +1 -0
  218. package/dist/examples/list-with-dropdown.vitest.d.ts +2 -0
  219. package/dist/examples/list-with-dropdown.vitest.d.ts.map +1 -0
  220. package/dist/examples/list-with-dropdown.vitest.js +337 -0
  221. package/dist/examples/list-with-dropdown.vitest.js.map +1 -0
  222. package/dist/examples/list-with-sections.js +5 -1
  223. package/dist/examples/list-with-sections.js.map +1 -1
  224. package/dist/examples/list-with-sections.vitest.d.ts +2 -0
  225. package/dist/examples/list-with-sections.vitest.d.ts.map +1 -0
  226. package/dist/examples/list-with-sections.vitest.js +601 -0
  227. package/dist/examples/list-with-sections.vitest.js.map +1 -0
  228. package/dist/examples/scrollbox-vertical-centering.d.ts +6 -0
  229. package/dist/examples/scrollbox-vertical-centering.d.ts.map +1 -0
  230. package/dist/examples/scrollbox-vertical-centering.js +17 -0
  231. package/dist/examples/scrollbox-vertical-centering.js.map +1 -0
  232. package/dist/examples/simple-file-picker.vitest.d.ts +2 -0
  233. package/dist/examples/simple-file-picker.vitest.d.ts.map +1 -0
  234. package/dist/examples/simple-file-picker.vitest.js +678 -0
  235. package/dist/examples/simple-file-picker.vitest.js.map +1 -0
  236. package/dist/examples/simple-grid.vitest.d.ts +2 -0
  237. package/dist/examples/simple-grid.vitest.d.ts.map +1 -0
  238. package/dist/examples/simple-grid.vitest.js +521 -0
  239. package/dist/examples/simple-grid.vitest.js.map +1 -0
  240. package/dist/examples/simple-navigation.js +10 -4
  241. package/dist/examples/simple-navigation.js.map +1 -1
  242. package/dist/examples/simple-navigation.vitest.d.ts +2 -0
  243. package/dist/examples/simple-navigation.vitest.d.ts.map +1 -0
  244. package/dist/examples/simple-navigation.vitest.js +718 -0
  245. package/dist/examples/simple-navigation.vitest.js.map +1 -0
  246. package/dist/examples/store.vitest.d.ts +2 -0
  247. package/dist/examples/store.vitest.d.ts.map +1 -0
  248. package/dist/examples/store.vitest.js +69 -0
  249. package/dist/examples/store.vitest.js.map +1 -0
  250. package/dist/extensions/dev.d.ts +4 -2
  251. package/dist/extensions/dev.d.ts.map +1 -1
  252. package/dist/extensions/dev.js +61 -10
  253. package/dist/extensions/dev.js.map +1 -1
  254. package/dist/extensions/dev.vitest.d.ts +2 -0
  255. package/dist/extensions/dev.vitest.d.ts.map +1 -0
  256. package/dist/extensions/dev.vitest.js +197 -0
  257. package/dist/extensions/dev.vitest.js.map +1 -0
  258. package/dist/extensions/home.d.ts.map +1 -1
  259. package/dist/extensions/home.js +3 -0
  260. package/dist/extensions/home.js.map +1 -1
  261. package/dist/home-command.d.ts +8 -0
  262. package/dist/home-command.d.ts.map +1 -0
  263. package/dist/home-command.js +181 -0
  264. package/dist/home-command.js.map +1 -0
  265. package/dist/hover-repro.d.ts +2 -0
  266. package/dist/hover-repro.d.ts.map +1 -0
  267. package/dist/hover-repro.js +20 -0
  268. package/dist/hover-repro.js.map +1 -0
  269. package/dist/index.d.ts +1 -0
  270. package/dist/index.d.ts.map +1 -1
  271. package/dist/index.js +2 -0
  272. package/dist/index.js.map +1 -1
  273. package/dist/internal/dialog.d.ts +1 -0
  274. package/dist/internal/dialog.d.ts.map +1 -1
  275. package/dist/internal/dialog.js +27 -18
  276. package/dist/internal/dialog.js.map +1 -1
  277. package/dist/internal/navigation.d.ts +9 -1
  278. package/dist/internal/navigation.d.ts.map +1 -1
  279. package/dist/internal/navigation.js +5 -5
  280. package/dist/internal/navigation.js.map +1 -1
  281. package/dist/internal/offscreen.d.ts +6 -0
  282. package/dist/internal/offscreen.d.ts.map +1 -0
  283. package/dist/internal/offscreen.js +10 -0
  284. package/dist/internal/offscreen.js.map +1 -0
  285. package/dist/internal/providers.d.ts.map +1 -1
  286. package/dist/internal/providers.js +2 -2
  287. package/dist/internal/providers.js.map +1 -1
  288. package/dist/internal/scrollbox.d.ts +1 -10
  289. package/dist/internal/scrollbox.d.ts.map +1 -1
  290. package/dist/internal/scrollbox.js +2 -1
  291. package/dist/internal/scrollbox.js.map +1 -1
  292. package/dist/localstorage.d.ts +13 -0
  293. package/dist/localstorage.d.ts.map +1 -0
  294. package/dist/localstorage.js +190 -0
  295. package/dist/localstorage.js.map +1 -0
  296. package/dist/oauth.d.ts +142 -0
  297. package/dist/oauth.d.ts.map +1 -0
  298. package/dist/oauth.js +551 -0
  299. package/dist/oauth.js.map +1 -0
  300. package/dist/preferences.d.ts +23 -0
  301. package/dist/preferences.d.ts.map +1 -0
  302. package/dist/preferences.js +105 -0
  303. package/dist/preferences.js.map +1 -0
  304. package/dist/state.d.ts +2 -0
  305. package/dist/state.d.ts.map +1 -1
  306. package/dist/state.js +3 -0
  307. package/dist/state.js.map +1 -1
  308. package/dist/store.d.ts +21 -0
  309. package/dist/store.d.ts.map +1 -0
  310. package/dist/store.js +84 -0
  311. package/dist/store.js.map +1 -0
  312. package/dist/swift-loader.d.ts +3 -0
  313. package/dist/swift-loader.d.ts.map +1 -0
  314. package/dist/swift-loader.js +193 -0
  315. package/dist/swift-loader.js.map +1 -0
  316. package/dist/swift-runtime.d.ts +2 -0
  317. package/dist/swift-runtime.d.ts.map +1 -0
  318. package/dist/swift-runtime.js +27 -0
  319. package/dist/swift-runtime.js.map +1 -0
  320. package/dist/toast.d.ts +44 -0
  321. package/dist/toast.d.ts.map +1 -0
  322. package/dist/toast.js +221 -0
  323. package/dist/toast.js.map +1 -0
  324. package/dist/utils/file-system.d.ts +9 -0
  325. package/dist/utils/file-system.d.ts.map +1 -1
  326. package/dist/utils/file-system.js +49 -0
  327. package/dist/utils/file-system.js.map +1 -1
  328. package/dist/utils/run-command.d.ts +25 -1
  329. package/dist/utils/run-command.d.ts.map +1 -1
  330. package/dist/utils/run-command.js +47 -4
  331. package/dist/utils/run-command.js.map +1 -1
  332. package/dist/window.d.ts +12 -0
  333. package/dist/window.d.ts.map +1 -0
  334. package/dist/window.js +48 -0
  335. package/dist/window.js.map +1 -0
  336. package/package.json +12 -10
  337. package/src/apis/browser-extension.tsx +29 -0
  338. package/src/apis/localstorage.test.ts +14 -6
  339. package/src/apis/localstorage.tsx +8 -5
  340. package/src/apis/oauth.tsx +5 -1
  341. package/src/apis/preferences.tsx +48 -22
  342. package/src/build.test.tsx +52 -0
  343. package/src/build.tsx +2 -1
  344. package/src/cli.tsx +78 -37
  345. package/src/compile.tsx +22 -5
  346. package/src/components/actions.tsx +94 -32
  347. package/src/components/detail.tsx +4 -0
  348. package/src/components/dropdown.tsx +47 -14
  349. package/src/components/extension-preferences.tsx +44 -16
  350. package/src/components/form/checkbox.tsx +12 -6
  351. package/src/components/form/date-picker.tsx +12 -6
  352. package/src/components/form/description.tsx +7 -2
  353. package/src/components/form/dropdown.tsx +131 -119
  354. package/src/components/form/file-autocomplete.tsx +90 -108
  355. package/src/components/form/file-picker.tsx +54 -43
  356. package/src/components/form/form-end.tsx +23 -2
  357. package/src/components/form/index.tsx +152 -34
  358. package/src/components/form/password-field.tsx +12 -6
  359. package/src/components/form/text-area.tsx +13 -6
  360. package/src/components/form/text-field.tsx +13 -6
  361. package/src/components/form/types.tsx +6 -0
  362. package/src/components/form/with-left-border.tsx +41 -8
  363. package/src/components/icon.tsx +27 -8
  364. package/src/components/list.tsx +193 -74
  365. package/src/components/loading-bar.tsx +3 -2
  366. package/src/components/loading-text.tsx +79 -0
  367. package/src/descendants.tsx +1 -0
  368. package/src/examples/file-autocomplete.vitest.tsx +130 -125
  369. package/src/examples/form-basic.vitest.tsx +376 -176
  370. package/src/examples/form-dropdown.vitest.tsx +126 -126
  371. package/src/examples/form-scroll.tsx +2 -0
  372. package/src/examples/form-scroll.vitest.tsx +58 -58
  373. package/src/examples/form-tagpicker.vitest.tsx +99 -99
  374. package/src/examples/internal/descendants-filtering.tsx +1 -0
  375. package/src/examples/internal/descendants.tsx +1 -0
  376. package/src/examples/internal/rhf-custom-ref.tsx +2 -0
  377. package/src/examples/internal/scrollbox-demo.tsx +3 -27
  378. package/src/examples/internal/scrollbox-with-descendants.tsx +4 -7
  379. package/src/examples/internal/simple-scrollbox.vitest.tsx +7 -5
  380. package/src/examples/list-detail-metadata.tsx +49 -0
  381. package/src/examples/list-detail-metadata.vitest.tsx +88 -0
  382. package/src/examples/list-dropdown-default.vitest.tsx +51 -51
  383. package/src/examples/list-fetch-data.vitest.tsx +4 -4
  384. package/src/examples/list-scrollbox.vitest.tsx +73 -14
  385. package/src/examples/list-with-detail-long.tsx +70 -0
  386. package/src/examples/list-with-detail.vitest.tsx +198 -92
  387. package/src/examples/list-with-dropdown.vitest.tsx +53 -53
  388. package/src/examples/list-with-sections.tsx +1 -0
  389. package/src/examples/list-with-sections.vitest.tsx +213 -89
  390. package/src/examples/list-with-toast.vitest.tsx +4 -4
  391. package/src/examples/simple-file-picker.vitest.tsx +61 -471
  392. package/src/examples/simple-grid.vitest.tsx +238 -233
  393. package/src/examples/simple-navigation.tsx +15 -7
  394. package/src/examples/simple-navigation.vitest.tsx +121 -210
  395. package/src/examples/store.vitest.tsx +1 -1
  396. package/src/examples/swift-extension.vitest.tsx +148 -0
  397. package/src/examples/synonyms.vitest.tsx +159 -0
  398. package/src/extensions/dev.tsx +74 -7
  399. package/src/extensions/dev.vitest.tsx +97 -31
  400. package/src/extensions/home.tsx +6 -0
  401. package/src/index.tsx +3 -0
  402. package/src/internal/dialog.tsx +43 -30
  403. package/src/internal/navigation.tsx +3 -1
  404. package/src/internal/offscreen.tsx +15 -0
  405. package/src/internal/providers.tsx +4 -2
  406. package/src/internal/scrollbox.tsx +4 -8
  407. package/src/keyboard.test.tsx +69 -0
  408. package/src/state.tsx +7 -0
  409. package/src/swift-loader.tsx +239 -0
  410. package/src/swift-runtime.tsx +36 -0
  411. package/src/utils/file-system.ts +61 -0
  412. package/src/utils/run-command.tsx +75 -6
@@ -7,8 +7,10 @@ import { useFormContext, Controller } from 'react-hook-form'
7
7
  import { useFocusContext, useFormFieldDescendant } from './index'
8
8
  import { useKeyboard } from '@opentui/react'
9
9
  import { useIsInFocus } from 'termcast/src/internal/focus-context'
10
- import { FileAutocomplete } from './file-autocomplete'
10
+ import { FileAutocompleteDialog } from './file-autocomplete'
11
11
  import { useFormNavigationHelpers } from './use-form-navigation'
12
+ import { useDialog } from 'termcast/src/internal/dialog'
13
+ import { LoadingText } from 'termcast/src/components/loading-text'
12
14
 
13
15
  export interface FilePickerProps extends FormItemProps<string[]> {
14
16
  /**
@@ -52,6 +54,7 @@ const FilePickerField = ({
52
54
  props,
53
55
  isFocused,
54
56
  setFocusedField,
57
+ isFormLoading,
55
58
  }: {
56
59
  field: any
57
60
  fieldState: any
@@ -59,12 +62,40 @@ const FilePickerField = ({
59
62
  props: FilePickerProps
60
63
  isFocused: boolean
61
64
  setFocusedField: (id: string) => void
65
+ isFormLoading: boolean
62
66
  }): any => {
63
67
  const isInFocus = useIsInFocus()
64
- const [showAutocomplete, setShowAutocomplete] = React.useState(false)
65
- const [searchTrigger, setSearchTrigger] = React.useState(0)
66
68
  const inputRef = React.useRef<TextareaRenderable>(null)
67
- const anchorRef = React.useRef<any>(null)
69
+ const dialog = useDialog()
70
+
71
+ const showAutocomplete = () => {
72
+ if (dialog.stack.length > 0) return
73
+
74
+ const handleSelect = (path: string) => {
75
+ const currentFiles = field.value || []
76
+ const newFiles =
77
+ props.allowMultipleSelection !== false ? [...currentFiles, path] : [path]
78
+ field.onChange(newFiles)
79
+ if (props.onChange) {
80
+ props.onChange(newFiles)
81
+ }
82
+ inputRef.current?.setText('')
83
+ dialog.clear()
84
+ }
85
+
86
+ dialog.push(
87
+ <FileAutocompleteDialog
88
+ onSelect={handleSelect}
89
+ onClose={() => {
90
+ dialog.clear()
91
+ }}
92
+ inputRef={inputRef}
93
+ canChooseFiles={props.canChooseFiles}
94
+ canChooseDirectories={props.canChooseDirectories}
95
+ initialDirectory={props.initialDirectory}
96
+ />
97
+ )
98
+ }
68
99
 
69
100
  // Handle Enter key and left arrow for removing last file
70
101
  useKeyboard((evt) => {
@@ -86,12 +117,11 @@ const FilePickerField = ({
86
117
  if (evt.name === 'return') {
87
118
  const inputValue = inputRef.current?.plainText || ''
88
119
  // If input is empty, show files in current directory
89
- if (!inputValue && !showAutocomplete) {
90
- setShowAutocomplete(true)
91
- setSearchTrigger((n) => n + 1)
120
+ if (!inputValue) {
121
+ showAutocomplete()
92
122
  }
93
- // If autocomplete is not visible and input has value, add the path
94
- else if (inputValue.trim() && !showAutocomplete) {
123
+ // If input has value, add the path directly
124
+ else if (inputValue.trim()) {
95
125
  const currentFiles = field.value || []
96
126
  const newFiles =
97
127
  props.allowMultipleSelection !== false
@@ -106,37 +136,30 @@ const FilePickerField = ({
106
136
  }
107
137
  })
108
138
 
109
- const handleSelectFile = (path: string) => {
110
- const currentFiles = field.value || []
111
- const newFiles =
112
- props.allowMultipleSelection !== false ? [...currentFiles, path] : [path]
113
- field.onChange(newFiles)
114
- if (props.onChange) {
115
- props.onChange(newFiles)
116
- }
117
- inputRef.current?.setText('')
118
- setShowAutocomplete(false)
119
- }
120
-
121
139
  const selectedFiles = field.value || []
122
140
 
123
141
  return (
124
142
  <box flexDirection='column'>
125
- <WithLeftBorder withDiamond isFocused={isFocused}>
126
- <text
127
- fg={isFocused ? Theme.primary : Theme.text}
143
+ <WithLeftBorder withDiamond isFocused={isFocused} isLoading={isFormLoading}>
144
+ <box
128
145
  onMouseDown={() => {
129
146
  setFocusedField(props.id)
130
147
  }}
131
148
  >
132
- {props.title || 'File Path'}
133
- </text>
149
+ <LoadingText
150
+ isLoading={isFocused && isFormLoading}
151
+ color={isFocused ? Theme.primary : Theme.text}
152
+ >
153
+ {props.title || 'File Path'}
154
+ </LoadingText>
155
+ </box>
134
156
  </WithLeftBorder>
135
157
  <WithLeftBorder isFocused={isFocused}>
136
- <box flexDirection='column' ref={anchorRef}>
158
+ <box flexDirection='column'>
137
159
  <textarea
138
160
  ref={inputRef}
139
161
  height={1}
162
+ wrapMode='none'
140
163
  keyBindings={[
141
164
  { name: 'return', action: 'submit' },
142
165
  { name: 'linefeed', action: 'submit' },
@@ -148,10 +171,7 @@ const FilePickerField = ({
148
171
  onContentChange={() => {
149
172
  const value = inputRef.current?.plainText || ''
150
173
  if (value && isFocused) {
151
- setShowAutocomplete(true)
152
- setSearchTrigger((n) => n + 1)
153
- } else if (!value) {
154
- setShowAutocomplete(false)
174
+ showAutocomplete()
155
175
  }
156
176
  }}
157
177
  />
@@ -179,24 +199,14 @@ const FilePickerField = ({
179
199
  <text fg={Theme.textMuted}>{props.info}</text>
180
200
  </WithLeftBorder>
181
201
  )}
182
- <FileAutocomplete
183
- onSelect={handleSelectFile}
184
- visible={showAutocomplete}
185
- onVisibilityChange={setShowAutocomplete}
186
- inputRef={inputRef}
187
- anchorRef={anchorRef}
188
- searchTrigger={searchTrigger}
189
- canChooseFiles={props.canChooseFiles}
190
- canChooseDirectories={props.canChooseDirectories}
191
- initialDirectory={props.initialDirectory}
192
- />
193
202
  </box>
194
203
  ) as React.ReactElement
195
204
  }
196
205
 
197
206
  export const FilePicker = (props: FilePickerProps): any => {
198
207
  const { control } = useFormContext()
199
- const { focusedField, setFocusedField } = useFocusContext()
208
+ const focusContext = useFocusContext()
209
+ const { focusedField, setFocusedField } = focusContext
200
210
  const isFocused = focusedField === props.id
201
211
  const isInFocus = useIsInFocus()
202
212
 
@@ -236,6 +246,7 @@ export const FilePicker = (props: FilePickerProps): any => {
236
246
  props={props}
237
247
  isFocused={isFocused}
238
248
  setFocusedField={setFocusedField}
249
+ isFormLoading={focusContext.isLoading}
239
250
  />
240
251
  </box>
241
252
  ) as React.ReactElement
@@ -1,6 +1,27 @@
1
- import React from 'react'
1
+ import React, { useState, useLayoutEffect } from 'react'
2
2
  import { Theme } from 'termcast/src/theme'
3
+ import { useFocusContext, useFormScrollContext } from './index'
3
4
 
4
5
  export const FormEnd = (): any => {
5
- return <text fg={Theme.text}>└</text>
6
+ const { focusedField } = useFocusContext()
7
+ const scrollContext = useFormScrollContext()
8
+ const [isLastFieldFocused, setIsLastFieldFocused] = useState(false)
9
+
10
+ useLayoutEffect(() => {
11
+ if (!scrollContext || !focusedField) {
12
+ setIsLastFieldFocused(false)
13
+ return
14
+ }
15
+ const descendants = Object.values(scrollContext.descendantsContext.map.current)
16
+ .filter((item) => item.index !== -1 && item.props?.id)
17
+ .sort((a, b) => a.index - b.index)
18
+ if (descendants.length === 0) {
19
+ setIsLastFieldFocused(false)
20
+ return
21
+ }
22
+ const lastField = descendants[descendants.length - 1]
23
+ setIsLastFieldFocused(lastField.props?.id === focusedField)
24
+ }, [focusedField])
25
+
26
+ return <text fg={isLastFieldFocused ? Theme.accent : Theme.text}>└</text>
6
27
  }
@@ -4,6 +4,7 @@ import React, {
4
4
  useContext,
5
5
  useLayoutEffect,
6
6
  useRef,
7
+ useEffect,
7
8
  } from 'react'
8
9
  import { useKeyboard } from '@opentui/react'
9
10
  import { useForm, FormProvider } from 'react-hook-form'
@@ -12,6 +13,7 @@ import { logger } from 'termcast/src/logger'
12
13
  import { InFocus, useIsInFocus } from 'termcast/src/internal/focus-context'
13
14
  import { useDialog } from 'termcast/src/internal/dialog'
14
15
  import { Theme } from 'termcast/src/theme'
16
+ import { useStore } from 'termcast/src/state'
15
17
  import {
16
18
  TextAttributes,
17
19
  ScrollBoxRenderable,
@@ -33,7 +35,10 @@ import {
33
35
  FormValues_2,
34
36
  FormProps_2,
35
37
  FormItemProps_2,
38
+ LinkAccessoryProps,
36
39
  } from './types'
40
+ import { LoadingBar } from 'termcast/src/components/loading-bar'
41
+ import { useNavigationPending } from 'termcast/src/internal/navigation'
37
42
  import { FORM_MAX_WIDTH } from './description'
38
43
  import { ScrollBox } from 'termcast/src/internal/scrollbox'
39
44
 
@@ -67,10 +72,11 @@ export const useFormScrollContext = () => {
67
72
  return useContext(FormScrollContext)
68
73
  }
69
74
 
70
- // Context for managing focused field
75
+ // Context for managing focused field and loading state
71
76
  interface FocusContextValue {
72
77
  focusedField: string | null
73
78
  setFocusedField: (id: string | null) => void
79
+ isLoading: boolean
74
80
  }
75
81
 
76
82
  const FocusContext = createContext<FocusContextValue | null>(null)
@@ -107,14 +113,16 @@ function FormFooter(): any {
107
113
  flexDirection: 'row',
108
114
  }}
109
115
  >
110
- <text fg={Theme.text} attributes={TextAttributes.BOLD}>ctrl ↵</text>
116
+ <text fg={Theme.text} attributes={TextAttributes.BOLD}>
117
+ ctrl ↵
118
+ </text>
111
119
  <text fg={Theme.textMuted}> submit</text>
112
120
  <text fg={Theme.text} attributes={TextAttributes.BOLD}>
113
- {' '}↑↓
121
+ {' '}tab
114
122
  </text>
115
123
  <text fg={Theme.textMuted}> navigate</text>
116
124
  <text fg={Theme.text} attributes={TextAttributes.BOLD}>
117
- {' '}^k
125
+ {' '}^k
118
126
  </text>
119
127
  <text fg={Theme.textMuted}> actions</text>
120
128
  </box>
@@ -171,19 +179,30 @@ interface FormType {
171
179
  FilePicker: (props: FilePickerProps) => any
172
180
  Separator: () => any
173
181
  Description: (props: DescriptionProps) => any
182
+ LinkAccessory: (props: LinkAccessoryProps) => any
174
183
  }
175
184
 
176
185
  export const Form: FormType = ((props) => {
186
+ const { navigationTitle, isLoading, searchBarAccessory } = props
177
187
  const methods = useForm<FormValues>({
178
188
  // defaultValues: {},
179
189
  // mode: 'onChange',
180
190
  })
181
191
 
182
192
  const [focusedField, setFocusedFieldRaw] = useState<string | null>(null)
193
+ const navigationPending = useNavigationPending()
183
194
 
184
195
  const scrollBoxRef = useRef<ScrollBoxRenderable>(null)
185
196
  const descendantsContext = useFormFieldDescendants()
186
197
 
198
+ // Helper to get sorted field IDs
199
+ const getFieldIds = () => {
200
+ return Object.values(descendantsContext.map.current)
201
+ .filter((item) => item.index !== -1 && item.props?.id)
202
+ .sort((a, b) => a.index - b.index)
203
+ .map((item) => item.props!.id)
204
+ }
205
+
187
206
  const scrollToField = (fieldId: string) => {
188
207
  const scrollBox = scrollBoxRef.current
189
208
  if (!scrollBox) return
@@ -197,17 +216,13 @@ export const Form: FormType = ((props) => {
197
216
 
198
217
  const contentY = scrollBox.content?.y || 0
199
218
  const viewportHeight = scrollBox.viewport?.height || 10
200
- const currentScrollTop = scrollBox.scrollTop || 0
201
219
 
202
220
  // Access current position from the BoxRenderable ref
203
221
  const itemTop = elementRef.y - contentY
204
- const itemBottom = itemTop + elementRef.height
205
222
 
206
- if (itemTop < currentScrollTop) {
207
- scrollBox.scrollTo(itemTop)
208
- } else if (itemBottom > currentScrollTop + viewportHeight) {
209
- scrollBox.scrollTo(itemBottom - viewportHeight)
210
- }
223
+ // Scroll so the top of the item is centered in the viewport
224
+ const targetScrollTop = itemTop - viewportHeight / 2
225
+ scrollBox.scrollTo(Math.max(0, targetScrollTop))
211
226
  }
212
227
 
213
228
  const setFocusedField = (id: string | null) => {
@@ -217,30 +232,82 @@ export const Form: FormType = ((props) => {
217
232
  }
218
233
  }
219
234
 
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)
230
- } else {
231
- logger.log(`no fields to focus in form`)
235
+ // Focus first field helper
236
+ const focusFirstField = () => {
237
+ const fieldIds = getFieldIds()
238
+ if (fieldIds.length > 0) {
239
+ logger.log(`focusing first field:`, fieldIds[0])
240
+ setFocusedField(fieldIds[0])
241
+ return true
232
242
  }
233
- }, [])
243
+ return false
244
+ }
245
+
246
+ // Focus last field helper
247
+ const focusLastField = () => {
248
+ const fieldIds = getFieldIds()
249
+ if (fieldIds.length > 0) {
250
+ logger.log(`focusing last field:`, fieldIds[fieldIds.length - 1])
251
+ setFocusedField(fieldIds[fieldIds.length - 1])
252
+ return true
253
+ }
254
+ return false
255
+ }
256
+
257
+ // Auto-focus first field when descendants become available
258
+ // Runs on every render until a field is focused (handles async loading)
259
+ useEffect(() => {
260
+ if (focusedField) return
261
+
262
+ const fieldIds = getFieldIds()
263
+ if (fieldIds.length > 0) {
264
+ logger.log(`auto-focusing first field:`, fieldIds[0])
265
+ setFocusedFieldRaw(fieldIds[0])
266
+ }
267
+ })
234
268
 
235
269
  // Get focus state and dialog
236
270
  const inFocus = useIsInFocus()
237
271
  const dialog = useDialog()
238
272
 
239
- // Handle action keys and page scrolling
273
+ // Handle action keys, tab navigation, and page scrolling
240
274
  useKeyboard((evt) => {
241
275
  // Only handle keyboard events when form is in focus
242
276
  if (!inFocus) return
243
277
 
278
+ // Tab navigation at Form level - handles case when no field is focused
279
+ // or navigates between fields
280
+ if (evt.name === 'tab') {
281
+ if (!focusedField) {
282
+ // No field focused yet - focus first or last based on shift
283
+ if (evt.shift) {
284
+ focusLastField()
285
+ } else {
286
+ focusFirstField()
287
+ }
288
+ } else {
289
+ // A field is focused - navigate to next/previous
290
+ const fieldIds = getFieldIds()
291
+ const currentIndex = fieldIds.indexOf(focusedField)
292
+ if (evt.shift) {
293
+ // Shift+Tab - go to previous
294
+ if (currentIndex > 0) {
295
+ setFocusedField(fieldIds[currentIndex - 1])
296
+ } else {
297
+ setFocusedField(fieldIds[fieldIds.length - 1])
298
+ }
299
+ } else {
300
+ // Tab - go to next
301
+ if (currentIndex < fieldIds.length - 1) {
302
+ setFocusedField(fieldIds[currentIndex + 1])
303
+ } else {
304
+ setFocusedField(fieldIds[0])
305
+ }
306
+ }
307
+ }
308
+ return
309
+ }
310
+
244
311
  // Page up/down scrolling
245
312
  if (evt.name === 'pageup' || evt.name === 'pagedown') {
246
313
  const scrollBox = scrollBoxRef.current
@@ -259,7 +326,7 @@ export const Form: FormType = ((props) => {
259
326
  }
260
327
 
261
328
  if (evt.name === 'k' && evt.ctrl && props.actions) {
262
- // Ctrl+K shows actions
329
+ // Ctrl+K shows actions (always show sheet)
263
330
  dialog.push(
264
331
  <FormSubmitContext.Provider value={submitContextValue}>
265
332
  {props.actions}
@@ -267,7 +334,8 @@ export const Form: FormType = ((props) => {
267
334
  'bottom-right',
268
335
  )
269
336
  } else if (evt.name === 'return' && evt.ctrl && props.actions) {
270
- // Ctrl+Return shows actions (form submit shortcut)
337
+ // Ctrl+Return executes first action directly
338
+ useStore.setState({ shouldAutoExecuteFirstAction: true })
271
339
  dialog.push(
272
340
  <FormSubmitContext.Provider value={submitContextValue}>
273
341
  {props.actions}
@@ -275,7 +343,8 @@ export const Form: FormType = ((props) => {
275
343
  'bottom-right',
276
344
  )
277
345
  } else if (evt.name === 'return' && evt.meta && props.actions) {
278
- // Cmd+Return also shows actions (consistent with List)
346
+ // Cmd+Return also executes first action directly
347
+ useStore.setState({ shouldAutoExecuteFirstAction: true })
279
348
  dialog.push(
280
349
  <FormSubmitContext.Provider value={submitContextValue}>
281
350
  {props.actions}
@@ -298,9 +367,9 @@ export const Form: FormType = ((props) => {
298
367
  <FormProvider {...methods}>
299
368
  <FormSubmitContext.Provider value={submitContextValue}>
300
369
  <FormScrollContext.Provider value={scrollContextValue}>
301
- <FocusContext.Provider value={{ focusedField, setFocusedField }}>
370
+ <FocusContext.Provider value={{ focusedField, setFocusedField, isLoading: isLoading || false }}>
302
371
  <box flexDirection='row' flexGrow={1} justifyContent='center'>
303
- <box flexDirection='column'>
372
+ <box flexGrow={0} flexDirection='column'>
304
373
  <ScrollBox
305
374
  ref={scrollBoxRef}
306
375
  flexGrow={1}
@@ -308,15 +377,40 @@ export const Form: FormType = ((props) => {
308
377
  rootOptions: {
309
378
  maxWidth: FORM_MAX_WIDTH,
310
379
  },
380
+
311
381
  contentOptions: {
312
382
  justifyContent: 'center',
313
383
  },
314
384
  }}
315
385
  >
316
- <FormFieldDescendantsProvider value={descendantsContext}>
317
- {props.children}
318
- <FormEnd />
319
- </FormFieldDescendantsProvider>
386
+ {/* Navigation header with title, loading bar, and accessory */}
387
+
388
+ {/*<box
389
+ border={false}
390
+ style={{
391
+ flexShrink: 0,
392
+ flexDirection: 'row',
393
+ paddingBottom: 1,
394
+ justifyContent: 'space-between',
395
+ alignItems: 'center',
396
+ gap: 3,
397
+ }}
398
+ >
399
+ <box overflow='hidden'>
400
+ <LoadingBar
401
+ title={navigationTitle || ''}
402
+ isLoading={isLoading || navigationPending}
403
+ />
404
+ </box>
405
+ {searchBarAccessory}
406
+ </box>*/}
407
+
408
+ <box>
409
+ <FormFieldDescendantsProvider value={descendantsContext}>
410
+ {props.children}
411
+ <FormEnd />
412
+ </FormFieldDescendantsProvider>
413
+ </box>
320
414
  </ScrollBox>
321
415
  <FormFooter />
322
416
  </box>
@@ -351,3 +445,27 @@ Form.TagPicker = TagPicker
351
445
  Form.FilePicker = FilePicker
352
446
  Form.Separator = Separator
353
447
  Form.Description = Description
448
+
449
+ // LinkAccessory component - shows a link in the navigation bar
450
+ function LinkAccessory(props: LinkAccessoryProps): any {
451
+ return (
452
+ <box
453
+ style={{
454
+ flexShrink: 0,
455
+ maxWidth: '50%',
456
+ overflow: 'hidden',
457
+ paddingRight: 1,
458
+ }}
459
+ >
460
+ <text
461
+ fg={Theme.textMuted}
462
+ attributes={TextAttributes.UNDERLINE}
463
+ wrapMode='none'
464
+ >
465
+ {props.text}
466
+ </text>
467
+ </box>
468
+ )
469
+ }
470
+
471
+ Form.LinkAccessory = LinkAccessory
@@ -6,6 +6,7 @@ import { FormItemProps, FormItemRef } from './types'
6
6
  import { Theme } from 'termcast/src/theme'
7
7
  import { WithLeftBorder } from './with-left-border'
8
8
  import { useFormNavigation } from './use-form-navigation'
9
+ import { LoadingText } from 'termcast/src/components/loading-text'
9
10
 
10
11
  export interface PasswordFieldProps extends FormItemProps<string> {
11
12
  placeholder?: string
@@ -15,7 +16,8 @@ export type PasswordFieldRef = FormItemRef
15
16
 
16
17
  export const PasswordField = (props: PasswordFieldProps): any => {
17
18
  const { control } = useFormContext()
18
- const { focusedField, setFocusedField } = useFocusContext()
19
+ const focusContext = useFocusContext()
20
+ const { focusedField, setFocusedField } = focusContext
19
21
  const isFocused = focusedField === props.id
20
22
  const elementRef = useRef<BoxRenderable>(null)
21
23
  const realValueRef = useRef(props.defaultValue || props.value || '')
@@ -38,15 +40,19 @@ export const PasswordField = (props: PasswordFieldProps): any => {
38
40
 
39
41
  return (
40
42
  <box ref={elementRef} flexDirection="column">
41
- <WithLeftBorder withDiamond isFocused={isFocused}>
42
- <text
43
- fg={isFocused ? Theme.primary : Theme.text}
43
+ <WithLeftBorder withDiamond isFocused={isFocused} isLoading={focusContext.isLoading}>
44
+ <box
44
45
  onMouseDown={() => {
45
46
  setFocusedField(props.id)
46
47
  }}
47
48
  >
48
- {props.title}
49
- </text>
49
+ <LoadingText
50
+ isLoading={isFocused && focusContext.isLoading}
51
+ color={isFocused ? Theme.primary : Theme.text}
52
+ >
53
+ {props.title || ''}
54
+ </LoadingText>
55
+ </box>
50
56
  </WithLeftBorder>
51
57
  <WithLeftBorder isFocused={isFocused}>
52
58
  <input
@@ -7,6 +7,7 @@ import { Theme } from 'termcast/src/theme'
7
7
  import { WithLeftBorder } from './with-left-border'
8
8
  import { useFormNavigation } from './use-form-navigation'
9
9
  import { createTextareaFormRef } from './form-ref'
10
+ import { LoadingText } from 'termcast/src/components/loading-text'
10
11
 
11
12
  export interface TextAreaProps extends FormItemProps<string> {
12
13
  placeholder?: string
@@ -17,7 +18,8 @@ export type TextAreaRef = FormItemRef
17
18
 
18
19
  export const TextArea = (props: TextAreaProps): any => {
19
20
  const { register, formState } = useFormContext()
20
- const { focusedField, setFocusedField } = useFocusContext()
21
+ const focusContext = useFocusContext()
22
+ const { focusedField, setFocusedField } = focusContext
21
23
  const isFocused = focusedField === props.id
22
24
 
23
25
  const elementRef = useRef<BoxRenderable>(null)
@@ -58,21 +60,26 @@ export const TextArea = (props: TextAreaProps): any => {
58
60
 
59
61
  return (
60
62
  <box ref={elementRef} flexDirection="column">
61
- <WithLeftBorder withDiamond isFocused={isFocused}>
62
- <text
63
- fg={isFocused ? Theme.primary : Theme.text}
63
+ <WithLeftBorder withDiamond isFocused={isFocused} isLoading={focusContext.isLoading}>
64
+ <box
64
65
  onMouseDown={() => {
65
66
  setFocusedField(props.id)
66
67
  }}
67
68
  >
68
- {props.title}
69
- </text>
69
+ <LoadingText
70
+ isLoading={isFocused && focusContext.isLoading}
71
+ color={isFocused ? Theme.primary : Theme.text}
72
+ >
73
+ {props.title || ''}
74
+ </LoadingText>
75
+ </box>
70
76
  </WithLeftBorder>
71
77
 
72
78
  <WithLeftBorder isFocused={isFocused}>
73
79
  <box flexGrow={1}>
74
80
  <textarea
75
81
  ref={handleRef}
82
+ wrapMode='none'
76
83
  initialValue={props.defaultValue || props.value || ''}
77
84
  onContentChange={handleContentChange}
78
85
  minHeight={4}
@@ -7,6 +7,7 @@ import { Theme } from 'termcast/src/theme'
7
7
  import { WithLeftBorder } from './with-left-border'
8
8
  import { useFormNavigation } from './use-form-navigation'
9
9
  import { createTextareaFormRef } from './form-ref'
10
+ import { LoadingText } from 'termcast/src/components/loading-text'
10
11
 
11
12
  export interface TextFieldProps extends FormItemProps<string> {
12
13
  placeholder?: string
@@ -16,7 +17,8 @@ export type TextFieldRef = FormItemRef
16
17
 
17
18
  export const TextField = (props: TextFieldProps): any => {
18
19
  const { register, formState } = useFormContext()
19
- const { focusedField, setFocusedField } = useFocusContext()
20
+ const focusContext = useFocusContext()
21
+ const { focusedField, setFocusedField } = focusContext
20
22
  const isFocused = focusedField === props.id
21
23
 
22
24
  const elementRef = useRef<BoxRenderable>(null)
@@ -59,15 +61,19 @@ export const TextField = (props: TextFieldProps): any => {
59
61
 
60
62
  return (
61
63
  <box ref={elementRef} flexDirection="column">
62
- <WithLeftBorder withDiamond isFocused={isFocused}>
63
- <text
64
- fg={isFocused ? Theme.primary : Theme.text}
64
+ <WithLeftBorder withDiamond isFocused={isFocused} isLoading={focusContext.isLoading}>
65
+ <box
65
66
  onMouseDown={() => {
66
67
  setFocusedField(props.id)
67
68
  }}
68
69
  >
69
- {props.title}
70
- </text>
70
+ <LoadingText
71
+ isLoading={isFocused && focusContext.isLoading}
72
+ color={isFocused ? Theme.primary : Theme.text}
73
+ >
74
+ {props.title || ''}
75
+ </LoadingText>
76
+ </box>
71
77
  </WithLeftBorder>
72
78
  <WithLeftBorder isFocused={isFocused}>
73
79
  <textarea
@@ -77,6 +83,7 @@ export const TextField = (props: TextFieldProps): any => {
77
83
  { name: 'return', action: 'submit' },
78
84
  { name: 'linefeed', action: 'submit' },
79
85
  ]}
86
+ wrapMode='none'
80
87
  initialValue={props.defaultValue || props.value || ''}
81
88
  onContentChange={handleContentChange}
82
89
  placeholder={props.placeholder}
@@ -6,12 +6,18 @@ export interface FormValues {
6
6
  [key: string]: FormValue
7
7
  }
8
8
 
9
+ export interface LinkAccessoryProps {
10
+ target: string
11
+ text: string
12
+ }
13
+
9
14
  export interface FormProps {
10
15
  actions?: React.ReactNode
11
16
  children?: React.ReactNode
12
17
  navigationTitle?: string
13
18
  isLoading?: boolean
14
19
  enableDrafts?: boolean
20
+ searchBarAccessory?: React.ReactElement<LinkAccessoryProps> | null
15
21
  }
16
22
 
17
23
  export interface FormItemProps<T> {