@cosmicdrift/kumiko-renderer 0.374.0 → 0.377.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (455) hide show
  1. package/README.md +2 -2
  2. package/dist/app/action-form-support.d.ts +9 -2
  3. package/dist/app/action-form-support.js +24 -2
  4. package/dist/app/client-actions.d.ts +2 -2
  5. package/dist/app/column-renderers.js +1 -1
  6. package/dist/app/config-edit-shim.js +1 -1
  7. package/dist/app/content-preview.js +1 -6
  8. package/dist/app/custom-screens.js +1 -1
  9. package/dist/app/dashboard-body.d.ts +1 -1
  10. package/dist/app/entity-list-expanded-row.d.ts +1 -1
  11. package/dist/app/form-schema.d.ts +1 -1
  12. package/dist/app/form-schema.js +2 -2
  13. package/dist/app/kumiko-screen.d.ts +1 -1
  14. package/dist/app/kumiko-screen.js +7 -6
  15. package/dist/app/list-empty-content.d.ts +1 -1
  16. package/dist/app/list-facets.d.ts +1 -1
  17. package/dist/app/nav.js +2 -2
  18. package/dist/app/query-state-banners.d.ts +1 -1
  19. package/dist/app/reference-link.d.ts +1 -1
  20. package/dist/app/row-actions.d.ts +1 -1
  21. package/dist/app/secret-mint-body.d.ts +1 -1
  22. package/dist/app/secrets-edit-body.d.ts +1 -1
  23. package/dist/app/use-record-actions.d.ts +1 -1
  24. package/dist/app/write-failed-error.d.ts +1 -1
  25. package/dist/components/availability-options-select.d.ts +1 -1
  26. package/dist/components/embedded-list-field.d.ts +1 -1
  27. package/dist/components/embedded-list-field.js +1 -1
  28. package/dist/components/file-list-field.d.ts +1 -1
  29. package/dist/components/grid-cell-for-field.d.ts +1 -1
  30. package/dist/components/multi-select-checkboxes.d.ts +1 -1
  31. package/dist/components/multi-select-checkboxes.js +1 -1
  32. package/dist/components/query-options-select.d.ts +1 -1
  33. package/dist/components/related-list-section.d.ts +1 -1
  34. package/dist/components/render-edit-logic.d.ts +1 -1
  35. package/dist/components/render-edit-types.d.ts +2 -2
  36. package/dist/components/render-edit.d.ts +1 -1
  37. package/dist/components/render-edit.js +4 -2
  38. package/dist/components/render-field.d.ts +1 -1
  39. package/dist/components/render-field.js +4 -1
  40. package/dist/components/render-list.d.ts +1 -1
  41. package/dist/components/render-list.js +20 -4
  42. package/dist/components/write-form-section.d.ts +1 -1
  43. package/dist/components/write-form-section.js +1 -1
  44. package/dist/context/dispatcher-context.d.ts +1 -1
  45. package/dist/format-when.js +1 -1
  46. package/dist/headless/apex/css.d.ts +3 -0
  47. package/dist/headless/apex/css.js +298 -0
  48. package/dist/headless/apex/index.d.ts +236 -0
  49. package/dist/headless/apex/index.js +377 -0
  50. package/dist/headless/apex/lightbox.d.ts +4 -0
  51. package/dist/headless/apex/lightbox.js +81 -0
  52. package/dist/headless/contracts/asset.d.ts +15 -0
  53. package/dist/headless/contracts/asset.js +17 -0
  54. package/dist/headless/contracts/index.d.ts +3 -0
  55. package/dist/headless/contracts/index.js +1 -0
  56. package/dist/headless/contracts/locale.d.ts +7 -0
  57. package/dist/headless/contracts/locale.js +11 -0
  58. package/dist/headless/contracts/primitives.d.ts +93 -0
  59. package/dist/headless/contracts/primitives.js +21 -0
  60. package/dist/headless/dispatcher/index.d.ts +2 -0
  61. package/dist/headless/dispatcher/index.js +1 -0
  62. package/dist/headless/dispatcher/types.d.ts +86 -0
  63. package/dist/headless/dispatcher/types.js +34 -0
  64. package/dist/headless/form/form-controller.d.ts +2 -0
  65. package/dist/headless/form/form-controller.js +409 -0
  66. package/dist/headless/form/index.d.ts +3 -0
  67. package/dist/headless/form/index.js +2 -0
  68. package/dist/headless/form/types.d.ts +76 -0
  69. package/dist/headless/form/types.js +1 -0
  70. package/dist/headless/form/zod-bridge.d.ts +10 -0
  71. package/dist/headless/form/zod-bridge.js +103 -0
  72. package/dist/headless/format/document-values.d.ts +12 -0
  73. package/dist/headless/format/document-values.js +70 -0
  74. package/dist/headless/format/escape.d.ts +5 -0
  75. package/dist/headless/format/escape.js +75 -0
  76. package/dist/headless/format/html-template.d.ts +9 -0
  77. package/dist/headless/format/html-template.js +39 -0
  78. package/dist/headless/format/index.d.ts +12 -0
  79. package/dist/headless/format/index.js +237 -0
  80. package/dist/headless/format/money.d.ts +3 -0
  81. package/dist/headless/format/money.js +39 -0
  82. package/dist/headless/index.d.ts +14 -0
  83. package/dist/headless/index.js +9 -0
  84. package/dist/headless/locale-routing/index.d.ts +41 -0
  85. package/dist/headless/locale-routing/index.js +145 -0
  86. package/dist/headless/nav/index.d.ts +2 -0
  87. package/dist/headless/nav/index.js +1 -0
  88. package/dist/headless/nav/resolve.d.ts +2 -0
  89. package/dist/headless/nav/resolve.js +71 -0
  90. package/dist/headless/nav/types.d.ts +26 -0
  91. package/dist/headless/nav/types.js +1 -0
  92. package/dist/headless/store/create-store.d.ts +2 -0
  93. package/dist/headless/store/create-store.js +43 -0
  94. package/dist/headless/store/equality.d.ts +1 -0
  95. package/dist/headless/store/equality.js +34 -0
  96. package/dist/headless/store/index.d.ts +3 -0
  97. package/dist/headless/store/index.js +2 -0
  98. package/dist/headless/store/types.d.ts +7 -0
  99. package/dist/headless/store/types.js +10 -0
  100. package/dist/headless/view-model/edit.d.ts +18 -0
  101. package/dist/headless/view-model/edit.js +501 -0
  102. package/dist/headless/view-model/embedded-list.d.ts +27 -0
  103. package/dist/headless/view-model/embedded-list.js +63 -0
  104. package/dist/headless/view-model/index.d.ts +7 -0
  105. package/dist/headless/view-model/index.js +3 -0
  106. package/dist/headless/view-model/list-file-display.d.ts +3 -0
  107. package/dist/headless/view-model/list-file-display.js +27 -0
  108. package/dist/headless/view-model/list.d.ts +16 -0
  109. package/dist/headless/view-model/list.js +229 -0
  110. package/dist/headless/view-model/types.d.ts +382 -0
  111. package/dist/headless/view-model/types.js +1 -0
  112. package/dist/hooks/use-ai-text.d.ts +1 -1
  113. package/dist/hooks/use-form.d.ts +1 -1
  114. package/dist/hooks/use-form.js +1 -1
  115. package/dist/hooks/use-mutation.d.ts +1 -1
  116. package/dist/hooks/use-query.d.ts +1 -1
  117. package/dist/hooks/use-store.d.ts +1 -1
  118. package/dist/hooks/use-store.js +7 -7
  119. package/dist/hooks/use-stream-handler.d.ts +1 -1
  120. package/dist/i18n.d.ts +1 -1
  121. package/dist/i18n.js +1 -1
  122. package/dist/index.js +4 -4
  123. package/dist/live/csrf.d.ts +2 -0
  124. package/dist/live/csrf.js +60 -0
  125. package/dist/live/dispatcher-live.d.ts +7 -0
  126. package/dist/live/dispatcher-live.js +286 -0
  127. package/dist/live/envelopes.d.ts +101 -0
  128. package/dist/live/envelopes.js +35 -0
  129. package/dist/live/error-mapping.d.ts +16 -0
  130. package/dist/live/error-mapping.js +82 -0
  131. package/dist/live/index.d.ts +7 -0
  132. package/dist/live/index.js +5 -0
  133. package/dist/live/locale.d.ts +2 -0
  134. package/dist/live/locale.js +25 -0
  135. package/dist/live/sse-stream.d.ts +14 -0
  136. package/dist/live/sse-stream.js +128 -0
  137. package/dist/primitives.d.ts +35 -36
  138. package/dist/primitives.js +1 -1
  139. package/dist/session/session-ended.d.ts +1 -1
  140. package/dist/tokens.d.ts +3 -4
  141. package/dist/tokens.js +4 -5
  142. package/dist/web/app/app-schema-boot.d.ts +19 -0
  143. package/dist/web/app/app-schema-boot.js +124 -0
  144. package/dist/web/app/browser-locale.d.ts +22 -0
  145. package/dist/web/app/browser-locale.js +78 -0
  146. package/dist/web/app/client-plugin.d.ts +106 -0
  147. package/dist/web/app/client-plugin.js +12 -0
  148. package/dist/web/app/create-app.d.ts +104 -0
  149. package/dist/web/app/create-app.js +498 -0
  150. package/dist/web/app/create-public-surface.d.ts +36 -0
  151. package/dist/web/app/create-public-surface.js +40 -0
  152. package/dist/web/app/dashboard-body.d.ts +3 -0
  153. package/dist/web/app/dashboard-body.js +865 -0
  154. package/dist/web/app/dashboard-refresh.d.ts +6 -0
  155. package/dist/web/app/dashboard-refresh.js +15 -0
  156. package/dist/web/app/document-lang-sync.d.ts +4 -0
  157. package/dist/web/app/document-lang-sync.js +27 -0
  158. package/dist/web/app/draft-storage.d.ts +11 -0
  159. package/dist/web/app/draft-storage.js +53 -0
  160. package/dist/web/app/nav-providers-context.d.ts +13 -0
  161. package/dist/web/app/nav-providers-context.js +18 -0
  162. package/dist/web/app/nav.d.ts +40 -0
  163. package/dist/web/app/nav.js +231 -0
  164. package/dist/web/app/plain-content-editor.d.ts +3 -0
  165. package/dist/web/app/plain-content-editor.js +30 -0
  166. package/dist/web/app/relative-time.d.ts +10 -0
  167. package/dist/web/app/relative-time.js +56 -0
  168. package/dist/web/app/resolvers-context.d.ts +12 -0
  169. package/dist/web/app/resolvers-context.js +10 -0
  170. package/dist/web/app/rich-content-editor.d.ts +3 -0
  171. package/dist/web/app/rich-content-editor.js +15 -0
  172. package/dist/web/app/root-error-fallback.d.ts +2 -0
  173. package/dist/web/app/root-error-fallback.js +21 -0
  174. package/dist/web/app/screen-error-fallback.d.ts +6 -0
  175. package/dist/web/app/screen-error-fallback.js +17 -0
  176. package/dist/web/app/tiptap-editor.d.ts +3 -0
  177. package/dist/web/app/tiptap-editor.js +120 -0
  178. package/dist/web/app/toast-action-notifier.d.ts +5 -0
  179. package/dist/web/app/toast-action-notifier.js +17 -0
  180. package/dist/web/components/config-cascade.d.ts +13 -0
  181. package/dist/web/components/config-cascade.js +155 -0
  182. package/dist/web/components/config-source-badge.d.ts +6 -0
  183. package/dist/web/components/config-source-badge.js +55 -0
  184. package/dist/web/default-color-scheme.d.ts +2 -0
  185. package/dist/web/default-color-scheme.js +3 -0
  186. package/dist/web/icons.d.ts +94 -0
  187. package/dist/web/icons.js +100 -0
  188. package/dist/web/index.d.ts +78 -0
  189. package/dist/web/index.js +53 -0
  190. package/dist/web/layout/app-layout.d.ts +21 -0
  191. package/dist/web/layout/app-layout.js +8 -0
  192. package/dist/web/layout/avatar.d.ts +12 -0
  193. package/dist/web/layout/avatar.js +63 -0
  194. package/dist/web/layout/default-app-shell.d.ts +41 -0
  195. package/dist/web/layout/default-app-shell.js +16 -0
  196. package/dist/web/layout/editor-panel.d.ts +10 -0
  197. package/dist/web/layout/editor-panel.js +29 -0
  198. package/dist/web/layout/fill-classes.d.ts +9 -0
  199. package/dist/web/layout/fill-classes.js +15 -0
  200. package/dist/web/layout/filter-app-schema-navs.d.ts +7 -0
  201. package/dist/web/layout/filter-app-schema-navs.js +68 -0
  202. package/dist/web/layout/header-action-group.d.ts +6 -0
  203. package/dist/web/layout/header-action-group.js +8 -0
  204. package/dist/web/layout/header-overflow-menu.d.ts +4 -0
  205. package/dist/web/layout/header-overflow-menu.js +8 -0
  206. package/dist/web/layout/language-menu-items.d.ts +8 -0
  207. package/dist/web/layout/language-menu-items.js +16 -0
  208. package/dist/web/layout/language-switcher.d.ts +25 -0
  209. package/dist/web/layout/language-switcher.js +46 -0
  210. package/dist/web/layout/nav-tree.d.ts +19 -0
  211. package/dist/web/layout/nav-tree.js +536 -0
  212. package/dist/web/layout/page-header-slot.d.ts +25 -0
  213. package/dist/web/layout/page-header-slot.js +50 -0
  214. package/dist/web/layout/profile-menu.d.ts +13 -0
  215. package/dist/web/layout/profile-menu.js +6 -0
  216. package/dist/web/layout/shell-breadcrumb.d.ts +11 -0
  217. package/dist/web/layout/shell-breadcrumb.js +34 -0
  218. package/dist/web/layout/shell-header.d.ts +12 -0
  219. package/dist/web/layout/shell-header.js +159 -0
  220. package/dist/web/layout/sidebar-brand.d.ts +15 -0
  221. package/dist/web/layout/sidebar-brand.js +9 -0
  222. package/dist/web/layout/sidebar-panel.d.ts +41 -0
  223. package/dist/web/layout/sidebar-panel.js +107 -0
  224. package/dist/web/layout/sidebar-user.d.ts +8 -0
  225. package/dist/web/layout/sidebar-user.js +17 -0
  226. package/dist/web/layout/sidebar.d.ts +17 -0
  227. package/dist/web/layout/sidebar.js +11 -0
  228. package/dist/web/layout/target-resolver-stub.d.ts +13 -0
  229. package/dist/web/layout/target-resolver-stub.js +61 -0
  230. package/dist/web/layout/target-url.d.ts +12 -0
  231. package/dist/web/layout/target-url.js +77 -0
  232. package/dist/web/layout/theme-label.d.ts +15 -0
  233. package/dist/web/layout/theme-label.js +35 -0
  234. package/dist/web/layout/theme-menu-item.d.ts +12 -0
  235. package/dist/web/layout/theme-menu-item.js +16 -0
  236. package/dist/web/layout/theme-toggle.d.ts +17 -0
  237. package/dist/web/layout/theme-toggle.js +22 -0
  238. package/dist/web/layout/topbar.d.ts +8 -0
  239. package/dist/web/layout/topbar.js +4 -0
  240. package/dist/web/layout/workspace-shell.d.ts +63 -0
  241. package/dist/web/layout/workspace-shell.js +220 -0
  242. package/dist/web/layout/workspace-switcher.d.ts +15 -0
  243. package/dist/web/layout/workspace-switcher.js +36 -0
  244. package/dist/web/lib/accept-attr.d.ts +1 -0
  245. package/dist/web/lib/accept-attr.js +6 -0
  246. package/dist/web/lib/clamp.d.ts +1 -0
  247. package/dist/web/lib/clamp.js +3 -0
  248. package/dist/web/lib/cn.d.ts +6 -0
  249. package/dist/web/lib/cn.js +9 -0
  250. package/dist/web/lib/download.d.ts +2 -0
  251. package/dist/web/lib/download.js +24 -0
  252. package/dist/web/lib/file-ref-download.d.ts +15 -0
  253. package/dist/web/lib/file-ref-download.js +62 -0
  254. package/dist/web/lib/file-upload-form-data.d.ts +7 -0
  255. package/dist/web/lib/file-upload-form-data.js +11 -0
  256. package/dist/web/lib/resize-image.d.ts +1 -0
  257. package/dist/web/lib/resize-image.js +66 -0
  258. package/dist/web/lib/upload-file-ref.d.ts +9 -0
  259. package/dist/web/lib/upload-file-ref.js +29 -0
  260. package/dist/web/primitives/action-menu.d.ts +34 -0
  261. package/dist/web/primitives/action-menu.js +30 -0
  262. package/dist/web/primitives/calendar-popover.d.ts +13 -0
  263. package/dist/web/primitives/calendar-popover.js +76 -0
  264. package/dist/web/primitives/combobox.d.ts +56 -0
  265. package/dist/web/primitives/combobox.js +127 -0
  266. package/dist/web/primitives/date-field.d.ts +18 -0
  267. package/dist/web/primitives/date-field.js +74 -0
  268. package/dist/web/primitives/date-input.d.ts +1 -0
  269. package/dist/web/primitives/date-input.js +5 -0
  270. package/dist/web/primitives/date-parse.d.ts +11 -0
  271. package/dist/web/primitives/date-parse.js +159 -0
  272. package/dist/web/primitives/dialog.d.ts +3 -0
  273. package/dist/web/primitives/dialog.js +42 -0
  274. package/dist/web/primitives/drawer.d.ts +3 -0
  275. package/dist/web/primitives/drawer.js +17 -0
  276. package/dist/web/primitives/dropdown-menu.d.ts +15 -0
  277. package/dist/web/primitives/dropdown-menu.js +56 -0
  278. package/dist/web/primitives/embedded-list-input.d.ts +9 -0
  279. package/dist/web/primitives/embedded-list-input.js +382 -0
  280. package/dist/web/primitives/file-thumbnail.d.ts +25 -0
  281. package/dist/web/primitives/file-thumbnail.js +90 -0
  282. package/dist/web/primitives/file-upload.d.ts +21 -0
  283. package/dist/web/primitives/file-upload.js +56 -0
  284. package/dist/web/primitives/index.d.ts +34 -0
  285. package/dist/web/primitives/index.js +2021 -0
  286. package/dist/web/primitives/json-view.d.ts +7 -0
  287. package/dist/web/primitives/json-view.js +84 -0
  288. package/dist/web/primitives/layout.d.ts +27 -0
  289. package/dist/web/primitives/layout.js +30 -0
  290. package/dist/web/primitives/lightbox.d.ts +3 -0
  291. package/dist/web/primitives/lightbox.js +36 -0
  292. package/dist/web/primitives/locale-number.d.ts +2 -0
  293. package/dist/web/primitives/locale-number.js +54 -0
  294. package/dist/web/primitives/located-timestamp-input.d.ts +22 -0
  295. package/dist/web/primitives/located-timestamp-input.js +22 -0
  296. package/dist/web/primitives/metric.d.ts +5 -0
  297. package/dist/web/primitives/metric.js +18 -0
  298. package/dist/web/primitives/modal-shell.d.ts +17 -0
  299. package/dist/web/primitives/modal-shell.js +9 -0
  300. package/dist/web/primitives/modal.d.ts +3 -0
  301. package/dist/web/primitives/modal.js +13 -0
  302. package/dist/web/primitives/money-input.d.ts +18 -0
  303. package/dist/web/primitives/money-input.js +122 -0
  304. package/dist/web/primitives/narrow-pinned-footer.d.ts +12 -0
  305. package/dist/web/primitives/narrow-pinned-footer.js +153 -0
  306. package/dist/web/primitives/number-input.d.ts +18 -0
  307. package/dist/web/primitives/number-input.js +53 -0
  308. package/dist/web/primitives/option-groups.d.ts +11 -0
  309. package/dist/web/primitives/option-groups.js +27 -0
  310. package/dist/web/primitives/page-header.d.ts +3 -0
  311. package/dist/web/primitives/page-header.js +52 -0
  312. package/dist/web/primitives/promo-panel.d.ts +3 -0
  313. package/dist/web/primitives/promo-panel.js +11 -0
  314. package/dist/web/primitives/segmented-select.d.ts +21 -0
  315. package/dist/web/primitives/segmented-select.js +62 -0
  316. package/dist/web/primitives/share-actions.d.ts +4 -0
  317. package/dist/web/primitives/share-actions.js +56 -0
  318. package/dist/web/primitives/status-badge.d.ts +3 -0
  319. package/dist/web/primitives/status-badge.js +5 -0
  320. package/dist/web/primitives/sticky-action-bar.d.ts +5 -0
  321. package/dist/web/primitives/sticky-action-bar.js +15 -0
  322. package/dist/web/primitives/tabs.d.ts +3 -0
  323. package/dist/web/primitives/tabs.js +107 -0
  324. package/dist/web/primitives/timestamp-input.d.ts +27 -0
  325. package/dist/web/primitives/timestamp-input.js +87 -0
  326. package/dist/web/primitives/toast.d.ts +19 -0
  327. package/dist/web/primitives/toast.js +63 -0
  328. package/dist/web/primitives/tz-input.d.ts +11 -0
  329. package/dist/web/primitives/tz-input.js +8 -0
  330. package/dist/web/primitives/tz-options.d.ts +4 -0
  331. package/dist/web/primitives/tz-options.js +17 -0
  332. package/dist/web/primitives/use-keyboard-inset.d.ts +1 -0
  333. package/dist/web/primitives/use-keyboard-inset.js +28 -0
  334. package/dist/web/primitives/use-narrow-viewport.d.ts +4 -0
  335. package/dist/web/primitives/use-narrow-viewport.js +46 -0
  336. package/dist/web/react-css-custom-properties.d.ts +6 -0
  337. package/dist/web/react-css-custom-properties.js +1 -0
  338. package/dist/web/sse/live-events.d.ts +15 -0
  339. package/dist/web/sse/live-events.js +148 -0
  340. package/dist/web/theme-plugin.d.ts +41 -0
  341. package/dist/web/theme-plugin.js +123 -0
  342. package/dist/web/tokens.d.ts +24 -0
  343. package/dist/web/tokens.js +203 -0
  344. package/dist/web/ui/avatar.d.ts +11 -0
  345. package/dist/web/ui/avatar.js +25 -0
  346. package/dist/web/ui/badge.d.ts +9 -0
  347. package/dist/web/ui/badge.js +26 -0
  348. package/dist/web/ui/breadcrumb.d.ts +11 -0
  349. package/dist/web/ui/breadcrumb.js +29 -0
  350. package/dist/web/ui/button.d.ts +10 -0
  351. package/dist/web/ui/button.js +37 -0
  352. package/dist/web/ui/checkbox.d.ts +4 -0
  353. package/dist/web/ui/checkbox.js +11 -0
  354. package/dist/web/ui/input.d.ts +3 -0
  355. package/dist/web/ui/input.js +8 -0
  356. package/dist/web/ui/label.d.ts +4 -0
  357. package/dist/web/ui/label.js +10 -0
  358. package/dist/web/ui/resizable.d.ts +7 -0
  359. package/dist/web/ui/resizable.js +16 -0
  360. package/dist/web/ui/separator.d.ts +4 -0
  361. package/dist/web/ui/separator.js +10 -0
  362. package/dist/web/ui/sheet.d.ts +14 -0
  363. package/dist/web/ui/sheet.js +42 -0
  364. package/dist/web/ui/sidebar.d.ts +73 -0
  365. package/dist/web/ui/sidebar.js +211 -0
  366. package/dist/web/ui/skeleton.d.ts +2 -0
  367. package/dist/web/ui/skeleton.js +7 -0
  368. package/dist/web/ui/switch.d.ts +6 -0
  369. package/dist/web/ui/switch.js +10 -0
  370. package/dist/web/ui/table.d.ts +10 -0
  371. package/dist/web/ui/table.js +30 -0
  372. package/dist/web/ui/tabs.d.ts +11 -0
  373. package/dist/web/ui/tabs.js +31 -0
  374. package/dist/web/ui/textarea.d.ts +3 -0
  375. package/dist/web/ui/textarea.js +8 -0
  376. package/dist/web/ui/tooltip.d.ts +7 -0
  377. package/dist/web/ui/tooltip.js +19 -0
  378. package/dist/web/ui/use-mobile.d.ts +1 -0
  379. package/dist/web/ui/use-mobile.js +16 -0
  380. package/dist/web/version/update-checker.d.ts +9 -0
  381. package/dist/web/version/update-checker.js +88 -0
  382. package/dist/web/widgets/ai-text-field.d.ts +36 -0
  383. package/dist/web/widgets/ai-text-field.js +137 -0
  384. package/dist/web/widgets/chart-window.d.ts +58 -0
  385. package/dist/web/widgets/chart-window.js +190 -0
  386. package/dist/web/widgets/charts.d.ts +208 -0
  387. package/dist/web/widgets/charts.js +685 -0
  388. package/dist/web/widgets/collapsible-section.d.ts +10 -0
  389. package/dist/web/widgets/collapsible-section.js +17 -0
  390. package/dist/web/widgets/dashboard-list.d.ts +21 -0
  391. package/dist/web/widgets/dashboard-list.js +45 -0
  392. package/dist/web/widgets/detail-list.d.ts +15 -0
  393. package/dist/web/widgets/detail-list.js +8 -0
  394. package/dist/web/widgets/drawer.d.ts +65 -0
  395. package/dist/web/widgets/drawer.js +142 -0
  396. package/dist/web/widgets/feed-list.d.ts +13 -0
  397. package/dist/web/widgets/feed-list.js +9 -0
  398. package/dist/web/widgets/floating-panel.d.ts +30 -0
  399. package/dist/web/widgets/floating-panel.js +242 -0
  400. package/dist/web/widgets/form-fields.d.ts +93 -0
  401. package/dist/web/widgets/form-fields.js +50 -0
  402. package/dist/web/widgets/index.d.ts +27 -0
  403. package/dist/web/widgets/index.js +30 -0
  404. package/dist/web/widgets/infinity-list.d.ts +46 -0
  405. package/dist/web/widgets/infinity-list.js +211 -0
  406. package/dist/web/widgets/mode-switch.d.ts +21 -0
  407. package/dist/web/widgets/mode-switch.js +25 -0
  408. package/dist/web/widgets/photo-slots.d.ts +29 -0
  409. package/dist/web/widgets/photo-slots.js +52 -0
  410. package/dist/web/widgets/plan-card.d.ts +36 -0
  411. package/dist/web/widgets/plan-card.js +13 -0
  412. package/dist/web/widgets/progress-bar.d.ts +19 -0
  413. package/dist/web/widgets/progress-bar.js +24 -0
  414. package/dist/web/widgets/progress-list.d.ts +19 -0
  415. package/dist/web/widgets/progress-list.js +11 -0
  416. package/dist/web/widgets/public-shell.d.ts +22 -0
  417. package/dist/web/widgets/public-shell.js +15 -0
  418. package/dist/web/widgets/query-table.d.ts +29 -0
  419. package/dist/web/widgets/query-table.js +30 -0
  420. package/dist/web/widgets/result-panel.d.ts +69 -0
  421. package/dist/web/widgets/result-panel.js +30 -0
  422. package/dist/web/widgets/section-card.d.ts +12 -0
  423. package/dist/web/widgets/section-card.js +9 -0
  424. package/dist/web/widgets/sheet-parts.d.ts +9 -0
  425. package/dist/web/widgets/sheet-parts.js +11 -0
  426. package/dist/web/widgets/side-by-side-table.d.ts +31 -0
  427. package/dist/web/widgets/side-by-side-table.js +11 -0
  428. package/dist/web/widgets/stat.d.ts +54 -0
  429. package/dist/web/widgets/stat.js +65 -0
  430. package/dist/web/widgets/states.d.ts +23 -0
  431. package/dist/web/widgets/states.js +20 -0
  432. package/dist/web/widgets/status-badge.d.ts +15 -0
  433. package/dist/web/widgets/status-badge.js +24 -0
  434. package/dist/web/widgets/step-bar.d.ts +35 -0
  435. package/dist/web/widgets/step-bar.js +60 -0
  436. package/dist/web/widgets/upload-zone.d.ts +24 -0
  437. package/dist/web/widgets/upload-zone.js +108 -0
  438. package/dist/web/widgets/use-draft.d.ts +20 -0
  439. package/dist/web/widgets/use-draft.js +21 -0
  440. package/dist/web/widgets/use-pointer-drag.d.ts +21 -0
  441. package/dist/web/widgets/use-pointer-drag.js +53 -0
  442. package/package.json +105 -7
  443. package/src/changes.json +53 -0
  444. package/src/web/fonts/OFL.txt +93 -0
  445. package/src/web/fonts/ibm-plex-mono-latin-400.woff2 +0 -0
  446. package/src/web/fonts/ibm-plex-mono-latin-500.woff2 +0 -0
  447. package/src/web/fonts/ibm-plex-mono-latin-ext-400.woff2 +0 -0
  448. package/src/web/fonts/ibm-plex-mono-latin-ext-500.woff2 +0 -0
  449. package/src/web/fonts/ibm-plex-sans-latin-400.woff2 +0 -0
  450. package/src/web/fonts/ibm-plex-sans-latin-500.woff2 +0 -0
  451. package/src/web/fonts/ibm-plex-sans-latin-600.woff2 +0 -0
  452. package/src/web/fonts/ibm-plex-sans-latin-ext-400.woff2 +0 -0
  453. package/src/web/fonts/ibm-plex-sans-latin-ext-500.woff2 +0 -0
  454. package/src/web/fonts/ibm-plex-sans-latin-ext-600.woff2 +0 -0
  455. package/src/web/styles.css +401 -0
