@pilotiq/pilotiq 0.24.1 → 0.24.2

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 (480) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/boost/guidelines.md +566 -0
  3. package/boost/skills/pilotiq-fields/SKILL.md +47 -0
  4. package/boost/skills/pilotiq-fields/rules/field-catalog.md +288 -0
  5. package/boost/skills/pilotiq-fields/rules/reactive-fields.md +199 -0
  6. package/boost/skills/pilotiq-fields/rules/validation.md +198 -0
  7. package/boost/skills/pilotiq-relations/SKILL.md +47 -0
  8. package/boost/skills/pilotiq-relations/rules/relation-managers.md +256 -0
  9. package/boost/skills/pilotiq-relations/rules/repeater-relationship.md +177 -0
  10. package/boost/skills/pilotiq-resource/SKILL.md +61 -0
  11. package/boost/skills/pilotiq-resource/rules/authorization.md +242 -0
  12. package/boost/skills/pilotiq-resource/rules/defining-resources.md +228 -0
  13. package/boost/skills/pilotiq-resource/rules/page-overrides.md +296 -0
  14. package/package.json +6 -1
  15. package/.turbo/turbo-build.log +0 -8
  16. package/CLAUDE.md +0 -265
  17. package/src/Cluster.test.ts +0 -283
  18. package/src/Cluster.ts +0 -83
  19. package/src/Column.test.ts +0 -199
  20. package/src/Column.ts +0 -710
  21. package/src/Global.test.ts +0 -367
  22. package/src/Global.ts +0 -169
  23. package/src/Page.test.ts +0 -114
  24. package/src/Page.ts +0 -208
  25. package/src/Pilotiq.perf.test.ts +0 -252
  26. package/src/Pilotiq.test.ts +0 -129
  27. package/src/Pilotiq.ts +0 -1158
  28. package/src/PilotiqRegistry.ts +0 -36
  29. package/src/PilotiqServiceProvider.ts +0 -121
  30. package/src/RelationManager.test.ts +0 -400
  31. package/src/RelationManager.ts +0 -527
  32. package/src/RenderHook.test.ts +0 -252
  33. package/src/RenderHook.ts +0 -242
  34. package/src/Resource.test.ts +0 -284
  35. package/src/Resource.ts +0 -526
  36. package/src/RightPanel.test.ts +0 -202
  37. package/src/RightPanel.ts +0 -132
  38. package/src/Tab.test.ts +0 -91
  39. package/src/Tab.ts +0 -156
  40. package/src/UserMenuItem.ts +0 -145
  41. package/src/actions/Action.test.ts +0 -2526
  42. package/src/actions/Action.ts +0 -1515
  43. package/src/actions/ActionGroup.test.ts +0 -112
  44. package/src/actions/ActionGroup.ts +0 -173
  45. package/src/actions/attachFactory.ts +0 -172
  46. package/src/actions/bulkFactories.ts +0 -168
  47. package/src/actions/crudFactories.ts +0 -220
  48. package/src/actions/exportFactory.ts +0 -225
  49. package/src/actions/factoryHelpers.ts +0 -177
  50. package/src/actions/importFactory.ts +0 -243
  51. package/src/actions/index.ts +0 -17
  52. package/src/actions/m2mFactories.ts +0 -193
  53. package/src/actions/relationFactories.ts +0 -372
  54. package/src/applyPageHooks.test.ts +0 -463
  55. package/src/applyPageHooks.ts +0 -330
  56. package/src/authorization.test.ts +0 -483
  57. package/src/breadcrumbs.test.ts +0 -238
  58. package/src/cells/coerce.test.ts +0 -85
  59. package/src/cells/coerce.ts +0 -84
  60. package/src/clusterPaths.ts +0 -35
  61. package/src/columns/BadgeColumn.test.ts +0 -54
  62. package/src/columns/BadgeColumn.ts +0 -32
  63. package/src/columns/BooleanColumn.test.ts +0 -41
  64. package/src/columns/BooleanColumn.ts +0 -18
  65. package/src/columns/ColorColumn.test.ts +0 -37
  66. package/src/columns/ColorColumn.ts +0 -38
  67. package/src/columns/IconColumn.test.ts +0 -54
  68. package/src/columns/IconColumn.ts +0 -37
  69. package/src/columns/ImageColumn.test.ts +0 -41
  70. package/src/columns/ImageColumn.ts +0 -28
  71. package/src/columns/SelectColumn.ts +0 -98
  72. package/src/columns/TextColumn.test.ts +0 -190
  73. package/src/columns/TextColumn.ts +0 -20
  74. package/src/columns/TextInputColumn.ts +0 -68
  75. package/src/columns/ToggleColumn.ts +0 -46
  76. package/src/columns/editableColumns.test.ts +0 -238
  77. package/src/columns/index.ts +0 -9
  78. package/src/defaultGlobalPages.ts +0 -95
  79. package/src/defaultPages.test.ts +0 -634
  80. package/src/defaultPages.ts +0 -617
  81. package/src/defaultViewPage.test.ts +0 -147
  82. package/src/elements/Form.test.ts +0 -223
  83. package/src/elements/Form.ts +0 -416
  84. package/src/elements/ListTabs.ts +0 -28
  85. package/src/elements/Table.test.ts +0 -422
  86. package/src/elements/Table.ts +0 -850
  87. package/src/elements/TableGroup.test.ts +0 -260
  88. package/src/elements/TableGroup.ts +0 -334
  89. package/src/elements/dispatchAction.test.ts +0 -463
  90. package/src/elements/dispatchAction.ts +0 -355
  91. package/src/elements/dispatchForm.test.ts +0 -477
  92. package/src/elements/dispatchForm.ts +0 -1993
  93. package/src/elements/dispatchTable.test.ts +0 -1514
  94. package/src/elements/dispatchTable.ts +0 -745
  95. package/src/elements/index.ts +0 -21
  96. package/src/entries/BadgeEntry.ts +0 -39
  97. package/src/entries/CodeEntry.test.ts +0 -40
  98. package/src/entries/CodeEntry.ts +0 -52
  99. package/src/entries/ColorEntry.ts +0 -63
  100. package/src/entries/ComponentEntry.test.ts +0 -173
  101. package/src/entries/ComponentEntry.ts +0 -95
  102. package/src/entries/Entry.ts +0 -304
  103. package/src/entries/IconEntry.ts +0 -49
  104. package/src/entries/ImageEntry.ts +0 -61
  105. package/src/entries/KeyValueEntry.ts +0 -47
  106. package/src/entries/RepeatableEntry.test.ts +0 -239
  107. package/src/entries/RepeatableEntry.ts +0 -173
  108. package/src/entries/TextEntry.test.ts +0 -394
  109. package/src/entries/TextEntry.ts +0 -60
  110. package/src/entries/index.ts +0 -12
  111. package/src/entries/leaves.test.ts +0 -306
  112. package/src/entries/registry.ts +0 -54
  113. package/src/fields/BuilderField.test.ts +0 -1188
  114. package/src/fields/BuilderField.ts +0 -605
  115. package/src/fields/BuilderRelationship.test.ts +0 -811
  116. package/src/fields/CheckboxField.test.ts +0 -44
  117. package/src/fields/CheckboxField.ts +0 -27
  118. package/src/fields/CheckboxListField.test.ts +0 -99
  119. package/src/fields/CheckboxListField.ts +0 -66
  120. package/src/fields/ColorPickerField.test.ts +0 -33
  121. package/src/fields/ColorPickerField.ts +0 -25
  122. package/src/fields/DateField.ts +0 -54
  123. package/src/fields/DateTimeField.test.ts +0 -55
  124. package/src/fields/EmailField.ts +0 -16
  125. package/src/fields/Field.test.ts +0 -654
  126. package/src/fields/Field.ts +0 -817
  127. package/src/fields/FileUploadField.test.ts +0 -143
  128. package/src/fields/FileUploadField.ts +0 -159
  129. package/src/fields/HiddenField.test.ts +0 -27
  130. package/src/fields/HiddenField.ts +0 -28
  131. package/src/fields/KeyValueField.test.ts +0 -105
  132. package/src/fields/KeyValueField.ts +0 -55
  133. package/src/fields/MarkdownField.test.ts +0 -167
  134. package/src/fields/MarkdownField.ts +0 -162
  135. package/src/fields/NumberField.ts +0 -33
  136. package/src/fields/RadioField.test.ts +0 -94
  137. package/src/fields/RadioField.ts +0 -67
  138. package/src/fields/RepeaterField.test.ts +0 -1806
  139. package/src/fields/RepeaterField.ts +0 -939
  140. package/src/fields/RepeaterRelationship.test.ts +0 -1923
  141. package/src/fields/RepeaterSimple.test.ts +0 -248
  142. package/src/fields/RowButton.test.ts +0 -219
  143. package/src/fields/RowButton.ts +0 -135
  144. package/src/fields/SelectField.test.ts +0 -192
  145. package/src/fields/SelectField.ts +0 -235
  146. package/src/fields/SliderField.test.ts +0 -50
  147. package/src/fields/SliderField.ts +0 -53
  148. package/src/fields/SlugField.ts +0 -24
  149. package/src/fields/TagsInputField.test.ts +0 -154
  150. package/src/fields/TagsInputField.ts +0 -133
  151. package/src/fields/TextField.test.ts +0 -213
  152. package/src/fields/TextField.ts +0 -177
  153. package/src/fields/TextareaField.test.ts +0 -58
  154. package/src/fields/TextareaField.ts +0 -59
  155. package/src/fields/ToggleButtonsField.test.ts +0 -106
  156. package/src/fields/ToggleButtonsField.ts +0 -59
  157. package/src/fields/ToggleField.ts +0 -16
  158. package/src/fields/disableOptionsWhenSelectedInSiblingRepeaterItems.test.ts +0 -319
  159. package/src/fields/optionsResolver.ts +0 -95
  160. package/src/fields/resolveField.ts +0 -28
  161. package/src/filters/BooleanFilter.ts +0 -35
  162. package/src/filters/DateRangeFilter.test.ts +0 -194
  163. package/src/filters/DateRangeFilter.ts +0 -148
  164. package/src/filters/Filter.test.ts +0 -268
  165. package/src/filters/Filter.ts +0 -184
  166. package/src/filters/FormFilter.test.ts +0 -238
  167. package/src/filters/FormFilter.ts +0 -215
  168. package/src/filters/MultiSelectFilter.test.ts +0 -119
  169. package/src/filters/MultiSelectFilter.ts +0 -78
  170. package/src/filters/QueryBuilderFilter.test.ts +0 -662
  171. package/src/filters/QueryBuilderFilter.ts +0 -398
  172. package/src/filters/SelectFilter.ts +0 -46
  173. package/src/filters/TernaryFilter.test.ts +0 -160
  174. package/src/filters/TernaryFilter.ts +0 -72
  175. package/src/filters/TrashedFilter.test.ts +0 -149
  176. package/src/filters/TrashedFilter.ts +0 -55
  177. package/src/filters/queryBuilder/BooleanConstraint.ts +0 -31
  178. package/src/filters/queryBuilder/Constraint.ts +0 -115
  179. package/src/filters/queryBuilder/DateConstraint.ts +0 -69
  180. package/src/filters/queryBuilder/NumberConstraint.ts +0 -66
  181. package/src/filters/queryBuilder/SelectConstraint.ts +0 -72
  182. package/src/filters/queryBuilder/TextConstraint.ts +0 -64
  183. package/src/filters/queryBuilder/index.ts +0 -12
  184. package/src/icons/index.ts +0 -2
  185. package/src/icons/lucide.ts +0 -204
  186. package/src/icons/registry.test.ts +0 -56
  187. package/src/icons/registry.ts +0 -41
  188. package/src/icons/types.ts +0 -47
  189. package/src/index.ts +0 -525
  190. package/src/io/csv.test.ts +0 -142
  191. package/src/io/csv.ts +0 -170
  192. package/src/nestedRelationManagerData.test.ts +0 -547
  193. package/src/notifications/Notification.test.ts +0 -210
  194. package/src/notifications/Notification.ts +0 -354
  195. package/src/notifications/broadcast.test.ts +0 -110
  196. package/src/notifications/broadcast.ts +0 -95
  197. package/src/notifications/database.test.ts +0 -383
  198. package/src/notifications/database.ts +0 -398
  199. package/src/notifications/databaseNotifications.test.ts +0 -187
  200. package/src/notifications/dispatchNotificationAction.test.ts +0 -341
  201. package/src/notifications/dispatchNotificationAction.ts +0 -142
  202. package/src/notifications/flash.test.ts +0 -89
  203. package/src/notifications/flash.ts +0 -71
  204. package/src/notifications/index.ts +0 -45
  205. package/src/notifications/registerBroadcastAuth.test.ts +0 -134
  206. package/src/notifications/registerBroadcastAuth.ts +0 -100
  207. package/src/notifications/resolveSavedNotification.test.ts +0 -82
  208. package/src/notifications/resolveSavedNotification.ts +0 -59
  209. package/src/notifications/types.ts +0 -93
  210. package/src/orm/m2mAccessor.ts +0 -66
  211. package/src/orm/modelDefaults.test.ts +0 -633
  212. package/src/orm/modelDefaults.ts +0 -666
  213. package/src/pageData/breadcrumbs.ts +0 -288
  214. package/src/pageData/forms.ts +0 -578
  215. package/src/pageData/helpers.ts +0 -857
  216. package/src/pageData/misc.ts +0 -347
  217. package/src/pageData/navigation.ts +0 -842
  218. package/src/pageData/relationPages.ts +0 -1248
  219. package/src/pageData/relationTabs.ts +0 -286
  220. package/src/pageData/resourcePages.ts +0 -609
  221. package/src/pageData.test.ts +0 -1545
  222. package/src/pageData.ts +0 -341
  223. package/src/plugins/index.ts +0 -8
  224. package/src/plugins/themeEditor.test.ts +0 -36
  225. package/src/plugins/themeEditor.ts +0 -45
  226. package/src/react/AppShell.tsx +0 -251
  227. package/src/react/CollabExtensionFactoryRegistry.ts +0 -55
  228. package/src/react/CollabRoomContext.ts +0 -98
  229. package/src/react/CollabTextRendererRegistry.ts +0 -102
  230. package/src/react/CommandPalette.tsx +0 -375
  231. package/src/react/CurrentUserContext.tsx +0 -50
  232. package/src/react/CustomPageWrapperGate.tsx +0 -69
  233. package/src/react/CustomPageWrapperRegistry.ts +0 -45
  234. package/src/react/FieldFocusReporterRegistry.ts +0 -37
  235. package/src/react/FieldLabelSlotRegistry.ts +0 -30
  236. package/src/react/FieldPresenceRegistry.ts +0 -46
  237. package/src/react/FormCollabBindingRegistry.ts +0 -242
  238. package/src/react/FormStateContext.tsx +0 -591
  239. package/src/react/HeadHooks.tsx +0 -126
  240. package/src/react/MarkdownEditorRegistry.test.ts +0 -38
  241. package/src/react/MarkdownEditorRegistry.ts +0 -107
  242. package/src/react/NotificationActionStrip.tsx +0 -263
  243. package/src/react/NotificationBell.tsx +0 -426
  244. package/src/react/PendingSuggestionApplierRegistry.test.ts +0 -97
  245. package/src/react/PendingSuggestionApplierRegistry.ts +0 -98
  246. package/src/react/PendingSuggestionOverlayRegistry.ts +0 -54
  247. package/src/react/PendingSuggestionsContext.tsx +0 -172
  248. package/src/react/RecordWrapperGate.tsx +0 -58
  249. package/src/react/RecordWrapperRegistry.ts +0 -39
  250. package/src/react/RenderHookSlot.tsx +0 -32
  251. package/src/react/RightSidebar.tsx +0 -257
  252. package/src/react/RightSidebarContext.tsx +0 -234
  253. package/src/react/RightSidebarTrigger.tsx +0 -53
  254. package/src/react/RowCoordsContext.tsx +0 -23
  255. package/src/react/SchemaRenderer.tsx +0 -549
  256. package/src/react/SearchTrigger.tsx +0 -46
  257. package/src/react/ThemeProvider.tsx +0 -93
  258. package/src/react/ThemeSettingsPage.tsx +0 -579
  259. package/src/react/ThemeToggle.tsx +0 -20
  260. package/src/react/Toaster.tsx +0 -158
  261. package/src/react/UserMenu.tsx +0 -196
  262. package/src/react/WidgetDataContext.tsx +0 -157
  263. package/src/react/cells/EditableCell.tsx +0 -389
  264. package/src/react/component-slots.test.ts +0 -103
  265. package/src/react/component-slots.ts +0 -116
  266. package/src/react/fieldJsHandler.test.ts +0 -166
  267. package/src/react/fieldJsHandler.ts +0 -79
  268. package/src/react/fields/BuilderInput.tsx +0 -1078
  269. package/src/react/fields/CheckboxInput.tsx +0 -39
  270. package/src/react/fields/CheckboxListInput.tsx +0 -102
  271. package/src/react/fields/ColorInput.tsx +0 -71
  272. package/src/react/fields/DateFieldInput.tsx +0 -70
  273. package/src/react/fields/DateTimeInput.tsx +0 -62
  274. package/src/react/fields/FieldShell.tsx +0 -348
  275. package/src/react/fields/FileUploadInput.tsx +0 -639
  276. package/src/react/fields/HiddenInput.tsx +0 -17
  277. package/src/react/fields/KeyValueInput.tsx +0 -230
  278. package/src/react/fields/MarkdownInput.tsx +0 -560
  279. package/src/react/fields/RadioInput.tsx +0 -81
  280. package/src/react/fields/RepeaterInput.test.ts +0 -116
  281. package/src/react/fields/RepeaterInput.tsx +0 -1420
  282. package/src/react/fields/SelectFieldInput.tsx +0 -280
  283. package/src/react/fields/SliderInput.tsx +0 -81
  284. package/src/react/fields/TagsInput.tsx +0 -283
  285. package/src/react/fields/TextLikeInput.tsx +0 -256
  286. package/src/react/fields/ToggleButtonsInput.tsx +0 -60
  287. package/src/react/fields/ToggleFieldInput.tsx +0 -56
  288. package/src/react/fields/relationshipRenameDispatch.test.ts +0 -106
  289. package/src/react/fields/relationshipRenameDispatch.ts +0 -97
  290. package/src/react/fields/repeaterReconcile.test.ts +0 -114
  291. package/src/react/fields/repeaterReconcile.ts +0 -104
  292. package/src/react/fields/rowChromeButton.tsx +0 -336
  293. package/src/react/fields/rowState.ts +0 -106
  294. package/src/react/fields/syncRowGates.test.ts +0 -202
  295. package/src/react/fields/syncRowGates.ts +0 -66
  296. package/src/react/fields/textInputControls.tsx +0 -238
  297. package/src/react/fields/useRowReorderDnd.ts +0 -78
  298. package/src/react/formStateHelpers.test.ts +0 -508
  299. package/src/react/formStateHelpers.ts +0 -381
  300. package/src/react/hooks/use-mobile.ts +0 -19
  301. package/src/react/icon-context.tsx +0 -60
  302. package/src/react/index.ts +0 -194
  303. package/src/react/layouts/SidebarLayout.tsx +0 -250
  304. package/src/react/layouts/TopbarLayout.tsx +0 -258
  305. package/src/react/navigate.tsx +0 -37
  306. package/src/react/onProviderSynced.test.ts +0 -90
  307. package/src/react/parseRecordEditUrl.test.ts +0 -122
  308. package/src/react/parseRecordEditUrl.ts +0 -94
  309. package/src/react/persistedState.ts +0 -40
  310. package/src/react/registry.ts +0 -48
  311. package/src/react/right-panel-registry.tsx +0 -47
  312. package/src/react/schemaRenderer/AlertRenderer.tsx +0 -112
  313. package/src/react/schemaRenderer/EntryRenderer.tsx +0 -501
  314. package/src/react/schemaRenderer/SectionRenderer.tsx +0 -120
  315. package/src/react/schemaRenderer/SimpleElements.tsx +0 -306
  316. package/src/react/schemaRenderer/TabsRenderer.tsx +0 -62
  317. package/src/react/schemaRenderer/WizardRenderer.tsx +0 -338
  318. package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +0 -177
  319. package/src/react/schemaRenderer/action/ActionModalDialog.tsx +0 -273
  320. package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +0 -61
  321. package/src/react/schemaRenderer/action/HandlerActionButton.tsx +0 -43
  322. package/src/react/schemaRenderer/action/MethodActionButton.tsx +0 -64
  323. package/src/react/schemaRenderer/action/buttons.tsx +0 -99
  324. package/src/react/schemaRenderer/action/helpers.ts +0 -140
  325. package/src/react/schemaRenderer/action/renderAction.tsx +0 -245
  326. package/src/react/schemaRenderer/columnFormat.ts +0 -65
  327. package/src/react/schemaRenderer/constants.ts +0 -50
  328. package/src/react/schemaRenderer/form/FormRenderer.tsx +0 -274
  329. package/src/react/schemaRenderer/form/renderField.tsx +0 -511
  330. package/src/react/schemaRenderer/helpers.tsx +0 -81
  331. package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +0 -308
  332. package/src/react/schemaRenderer/table/TableRenderer.tsx +0 -123
  333. package/src/react/schemaRenderer/table/TableRendererBody.tsx +0 -974
  334. package/src/react/schemaRenderer/table/filters.tsx +0 -1233
  335. package/src/react/schemaRenderer/table/formatCell.tsx +0 -264
  336. package/src/react/schemaRenderer/table/links.tsx +0 -112
  337. package/src/react/schemaRenderer/table/renderRowActions.tsx +0 -52
  338. package/src/react/schemaRenderer/table/url.tsx +0 -143
  339. package/src/react/theme-preview/apply.ts +0 -99
  340. package/src/react/theme-preview/build-html.ts +0 -436
  341. package/src/react/ui/button.tsx +0 -51
  342. package/src/react/ui/calendar.tsx +0 -67
  343. package/src/react/ui/checkbox.tsx +0 -29
  344. package/src/react/ui/dialog.tsx +0 -108
  345. package/src/react/ui/dropdown-menu.tsx +0 -97
  346. package/src/react/ui/input.tsx +0 -20
  347. package/src/react/ui/label.tsx +0 -21
  348. package/src/react/ui/popover.tsx +0 -50
  349. package/src/react/ui/select.tsx +0 -169
  350. package/src/react/ui/separator.tsx +0 -25
  351. package/src/react/ui/sheet.tsx +0 -136
  352. package/src/react/ui/sidebar.tsx +0 -723
  353. package/src/react/ui/skeleton.tsx +0 -13
  354. package/src/react/ui/slider.tsx +0 -34
  355. package/src/react/ui/switch.tsx +0 -28
  356. package/src/react/ui/table.tsx +0 -105
  357. package/src/react/ui/tabs.tsx +0 -63
  358. package/src/react/ui/textarea.tsx +0 -18
  359. package/src/react/ui/tooltip.tsx +0 -64
  360. package/src/react/useResizableWidth.ts +0 -139
  361. package/src/react/utils.ts +0 -6
  362. package/src/react/widgetRegistry.test.ts +0 -43
  363. package/src/react/widgetRegistry.ts +0 -50
  364. package/src/react/widgets/StatsOverviewRenderer.tsx +0 -232
  365. package/src/react/widgets/TableWidgetRenderer.tsx +0 -231
  366. package/src/react/widgets/ViewRenderer.tsx +0 -71
  367. package/src/relationManagerData.test.ts +0 -1595
  368. package/src/richtext/index.ts +0 -8
  369. package/src/richtext/registry.ts +0 -89
  370. package/src/routes/globals.ts +0 -148
  371. package/src/routes/guard.test.ts +0 -325
  372. package/src/routes/helpers.ts +0 -704
  373. package/src/routes/pages.ts +0 -175
  374. package/src/routes/panel.ts +0 -204
  375. package/src/routes/relations.ts +0 -1243
  376. package/src/routes/resources.ts +0 -781
  377. package/src/routes/theme.ts +0 -91
  378. package/src/routes-nested-relations.test.ts +0 -676
  379. package/src/routes-relations.test.ts +0 -972
  380. package/src/routes.test.ts +0 -2027
  381. package/src/routes.ts +0 -303
  382. package/src/schema/Alert.test.ts +0 -109
  383. package/src/schema/Alert.ts +0 -131
  384. package/src/schema/Block.ts +0 -169
  385. package/src/schema/Breadcrumbs.ts +0 -40
  386. package/src/schema/Card.ts +0 -35
  387. package/src/schema/Divider.ts +0 -20
  388. package/src/schema/Element.ts +0 -219
  389. package/src/schema/EmptyState.test.ts +0 -37
  390. package/src/schema/EmptyState.ts +0 -63
  391. package/src/schema/Fieldset.ts +0 -43
  392. package/src/schema/Grid.ts +0 -43
  393. package/src/schema/Group.ts +0 -30
  394. package/src/schema/Heading.ts +0 -39
  395. package/src/schema/Html.ts +0 -67
  396. package/src/schema/Icon.ts +0 -54
  397. package/src/schema/Image.ts +0 -57
  398. package/src/schema/LinkTag.ts +0 -41
  399. package/src/schema/Markdown.ts +0 -85
  400. package/src/schema/MetaTag.ts +0 -41
  401. package/src/schema/RelationTabs.ts +0 -71
  402. package/src/schema/ScriptTag.ts +0 -55
  403. package/src/schema/Section.ts +0 -160
  404. package/src/schema/ServerDataElement.test.ts +0 -140
  405. package/src/schema/ServerDataElement.ts +0 -156
  406. package/src/schema/SlotComponent.test.ts +0 -77
  407. package/src/schema/SlotComponent.ts +0 -71
  408. package/src/schema/Split.ts +0 -50
  409. package/src/schema/Stat.test.ts +0 -118
  410. package/src/schema/Stat.ts +0 -154
  411. package/src/schema/StatsOverview.test.ts +0 -141
  412. package/src/schema/StatsOverview.ts +0 -119
  413. package/src/schema/StyleTag.ts +0 -35
  414. package/src/schema/TableWidget.test.ts +0 -297
  415. package/src/schema/TableWidget.ts +0 -289
  416. package/src/schema/Tabs.ts +0 -79
  417. package/src/schema/Text.ts +0 -58
  418. package/src/schema/UnorderedList.ts +0 -49
  419. package/src/schema/View.test.ts +0 -111
  420. package/src/schema/View.ts +0 -127
  421. package/src/schema/Wizard.ts +0 -220
  422. package/src/schema/containers.test.ts +0 -564
  423. package/src/schema/headTags.test.ts +0 -134
  424. package/src/schema/index.ts +0 -40
  425. package/src/schema/primes.test.ts +0 -269
  426. package/src/schema/resolveSchema.test.ts +0 -379
  427. package/src/schema/resolveSchema.ts +0 -917
  428. package/src/schema/sanitize.ts +0 -58
  429. package/src/search.test.ts +0 -446
  430. package/src/search.ts +0 -178
  431. package/src/sessionFilters.test.ts +0 -375
  432. package/src/sessionFilters.ts +0 -143
  433. package/src/slot-components/index.ts +0 -10
  434. package/src/slot-components/registry.ts +0 -56
  435. package/src/styles/file-upload.css +0 -13
  436. package/src/summarizers/Summarizer.test.ts +0 -84
  437. package/src/summarizers/Summarizer.ts +0 -123
  438. package/src/summarizers/index.ts +0 -11
  439. package/src/theme/base-colors.ts +0 -68
  440. package/src/theme/chart-colors.ts +0 -50
  441. package/src/theme/colors.ts +0 -447
  442. package/src/theme/generate-css.test.ts +0 -139
  443. package/src/theme/generate-css.ts +0 -44
  444. package/src/theme/generate-scale.test.ts +0 -106
  445. package/src/theme/generate-scale.ts +0 -97
  446. package/src/theme/icon-map.ts +0 -42
  447. package/src/theme/index.ts +0 -34
  448. package/src/theme/migrate.test.ts +0 -178
  449. package/src/theme/migrate.ts +0 -81
  450. package/src/theme/presets.ts +0 -135
  451. package/src/theme/radius.ts +0 -18
  452. package/src/theme/resolve.test.ts +0 -238
  453. package/src/theme/resolve.ts +0 -96
  454. package/src/theme/spacing.ts +0 -18
  455. package/src/theme/storage.test.ts +0 -126
  456. package/src/theme/storage.ts +0 -106
  457. package/src/theme/theme-colors.ts +0 -88
  458. package/src/theme/types.ts +0 -125
  459. package/src/uploads/UploadAdapter.ts +0 -35
  460. package/src/uploads/index.ts +0 -2
  461. package/src/uploads/localUpload.test.ts +0 -70
  462. package/src/uploads/localUpload.ts +0 -84
  463. package/src/validation/Validator.ts +0 -49
  464. package/src/validation/index.ts +0 -28
  465. package/src/validation/rules.ts +0 -78
  466. package/src/validation/runValidators.ts +0 -435
  467. package/src/validation/uniqueValidator.test.ts +0 -196
  468. package/src/validation/uniqueValidator.ts +0 -133
  469. package/src/validation/validators.test.ts +0 -268
  470. package/src/vite.test.ts +0 -184
  471. package/src/vite.ts +0 -787
  472. package/src/widgets/index.ts +0 -10
  473. package/src/widgets/registry.ts +0 -45
  474. package/src/widgets.test.ts +0 -592
  475. package/tsconfig.build.json +0 -11
  476. package/tsconfig.json +0 -4
  477. package/tsconfig.test.json +0 -10
  478. package/views/react/Dashboard.tsx +0 -27
  479. package/views/react/Resources/Form.tsx +0 -102
  480. package/views/react/Resources/Index.tsx +0 -49
