@pilotiq/pilotiq 0.23.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 (500) hide show
  1. package/CHANGELOG.md +91 -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/dist/actions/exportFactory.d.ts +10 -0
  15. package/dist/actions/exportFactory.d.ts.map +1 -1
  16. package/dist/actions/exportFactory.js +10 -0
  17. package/dist/actions/exportFactory.js.map +1 -1
  18. package/dist/react/CollabRoomContext.d.ts +5 -5
  19. package/dist/react/index.d.ts +0 -1
  20. package/dist/react/index.d.ts.map +1 -1
  21. package/dist/react/index.js +0 -1
  22. package/dist/react/index.js.map +1 -1
  23. package/dist/routes/helpers.d.ts.map +1 -1
  24. package/dist/routes/helpers.js +6 -2
  25. package/dist/routes/helpers.js.map +1 -1
  26. package/dist/routes/relations.d.ts.map +1 -1
  27. package/dist/routes/relations.js +12 -0
  28. package/dist/routes/relations.js.map +1 -1
  29. package/package.json +6 -1
  30. package/.turbo/turbo-build.log +0 -8
  31. package/CLAUDE.md +0 -265
  32. package/dist/react/useCollabSeed.d.ts +0 -23
  33. package/dist/react/useCollabSeed.d.ts.map +0 -1
  34. package/dist/react/useCollabSeed.js +0 -82
  35. package/dist/react/useCollabSeed.js.map +0 -1
  36. package/src/Cluster.test.ts +0 -283
  37. package/src/Cluster.ts +0 -83
  38. package/src/Column.test.ts +0 -199
  39. package/src/Column.ts +0 -710
  40. package/src/Global.test.ts +0 -367
  41. package/src/Global.ts +0 -169
  42. package/src/Page.test.ts +0 -114
  43. package/src/Page.ts +0 -208
  44. package/src/Pilotiq.perf.test.ts +0 -252
  45. package/src/Pilotiq.test.ts +0 -129
  46. package/src/Pilotiq.ts +0 -1158
  47. package/src/PilotiqRegistry.ts +0 -36
  48. package/src/PilotiqServiceProvider.ts +0 -121
  49. package/src/RelationManager.test.ts +0 -400
  50. package/src/RelationManager.ts +0 -527
  51. package/src/RenderHook.test.ts +0 -252
  52. package/src/RenderHook.ts +0 -242
  53. package/src/Resource.test.ts +0 -284
  54. package/src/Resource.ts +0 -526
  55. package/src/RightPanel.test.ts +0 -202
  56. package/src/RightPanel.ts +0 -132
  57. package/src/Tab.test.ts +0 -91
  58. package/src/Tab.ts +0 -156
  59. package/src/UserMenuItem.ts +0 -145
  60. package/src/actions/Action.test.ts +0 -2526
  61. package/src/actions/Action.ts +0 -1515
  62. package/src/actions/ActionGroup.test.ts +0 -112
  63. package/src/actions/ActionGroup.ts +0 -173
  64. package/src/actions/attachFactory.ts +0 -172
  65. package/src/actions/bulkFactories.ts +0 -168
  66. package/src/actions/crudFactories.ts +0 -220
  67. package/src/actions/exportFactory.ts +0 -215
  68. package/src/actions/factoryHelpers.ts +0 -177
  69. package/src/actions/importFactory.ts +0 -243
  70. package/src/actions/index.ts +0 -17
  71. package/src/actions/m2mFactories.ts +0 -193
  72. package/src/actions/relationFactories.ts +0 -372
  73. package/src/applyPageHooks.test.ts +0 -463
  74. package/src/applyPageHooks.ts +0 -330
  75. package/src/authorization.test.ts +0 -483
  76. package/src/breadcrumbs.test.ts +0 -238
  77. package/src/cells/coerce.test.ts +0 -85
  78. package/src/cells/coerce.ts +0 -84
  79. package/src/clusterPaths.ts +0 -35
  80. package/src/columns/BadgeColumn.test.ts +0 -54
  81. package/src/columns/BadgeColumn.ts +0 -32
  82. package/src/columns/BooleanColumn.test.ts +0 -41
  83. package/src/columns/BooleanColumn.ts +0 -18
  84. package/src/columns/ColorColumn.test.ts +0 -37
  85. package/src/columns/ColorColumn.ts +0 -38
  86. package/src/columns/IconColumn.test.ts +0 -54
  87. package/src/columns/IconColumn.ts +0 -37
  88. package/src/columns/ImageColumn.test.ts +0 -41
  89. package/src/columns/ImageColumn.ts +0 -28
  90. package/src/columns/SelectColumn.ts +0 -98
  91. package/src/columns/TextColumn.test.ts +0 -190
  92. package/src/columns/TextColumn.ts +0 -20
  93. package/src/columns/TextInputColumn.ts +0 -68
  94. package/src/columns/ToggleColumn.ts +0 -46
  95. package/src/columns/editableColumns.test.ts +0 -238
  96. package/src/columns/index.ts +0 -9
  97. package/src/defaultGlobalPages.ts +0 -95
  98. package/src/defaultPages.test.ts +0 -634
  99. package/src/defaultPages.ts +0 -617
  100. package/src/defaultViewPage.test.ts +0 -147
  101. package/src/elements/Form.test.ts +0 -223
  102. package/src/elements/Form.ts +0 -416
  103. package/src/elements/ListTabs.ts +0 -28
  104. package/src/elements/Table.test.ts +0 -422
  105. package/src/elements/Table.ts +0 -850
  106. package/src/elements/TableGroup.test.ts +0 -260
  107. package/src/elements/TableGroup.ts +0 -334
  108. package/src/elements/dispatchAction.test.ts +0 -463
  109. package/src/elements/dispatchAction.ts +0 -355
  110. package/src/elements/dispatchForm.test.ts +0 -477
  111. package/src/elements/dispatchForm.ts +0 -1993
  112. package/src/elements/dispatchTable.test.ts +0 -1514
  113. package/src/elements/dispatchTable.ts +0 -745
  114. package/src/elements/index.ts +0 -21
  115. package/src/entries/BadgeEntry.ts +0 -39
  116. package/src/entries/CodeEntry.test.ts +0 -40
  117. package/src/entries/CodeEntry.ts +0 -52
  118. package/src/entries/ColorEntry.ts +0 -63
  119. package/src/entries/ComponentEntry.test.ts +0 -173
  120. package/src/entries/ComponentEntry.ts +0 -95
  121. package/src/entries/Entry.ts +0 -304
  122. package/src/entries/IconEntry.ts +0 -49
  123. package/src/entries/ImageEntry.ts +0 -61
  124. package/src/entries/KeyValueEntry.ts +0 -47
  125. package/src/entries/RepeatableEntry.test.ts +0 -239
  126. package/src/entries/RepeatableEntry.ts +0 -173
  127. package/src/entries/TextEntry.test.ts +0 -394
  128. package/src/entries/TextEntry.ts +0 -60
  129. package/src/entries/index.ts +0 -12
  130. package/src/entries/leaves.test.ts +0 -306
  131. package/src/entries/registry.ts +0 -54
  132. package/src/fields/BuilderField.test.ts +0 -1188
  133. package/src/fields/BuilderField.ts +0 -605
  134. package/src/fields/BuilderRelationship.test.ts +0 -811
  135. package/src/fields/CheckboxField.test.ts +0 -44
  136. package/src/fields/CheckboxField.ts +0 -27
  137. package/src/fields/CheckboxListField.test.ts +0 -99
  138. package/src/fields/CheckboxListField.ts +0 -66
  139. package/src/fields/ColorPickerField.test.ts +0 -33
  140. package/src/fields/ColorPickerField.ts +0 -25
  141. package/src/fields/DateField.ts +0 -54
  142. package/src/fields/DateTimeField.test.ts +0 -55
  143. package/src/fields/EmailField.ts +0 -16
  144. package/src/fields/Field.test.ts +0 -654
  145. package/src/fields/Field.ts +0 -817
  146. package/src/fields/FileUploadField.test.ts +0 -143
  147. package/src/fields/FileUploadField.ts +0 -159
  148. package/src/fields/HiddenField.test.ts +0 -27
  149. package/src/fields/HiddenField.ts +0 -28
  150. package/src/fields/KeyValueField.test.ts +0 -105
  151. package/src/fields/KeyValueField.ts +0 -55
  152. package/src/fields/MarkdownField.test.ts +0 -167
  153. package/src/fields/MarkdownField.ts +0 -162
  154. package/src/fields/NumberField.ts +0 -33
  155. package/src/fields/RadioField.test.ts +0 -94
  156. package/src/fields/RadioField.ts +0 -67
  157. package/src/fields/RepeaterField.test.ts +0 -1806
  158. package/src/fields/RepeaterField.ts +0 -939
  159. package/src/fields/RepeaterRelationship.test.ts +0 -1923
  160. package/src/fields/RepeaterSimple.test.ts +0 -248
  161. package/src/fields/RowButton.test.ts +0 -219
  162. package/src/fields/RowButton.ts +0 -135
  163. package/src/fields/SelectField.test.ts +0 -192
  164. package/src/fields/SelectField.ts +0 -235
  165. package/src/fields/SliderField.test.ts +0 -50
  166. package/src/fields/SliderField.ts +0 -53
  167. package/src/fields/SlugField.ts +0 -24
  168. package/src/fields/TagsInputField.test.ts +0 -154
  169. package/src/fields/TagsInputField.ts +0 -133
  170. package/src/fields/TextField.test.ts +0 -213
  171. package/src/fields/TextField.ts +0 -177
  172. package/src/fields/TextareaField.test.ts +0 -58
  173. package/src/fields/TextareaField.ts +0 -59
  174. package/src/fields/ToggleButtonsField.test.ts +0 -106
  175. package/src/fields/ToggleButtonsField.ts +0 -59
  176. package/src/fields/ToggleField.ts +0 -16
  177. package/src/fields/disableOptionsWhenSelectedInSiblingRepeaterItems.test.ts +0 -319
  178. package/src/fields/optionsResolver.ts +0 -95
  179. package/src/fields/resolveField.ts +0 -28
  180. package/src/filters/BooleanFilter.ts +0 -35
  181. package/src/filters/DateRangeFilter.test.ts +0 -194
  182. package/src/filters/DateRangeFilter.ts +0 -148
  183. package/src/filters/Filter.test.ts +0 -268
  184. package/src/filters/Filter.ts +0 -184
  185. package/src/filters/FormFilter.test.ts +0 -238
  186. package/src/filters/FormFilter.ts +0 -215
  187. package/src/filters/MultiSelectFilter.test.ts +0 -119
  188. package/src/filters/MultiSelectFilter.ts +0 -78
  189. package/src/filters/QueryBuilderFilter.test.ts +0 -662
  190. package/src/filters/QueryBuilderFilter.ts +0 -398
  191. package/src/filters/SelectFilter.ts +0 -46
  192. package/src/filters/TernaryFilter.test.ts +0 -160
  193. package/src/filters/TernaryFilter.ts +0 -72
  194. package/src/filters/TrashedFilter.test.ts +0 -149
  195. package/src/filters/TrashedFilter.ts +0 -55
  196. package/src/filters/queryBuilder/BooleanConstraint.ts +0 -31
  197. package/src/filters/queryBuilder/Constraint.ts +0 -115
  198. package/src/filters/queryBuilder/DateConstraint.ts +0 -69
  199. package/src/filters/queryBuilder/NumberConstraint.ts +0 -66
  200. package/src/filters/queryBuilder/SelectConstraint.ts +0 -72
  201. package/src/filters/queryBuilder/TextConstraint.ts +0 -64
  202. package/src/filters/queryBuilder/index.ts +0 -12
  203. package/src/icons/index.ts +0 -2
  204. package/src/icons/lucide.ts +0 -204
  205. package/src/icons/registry.test.ts +0 -56
  206. package/src/icons/registry.ts +0 -41
  207. package/src/icons/types.ts +0 -47
  208. package/src/index.ts +0 -525
  209. package/src/io/csv.test.ts +0 -142
  210. package/src/io/csv.ts +0 -170
  211. package/src/nestedRelationManagerData.test.ts +0 -547
  212. package/src/notifications/Notification.test.ts +0 -210
  213. package/src/notifications/Notification.ts +0 -354
  214. package/src/notifications/broadcast.test.ts +0 -110
  215. package/src/notifications/broadcast.ts +0 -95
  216. package/src/notifications/database.test.ts +0 -383
  217. package/src/notifications/database.ts +0 -398
  218. package/src/notifications/databaseNotifications.test.ts +0 -187
  219. package/src/notifications/dispatchNotificationAction.test.ts +0 -341
  220. package/src/notifications/dispatchNotificationAction.ts +0 -142
  221. package/src/notifications/flash.test.ts +0 -89
  222. package/src/notifications/flash.ts +0 -71
  223. package/src/notifications/index.ts +0 -45
  224. package/src/notifications/registerBroadcastAuth.test.ts +0 -134
  225. package/src/notifications/registerBroadcastAuth.ts +0 -100
  226. package/src/notifications/resolveSavedNotification.test.ts +0 -82
  227. package/src/notifications/resolveSavedNotification.ts +0 -59
  228. package/src/notifications/types.ts +0 -93
  229. package/src/orm/m2mAccessor.ts +0 -66
  230. package/src/orm/modelDefaults.test.ts +0 -633
  231. package/src/orm/modelDefaults.ts +0 -666
  232. package/src/pageData/breadcrumbs.ts +0 -288
  233. package/src/pageData/forms.ts +0 -578
  234. package/src/pageData/helpers.ts +0 -857
  235. package/src/pageData/misc.ts +0 -347
  236. package/src/pageData/navigation.ts +0 -842
  237. package/src/pageData/relationPages.ts +0 -1248
  238. package/src/pageData/relationTabs.ts +0 -286
  239. package/src/pageData/resourcePages.ts +0 -609
  240. package/src/pageData.test.ts +0 -1545
  241. package/src/pageData.ts +0 -341
  242. package/src/plugins/index.ts +0 -8
  243. package/src/plugins/themeEditor.test.ts +0 -36
  244. package/src/plugins/themeEditor.ts +0 -45
  245. package/src/react/AppShell.tsx +0 -251
  246. package/src/react/CollabExtensionFactoryRegistry.ts +0 -55
  247. package/src/react/CollabRoomContext.ts +0 -98
  248. package/src/react/CollabTextRendererRegistry.ts +0 -102
  249. package/src/react/CommandPalette.tsx +0 -375
  250. package/src/react/CurrentUserContext.tsx +0 -50
  251. package/src/react/CustomPageWrapperGate.tsx +0 -69
  252. package/src/react/CustomPageWrapperRegistry.ts +0 -45
  253. package/src/react/FieldFocusReporterRegistry.ts +0 -37
  254. package/src/react/FieldLabelSlotRegistry.ts +0 -30
  255. package/src/react/FieldPresenceRegistry.ts +0 -46
  256. package/src/react/FormCollabBindingRegistry.ts +0 -242
  257. package/src/react/FormStateContext.tsx +0 -591
  258. package/src/react/HeadHooks.tsx +0 -126
  259. package/src/react/MarkdownEditorRegistry.test.ts +0 -38
  260. package/src/react/MarkdownEditorRegistry.ts +0 -107
  261. package/src/react/NotificationActionStrip.tsx +0 -263
  262. package/src/react/NotificationBell.tsx +0 -426
  263. package/src/react/PendingSuggestionApplierRegistry.test.ts +0 -97
  264. package/src/react/PendingSuggestionApplierRegistry.ts +0 -98
  265. package/src/react/PendingSuggestionOverlayRegistry.ts +0 -54
  266. package/src/react/PendingSuggestionsContext.tsx +0 -172
  267. package/src/react/RecordWrapperGate.tsx +0 -58
  268. package/src/react/RecordWrapperRegistry.ts +0 -39
  269. package/src/react/RenderHookSlot.tsx +0 -32
  270. package/src/react/RightSidebar.tsx +0 -257
  271. package/src/react/RightSidebarContext.tsx +0 -234
  272. package/src/react/RightSidebarTrigger.tsx +0 -53
  273. package/src/react/RowCoordsContext.tsx +0 -23
  274. package/src/react/SchemaRenderer.tsx +0 -549
  275. package/src/react/SearchTrigger.tsx +0 -46
  276. package/src/react/ThemeProvider.tsx +0 -93
  277. package/src/react/ThemeSettingsPage.tsx +0 -579
  278. package/src/react/ThemeToggle.tsx +0 -20
  279. package/src/react/Toaster.tsx +0 -158
  280. package/src/react/UserMenu.tsx +0 -196
  281. package/src/react/WidgetDataContext.tsx +0 -157
  282. package/src/react/cells/EditableCell.tsx +0 -389
  283. package/src/react/component-slots.test.ts +0 -103
  284. package/src/react/component-slots.ts +0 -116
  285. package/src/react/fieldJsHandler.test.ts +0 -166
  286. package/src/react/fieldJsHandler.ts +0 -79
  287. package/src/react/fields/BuilderInput.tsx +0 -1078
  288. package/src/react/fields/CheckboxInput.tsx +0 -39
  289. package/src/react/fields/CheckboxListInput.tsx +0 -102
  290. package/src/react/fields/ColorInput.tsx +0 -71
  291. package/src/react/fields/DateFieldInput.tsx +0 -70
  292. package/src/react/fields/DateTimeInput.tsx +0 -62
  293. package/src/react/fields/FieldShell.tsx +0 -348
  294. package/src/react/fields/FileUploadInput.tsx +0 -639
  295. package/src/react/fields/HiddenInput.tsx +0 -17
  296. package/src/react/fields/KeyValueInput.tsx +0 -230
  297. package/src/react/fields/MarkdownInput.tsx +0 -560
  298. package/src/react/fields/RadioInput.tsx +0 -81
  299. package/src/react/fields/RepeaterInput.test.ts +0 -116
  300. package/src/react/fields/RepeaterInput.tsx +0 -1420
  301. package/src/react/fields/SelectFieldInput.tsx +0 -280
  302. package/src/react/fields/SliderInput.tsx +0 -81
  303. package/src/react/fields/TagsInput.tsx +0 -283
  304. package/src/react/fields/TextLikeInput.tsx +0 -256
  305. package/src/react/fields/ToggleButtonsInput.tsx +0 -60
  306. package/src/react/fields/ToggleFieldInput.tsx +0 -56
  307. package/src/react/fields/relationshipRenameDispatch.test.ts +0 -106
  308. package/src/react/fields/relationshipRenameDispatch.ts +0 -97
  309. package/src/react/fields/repeaterReconcile.test.ts +0 -114
  310. package/src/react/fields/repeaterReconcile.ts +0 -104
  311. package/src/react/fields/rowChromeButton.tsx +0 -336
  312. package/src/react/fields/rowState.ts +0 -106
  313. package/src/react/fields/syncRowGates.test.ts +0 -202
  314. package/src/react/fields/syncRowGates.ts +0 -66
  315. package/src/react/fields/textInputControls.tsx +0 -238
  316. package/src/react/fields/useRowReorderDnd.ts +0 -78
  317. package/src/react/formStateHelpers.test.ts +0 -508
  318. package/src/react/formStateHelpers.ts +0 -381
  319. package/src/react/hooks/use-mobile.ts +0 -19
  320. package/src/react/icon-context.tsx +0 -60
  321. package/src/react/index.ts +0 -195
  322. package/src/react/layouts/SidebarLayout.tsx +0 -250
  323. package/src/react/layouts/TopbarLayout.tsx +0 -258
  324. package/src/react/navigate.tsx +0 -37
  325. package/src/react/onProviderSynced.test.ts +0 -90
  326. package/src/react/parseRecordEditUrl.test.ts +0 -122
  327. package/src/react/parseRecordEditUrl.ts +0 -94
  328. package/src/react/persistedState.ts +0 -40
  329. package/src/react/registry.ts +0 -48
  330. package/src/react/right-panel-registry.tsx +0 -47
  331. package/src/react/schemaRenderer/AlertRenderer.tsx +0 -112
  332. package/src/react/schemaRenderer/EntryRenderer.tsx +0 -501
  333. package/src/react/schemaRenderer/SectionRenderer.tsx +0 -120
  334. package/src/react/schemaRenderer/SimpleElements.tsx +0 -306
  335. package/src/react/schemaRenderer/TabsRenderer.tsx +0 -62
  336. package/src/react/schemaRenderer/WizardRenderer.tsx +0 -338
  337. package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +0 -177
  338. package/src/react/schemaRenderer/action/ActionModalDialog.tsx +0 -273
  339. package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +0 -61
  340. package/src/react/schemaRenderer/action/HandlerActionButton.tsx +0 -43
  341. package/src/react/schemaRenderer/action/MethodActionButton.tsx +0 -64
  342. package/src/react/schemaRenderer/action/buttons.tsx +0 -99
  343. package/src/react/schemaRenderer/action/helpers.ts +0 -140
  344. package/src/react/schemaRenderer/action/renderAction.tsx +0 -245
  345. package/src/react/schemaRenderer/columnFormat.ts +0 -65
  346. package/src/react/schemaRenderer/constants.ts +0 -50
  347. package/src/react/schemaRenderer/form/FormRenderer.tsx +0 -274
  348. package/src/react/schemaRenderer/form/renderField.tsx +0 -511
  349. package/src/react/schemaRenderer/helpers.tsx +0 -81
  350. package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +0 -308
  351. package/src/react/schemaRenderer/table/TableRenderer.tsx +0 -123
  352. package/src/react/schemaRenderer/table/TableRendererBody.tsx +0 -974
  353. package/src/react/schemaRenderer/table/filters.tsx +0 -1233
  354. package/src/react/schemaRenderer/table/formatCell.tsx +0 -264
  355. package/src/react/schemaRenderer/table/links.tsx +0 -112
  356. package/src/react/schemaRenderer/table/renderRowActions.tsx +0 -52
  357. package/src/react/schemaRenderer/table/url.tsx +0 -143
  358. package/src/react/theme-preview/apply.ts +0 -99
  359. package/src/react/theme-preview/build-html.ts +0 -436
  360. package/src/react/ui/button.tsx +0 -51
  361. package/src/react/ui/calendar.tsx +0 -67
  362. package/src/react/ui/checkbox.tsx +0 -29
  363. package/src/react/ui/dialog.tsx +0 -108
  364. package/src/react/ui/dropdown-menu.tsx +0 -97
  365. package/src/react/ui/input.tsx +0 -20
  366. package/src/react/ui/label.tsx +0 -21
  367. package/src/react/ui/popover.tsx +0 -50
  368. package/src/react/ui/select.tsx +0 -169
  369. package/src/react/ui/separator.tsx +0 -25
  370. package/src/react/ui/sheet.tsx +0 -136
  371. package/src/react/ui/sidebar.tsx +0 -723
  372. package/src/react/ui/skeleton.tsx +0 -13
  373. package/src/react/ui/slider.tsx +0 -34
  374. package/src/react/ui/switch.tsx +0 -28
  375. package/src/react/ui/table.tsx +0 -105
  376. package/src/react/ui/tabs.tsx +0 -63
  377. package/src/react/ui/textarea.tsx +0 -18
  378. package/src/react/ui/tooltip.tsx +0 -64
  379. package/src/react/useCollabSeed.ts +0 -86
  380. package/src/react/useResizableWidth.ts +0 -139
  381. package/src/react/utils.ts +0 -6
  382. package/src/react/widgetRegistry.test.ts +0 -43
  383. package/src/react/widgetRegistry.ts +0 -50
  384. package/src/react/widgets/StatsOverviewRenderer.tsx +0 -232
  385. package/src/react/widgets/TableWidgetRenderer.tsx +0 -231
  386. package/src/react/widgets/ViewRenderer.tsx +0 -71
  387. package/src/relationManagerData.test.ts +0 -1595
  388. package/src/richtext/index.ts +0 -8
  389. package/src/richtext/registry.ts +0 -89
  390. package/src/routes/globals.ts +0 -148
  391. package/src/routes/guard.test.ts +0 -325
  392. package/src/routes/helpers.ts +0 -700
  393. package/src/routes/pages.ts +0 -175
  394. package/src/routes/panel.ts +0 -204
  395. package/src/routes/relations.ts +0 -1227
  396. package/src/routes/resources.ts +0 -781
  397. package/src/routes/theme.ts +0 -91
  398. package/src/routes-nested-relations.test.ts +0 -676
  399. package/src/routes-relations.test.ts +0 -972
  400. package/src/routes.test.ts +0 -2027
  401. package/src/routes.ts +0 -303
  402. package/src/schema/Alert.test.ts +0 -109
  403. package/src/schema/Alert.ts +0 -131
  404. package/src/schema/Block.ts +0 -169
  405. package/src/schema/Breadcrumbs.ts +0 -40
  406. package/src/schema/Card.ts +0 -35
  407. package/src/schema/Divider.ts +0 -20
  408. package/src/schema/Element.ts +0 -219
  409. package/src/schema/EmptyState.test.ts +0 -37
  410. package/src/schema/EmptyState.ts +0 -63
  411. package/src/schema/Fieldset.ts +0 -43
  412. package/src/schema/Grid.ts +0 -43
  413. package/src/schema/Group.ts +0 -30
  414. package/src/schema/Heading.ts +0 -39
  415. package/src/schema/Html.ts +0 -67
  416. package/src/schema/Icon.ts +0 -54
  417. package/src/schema/Image.ts +0 -57
  418. package/src/schema/LinkTag.ts +0 -41
  419. package/src/schema/Markdown.ts +0 -85
  420. package/src/schema/MetaTag.ts +0 -41
  421. package/src/schema/RelationTabs.ts +0 -71
  422. package/src/schema/ScriptTag.ts +0 -55
  423. package/src/schema/Section.ts +0 -160
  424. package/src/schema/ServerDataElement.test.ts +0 -140
  425. package/src/schema/ServerDataElement.ts +0 -156
  426. package/src/schema/SlotComponent.test.ts +0 -77
  427. package/src/schema/SlotComponent.ts +0 -71
  428. package/src/schema/Split.ts +0 -50
  429. package/src/schema/Stat.test.ts +0 -118
  430. package/src/schema/Stat.ts +0 -154
  431. package/src/schema/StatsOverview.test.ts +0 -141
  432. package/src/schema/StatsOverview.ts +0 -119
  433. package/src/schema/StyleTag.ts +0 -35
  434. package/src/schema/TableWidget.test.ts +0 -297
  435. package/src/schema/TableWidget.ts +0 -289
  436. package/src/schema/Tabs.ts +0 -79
  437. package/src/schema/Text.ts +0 -58
  438. package/src/schema/UnorderedList.ts +0 -49
  439. package/src/schema/View.test.ts +0 -111
  440. package/src/schema/View.ts +0 -127
  441. package/src/schema/Wizard.ts +0 -220
  442. package/src/schema/containers.test.ts +0 -564
  443. package/src/schema/headTags.test.ts +0 -134
  444. package/src/schema/index.ts +0 -40
  445. package/src/schema/primes.test.ts +0 -269
  446. package/src/schema/resolveSchema.test.ts +0 -379
  447. package/src/schema/resolveSchema.ts +0 -917
  448. package/src/schema/sanitize.ts +0 -58
  449. package/src/search.test.ts +0 -446
  450. package/src/search.ts +0 -178
  451. package/src/sessionFilters.test.ts +0 -375
  452. package/src/sessionFilters.ts +0 -143
  453. package/src/slot-components/index.ts +0 -10
  454. package/src/slot-components/registry.ts +0 -56
  455. package/src/styles/file-upload.css +0 -13
  456. package/src/summarizers/Summarizer.test.ts +0 -84
  457. package/src/summarizers/Summarizer.ts +0 -123
  458. package/src/summarizers/index.ts +0 -11
  459. package/src/theme/base-colors.ts +0 -68
  460. package/src/theme/chart-colors.ts +0 -50
  461. package/src/theme/colors.ts +0 -447
  462. package/src/theme/generate-css.test.ts +0 -139
  463. package/src/theme/generate-css.ts +0 -44
  464. package/src/theme/generate-scale.test.ts +0 -106
  465. package/src/theme/generate-scale.ts +0 -97
  466. package/src/theme/icon-map.ts +0 -42
  467. package/src/theme/index.ts +0 -34
  468. package/src/theme/migrate.test.ts +0 -178
  469. package/src/theme/migrate.ts +0 -81
  470. package/src/theme/presets.ts +0 -135
  471. package/src/theme/radius.ts +0 -18
  472. package/src/theme/resolve.test.ts +0 -238
  473. package/src/theme/resolve.ts +0 -96
  474. package/src/theme/spacing.ts +0 -18
  475. package/src/theme/storage.test.ts +0 -126
  476. package/src/theme/storage.ts +0 -106
  477. package/src/theme/theme-colors.ts +0 -88
  478. package/src/theme/types.ts +0 -125
  479. package/src/uploads/UploadAdapter.ts +0 -35
  480. package/src/uploads/index.ts +0 -2
  481. package/src/uploads/localUpload.test.ts +0 -70
  482. package/src/uploads/localUpload.ts +0 -84
  483. package/src/validation/Validator.ts +0 -49
  484. package/src/validation/index.ts +0 -28
  485. package/src/validation/rules.ts +0 -78
  486. package/src/validation/runValidators.ts +0 -435
  487. package/src/validation/uniqueValidator.test.ts +0 -196
  488. package/src/validation/uniqueValidator.ts +0 -133
  489. package/src/validation/validators.test.ts +0 -268
  490. package/src/vite.test.ts +0 -184
  491. package/src/vite.ts +0 -787
  492. package/src/widgets/index.ts +0 -10
  493. package/src/widgets/registry.ts +0 -45
  494. package/src/widgets.test.ts +0 -592
  495. package/tsconfig.build.json +0 -11
  496. package/tsconfig.json +0 -4
  497. package/tsconfig.test.json +0 -10
  498. package/views/react/Dashboard.tsx +0 -27
  499. package/views/react/Resources/Form.tsx +0 -102
  500. 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
- }