@pilotiq/pilotiq 0.24.1 → 0.24.3

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