@@ -1,591 +0,0 @@
1
- import React, {
2
- createContext,
3
- useCallback,
4
- useContext,
5
- useEffect,
6
- useMemo,
7
- useRef,
8
- useState,
9
- } from 'react'
10
- import type { ElementMeta } from '../schema/Element.js'
11
- import {
12
- collectFieldDefaults,
13
- collectRowArrayFieldNames,
14
- findFieldMeta,
15
- parseFormDataToNested,
16
- readNestedValue,
17
- routeBindingWrite,
18
- writeNestedValue,
19
- } from './formStateHelpers.js'
20
- import { runJsHandler } from './fieldJsHandler.js'
21
- import { useToast } from './Toaster.js'
22
- import { useCollabRoom } from './CollabRoomContext.js'
23
- import {
24
- getFormCollabBinding,
25
- type FormCollabBinding,
26
- type RowBindingApi,
27
- } from './FormCollabBindingRegistry.js'
28
- import { registerRelationshipRenameHandler } from './fields/relationshipRenameDispatch.js'
29
-
30
- export type FieldStatus = 'idle' | 'pending'
31
-
32
- export interface FormStateApi {
33
- values: Record<string, unknown>
34
- setValue: (name: string, value: unknown) => void
35
- /** Fire the field's `live()` re-resolve hook. Plan #14 — `valueOverride`
36
- * lets uncontrolled inner-Repeater inputs pass their just-typed value
37
- * through without first writing it to the controlled `values` map.
38
- * When the form has a `formRef`, the latest DOM state of all
39
- * uncontrolled inputs is snapshotted via FormData before posting; the
40
- * override is then layered on top so the leaf carries the most recent
41
- * keystroke even when the snapshot races the input's commit. */
42
- triggerLive: (name: string, valueOverride?: unknown) => void
43
- errors: Record<string, string[]>
44
- /** Plan #8 — replace the errors map. Used by Wizard's step-validate
45
- * flow to surface per-field errors returned from the wizard endpoint. */
46
- applyErrors: (errors: Record<string, string[]>) => void
47
- formMeta: ElementMeta
48
- inFlight: boolean
49
- fieldStatus: (name: string) => FieldStatus
50
- /** Phase F.5 — per-Repeater/Builder row-array bindings. `null` outside a
51
- * collab room or when the binding doesn't implement F.5 row methods.
52
- * Each entry's API methods are pre-bound to the array name so renderers
53
- * call `.add(rowId, initial)` rather than `binding.addRow(name, …)`. */
54
- rowBindings: ReadonlyMap<string, RowBindingApi> | null
55
- }
56
-
57
- const FormStateContext = createContext<FormStateApi | null>(null)
58
-
59
- /** Hook for direct access to the form context. Returns `null` outside a
60
- * `FormStateProvider` (e.g. an action modal, or a form without any live
61
- * fields where the legacy uncontrolled path is in use). */
62
- export function useFormState(): FormStateApi | null {
63
- return useContext(FormStateContext)
64
- }
65
-
66
- /**
67
- * Plan #14 — minimal context that lets nested renderers (e.g. RepeaterInput)
68
- * see the parent `<form>`'s `formId` without reaching for `useFormState`
69
- * (which is null on uncontrolled forms) or sniffing the DOM. `FormRenderer`
70
- * wraps its children in this provider; readers hit the context with
71
- * `useContext(FormIdContext)` and fall back to a sentinel when missing.
72
- */
73
- export const FormIdContext = createContext<string>('')
74
-
75
- /**
76
- * Public accessor for the surrounding form's id, normalized to
77
- * `undefined` when no `FormRenderer` is mounted up-tree (the sentinel
78
- * empty string maps to `undefined`). Adapter packages — `@pilotiq/tiptap`,
79
- * `@pilotiq/codemirror` — consume this via the package's `react` re-export
80
- * to scope per-field registries (pending-suggestion appliers, focus
81
- * reporters) by form so multi-form pages route correctly to the matching
82
- * editor instance.
83
- */
84
- export function useFormId(): string | undefined {
85
- return useContext(FormIdContext) || undefined
86
- }
87
-
88
- export interface UseFieldStateResult {
89
- /** True when the field is inside a controlled form (live fields enabled).
90
- * Renderers should fall back to their `defaultValue` path when false.
91
- *
92
- * Plan #14 — dotted-name fields (inner Repeater rows) always return
93
- * `controlled: false`. Their inputs stay uncontrolled so reorder/clone
94
- * preserves typed values; the live trigger still works (snapshotting
95
- * via the form's FormData at trigger time). */
96
- controlled: boolean
97
- value: unknown
98
- setValue: (v: unknown) => void
99
- /** Notify the framework that this field's value has changed in a way that
100
- * should trigger its `live()` hook (if configured). No-op for non-live
101
- * fields and outside controlled forms. `valueOverride` lets uncontrolled
102
- * inputs pass their just-typed value (for dotted-path inner Repeater
103
- * fields). */
104
- triggerLive: (valueOverride?: unknown) => void
105
- /** True while a live re-resolve POST is in flight for this field. */
106
- pending: boolean
107
- errors: string[]
108
- }
109
-
110
- /**
111
- * Phase F.5 — return the row-array CRDT API for a Repeater/Builder field.
112
- * Returns `null` when:
113
- *
114
- * - No `FormStateProvider` is mounted (e.g. uncontrolled form path).
115
- * - No `<RecordCollabRoom>` is up-tree (no binding registered).
116
- * - The active binding doesn't implement F.5's row methods.
117
- * - The named field opted out via `.collab(false)` (skipped at meta walk).
118
- * - The named field isn't a Repeater/Builder.
119
- *
120
- * RepeaterInput + BuilderInput call this once per render and proceed
121
- * with the v1 local-only behaviour when null. The returned API methods
122
- * are pre-bound to the array name so consumers don't repeat it.
123
- */
124
- export function useRowBinding(arrayName: string): RowBindingApi | null {
125
- const ctx = useContext(FormStateContext)
126
- if (!ctx?.rowBindings) return null
127
- return ctx.rowBindings.get(arrayName) ?? null
128
- }
129
-
130
- /** Per-field accessor. Inside a `FormStateProvider` it returns the controlled
131
- * value + setter + live trigger; outside, it returns sentinels and callers
132
- * should fall back to `defaultValue` (uncontrolled inputs). */
133
- export function useFieldState(name: string): UseFieldStateResult {
134
- const ctx = useContext(FormStateContext)
135
- if (!ctx) {
136
- return {
137
- controlled: false,
138
- value: undefined,
139
- setValue: () => {},
140
- triggerLive: () => {},
141
- pending: false,
142
- errors: [],
143
- }
144
- }
145
- // Dotted-path fields (inner Repeater rows) always render uncontrolled
146
- // — see UseFieldStateResult.controlled doc for why.
147
- const dotted = name.includes('.')
148
- return {
149
- controlled: !dotted,
150
- value: dotted ? undefined : ctx.values[name],
151
- setValue: dotted ? () => {} : (v) => ctx.setValue(name, v),
152
- triggerLive: (valueOverride?: unknown) => ctx.triggerLive(name, valueOverride),
153
- pending: ctx.fieldStatus(name) === 'pending',
154
- errors: ctx.errors[name] ?? [],
155
- }
156
- }
157
-
158
- /** Response shape from `POST {base}/.../_form/:formId/state`. */
159
- interface FormStateResponse {
160
- ok: boolean
161
- form?: ElementMeta
162
- dirty?: string[]
163
- errors?: Record<string, string[]>
164
- error?: string
165
- }
166
-
167
- export interface FormStateProviderProps {
168
- /** Initial form meta from the server. The provider tracks subsequent
169
- * replacements after live POSTs internally. */
170
- initialMeta: ElementMeta
171
- initialErrors: Record<string, string[]>
172
- children: React.ReactNode
173
- /** Optional override fetch — used in tests. Defaults to the global `fetch`. */
174
- fetchImpl?: typeof fetch
175
- /** Optional callback when the form meta is replaced after a live POST.
176
- * Tests use this; production code reads from `useFormState().formMeta`. */
177
- onMetaUpdate?: (meta: ElementMeta) => void
178
- /** Plan #14 — ref to the wrapping `<form>` element. When set, live
179
- * triggers snapshot the form's full DOM state via `FormData` before
180
- * POSTing, which captures uncontrolled inner-Repeater inputs. The
181
- * controlled `values` map is overlaid on top, then any explicit
182
- * `valueOverride` from `triggerLive` wins last. */
183
- formRef?: React.RefObject<HTMLFormElement | null>
184
- }
185
-
186
- /** Provider component for the controlled form path. Holds the values map,
187
- * the current form meta (replaced wholesale on live POST), and the
188
- * per-field live trigger. Mounted by `FormRenderer` when the form has a
189
- * `stateUrl` set (i.e. at least one descendant field is `live()`). */
190
- export function FormStateProvider({
191
- initialMeta,
192
- initialErrors,
193
- children,
194
- fetchImpl,
195
- onMetaUpdate,
196
- formRef,
197
- }: FormStateProviderProps): React.ReactElement {
198
- const [formMeta, setFormMeta] = useState<ElementMeta>(initialMeta)
199
- const [values, setValuesState] = useState<Record<string, unknown>>(
200
- () => collectFieldDefaults(initialMeta),
201
- )
202
- const [errors, setErrors] = useState<Record<string, string[]>>(initialErrors)
203
- const [pendingNames, setPendingNames] = useState<Set<string>>(() => new Set())
204
- const [inFlight, setInFlight] = useState(false)
205
- // Phase F.5 — per-Repeater/Builder row-array stash. Populated on
206
- // collab mount when the binding implements F.5 row methods, cleared
207
- // on unmount. Stored in state (not a ref) so consumers of
208
- // `useRowBinding` re-render once the bindings land.
209
- const [rowBindings, setRowBindings] = useState<ReadonlyMap<string, RowBindingApi> | null>(null)
210
-
211
- const { notify } = useToast()
212
-
213
- // Track an incrementing in-flight id so out-of-order responses are dropped.
214
- // useRef (not useState) so React StrictMode dev double-invokes don't
215
- // produce stale closures over `inFlightId`.
216
- // See feedback_strict_mode_double_flash.md for the same pattern reasoning.
217
- const requestSeqRef = useRef(0)
218
- const latestSeenRef = useRef(0)
219
-
220
- // Per-field debounce timers. Mutating refs not state — never trigger a
221
- // re-render; just hold the timeout handle.
222
- const debounceTimersRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map())
223
- useEffect(() => () => {
224
- for (const t of debounceTimersRef.current.values()) clearTimeout(t)
225
- debounceTimersRef.current.clear()
226
- }, [])
227
-
228
- // Always-current values ref so debounced/blur callbacks read the latest
229
- // map without needing to be re-created on every keystroke.
230
- const valuesRef = useRef(values)
231
- useEffect(() => { valuesRef.current = values }, [values])
232
-
233
- // Resolve helper: read the current (synchronous) form meta. Used to look
234
- // up a field's `live` config when its trigger fires.
235
- const formMetaRef = useRef(formMeta)
236
- useEffect(() => { formMetaRef.current = formMeta }, [formMeta])
237
-
238
- const stateUrl = (formMeta as { stateUrl?: string })['stateUrl']
239
- const formId = (formMeta as { formId?: string })['formId'] ?? ''
240
-
241
- // Phase F2 — collab binding. When `<RecordCollabRoom>` is mounted up-tree
242
- // AND `@pilotiq-pro/collab` registered a `FormCollabBinding` factory, we
243
- // construct a binding for this form, lift any already-synced state on
244
- // top of the SSR-rendered defaults, and proxy every local write through
245
- // it. Remote writes flow back via `subscribe`. Outside a room (or with
246
- // no factory registered), `bindingRef` stays null and the plain
247
- // local-state path runs unchanged.
248
- const collabRoom = useCollabRoom()
249
- const bindingFactory = getFormCollabBinding()
250
- const bindingRef = useRef<FormCollabBinding | null>(null)
251
-
252
- useEffect(() => {
253
- if (!collabRoom || !bindingFactory || !formId) return
254
-
255
- const binding = bindingFactory({
256
- room: collabRoom,
257
- formId,
258
- initial: valuesRef.current,
259
- formMeta: formMetaRef.current,
260
- })
261
- bindingRef.current = binding
262
-
263
- // Lift any state already in the room (subsequent joiners — first
264
- // mover sees an empty snapshot here and the local SSR defaults
265
- // stay authoritative). Shallow merge so fields the binding doesn't
266
- // know about (defaults the seed skipped, dotted-path Repeater rows
267
- // we don't sync in F2) survive.
268
- const synced = binding.get()
269
- if (Object.keys(synced).length > 0) {
270
- setValuesState((prev) => ({ ...prev, ...synced }))
271
- }
272
-
273
- // Phase F.5 — build a `RowBindingApi` per top-level Repeater/Builder
274
- // field when the binding implements all three lifecycle methods. The
275
- // walk reads from formMeta (structural — fields exist regardless of
276
- // whether the form has any rows yet); the API is then pre-bound to
277
- // the array name so `RepeaterInput` calls `rb.add(rowId, …)` rather
278
- // than `binding.addRow(name, rowId, …)`. Partial F.5 impls (e.g. a
279
- // binding that has addRow but not reorderRows) skip the stash — the
280
- // contract says all three or nothing.
281
- if (binding.addRow && binding.removeRow && binding.reorderRows) {
282
- const { addRow, removeRow, reorderRows, subscribeRows, getRowOrder } = binding
283
- const arrayNames = collectRowArrayFieldNames(formMetaRef.current)
284
- if (arrayNames.length > 0) {
285
- const rowStash = new Map<string, RowBindingApi>()
286
- for (const arrayName of arrayNames) {
287
- rowStash.set(arrayName, {
288
- add: (rowId, initial = {}) => addRow.call(binding, arrayName, rowId, initial),
289
- remove: (rowId) => removeRow.call(binding, arrayName, rowId),
290
- reorder: (newOrder) => reorderRows.call(binding, arrayName, newOrder),
291
- // Partial F.5 impl: a binding may ship add/remove/reorder
292
- // without `subscribeRows` (e.g. tests). Substitute a no-op
293
- // subscription so renderer code stays uniform — the cleanup
294
- // fn is still called on unmount but no events ever arrive.
295
- subscribe: subscribeRows
296
- ? (fn) => subscribeRows.call(binding, arrayName, fn)
297
- : () => () => {},
298
- // `getRowOrder` is optional — bindings that ship row CRUD
299
- // without a snapshot read (test stubs, older plugins) get a
300
- // `[]` substitute. The renderer's reconciler treats empty as
301
- // "no orphans known" and no-ops, which is the safest fallback.
302
- current: getRowOrder
303
- ? () => getRowOrder.call(binding, arrayName)
304
- : () => [],
305
- })
306
- }
307
- setRowBindings(rowStash)
308
- }
309
- }
310
-
311
- // Subscribe to remote changes. Local writes ALSO trigger this
312
- // (Yjs observers fire on local transactions too) — the per-key
313
- // Object.is short-circuit below collapses them into no-op renders.
314
- const unsubscribe = binding.subscribe((snapshot) => {
315
- setValuesState((prev) => {
316
- let changed = false
317
- const next: Record<string, unknown> = { ...prev }
318
- for (const [k, v] of Object.entries(snapshot)) {
319
- if (!Object.is(prev[k], v)) {
320
- next[k] = v
321
- changed = true
322
- }
323
- }
324
- return changed ? next : prev
325
- })
326
- })
327
-
328
- // PK-switch Phase B — register a per-formId rename handler so
329
- // `FormRenderer`'s submit-success path can re-key newly persisted
330
- // relationship rows on the CRDT without us having to expose the
331
- // binding through React context (FormRenderer lives outside this
332
- // provider). No-op when the active binding skipped the optional
333
- // `renameRow` method; the documented fallback is the submitter-only
334
- // Phase A reconciler (other peers reload to converge). See
335
- // `pilotiq-pro/docs/plans/repeater-relationship-pk-switch.md`.
336
- const renameRow = binding.renameRow
337
- const unregisterRename = renameRow
338
- ? registerRelationshipRenameHandler(formId, (renames) => {
339
- for (const r of renames) {
340
- renameRow.call(binding, r.field, r.old, r.new)
341
- }
342
- })
343
- : () => {}
344
-
345
- return () => {
346
- unsubscribe()
347
- unregisterRename()
348
- binding.destroy()
349
- bindingRef.current = null
350
- setRowBindings(null)
351
- }
352
- // `valuesRef.current` is intentionally read once at mount — initial
353
- // values seed the binding; subsequent edits flow through `setValue`
354
- // and remote changes flow through `subscribe`.
355
- // eslint-disable-next-line react-hooks/exhaustive-deps
356
- }, [collabRoom, bindingFactory, formId])
357
-
358
- /**
359
- * Tier-2 follow-up to Plan #5 — fire `Field.afterStateUpdatedJs(body)`
360
- * if the named field declares one. Independent of `live()`: runs
361
- * synchronously on every change, no debounce, no roundtrip.
362
- *
363
- * `$get / $set` proxy this provider's values map. Dotted-path names
364
- * (Repeater / Builder rows) read + write nested. `$state` is the
365
- * just-set value; `$get(thisField)` returns the same value (we
366
- * overlay it onto the snapshot so the handler sees a consistent
367
- * post-write view of the form). `$set` calls `setValuesState`
368
- * directly so React rerenders affected controlled inputs without
369
- * re-firing `live` / re-firing JS for the sibling (mirrors
370
- * server-side `$set` semantics — a write is not a user change).
371
- */
372
- const runFieldJs = useCallback((name: string, value: unknown): void => {
373
- const fieldMeta = findFieldMeta(formMetaRef.current, name)
374
- const body = (fieldMeta as { afterStateUpdatedJs?: string } | undefined)?.afterStateUpdatedJs
375
- if (!body) return
376
-
377
- // Snapshot the values map with the just-changed field overlaid so
378
- // `$get(name)` is consistent with `$state`. Cheap shallow clone —
379
- // only allocated when a JS handler is actually present.
380
- const snapshot: Record<string, unknown> = { ...valuesRef.current }
381
- if (name.includes('.')) writeNestedValue(snapshot, name, value)
382
- else snapshot[name] = value
383
-
384
- const $get = (n: string): unknown => {
385
- if (n.includes('.')) return readNestedValue(snapshot, n)
386
- return snapshot[n]
387
- }
388
- const $set = (n: string, v: unknown): void => {
389
- if (n.includes('.')) {
390
- setValuesState((prev) => {
391
- const next = { ...prev }
392
- writeNestedValue(next, n, v)
393
- return next
394
- })
395
- return
396
- }
397
- setValuesState((prev) => {
398
- if (Object.is(prev[n], v)) return prev
399
- return { ...prev, [n]: v }
400
- })
401
- }
402
-
403
- runJsHandler({ body, fieldName: name, state: value, $get, $set })
404
- }, [])
405
-
406
- const setValue = useCallback((name: string, value: unknown): void => {
407
- setValuesState((prev) => {
408
- if (Object.is(prev[name], value)) return prev
409
- return { ...prev, [name]: value }
410
- })
411
- // Phase F2 / F.5 — proxy the write through the collab binding when
412
- // active AND the field hasn't opted out via `.collab(false)`. Top-level
413
- // fields ride `binding.set`. Row leaves (dotted paths matching
414
- // `parseRowFieldPath`) route through `binding.setRow` when the
415
- // binding implements F.5 — otherwise stay local-only (same posture
416
- // as pre-F.5).
417
- routeBindingWrite(bindingRef.current, formMetaRef.current, valuesRef.current, name, value)
418
- // Fire the client-side JS hook synchronously after the state write.
419
- // Dotted-name fields don't go through here (their setter is a no-op
420
- // in `useFieldState`); they fire JS via `triggerLive` instead so we
421
- // never double-fire for the same change.
422
- runFieldJs(name, value)
423
- }, [runFieldJs])
424
-
425
- const performLivePost = useCallback(async (name: string, valueOverride?: unknown): Promise<void> => {
426
- if (!stateUrl) return
427
- const seq = ++requestSeqRef.current
428
- setPendingNames((prev) => {
429
- if (prev.has(name)) return prev
430
- const next = new Set(prev)
431
- next.add(name)
432
- return next
433
- })
434
- setInFlight(true)
435
-
436
- const doFetch = fetchImpl ?? fetch
437
- try {
438
- // Plan #14 — build the values payload:
439
- // 1. Start from controlled values (top-level fields the user has
440
- // typed into via FormStateProvider's setValue path).
441
- // 2. If a form ref is set, overlay FormData-derived nested values
442
- // so uncontrolled inputs (including inner Repeater fields)
443
- // contribute their current DOM state.
444
- // 3. If a valueOverride is provided, write it through to the
445
- // target field's path — this wins last, so the leaf carries
446
- // the just-typed value even if the snapshot races a debounce.
447
- let payloadValues: Record<string, unknown> = { ...valuesRef.current }
448
- const formEl = formRef?.current
449
- if (formEl) {
450
- const snapshot = parseFormDataToNested(new FormData(formEl))
451
- payloadValues = { ...payloadValues, ...snapshot }
452
- }
453
- if (valueOverride !== undefined) {
454
- writeNestedValue(payloadValues, name, valueOverride)
455
- }
456
-
457
- const res = await doFetch(stateUrl, {
458
- method: 'POST',
459
- headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
460
- body: JSON.stringify({ changed: name, values: payloadValues }),
461
- })
462
-
463
- // Drop stale responses — a newer request has already been issued.
464
- if (seq < latestSeenRef.current) return
465
- latestSeenRef.current = seq
466
-
467
- const data = await res.json().catch(() => ({})) as FormStateResponse
468
-
469
- if (!res.ok) {
470
- if (res.status === 422 && data.errors) {
471
- setErrors(data.errors)
472
- } else {
473
- notify({
474
- type: 'error',
475
- title: 'Form update failed',
476
- ...(data.error ? { body: data.error } : {}),
477
- })
478
- }
479
- return
480
- }
481
-
482
- if (data.form) {
483
- setFormMeta(data.form)
484
- onMetaUpdate?.(data.form)
485
- // Server may have $set'd sibling values — overlay them onto the
486
- // current values map. Keep client-typed values intact for fields
487
- // the server didn't touch.
488
- const serverValues = (data.form as { values?: Record<string, unknown> }).values
489
- if (serverValues) {
490
- setValuesState((prev) => ({ ...prev, ...serverValues }))
491
- // Phase F2 (Q2) / F.5 — derived fields propagate to peers via the
492
- // collab binding so every client sees the auto-`slug` / etc. without
493
- // each peer roundtripping the server. Row leaves route through
494
- // `setRow` when the binding implements F.5; top-level fields ride
495
- // `set`. The rowId lookup needs the freshest values — merge
496
- // `valuesRef.current` with the server overlay so a row-id stamped
497
- // by this very server-resolve response is visible to `rowIdAtIndex`.
498
- const binding = bindingRef.current
499
- if (binding) {
500
- const lookupValues = { ...valuesRef.current, ...serverValues }
501
- for (const [k, v] of Object.entries(serverValues)) {
502
- // routeBindingWrite handles `.collab(false)` opt-out internally.
503
- routeBindingWrite(binding, data.form, lookupValues, k, v)
504
- }
505
- }
506
- }
507
- setErrors({})
508
- }
509
- } catch (err) {
510
- // Network / parse error — surface a toast but don't roll back values.
511
- // Next keystroke will retry naturally.
512
- notify({
513
- type: 'error',
514
- title: 'Form update failed',
515
- body: err instanceof Error ? err.message : String(err),
516
- })
517
- } finally {
518
- setPendingNames((prev) => {
519
- if (!prev.has(name)) return prev
520
- const next = new Set(prev)
521
- next.delete(name)
522
- return next
523
- })
524
- // Only clear inFlight when no other field is pending.
525
- setPendingNames((prev) => {
526
- setInFlight(prev.size > 0)
527
- return prev
528
- })
529
- }
530
- }, [stateUrl, fetchImpl, notify, onMetaUpdate, formRef])
531
-
532
- const triggerLive = useCallback((name: string, valueOverride?: unknown): void => {
533
- // Dotted-name fields (Repeater / Builder rows) bypass the controlled
534
- // `setValue` path entirely — fire their JS hook here so a row-scoped
535
- // afterStateUpdatedJs runs even without `live()`. Top-level fields
536
- // already had JS dispatched via `setValue`; skip to avoid double-fire.
537
- if (name.includes('.') && valueOverride !== undefined) {
538
- runFieldJs(name, valueOverride)
539
- }
540
-
541
- if (!stateUrl) return
542
- const fieldMeta = findFieldMeta(formMetaRef.current, name)
543
- const liveCfg = fieldMeta?.['live']
544
- if (!liveCfg) return
545
-
546
- const opts = typeof liveCfg === 'object' ? liveCfg as { onBlur?: boolean; debounce?: number } : {}
547
- const debounce = typeof opts.debounce === 'number' && opts.debounce > 0 ? opts.debounce : 0
548
-
549
- // Clear any pending debounce for this name; the new event resets the timer.
550
- const timers = debounceTimersRef.current
551
- const prevTimer = timers.get(name)
552
- if (prevTimer) clearTimeout(prevTimer)
553
-
554
- if (debounce > 0) {
555
- const t = setTimeout(() => {
556
- timers.delete(name)
557
- void performLivePost(name, valueOverride)
558
- }, debounce)
559
- timers.set(name, t)
560
- return
561
- }
562
-
563
- void performLivePost(name, valueOverride)
564
- }, [stateUrl, performLivePost])
565
-
566
- const fieldStatus = useCallback((name: string): FieldStatus => {
567
- return pendingNames.has(name) ? 'pending' : 'idle'
568
- }, [pendingNames])
569
-
570
- const applyErrors = useCallback((next: Record<string, string[]>): void => {
571
- setErrors(next)
572
- }, [])
573
-
574
- const api = useMemo<FormStateApi>(() => ({
575
- values,
576
- setValue,
577
- triggerLive,
578
- errors,
579
- applyErrors,
580
- formMeta,
581
- inFlight,
582
- fieldStatus,
583
- rowBindings,
584
- }), [values, setValue, triggerLive, errors, applyErrors, formMeta, inFlight, fieldStatus, rowBindings])
585
-
586
- return (
587
- <FormStateContext.Provider value={api}>
588
- {children}
589
- </FormStateContext.Provider>
590
- )
591
- }