@@ -0,0 +1,86 @@
1
+ import type { FieldIssue } from "@cosmicdrift/kumiko-framework/errors";
2
+ import type { Store } from "../store/index.ts";
3
+ export type { FieldIssue };
4
+ export type DispatcherError = {
5
+ readonly code: string;
6
+ readonly httpStatus: number;
7
+ readonly i18nKey: string;
8
+ readonly i18nParams?: Readonly<Record<string, unknown>>;
9
+ readonly message: string;
10
+ readonly details?: {
11
+ readonly fields?: readonly FieldIssue[];
12
+ } & Record<string, unknown>;
13
+ readonly docsUrl?: string;
14
+ readonly requestId?: string;
15
+ };
16
+ export type WriteResult<TData = unknown> = {
17
+ readonly isSuccess: true;
18
+ readonly data: TData;
19
+ } | {
20
+ readonly isSuccess: false;
21
+ readonly error: DispatcherError;
22
+ };
23
+ export type QueryResult<TData = unknown> = {
24
+ readonly isSuccess: true;
25
+ readonly data: TData;
26
+ } | {
27
+ readonly isSuccess: false;
28
+ readonly error: DispatcherError;
29
+ };
30
+ export type BatchResult = {
31
+ readonly isSuccess: true;
32
+ readonly results: readonly WriteResult[];
33
+ } | {
34
+ readonly isSuccess: false;
35
+ readonly error: DispatcherError;
36
+ readonly failedIndex: number;
37
+ readonly results: readonly WriteResult[];
38
+ };
39
+ export type Command = {
40
+ readonly type: string;
41
+ readonly payload: unknown;
42
+ };
43
+ export type DispatcherStatus = "online" | "offline" | "syncing";
44
+ export type PendingWrite = {
45
+ readonly id: string;
46
+ readonly type: string;
47
+ readonly payload: unknown;
48
+ readonly queuedAt: string;
49
+ readonly attempts: number;
50
+ readonly lastError?: DispatcherError;
51
+ };
52
+ export type PendingFile = {
53
+ readonly id: string;
54
+ readonly fileName: string;
55
+ readonly sizeBytes: number;
56
+ readonly queuedAt: string;
57
+ readonly attempts: number;
58
+ readonly progress?: number;
59
+ readonly lastError?: DispatcherError;
60
+ };
61
+ export type WriteOpts = {
62
+ readonly requestId?: string;
63
+ readonly signal?: AbortSignal;
64
+ };
65
+ export type QueryOpts = {
66
+ readonly signal?: AbortSignal;
67
+ };
68
+ export type StreamOpts = {
69
+ readonly signal?: AbortSignal;
70
+ };
71
+ export declare const StreamFrame: {
72
+ readonly chunk: "chunk";
73
+ readonly ping: "ping";
74
+ readonly done: "done";
75
+ readonly error: "error";
76
+ };
77
+ export type StreamFrameEvent = (typeof StreamFrame)[keyof typeof StreamFrame];
78
+ export type Dispatcher = {
79
+ write<TData = unknown>(type: string, payload: unknown, opts?: WriteOpts): Promise<WriteResult<TData>>;
80
+ query<TData = unknown>(type: string, payload: unknown, opts?: QueryOpts): Promise<QueryResult<TData>>;
81
+ batch(commands: readonly Command[], opts?: WriteOpts): Promise<BatchResult>;
82
+ stream<TChunk = unknown>(type: string, payload: unknown, opts?: StreamOpts): AsyncGenerator<TChunk, void, undefined>;
83
+ readonly statusStore: Store<DispatcherStatus>;
84
+ pendingWrites(): readonly PendingWrite[];
85
+ pendingFiles(): readonly PendingFile[];
86
+ };
@@ -0,0 +1,34 @@
1
+ // Dispatcher Contract — the interface every client-side Dispatcher implements.
2
+ // Two implementations ship with Kumiko:
3
+ //
4
+ // - dispatcher-live: HTTP-only, always online. Writes go straight to
5
+ // /api/write or /api/batch; a failed network call is a failed write.
6
+ // Used on the web by default and on mobile when the app is "cockpit"-
7
+ // style (admin dashboards, back-office UIs).
8
+ //
9
+ // - dispatcher-savable: Local-first with an outbound queue. Writes land
10
+ // in a local store, the queue syncs when the network is back. Used on
11
+ // mobile and for PWA-style web apps.
12
+ //
13
+ // The UI code never reaches for either implementation directly — it takes
14
+ // a `Dispatcher` through a provider/context and calls `write` / `query` /
15
+ // `batch`. A feature-module can therefore be rendered identically on a
16
+ // live-online admin screen and on a mobile app that edits offline; the
17
+ // dispatcher choice is an app-level concern, not a feature-level one.
18
+ //
19
+ // Result shape: `{ isSuccess: true, data } | { isSuccess: false, error }`.
20
+ // Pattern matches the server's WriteResult but does not import it — this
21
+ // package stays free of @cosmicdrift/kumiko-framework so it runs in any JS runtime
22
+ // (browser, Expo/React Native, Web Worker). The HTTP dispatcher adapts
23
+ // the server response to this shape; the savable dispatcher synthesizes
24
+ // it from its own state machine. The shape is the boundary, not the
25
+ // internal detail.
26
+ // SSE frame event names for POST /api/stream — shared across the server route,
27
+ // the live-dispatcher wire parser, and their tests so a typo on one side
28
+ // fails the type-checker instead of the integration test.
29
+ export const StreamFrame = {
30
+ chunk: "chunk",
31
+ ping: "ping",
32
+ done: "done",
33
+ error: "error",
34
+ };
@@ -0,0 +1,2 @@
1
+ import type { FormController, FormControllerOptions, FormValues } from "./types.ts";
2
+ export declare function createFormController<TValues extends FormValues, TCtx = unknown>(options: FormControllerOptions<TValues, TCtx>): FormController<TValues>;
@@ -0,0 +1,409 @@
1
+ import { createStore } from "../store/index.js";
2
+ import { groupIssuesByPath, relevantFieldIssues, zodErrorToFieldIssues } from "./zod-bridge.js";
3
+ // Paths without the prefix (hook-level errors, "(root)") pass through unchanged.
4
+ function stripServerFieldPathPrefix(issues, prefix) {
5
+ return issues.map((issue) => issue.path.startsWith(prefix) ? { ...issue, path: issue.path.slice(prefix.length) } : issue);
6
+ }
7
+ // Resolve one condition-value (boolean | predicate) against current values + ctx.
8
+ // `undefined` means "condition not declared"; the caller substitutes the
9
+ // field-default (visible:true, readonly:false, required:false).
10
+ function evalCondition(condition, fallback, values, ctx) {
11
+ if (condition === undefined)
12
+ return fallback;
13
+ if (typeof condition === "boolean")
14
+ return condition;
15
+ return condition(values, ctx);
16
+ }
17
+ function computeFieldStates(rules, values, ctx) {
18
+ if (!rules)
19
+ return {};
20
+ const out = {};
21
+ for (const [fieldKey, rule] of Object.entries(rules)) {
22
+ out[fieldKey] = {
23
+ visible: evalCondition(rule.visible, true, values, ctx),
24
+ readonly: evalCondition(rule.readonly, false, values, ctx),
25
+ required: evalCondition(rule.required, false, values, ctx),
26
+ };
27
+ }
28
+ return out;
29
+ }
30
+ // Reference equality only. For primitive-valued fields this is exact; for
31
+ // object/array fields it treats "same reference" as "unchanged" — callers
32
+ // who mutate in place instead of swapping references (which is bad form
33
+ // anyway, but common in ad-hoc tests) would miss a change. Kumiko's
34
+ // form-controller contract is that mutators build new references.
35
+ // Object.is is chosen over === so NaN !== NaN doesn't mark a field dirty
36
+ // after you re-type the same number back in.
37
+ function valuesDiff(current, initial) {
38
+ const out = {};
39
+ // Iterate over BOTH sides — a field that existed on initial but was
40
+ // deleted from current (via `setValues({ foo: undefined })`) still
41
+ // counts as a change.
42
+ // FormValues<T> does not allow keyof-T iteration (T is generic). Cast
43
+ // to Record<string, unknown> for dynamic-key inspection.
44
+ const cur = current; // @cast-boundary form-values
45
+ const ini = initial; // @cast-boundary form-values
46
+ const o = out; // @cast-boundary form-values
47
+ const keys = new Set([...Object.keys(cur), ...Object.keys(ini)]);
48
+ for (const key of keys) {
49
+ const a = cur[key];
50
+ const b = ini[key];
51
+ if (!Object.is(a, b)) {
52
+ o[key] = a;
53
+ }
54
+ }
55
+ return out;
56
+ }
57
+ // buildInitialValues seeds untouched optional fields with "" for controlled
58
+ // inputs; a `.optional()` server schema accepts undefined but not "".
59
+ // String-only: number/money seed to 0 and boolean seeds to false, and an
60
+ // untouched optional field of those types still submits that seed instead
61
+ // of being omitted — no validation error surfaces it, unlike the "" case.
62
+ // Fixing that needs real per-field seed-tracking (which value was actually
63
+ // user-set vs. defaulted), not just a wider strip predicate here.
64
+ function stripUntouchedEmptyStrings(values, initial, forceIncludeEmpty) {
65
+ const v = values; // @cast-boundary form-values
66
+ const ini = initial; // @cast-boundary form-values
67
+ let out;
68
+ for (const key of Object.keys(v)) {
69
+ // Value-based seed strip, except keys previously submitted as "" after a
70
+ // real clear — those must survive post-rebase submits (#1858).
71
+ if (!forceIncludeEmpty.has(key) && v[key] === "" && ini[key] === "") {
72
+ out ??= { ...v };
73
+ delete out[key];
74
+ }
75
+ }
76
+ return (out ?? v);
77
+ }
78
+ // Shallow-freeze so accidental mutations on the snapshot (e.g. a test
79
+ // writing `snapshot.values.title = "x"`) throw in strict mode instead of
80
+ // silently diverging from the controller's internal state. Deep-freeze
81
+ // would be safer but expensive — keeping it shallow matches React's own
82
+ // immutability expectations and leaves room for sub-controllers to manage
83
+ // their own sub-trees without double-frozen parents blocking them.
84
+ function freezeSnapshot(snapshot) {
85
+ return Object.freeze(snapshot);
86
+ }
87
+ function omitPayloadFields(payload, omitFields) {
88
+ if (!omitFields || omitFields.length === 0)
89
+ return payload;
90
+ if (typeof payload !== "object" || payload === null || Array.isArray(payload))
91
+ return payload;
92
+ return Object.fromEntries(Object.entries(payload).filter(([key]) => !omitFields.includes(key)));
93
+ }
94
+ export function createFormController(options) {
95
+ // Shallow copy so mutating the input after creation doesn't bleed into
96
+ // the controller's internal state. `initial` stays fixed across the
97
+ // controller's lifetime unless rebase() replaces it.
98
+ let values = { ...options.initial };
99
+ let initial = { ...options.initial };
100
+ let errors = Object.freeze({});
101
+ // Keys last submitted as "" (intentional clear). Survives rebase so a later
102
+ // submit of other fields does not drop them (#1858).
103
+ const forceIncludeEmpty = new Set();
104
+ // ctx is cast-held as TCtx; `undefined` is valid when callers don't
105
+ // declare conditional predicates that depend on it.
106
+ let ctx = options.ctx ?? undefined;
107
+ // Snapshot lives in a Store — same reference across getSnapshot() calls
108
+ // until a mutator invalidates it via setState. React's useSyncExternalStore
109
+ // compares snapshot identity to decide on a re-render; the Store holds the
110
+ // identity stable until invalidate() swaps it for a fresh build.
111
+ const snapshotStore = createStore(buildSnapshot());
112
+ // In-flight submit tracker. When a submit() is pending, a second call
113
+ // awaits the same promise instead of firing a parallel write — avoids
114
+ // double-submission on double-click and keeps the rebase semantics
115
+ // coherent (rebasing twice in quick succession would mis-align the
116
+ // baseline with what the server actually saw).
117
+ let submitInFlight = null;
118
+ function buildSnapshot() {
119
+ const changes = valuesDiff(values, initial);
120
+ const isDirty = Object.keys(changes).length > 0;
121
+ const fields = Object.freeze(computeFieldStates(options.fields, values, ctx));
122
+ return freezeSnapshot({
123
+ values,
124
+ initial,
125
+ changes,
126
+ isDirty,
127
+ isUnchanged: !isDirty,
128
+ errors,
129
+ fields,
130
+ });
131
+ }
132
+ function invalidate() {
133
+ snapshotStore.setState(buildSnapshot());
134
+ }
135
+ // Local shared implementations so submit() can call validate/rebase
136
+ // without the this-in-object-literal dance. Both also expose themselves
137
+ // as methods on the returned controller.
138
+ // Clears/replaces errors for a scoped run without touching fields outside
139
+ // the scope — a step-2 validate() that comes back clean must not wipe
140
+ // step-1's still-unresolved errors from an earlier submit() (or vice
141
+ // versa when step-2 newly has issues).
142
+ function replaceScopedErrors(isInScope, grouped) {
143
+ if (isInScope === undefined) {
144
+ errors = Object.freeze(grouped);
145
+ // skip: unscoped replace — wholesale overwrite, nothing left to merge
146
+ return;
147
+ }
148
+ const merged = {};
149
+ for (const [path, issues] of Object.entries(errors)) {
150
+ if (!isInScope(path))
151
+ merged[path] = issues;
152
+ }
153
+ Object.assign(merged, grouped);
154
+ errors = Object.freeze(merged);
155
+ }
156
+ function runValidate(scope, opts) {
157
+ // See the validate() doc comment in types.ts for the scope contract.
158
+ // A scope entry matches its exact path and everything below it, but not
159
+ // siblings: `address.city` must not pull in `address.zip` (#1898).
160
+ const isInScope = scope === undefined
161
+ ? undefined
162
+ : (path) => scope.some((entry) => path === entry || path.startsWith(`${entry}.`));
163
+ const includeRoot = opts?.includeRoot === true;
164
+ if (!options.schema) {
165
+ if (Object.keys(errors).length > 0) {
166
+ replaceScopedErrors(isInScope, {});
167
+ invalidate();
168
+ }
169
+ return true;
170
+ }
171
+ const fieldStates = computeFieldStates(options.fields, values, ctx);
172
+ const parsed = options.schema.safeParse(values);
173
+ if (parsed.success) {
174
+ if (Object.keys(errors).length > 0) {
175
+ replaceScopedErrors(isInScope, {});
176
+ invalidate();
177
+ }
178
+ return true;
179
+ }
180
+ const hiddenFields = new Set();
181
+ for (const [fieldKey, state] of Object.entries(fieldStates)) {
182
+ if (!state.visible)
183
+ hiddenFields.add(fieldKey);
184
+ }
185
+ // Root-level .refine() issues use path "(root)". Wizard step validate()
186
+ // still drops them (#1885); submit() passes includeRoot so RenderEdit
187
+ // fields={…} cannot silently skip cross-field rules (#1907).
188
+ const relevantIssues = relevantFieldIssues(zodErrorToFieldIssues(parsed.error), {
189
+ hiddenFields,
190
+ ...(scope !== undefined && { scope }),
191
+ includeRoot,
192
+ });
193
+ if (relevantIssues.length === 0) {
194
+ if (Object.keys(errors).length > 0) {
195
+ replaceScopedErrors(isInScope, {});
196
+ invalidate();
197
+ }
198
+ return true;
199
+ }
200
+ replaceScopedErrors(isInScope, groupIssuesByPath(relevantIssues));
201
+ invalidate();
202
+ return false;
203
+ }
204
+ function runRebase() {
205
+ initial = { ...values };
206
+ if (Object.keys(errors).length > 0)
207
+ errors = Object.freeze({});
208
+ invalidate();
209
+ }
210
+ // Stale-submit-safe rebase. Difference to runRebase: the baseline is
211
+ // taken from the values that were actually SENT to the server, not
212
+ // from `values` at the moment the server replied. If the user typed
213
+ // into a field while the submit was in flight, those edits stay as
214
+ // dirty changes after the call — they never made it to the server, so
215
+ // pretending they did (via `initial = { ...values }`) would mask unsaved
216
+ // input. The race shows up as "user edits a field during a 2s save, sees
217
+ // it as saved, closes the tab — the edit is lost". This path keeps
218
+ // `values` untouched; only `initial` shifts to the submitted snapshot.
219
+ function runRebaseToSnapshot(snapped) {
220
+ initial = { ...snapped };
221
+ if (Object.keys(errors).length > 0)
222
+ errors = Object.freeze({});
223
+ invalidate();
224
+ }
225
+ return {
226
+ getSnapshot: snapshotStore.getSnapshot,
227
+ subscribe: snapshotStore.subscribe,
228
+ setField(key, value) {
229
+ // skip: value unchanged, avoid notify/re-render on identical set
230
+ // avoids a notify + re-render for "setField with same value" which
231
+ // happens a lot in controlled inputs on every keystroke of an
232
+ // untouched field.
233
+ if (Object.is(values[key], value))
234
+ return;
235
+ values = { ...values, [key]: value };
236
+ invalidate();
237
+ },
238
+ setValues(partial) {
239
+ // skip: partial matches current values, avoid no-op notify
240
+ // partial that matches current values shouldn't fire listeners.
241
+ let changed = false;
242
+ const v = values; // @cast-boundary form-values
243
+ const p = partial; // @cast-boundary form-values
244
+ for (const k of Object.keys(p)) {
245
+ if (!Object.is(v[k], p[k])) {
246
+ changed = true;
247
+ break;
248
+ }
249
+ }
250
+ // skip: no key in partial actually changed, avoid no-op notify
251
+ if (!changed)
252
+ return;
253
+ values = { ...values, ...partial };
254
+ invalidate();
255
+ },
256
+ clearErrors(path) {
257
+ if (path === undefined) {
258
+ // skip: no errors present, avoid no-op notify
259
+ if (Object.keys(errors).length === 0)
260
+ return;
261
+ errors = Object.freeze({});
262
+ }
263
+ else {
264
+ // skip: path has no error entry, avoid no-op notify
265
+ if (!(path in errors))
266
+ return;
267
+ const next = { ...errors };
268
+ delete next[path];
269
+ errors = Object.freeze(next);
270
+ }
271
+ invalidate();
272
+ },
273
+ setErrors(nextErrors) {
274
+ errors = Object.freeze({ ...nextErrors });
275
+ invalidate();
276
+ },
277
+ validate: runValidate,
278
+ reset() {
279
+ const alreadyClean = !snapshotStore.getSnapshot().isDirty && Object.keys(errors).length === 0;
280
+ // skip: already at baseline with no errors, no-op reset
281
+ if (alreadyClean)
282
+ return;
283
+ values = { ...initial };
284
+ errors = Object.freeze({});
285
+ forceIncludeEmpty.clear();
286
+ invalidate();
287
+ },
288
+ rebase: runRebase,
289
+ setCtx(nextCtx) {
290
+ // Cast back to TCtx: setCtx's public signature takes unknown so
291
+ // callers don't need to plumb generics. If conditions depend on ctx
292
+ // and callers pass the wrong shape, the predicate throws at
293
+ // evaluation time — which is the same fail mode as any other
294
+ // callback contract violation.
295
+ ctx = nextCtx;
296
+ invalidate();
297
+ },
298
+ async submit() {
299
+ const submitCfg = options.submit;
300
+ if (!submitCfg) {
301
+ throw new Error("createFormController: submit() called without a `submit` config. Configure `{ dispatcher, type }` on the controller or drive dispatching manually.");
302
+ }
303
+ // Concurrent-submit guard: a double-click (two invocations before
304
+ // the first network call returned) would otherwise fire two writes
305
+ // AND rebase twice — compounding with the stale-submit race below.
306
+ // Serialize: subsequent calls await the in-flight promise. Same
307
+ // pattern the server-side event-dispatcher uses (inFlightTurns).
308
+ if (submitInFlight)
309
+ return submitInFlight;
310
+ if (!runValidate(submitCfg.validateScope, { includeRoot: true })) {
311
+ return { validationBlocked: true, isSuccess: false };
312
+ }
313
+ const payloadMode = submitCfg.payloadMode ?? "values";
314
+ // Capture the whole snapshot AT submit-time. The user may keep
315
+ // typing while the network call is in flight; on success we rebase
316
+ // ONLY to the values that were actually sent, leaving any
317
+ // subsequent edits as a fresh dirty delta. Without this, an edit
318
+ // during the await would be swallowed into the new baseline and
319
+ // the user would see it as "saved" despite the server never seeing
320
+ // it. Same snapshot is fed to buildPayload — a custom transformer
321
+ // (nested-write case) sees exactly what submit() sees.
322
+ const submittedSnapshot = snapshotStore.getSnapshot();
323
+ const submittedValues = submittedSnapshot.values;
324
+ // buildPayload wins over payloadMode when both are set.
325
+ let payload;
326
+ if (submitCfg.buildPayload) {
327
+ payload = submitCfg.buildPayload(submittedSnapshot);
328
+ }
329
+ else if (payloadMode === "changes") {
330
+ if (submittedSnapshot.isUnchanged) {
331
+ return {
332
+ validationBlocked: false,
333
+ isSuccess: true,
334
+ isNoOp: true,
335
+ data: submittedValues,
336
+ };
337
+ }
338
+ payload = submittedSnapshot.changes;
339
+ }
340
+ else if (submitCfg.stripEmptySeeds === false) {
341
+ payload = submittedValues;
342
+ }
343
+ else {
344
+ payload = stripUntouchedEmptyStrings(submittedValues, submittedSnapshot.initial, forceIncludeEmpty);
345
+ }
346
+ payload = omitPayloadFields(payload, submitCfg.omitFields);
347
+ // Only enforced here, right before the write actually happens — a
348
+ // form that never reaches this point (blocked by validation, or a
349
+ // clean payloadMode "changes" no-op) must not throw just because no
350
+ // dispatcher was ever wired up.
351
+ const dispatcher = submitCfg.dispatcher;
352
+ if (!dispatcher) {
353
+ // Return SubmitResult (not throw) so RenderEdit's banner path shows
354
+ // the misconfiguration instead of an unhandled rejection on save.
355
+ return {
356
+ validationBlocked: false,
357
+ isSuccess: false,
358
+ isNoOp: false,
359
+ error: {
360
+ code: "misconfigured",
361
+ httpStatus: 500,
362
+ i18nKey: "errors.internal",
363
+ message: "createFormController: submit() called without a dispatcher (dispatcher was absent when the form mounted). Pass `submit.dispatcher` explicitly, or mount a <DispatcherProvider> above this form.",
364
+ },
365
+ };
366
+ }
367
+ const runWrite = async () => {
368
+ const result = await dispatcher.write(submitCfg.type, payload);
369
+ if (result.isSuccess) {
370
+ // Remember intentional empty clears so post-rebase submits keep them (#1858).
371
+ if (payload && typeof payload === "object" && !Array.isArray(payload)) {
372
+ for (const [k, v] of Object.entries(payload)) {
373
+ if (v === "")
374
+ forceIncludeEmpty.add(k);
375
+ else
376
+ forceIncludeEmpty.delete(k);
377
+ }
378
+ }
379
+ // Rebase to the SNAPSHOT, not to the current values — see
380
+ // runRebaseToSnapshot comment for why.
381
+ runRebaseToSnapshot(submittedValues);
382
+ return { validationBlocked: false, isSuccess: true, isNoOp: false, data: result.data };
383
+ }
384
+ const serverFields = result.error.details?.fields;
385
+ if (serverFields && serverFields.length > 0) {
386
+ const prefix = submitCfg.serverFieldPathPrefix;
387
+ const mappedFields = prefix
388
+ ? stripServerFieldPathPrefix(serverFields, prefix)
389
+ : serverFields;
390
+ errors = Object.freeze(groupIssuesByPath(mappedFields));
391
+ invalidate();
392
+ const error = prefix
393
+ ? { ...result.error, details: { ...result.error.details, fields: mappedFields } }
394
+ : result.error;
395
+ return { validationBlocked: false, isSuccess: false, isNoOp: false, error };
396
+ }
397
+ return { validationBlocked: false, isSuccess: false, isNoOp: false, error: result.error };
398
+ };
399
+ const writeInFlight = runWrite();
400
+ submitInFlight = writeInFlight;
401
+ try {
402
+ return await writeInFlight;
403
+ }
404
+ finally {
405
+ submitInFlight = null;
406
+ }
407
+ },
408
+ };
409
+ }
@@ -0,0 +1,3 @@
1
+ export { createFormController } from "./form-controller.ts";
2
+ export type { FieldConditionPredicate, FieldConditions, FieldConditionValue, FieldState, FormController, FormControllerOptions, FormSnapshot, FormValues, SubmitConfig, SubmitPayloadMode, SubmitResult, } from "./types.ts";
3
+ export { groupIssuesByPath, I18N_KEY_PARAM, relevantFieldIssues, zodErrorToFieldIssues, } from "./zod-bridge.ts";
@@ -0,0 +1,2 @@
1
+ export { createFormController } from "./form-controller.js";
2
+ export { groupIssuesByPath, I18N_KEY_PARAM, relevantFieldIssues, zodErrorToFieldIssues, } from "./zod-bridge.js";
@@ -0,0 +1,76 @@
1
+ import type { ZodType } from "zod";
2
+ import type { Dispatcher, FieldIssue, WriteResult } from "../dispatcher/index.ts";
3
+ export type FormValues = Record<string, unknown>;
4
+ export type FormScopeKey<TValues extends FormValues> = (keyof TValues & string) | `${keyof TValues & string}.${string}`;
5
+ export type FieldConditionPredicate<TValues extends FormValues, TCtx> = (values: TValues, ctx: TCtx) => boolean;
6
+ export type FieldConditionValue<TValues extends FormValues, TCtx> = boolean | FieldConditionPredicate<TValues, TCtx>;
7
+ export type FieldConditions<TValues extends FormValues, TCtx = unknown> = {
8
+ readonly visible?: FieldConditionValue<TValues, TCtx>;
9
+ readonly readonly?: FieldConditionValue<TValues, TCtx>;
10
+ readonly required?: FieldConditionValue<TValues, TCtx>;
11
+ };
12
+ export type FieldState = {
13
+ readonly visible: boolean;
14
+ readonly readonly: boolean;
15
+ readonly required: boolean;
16
+ };
17
+ export type FormSnapshot<TValues extends FormValues> = {
18
+ readonly values: TValues;
19
+ readonly initial: TValues;
20
+ readonly changes: Partial<TValues>;
21
+ readonly isDirty: boolean;
22
+ readonly isUnchanged: boolean;
23
+ readonly errors: Readonly<Record<string, readonly FieldIssue[]>>;
24
+ readonly fields: Readonly<Record<string, FieldState>>;
25
+ };
26
+ export type FormController<TValues extends FormValues> = {
27
+ getSnapshot(): FormSnapshot<TValues>;
28
+ subscribe(listener: () => void): () => void;
29
+ setField<K extends keyof TValues>(key: K, value: TValues[K]): void;
30
+ setValues(partial: Partial<TValues>): void;
31
+ clearErrors(path?: string): void;
32
+ setErrors(errors: Readonly<Record<string, readonly FieldIssue[]>>): void;
33
+ validate(scope?: readonly FormScopeKey<TValues>[]): boolean;
34
+ reset(): void;
35
+ rebase(): void;
36
+ setCtx(ctx: unknown): void;
37
+ submit<TData = unknown>(): Promise<SubmitResult<TData>>;
38
+ };
39
+ export type FormControllerOptions<TValues extends FormValues, TCtx = unknown> = {
40
+ readonly initial: TValues;
41
+ readonly schema?: ZodType;
42
+ readonly fields?: Readonly<Record<string, FieldConditions<TValues, TCtx>>>;
43
+ readonly ctx?: TCtx;
44
+ readonly submit?: SubmitConfig<TValues>;
45
+ };
46
+ export type SubmitPayloadMode = "values" | "changes";
47
+ export type SubmitConfig<TValues extends FormValues = FormValues> = {
48
+ readonly dispatcher?: Dispatcher;
49
+ readonly type: string;
50
+ readonly payloadMode?: SubmitPayloadMode;
51
+ /** Only applies to payloadMode "values". Controls whether untouched
52
+ * empty-string fields (still equal to their initial "" seed) are
53
+ * stripped from the payload before dispatch — see SubmitPayloadMode's
54
+ * "values" doc for why the stripping exists. Default `true` (current
55
+ * behavior). Set `false` to send those fields as `""` unchanged. */
56
+ readonly stripEmptySeeds?: boolean;
57
+ /** Top-level payload keys removed right before dispatch, for fields that
58
+ * are shown or validated but must not reach the write handler. Applied
59
+ * to the built payload only when it is a plain object. */
60
+ readonly omitFields?: readonly string[];
61
+ readonly buildPayload?: (snapshot: FormSnapshot<TValues>) => unknown;
62
+ readonly serverFieldPathPrefix?: string;
63
+ /** Restricts submit()'s internal validate() call to these field names —
64
+ * same contract as validate()'s `scope` param. Set by RenderEdit when the
65
+ * caller's `fields` prop narrows which fields are rendered, so an
66
+ * unrendered required field can't block a submit the user can't fix.
67
+ * Omitted = unscoped (existing behavior, validates everything). */
68
+ readonly validateScope?: readonly string[];
69
+ };
70
+ export type SubmitResult<TData = unknown> = ({
71
+ readonly validationBlocked: false;
72
+ readonly isNoOp?: boolean;
73
+ } & WriteResult<TData>) | {
74
+ readonly validationBlocked: true;
75
+ readonly isSuccess: false;
76
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import type { ZodError } from "zod";
2
+ import type { FieldIssue } from "../dispatcher/index.ts";
3
+ export declare function zodErrorToFieldIssues(error: ZodError): FieldIssue[];
4
+ export declare function relevantFieldIssues(issues: readonly FieldIssue[], opts: {
5
+ readonly hiddenFields: ReadonlySet<string>;
6
+ readonly scope?: readonly string[];
7
+ readonly includeRoot?: boolean;
8
+ }): FieldIssue[];
9
+ export declare const I18N_KEY_PARAM = "i18nKey";
10
+ export declare function groupIssuesByPath(issues: readonly FieldIssue[]): Record<string, readonly FieldIssue[]>;