@pilotiq/pilotiq 0.24.1 → 0.24.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (480) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/boost/guidelines.md +566 -0
  3. package/boost/skills/pilotiq-fields/SKILL.md +47 -0
  4. package/boost/skills/pilotiq-fields/rules/field-catalog.md +288 -0
  5. package/boost/skills/pilotiq-fields/rules/reactive-fields.md +199 -0
  6. package/boost/skills/pilotiq-fields/rules/validation.md +198 -0
  7. package/boost/skills/pilotiq-relations/SKILL.md +47 -0
  8. package/boost/skills/pilotiq-relations/rules/relation-managers.md +256 -0
  9. package/boost/skills/pilotiq-relations/rules/repeater-relationship.md +177 -0
  10. package/boost/skills/pilotiq-resource/SKILL.md +61 -0
  11. package/boost/skills/pilotiq-resource/rules/authorization.md +242 -0
  12. package/boost/skills/pilotiq-resource/rules/defining-resources.md +228 -0
  13. package/boost/skills/pilotiq-resource/rules/page-overrides.md +296 -0
  14. package/package.json +6 -1
  15. package/.turbo/turbo-build.log +0 -8
  16. package/CLAUDE.md +0 -265
  17. package/src/Cluster.test.ts +0 -283
  18. package/src/Cluster.ts +0 -83
  19. package/src/Column.test.ts +0 -199
  20. package/src/Column.ts +0 -710
  21. package/src/Global.test.ts +0 -367
  22. package/src/Global.ts +0 -169
  23. package/src/Page.test.ts +0 -114
  24. package/src/Page.ts +0 -208
  25. package/src/Pilotiq.perf.test.ts +0 -252
  26. package/src/Pilotiq.test.ts +0 -129
  27. package/src/Pilotiq.ts +0 -1158
  28. package/src/PilotiqRegistry.ts +0 -36
  29. package/src/PilotiqServiceProvider.ts +0 -121
  30. package/src/RelationManager.test.ts +0 -400
  31. package/src/RelationManager.ts +0 -527
  32. package/src/RenderHook.test.ts +0 -252
  33. package/src/RenderHook.ts +0 -242
  34. package/src/Resource.test.ts +0 -284
  35. package/src/Resource.ts +0 -526
  36. package/src/RightPanel.test.ts +0 -202
  37. package/src/RightPanel.ts +0 -132
  38. package/src/Tab.test.ts +0 -91
  39. package/src/Tab.ts +0 -156
  40. package/src/UserMenuItem.ts +0 -145
  41. package/src/actions/Action.test.ts +0 -2526
  42. package/src/actions/Action.ts +0 -1515
  43. package/src/actions/ActionGroup.test.ts +0 -112
  44. package/src/actions/ActionGroup.ts +0 -173
  45. package/src/actions/attachFactory.ts +0 -172
  46. package/src/actions/bulkFactories.ts +0 -168
  47. package/src/actions/crudFactories.ts +0 -220
  48. package/src/actions/exportFactory.ts +0 -225
  49. package/src/actions/factoryHelpers.ts +0 -177
  50. package/src/actions/importFactory.ts +0 -243
  51. package/src/actions/index.ts +0 -17
  52. package/src/actions/m2mFactories.ts +0 -193
  53. package/src/actions/relationFactories.ts +0 -372
  54. package/src/applyPageHooks.test.ts +0 -463
  55. package/src/applyPageHooks.ts +0 -330
  56. package/src/authorization.test.ts +0 -483
  57. package/src/breadcrumbs.test.ts +0 -238
  58. package/src/cells/coerce.test.ts +0 -85
  59. package/src/cells/coerce.ts +0 -84
  60. package/src/clusterPaths.ts +0 -35
  61. package/src/columns/BadgeColumn.test.ts +0 -54
  62. package/src/columns/BadgeColumn.ts +0 -32
  63. package/src/columns/BooleanColumn.test.ts +0 -41
  64. package/src/columns/BooleanColumn.ts +0 -18
  65. package/src/columns/ColorColumn.test.ts +0 -37
  66. package/src/columns/ColorColumn.ts +0 -38
  67. package/src/columns/IconColumn.test.ts +0 -54
  68. package/src/columns/IconColumn.ts +0 -37
  69. package/src/columns/ImageColumn.test.ts +0 -41
  70. package/src/columns/ImageColumn.ts +0 -28
  71. package/src/columns/SelectColumn.ts +0 -98
  72. package/src/columns/TextColumn.test.ts +0 -190
  73. package/src/columns/TextColumn.ts +0 -20
  74. package/src/columns/TextInputColumn.ts +0 -68
  75. package/src/columns/ToggleColumn.ts +0 -46
  76. package/src/columns/editableColumns.test.ts +0 -238
  77. package/src/columns/index.ts +0 -9
  78. package/src/defaultGlobalPages.ts +0 -95
  79. package/src/defaultPages.test.ts +0 -634
  80. package/src/defaultPages.ts +0 -617
  81. package/src/defaultViewPage.test.ts +0 -147
  82. package/src/elements/Form.test.ts +0 -223
  83. package/src/elements/Form.ts +0 -416
  84. package/src/elements/ListTabs.ts +0 -28
  85. package/src/elements/Table.test.ts +0 -422
  86. package/src/elements/Table.ts +0 -850
  87. package/src/elements/TableGroup.test.ts +0 -260
  88. package/src/elements/TableGroup.ts +0 -334
  89. package/src/elements/dispatchAction.test.ts +0 -463
  90. package/src/elements/dispatchAction.ts +0 -355
  91. package/src/elements/dispatchForm.test.ts +0 -477
  92. package/src/elements/dispatchForm.ts +0 -1993
  93. package/src/elements/dispatchTable.test.ts +0 -1514
  94. package/src/elements/dispatchTable.ts +0 -745
  95. package/src/elements/index.ts +0 -21
  96. package/src/entries/BadgeEntry.ts +0 -39
  97. package/src/entries/CodeEntry.test.ts +0 -40
  98. package/src/entries/CodeEntry.ts +0 -52
  99. package/src/entries/ColorEntry.ts +0 -63
  100. package/src/entries/ComponentEntry.test.ts +0 -173
  101. package/src/entries/ComponentEntry.ts +0 -95
  102. package/src/entries/Entry.ts +0 -304
  103. package/src/entries/IconEntry.ts +0 -49
  104. package/src/entries/ImageEntry.ts +0 -61
  105. package/src/entries/KeyValueEntry.ts +0 -47
  106. package/src/entries/RepeatableEntry.test.ts +0 -239
  107. package/src/entries/RepeatableEntry.ts +0 -173
  108. package/src/entries/TextEntry.test.ts +0 -394
  109. package/src/entries/TextEntry.ts +0 -60
  110. package/src/entries/index.ts +0 -12
  111. package/src/entries/leaves.test.ts +0 -306
  112. package/src/entries/registry.ts +0 -54
  113. package/src/fields/BuilderField.test.ts +0 -1188
  114. package/src/fields/BuilderField.ts +0 -605
  115. package/src/fields/BuilderRelationship.test.ts +0 -811
  116. package/src/fields/CheckboxField.test.ts +0 -44
  117. package/src/fields/CheckboxField.ts +0 -27
  118. package/src/fields/CheckboxListField.test.ts +0 -99
  119. package/src/fields/CheckboxListField.ts +0 -66
  120. package/src/fields/ColorPickerField.test.ts +0 -33
  121. package/src/fields/ColorPickerField.ts +0 -25
  122. package/src/fields/DateField.ts +0 -54
  123. package/src/fields/DateTimeField.test.ts +0 -55
  124. package/src/fields/EmailField.ts +0 -16
  125. package/src/fields/Field.test.ts +0 -654
  126. package/src/fields/Field.ts +0 -817
  127. package/src/fields/FileUploadField.test.ts +0 -143
  128. package/src/fields/FileUploadField.ts +0 -159
  129. package/src/fields/HiddenField.test.ts +0 -27
  130. package/src/fields/HiddenField.ts +0 -28
  131. package/src/fields/KeyValueField.test.ts +0 -105
  132. package/src/fields/KeyValueField.ts +0 -55
  133. package/src/fields/MarkdownField.test.ts +0 -167
  134. package/src/fields/MarkdownField.ts +0 -162
  135. package/src/fields/NumberField.ts +0 -33
  136. package/src/fields/RadioField.test.ts +0 -94
  137. package/src/fields/RadioField.ts +0 -67
  138. package/src/fields/RepeaterField.test.ts +0 -1806
  139. package/src/fields/RepeaterField.ts +0 -939
  140. package/src/fields/RepeaterRelationship.test.ts +0 -1923
  141. package/src/fields/RepeaterSimple.test.ts +0 -248
  142. package/src/fields/RowButton.test.ts +0 -219
  143. package/src/fields/RowButton.ts +0 -135
  144. package/src/fields/SelectField.test.ts +0 -192
  145. package/src/fields/SelectField.ts +0 -235
  146. package/src/fields/SliderField.test.ts +0 -50
  147. package/src/fields/SliderField.ts +0 -53
  148. package/src/fields/SlugField.ts +0 -24
  149. package/src/fields/TagsInputField.test.ts +0 -154
  150. package/src/fields/TagsInputField.ts +0 -133
  151. package/src/fields/TextField.test.ts +0 -213
  152. package/src/fields/TextField.ts +0 -177
  153. package/src/fields/TextareaField.test.ts +0 -58
  154. package/src/fields/TextareaField.ts +0 -59
  155. package/src/fields/ToggleButtonsField.test.ts +0 -106
  156. package/src/fields/ToggleButtonsField.ts +0 -59
  157. package/src/fields/ToggleField.ts +0 -16
  158. package/src/fields/disableOptionsWhenSelectedInSiblingRepeaterItems.test.ts +0 -319
  159. package/src/fields/optionsResolver.ts +0 -95
  160. package/src/fields/resolveField.ts +0 -28
  161. package/src/filters/BooleanFilter.ts +0 -35
  162. package/src/filters/DateRangeFilter.test.ts +0 -194
  163. package/src/filters/DateRangeFilter.ts +0 -148
  164. package/src/filters/Filter.test.ts +0 -268
  165. package/src/filters/Filter.ts +0 -184
  166. package/src/filters/FormFilter.test.ts +0 -238
  167. package/src/filters/FormFilter.ts +0 -215
  168. package/src/filters/MultiSelectFilter.test.ts +0 -119
  169. package/src/filters/MultiSelectFilter.ts +0 -78
  170. package/src/filters/QueryBuilderFilter.test.ts +0 -662
  171. package/src/filters/QueryBuilderFilter.ts +0 -398
  172. package/src/filters/SelectFilter.ts +0 -46
  173. package/src/filters/TernaryFilter.test.ts +0 -160
  174. package/src/filters/TernaryFilter.ts +0 -72
  175. package/src/filters/TrashedFilter.test.ts +0 -149
  176. package/src/filters/TrashedFilter.ts +0 -55
  177. package/src/filters/queryBuilder/BooleanConstraint.ts +0 -31
  178. package/src/filters/queryBuilder/Constraint.ts +0 -115
  179. package/src/filters/queryBuilder/DateConstraint.ts +0 -69
  180. package/src/filters/queryBuilder/NumberConstraint.ts +0 -66
  181. package/src/filters/queryBuilder/SelectConstraint.ts +0 -72
  182. package/src/filters/queryBuilder/TextConstraint.ts +0 -64
  183. package/src/filters/queryBuilder/index.ts +0 -12
  184. package/src/icons/index.ts +0 -2
  185. package/src/icons/lucide.ts +0 -204
  186. package/src/icons/registry.test.ts +0 -56
  187. package/src/icons/registry.ts +0 -41
  188. package/src/icons/types.ts +0 -47
  189. package/src/index.ts +0 -525
  190. package/src/io/csv.test.ts +0 -142
  191. package/src/io/csv.ts +0 -170
  192. package/src/nestedRelationManagerData.test.ts +0 -547
  193. package/src/notifications/Notification.test.ts +0 -210
  194. package/src/notifications/Notification.ts +0 -354
  195. package/src/notifications/broadcast.test.ts +0 -110
  196. package/src/notifications/broadcast.ts +0 -95
  197. package/src/notifications/database.test.ts +0 -383
  198. package/src/notifications/database.ts +0 -398
  199. package/src/notifications/databaseNotifications.test.ts +0 -187
  200. package/src/notifications/dispatchNotificationAction.test.ts +0 -341
  201. package/src/notifications/dispatchNotificationAction.ts +0 -142
  202. package/src/notifications/flash.test.ts +0 -89
  203. package/src/notifications/flash.ts +0 -71
  204. package/src/notifications/index.ts +0 -45
  205. package/src/notifications/registerBroadcastAuth.test.ts +0 -134
  206. package/src/notifications/registerBroadcastAuth.ts +0 -100
  207. package/src/notifications/resolveSavedNotification.test.ts +0 -82
  208. package/src/notifications/resolveSavedNotification.ts +0 -59
  209. package/src/notifications/types.ts +0 -93
  210. package/src/orm/m2mAccessor.ts +0 -66
  211. package/src/orm/modelDefaults.test.ts +0 -633
  212. package/src/orm/modelDefaults.ts +0 -666
  213. package/src/pageData/breadcrumbs.ts +0 -288
  214. package/src/pageData/forms.ts +0 -578
  215. package/src/pageData/helpers.ts +0 -857
  216. package/src/pageData/misc.ts +0 -347
  217. package/src/pageData/navigation.ts +0 -842
  218. package/src/pageData/relationPages.ts +0 -1248
  219. package/src/pageData/relationTabs.ts +0 -286
  220. package/src/pageData/resourcePages.ts +0 -609
  221. package/src/pageData.test.ts +0 -1545
  222. package/src/pageData.ts +0 -341
  223. package/src/plugins/index.ts +0 -8
  224. package/src/plugins/themeEditor.test.ts +0 -36
  225. package/src/plugins/themeEditor.ts +0 -45
  226. package/src/react/AppShell.tsx +0 -251
  227. package/src/react/CollabExtensionFactoryRegistry.ts +0 -55
  228. package/src/react/CollabRoomContext.ts +0 -98
  229. package/src/react/CollabTextRendererRegistry.ts +0 -102
  230. package/src/react/CommandPalette.tsx +0 -375
  231. package/src/react/CurrentUserContext.tsx +0 -50
  232. package/src/react/CustomPageWrapperGate.tsx +0 -69
  233. package/src/react/CustomPageWrapperRegistry.ts +0 -45
  234. package/src/react/FieldFocusReporterRegistry.ts +0 -37
  235. package/src/react/FieldLabelSlotRegistry.ts +0 -30
  236. package/src/react/FieldPresenceRegistry.ts +0 -46
  237. package/src/react/FormCollabBindingRegistry.ts +0 -242
  238. package/src/react/FormStateContext.tsx +0 -591
  239. package/src/react/HeadHooks.tsx +0 -126
  240. package/src/react/MarkdownEditorRegistry.test.ts +0 -38
  241. package/src/react/MarkdownEditorRegistry.ts +0 -107
  242. package/src/react/NotificationActionStrip.tsx +0 -263
  243. package/src/react/NotificationBell.tsx +0 -426
  244. package/src/react/PendingSuggestionApplierRegistry.test.ts +0 -97
  245. package/src/react/PendingSuggestionApplierRegistry.ts +0 -98
  246. package/src/react/PendingSuggestionOverlayRegistry.ts +0 -54
  247. package/src/react/PendingSuggestionsContext.tsx +0 -172
  248. package/src/react/RecordWrapperGate.tsx +0 -58
  249. package/src/react/RecordWrapperRegistry.ts +0 -39
  250. package/src/react/RenderHookSlot.tsx +0 -32
  251. package/src/react/RightSidebar.tsx +0 -257
  252. package/src/react/RightSidebarContext.tsx +0 -234
  253. package/src/react/RightSidebarTrigger.tsx +0 -53
  254. package/src/react/RowCoordsContext.tsx +0 -23
  255. package/src/react/SchemaRenderer.tsx +0 -549
  256. package/src/react/SearchTrigger.tsx +0 -46
  257. package/src/react/ThemeProvider.tsx +0 -93
  258. package/src/react/ThemeSettingsPage.tsx +0 -579
  259. package/src/react/ThemeToggle.tsx +0 -20
  260. package/src/react/Toaster.tsx +0 -158
  261. package/src/react/UserMenu.tsx +0 -196
  262. package/src/react/WidgetDataContext.tsx +0 -157
  263. package/src/react/cells/EditableCell.tsx +0 -389
  264. package/src/react/component-slots.test.ts +0 -103
  265. package/src/react/component-slots.ts +0 -116
  266. package/src/react/fieldJsHandler.test.ts +0 -166
  267. package/src/react/fieldJsHandler.ts +0 -79
  268. package/src/react/fields/BuilderInput.tsx +0 -1078
  269. package/src/react/fields/CheckboxInput.tsx +0 -39
  270. package/src/react/fields/CheckboxListInput.tsx +0 -102
  271. package/src/react/fields/ColorInput.tsx +0 -71
  272. package/src/react/fields/DateFieldInput.tsx +0 -70
  273. package/src/react/fields/DateTimeInput.tsx +0 -62
  274. package/src/react/fields/FieldShell.tsx +0 -348
  275. package/src/react/fields/FileUploadInput.tsx +0 -639
  276. package/src/react/fields/HiddenInput.tsx +0 -17
  277. package/src/react/fields/KeyValueInput.tsx +0 -230
  278. package/src/react/fields/MarkdownInput.tsx +0 -560
  279. package/src/react/fields/RadioInput.tsx +0 -81
  280. package/src/react/fields/RepeaterInput.test.ts +0 -116
  281. package/src/react/fields/RepeaterInput.tsx +0 -1420
  282. package/src/react/fields/SelectFieldInput.tsx +0 -280
  283. package/src/react/fields/SliderInput.tsx +0 -81
  284. package/src/react/fields/TagsInput.tsx +0 -283
  285. package/src/react/fields/TextLikeInput.tsx +0 -256
  286. package/src/react/fields/ToggleButtonsInput.tsx +0 -60
  287. package/src/react/fields/ToggleFieldInput.tsx +0 -56
  288. package/src/react/fields/relationshipRenameDispatch.test.ts +0 -106
  289. package/src/react/fields/relationshipRenameDispatch.ts +0 -97
  290. package/src/react/fields/repeaterReconcile.test.ts +0 -114
  291. package/src/react/fields/repeaterReconcile.ts +0 -104
  292. package/src/react/fields/rowChromeButton.tsx +0 -336
  293. package/src/react/fields/rowState.ts +0 -106
  294. package/src/react/fields/syncRowGates.test.ts +0 -202
  295. package/src/react/fields/syncRowGates.ts +0 -66
  296. package/src/react/fields/textInputControls.tsx +0 -238
  297. package/src/react/fields/useRowReorderDnd.ts +0 -78
  298. package/src/react/formStateHelpers.test.ts +0 -508
  299. package/src/react/formStateHelpers.ts +0 -381
  300. package/src/react/hooks/use-mobile.ts +0 -19
  301. package/src/react/icon-context.tsx +0 -60
  302. package/src/react/index.ts +0 -194
  303. package/src/react/layouts/SidebarLayout.tsx +0 -250
  304. package/src/react/layouts/TopbarLayout.tsx +0 -258
  305. package/src/react/navigate.tsx +0 -37
  306. package/src/react/onProviderSynced.test.ts +0 -90
  307. package/src/react/parseRecordEditUrl.test.ts +0 -122
  308. package/src/react/parseRecordEditUrl.ts +0 -94
  309. package/src/react/persistedState.ts +0 -40
  310. package/src/react/registry.ts +0 -48
  311. package/src/react/right-panel-registry.tsx +0 -47
  312. package/src/react/schemaRenderer/AlertRenderer.tsx +0 -112
  313. package/src/react/schemaRenderer/EntryRenderer.tsx +0 -501
  314. package/src/react/schemaRenderer/SectionRenderer.tsx +0 -120
  315. package/src/react/schemaRenderer/SimpleElements.tsx +0 -306
  316. package/src/react/schemaRenderer/TabsRenderer.tsx +0 -62
  317. package/src/react/schemaRenderer/WizardRenderer.tsx +0 -338
  318. package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +0 -177
  319. package/src/react/schemaRenderer/action/ActionModalDialog.tsx +0 -273
  320. package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +0 -61
  321. package/src/react/schemaRenderer/action/HandlerActionButton.tsx +0 -43
  322. package/src/react/schemaRenderer/action/MethodActionButton.tsx +0 -64
  323. package/src/react/schemaRenderer/action/buttons.tsx +0 -99
  324. package/src/react/schemaRenderer/action/helpers.ts +0 -140
  325. package/src/react/schemaRenderer/action/renderAction.tsx +0 -245
  326. package/src/react/schemaRenderer/columnFormat.ts +0 -65
  327. package/src/react/schemaRenderer/constants.ts +0 -50
  328. package/src/react/schemaRenderer/form/FormRenderer.tsx +0 -274
  329. package/src/react/schemaRenderer/form/renderField.tsx +0 -511
  330. package/src/react/schemaRenderer/helpers.tsx +0 -81
  331. package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +0 -308
  332. package/src/react/schemaRenderer/table/TableRenderer.tsx +0 -123
  333. package/src/react/schemaRenderer/table/TableRendererBody.tsx +0 -974
  334. package/src/react/schemaRenderer/table/filters.tsx +0 -1233
  335. package/src/react/schemaRenderer/table/formatCell.tsx +0 -264
  336. package/src/react/schemaRenderer/table/links.tsx +0 -112
  337. package/src/react/schemaRenderer/table/renderRowActions.tsx +0 -52
  338. package/src/react/schemaRenderer/table/url.tsx +0 -143
  339. package/src/react/theme-preview/apply.ts +0 -99
  340. package/src/react/theme-preview/build-html.ts +0 -436
  341. package/src/react/ui/button.tsx +0 -51
  342. package/src/react/ui/calendar.tsx +0 -67
  343. package/src/react/ui/checkbox.tsx +0 -29
  344. package/src/react/ui/dialog.tsx +0 -108
  345. package/src/react/ui/dropdown-menu.tsx +0 -97
  346. package/src/react/ui/input.tsx +0 -20
  347. package/src/react/ui/label.tsx +0 -21
  348. package/src/react/ui/popover.tsx +0 -50
  349. package/src/react/ui/select.tsx +0 -169
  350. package/src/react/ui/separator.tsx +0 -25
  351. package/src/react/ui/sheet.tsx +0 -136
  352. package/src/react/ui/sidebar.tsx +0 -723
  353. package/src/react/ui/skeleton.tsx +0 -13
  354. package/src/react/ui/slider.tsx +0 -34
  355. package/src/react/ui/switch.tsx +0 -28
  356. package/src/react/ui/table.tsx +0 -105
  357. package/src/react/ui/tabs.tsx +0 -63
  358. package/src/react/ui/textarea.tsx +0 -18
  359. package/src/react/ui/tooltip.tsx +0 -64
  360. package/src/react/useResizableWidth.ts +0 -139
  361. package/src/react/utils.ts +0 -6
  362. package/src/react/widgetRegistry.test.ts +0 -43
  363. package/src/react/widgetRegistry.ts +0 -50
  364. package/src/react/widgets/StatsOverviewRenderer.tsx +0 -232
  365. package/src/react/widgets/TableWidgetRenderer.tsx +0 -231
  366. package/src/react/widgets/ViewRenderer.tsx +0 -71
  367. package/src/relationManagerData.test.ts +0 -1595
  368. package/src/richtext/index.ts +0 -8
  369. package/src/richtext/registry.ts +0 -89
  370. package/src/routes/globals.ts +0 -148
  371. package/src/routes/guard.test.ts +0 -325
  372. package/src/routes/helpers.ts +0 -704
  373. package/src/routes/pages.ts +0 -175
  374. package/src/routes/panel.ts +0 -204
  375. package/src/routes/relations.ts +0 -1243
  376. package/src/routes/resources.ts +0 -781
  377. package/src/routes/theme.ts +0 -91
  378. package/src/routes-nested-relations.test.ts +0 -676
  379. package/src/routes-relations.test.ts +0 -972
  380. package/src/routes.test.ts +0 -2027
  381. package/src/routes.ts +0 -303
  382. package/src/schema/Alert.test.ts +0 -109
  383. package/src/schema/Alert.ts +0 -131
  384. package/src/schema/Block.ts +0 -169
  385. package/src/schema/Breadcrumbs.ts +0 -40
  386. package/src/schema/Card.ts +0 -35
  387. package/src/schema/Divider.ts +0 -20
  388. package/src/schema/Element.ts +0 -219
  389. package/src/schema/EmptyState.test.ts +0 -37
  390. package/src/schema/EmptyState.ts +0 -63
  391. package/src/schema/Fieldset.ts +0 -43
  392. package/src/schema/Grid.ts +0 -43
  393. package/src/schema/Group.ts +0 -30
  394. package/src/schema/Heading.ts +0 -39
  395. package/src/schema/Html.ts +0 -67
  396. package/src/schema/Icon.ts +0 -54
  397. package/src/schema/Image.ts +0 -57
  398. package/src/schema/LinkTag.ts +0 -41
  399. package/src/schema/Markdown.ts +0 -85
  400. package/src/schema/MetaTag.ts +0 -41
  401. package/src/schema/RelationTabs.ts +0 -71
  402. package/src/schema/ScriptTag.ts +0 -55
  403. package/src/schema/Section.ts +0 -160
  404. package/src/schema/ServerDataElement.test.ts +0 -140
  405. package/src/schema/ServerDataElement.ts +0 -156
  406. package/src/schema/SlotComponent.test.ts +0 -77
  407. package/src/schema/SlotComponent.ts +0 -71
  408. package/src/schema/Split.ts +0 -50
  409. package/src/schema/Stat.test.ts +0 -118
  410. package/src/schema/Stat.ts +0 -154
  411. package/src/schema/StatsOverview.test.ts +0 -141
  412. package/src/schema/StatsOverview.ts +0 -119
  413. package/src/schema/StyleTag.ts +0 -35
  414. package/src/schema/TableWidget.test.ts +0 -297
  415. package/src/schema/TableWidget.ts +0 -289
  416. package/src/schema/Tabs.ts +0 -79
  417. package/src/schema/Text.ts +0 -58
  418. package/src/schema/UnorderedList.ts +0 -49
  419. package/src/schema/View.test.ts +0 -111
  420. package/src/schema/View.ts +0 -127
  421. package/src/schema/Wizard.ts +0 -220
  422. package/src/schema/containers.test.ts +0 -564
  423. package/src/schema/headTags.test.ts +0 -134
  424. package/src/schema/index.ts +0 -40
  425. package/src/schema/primes.test.ts +0 -269
  426. package/src/schema/resolveSchema.test.ts +0 -379
  427. package/src/schema/resolveSchema.ts +0 -917
  428. package/src/schema/sanitize.ts +0 -58
  429. package/src/search.test.ts +0 -446
  430. package/src/search.ts +0 -178
  431. package/src/sessionFilters.test.ts +0 -375
  432. package/src/sessionFilters.ts +0 -143
  433. package/src/slot-components/index.ts +0 -10
  434. package/src/slot-components/registry.ts +0 -56
  435. package/src/styles/file-upload.css +0 -13
  436. package/src/summarizers/Summarizer.test.ts +0 -84
  437. package/src/summarizers/Summarizer.ts +0 -123
  438. package/src/summarizers/index.ts +0 -11
  439. package/src/theme/base-colors.ts +0 -68
  440. package/src/theme/chart-colors.ts +0 -50
  441. package/src/theme/colors.ts +0 -447
  442. package/src/theme/generate-css.test.ts +0 -139
  443. package/src/theme/generate-css.ts +0 -44
  444. package/src/theme/generate-scale.test.ts +0 -106
  445. package/src/theme/generate-scale.ts +0 -97
  446. package/src/theme/icon-map.ts +0 -42
  447. package/src/theme/index.ts +0 -34
  448. package/src/theme/migrate.test.ts +0 -178
  449. package/src/theme/migrate.ts +0 -81
  450. package/src/theme/presets.ts +0 -135
  451. package/src/theme/radius.ts +0 -18
  452. package/src/theme/resolve.test.ts +0 -238
  453. package/src/theme/resolve.ts +0 -96
  454. package/src/theme/spacing.ts +0 -18
  455. package/src/theme/storage.test.ts +0 -126
  456. package/src/theme/storage.ts +0 -106
  457. package/src/theme/theme-colors.ts +0 -88
  458. package/src/theme/types.ts +0 -125
  459. package/src/uploads/UploadAdapter.ts +0 -35
  460. package/src/uploads/index.ts +0 -2
  461. package/src/uploads/localUpload.test.ts +0 -70
  462. package/src/uploads/localUpload.ts +0 -84
  463. package/src/validation/Validator.ts +0 -49
  464. package/src/validation/index.ts +0 -28
  465. package/src/validation/rules.ts +0 -78
  466. package/src/validation/runValidators.ts +0 -435
  467. package/src/validation/uniqueValidator.test.ts +0 -196
  468. package/src/validation/uniqueValidator.ts +0 -133
  469. package/src/validation/validators.test.ts +0 -268
  470. package/src/vite.test.ts +0 -184
  471. package/src/vite.ts +0 -787
  472. package/src/widgets/index.ts +0 -10
  473. package/src/widgets/registry.ts +0 -45
  474. package/src/widgets.test.ts +0 -592
  475. package/tsconfig.build.json +0 -11
  476. package/tsconfig.json +0 -4
  477. package/tsconfig.test.json +0 -10
  478. package/views/react/Dashboard.tsx +0 -27
  479. package/views/react/Resources/Form.tsx +0 -102
  480. package/views/react/Resources/Index.tsx +0 -49
@@ -1,917 +0,0 @@
1
- import { Element, type ElementMeta, type LayoutContext } from './Element.js'
2
- import { Field } from '../fields/Field.js'
3
- import {
4
- RepeaterField,
5
- type RepeaterItemHiddenRule,
6
- type RepeaterItemCanRule,
7
- type RepeaterRowMeta,
8
- } from '../fields/RepeaterField.js'
9
- import {
10
- BuilderField,
11
- type BuilderRowMeta,
12
- } from '../fields/BuilderField.js'
13
- import type { BlockMeta } from './Block.js'
14
- import { Action, type ActionMeta, type ActionVisibilityContext } from '../actions/Action.js'
15
- import { ActionGroup } from '../actions/ActionGroup.js'
16
- import { Filter } from '../filters/Filter.js'
17
- import { isServerDataElement, stampServerDataMeta } from './ServerDataElement.js'
18
- import { Entry } from '../entries/Entry.js'
19
- import {
20
- RepeatableEntry,
21
- readRepeatableRowId,
22
- type RepeatableEntryRowMeta,
23
- } from '../entries/RepeatableEntry.js'
24
- import { Section } from './Section.js'
25
- import { Wizard, type WizardActionCustomizer } from './Wizard.js'
26
- import { TextField } from '../fields/TextField.js'
27
- import { Form } from '../elements/Form.js'
28
-
29
- export interface SchemaContext {
30
- user?: { name?: string; email?: string; [key: string]: unknown }
31
- [key: string]: unknown
32
- }
33
-
34
- /**
35
- * Render context — extends `SchemaContext` with the rendering mode and
36
- * optional record. Used by the field resolver to evaluate visibility flags
37
- * (`hideFromTable` / `hideFromEdit` / etc.) and condition callbacks
38
- * (`showWhen` / `hideWhen` / `disabledWhen`) against the current page.
39
- *
40
- * `mode` is unset on schema-only routes (custom Pages). Field visibility is
41
- * a no-op in that case.
42
- */
43
- export type RenderMode = 'table' | 'create' | 'edit' | 'view'
44
-
45
- export interface RenderContext extends SchemaContext {
46
- mode?: RenderMode
47
- record?: unknown
48
- /**
49
- * Current form values for the in-progress resolve cycle. Populated by
50
- * the partial-resolve endpoint (Plan #5) and by edit/create page
51
- * builders that prefill from a record. When present, `$get / $set`
52
- * are framework-bound so field condition callbacks and reactive
53
- * `options(fn)` handlers can read/write sibling values.
54
- */
55
- values?: Record<string, unknown>
56
- /** Read a sibling field's current value from the resolve cycle's values map. */
57
- $get?: (name: string) => unknown
58
- /** Mutate a sibling field's value during this resolve cycle. */
59
- $set?: (name: string, value: unknown) => void
60
- /**
61
- * Name of the field whose change triggered this resolve, if any.
62
- * Populated only by the partial-resolve endpoint; undefined on
63
- * initial GET render.
64
- */
65
- changed?: string
66
- /**
67
- * URL the FileUpload field should POST to. Stamped onto every
68
- * page-data ctx by the route handlers / page-data builders so the
69
- * field's `toMeta` doesn't need to know the panel base path. Single
70
- * panel-level URL — no per-field variation.
71
- */
72
- uploadUrl?: string
73
- /**
74
- * `true` when the panel has registered an `UploadAdapter` via
75
- * `Pilotiq.uploads({ adapter })`. Distinct from `uploadUrl` (which is
76
- * always stamped so `FileUpload` can show a clear error if missed) —
77
- * this flag tells fields whose upload integration is *optional* (e.g.
78
- * `MarkdownField`'s `attachFiles` toolbar button) whether to surface
79
- * the affordance at all.
80
- */
81
- hasUploadAdapter?: boolean
82
- /**
83
- * Plan #15 — per-widget filter value rode in on the polling-endpoint
84
- * body. Opaque to the framework: each widget's `getData(ctx)` decodes
85
- * `ctx.filter` however it wants (`'today' | 'week' | 'month'`, JSON
86
- * blob, …). Unset on the initial-render path; set on every polling
87
- * fetch when the user picked something from the per-chart filter
88
- * dropdown.
89
- */
90
- filter?: string
91
- /**
92
- * Plan #14 row-scoped sugar inside a Repeater. When the resolver is
93
- * walking a row's inner schema, `row.index` is the row's position and
94
- * `row.$get / row.$set` are bound to the row's local values map. The
95
- * top-level `ctx.$get / $set` still see the whole form (cross-row
96
- * reads via dotted paths like `items.0.title`); `row.$get(name)` is
97
- * just sugar for the common case of "read the same row's siblings".
98
- */
99
- row?: {
100
- index: number
101
- $get: (name: string) => unknown
102
- $set: (name: string, value: unknown) => void
103
- /**
104
- * Other rows' values maps, excluding the current row at `index`.
105
- * Stamped by `resolveRepeaterRows` (every sibling) and
106
- * `resolveBuilderRows` (only same-block-type siblings; each entry is
107
- * the row's `data` map). Read by
108
- * `disableOptionsWhenSelectedInSiblingRepeaterItems()` to mark
109
- * already-picked options as disabled.
110
- */
111
- siblings?: ReadonlyArray<Record<string, unknown>>
112
- /**
113
- * Present only when the row lives inside a `BuilderField` — the row's
114
- * block name. See `LayoutContext.row.blockType` for the rationale.
115
- */
116
- blockType?: string
117
- }
118
- /**
119
- * Cascading `inlineLabel` default set by an ancestor `Form` or
120
- * `Section` via `.inlineLabel(true)`. Read by `Field.buildMeta` when the
121
- * field hasn't called `inlineLabel()` itself; explicit field-level
122
- * setting always wins. A nested container's `.inlineLabel(false)`
123
- * overrides an outer container's `.inlineLabel(true)` for its subtree.
124
- */
125
- inlineLabelDefault?: boolean
126
- }
127
-
128
- export type SchemaDefinition =
129
- | Element[]
130
- | ((ctx: SchemaContext) => Element[] | Promise<Element[]>)
131
-
132
- /**
133
- * Plugin resolver — replaces the default toMeta+recurse behavior for a
134
- * given element type. Plugins receive the original element, the context,
135
- * and a `recurse` helper that resolves a child array. Useful when a
136
- * pro/feature plugin needs to inject computed data, augment meta, or
137
- * reshape children before serialization.
138
- *
139
- * If no resolver is registered for a type, the default (call `toMeta()`,
140
- * recurse into `getChildren()` if present, attach as `meta.children`) runs.
141
- */
142
- export type ElementResolver = (
143
- el: Element,
144
- ctx: SchemaContext,
145
- recurse: (children: Element[]) => Promise<ElementMeta[]>,
146
- ) => Promise<ElementMeta> | ElementMeta
147
-
148
- const registry = new Map<string, ElementResolver>()
149
-
150
- /** Register a custom resolver for a given element type. Plugins call this at boot. */
151
- export function registerResolver(type: string, fn: ElementResolver): void {
152
- registry.set(type, fn)
153
- }
154
-
155
- /** Internal — used by tests to reset state. Not part of the public API. */
156
- export function _resetResolverRegistry(): void {
157
- registry.clear()
158
- }
159
-
160
- /**
161
- * Resolve a schema definition into serializable metadata.
162
- *
163
- * Walks the Element tree, calling each element's `toMeta()` (or a registered
164
- * custom resolver). Children of container elements are resolved recursively
165
- * and attached as `meta.children`. Fields evaluate visibility flags + record
166
- * conditions against the `RenderContext`; hidden fields are dropped.
167
- *
168
- * Accepts a `SchemaContext` (or the richer `RenderContext`) — the latter is
169
- * required for Field visibility/condition evaluation to do anything useful.
170
- */
171
- export async function resolveSchema(
172
- definition: SchemaDefinition | undefined,
173
- ctx: RenderContext = {},
174
- ): Promise<ElementMeta[]> {
175
- if (!definition) return []
176
-
177
- const elements = typeof definition === 'function'
178
- ? await definition(ctx)
179
- : definition
180
-
181
- return resolveAll(elements, ctx)
182
- }
183
-
184
- async function resolveAll(elements: Element[], ctx: RenderContext): Promise<ElementMeta[]> {
185
- const results = await Promise.all(elements.map(el => resolveOne(el, ctx)))
186
- // Filter null entries from hidden fields.
187
- return results.filter((m): m is ElementMeta => m !== null)
188
- }
189
-
190
- async function resolveOne(el: Element, ctx: RenderContext): Promise<ElementMeta | null> {
191
- // Field visibility — drop the entire element if hidden in the current
192
- // render context. Done before custom resolvers so plugins can't accidentally
193
- // resurrect a hidden field.
194
- if (el instanceof Field && el.isHiddenIn(ctx)) {
195
- return null
196
- }
197
-
198
- // Action visibility — non-row placements evaluate against the page-level
199
- // context here; row-placement actions defer to per-row evaluation in
200
- // `loadTableRecords` (we always include them in the tree). Disabled
201
- // gets stamped on meta either way.
202
- if (el instanceof Action && el.hasVisibilityRules() && el.getPlacement() !== 'row') {
203
- const evalCtx: { record?: unknown; user?: unknown } = {}
204
- if (ctx.record !== undefined) evalCtx.record = ctx.record
205
- if (ctx.user !== undefined) evalCtx.user = ctx.user
206
- const { visible } = await el.evaluate(evalCtx)
207
- if (!visible) return null
208
- }
209
-
210
- // ActionGroup visibility — same shape as Action; the dropdown trigger
211
- // hides when the group rule resolves false. Also drop the group when
212
- // every child action is hidden (group with no usable items is dead UX).
213
- if (el instanceof ActionGroup) {
214
- if (el.hasVisibilityRules()) {
215
- const evalCtx: { record?: unknown; user?: unknown } = {}
216
- if (ctx.record !== undefined) evalCtx.record = ctx.record
217
- if (ctx.user !== undefined) evalCtx.user = ctx.user
218
- const { visible } = await el.evaluate(evalCtx)
219
- if (!visible) return null
220
- }
221
- }
222
-
223
- // Layout-level visibility (Plan #8). Field/Action/ActionGroup own
224
- // their visibility above; this catches every other Element with a
225
- // `.visible(...)` rule (Section, Card, Tabs, Tab, Grid, Split,
226
- // Wizard, Step, Group, Fieldset, Heading, Text, …).
227
- if (!(el instanceof Field) &&
228
- !(el instanceof Action) &&
229
- !(el instanceof ActionGroup) &&
230
- el.hasVisibilityRule()) {
231
- const layoutCtx = buildLayoutContext(ctx)
232
- const visible = await el.evaluateVisibility(layoutCtx)
233
- if (!visible) return null
234
- }
235
-
236
- const type = el.getType()
237
-
238
- const customResolver = registry.get(type)
239
- if (customResolver) {
240
- return customResolver(el, ctx, children => resolveAll(children, ctx))
241
- }
242
-
243
- // Default resolution: toMeta() + recurse children if container. Fields
244
- // get their ctx-aware overload so disabledWhen and reactive subclasses
245
- // (Plan #5) see the full RenderContext. Async toMeta is awaited —
246
- // SelectField with a resolver-style `options(fn)` may be async.
247
- // Filters receive ctx too so subclasses like FormFilter can resolve
248
- // their inner form schemas with the same render context (e.g. the
249
- // active user, for option resolvers inside a filter form). Entries
250
- // (Plan #16) need ctx.record to resolve their state value.
251
- const meta = await Promise.resolve(
252
- el instanceof Field || el instanceof Filter || el instanceof Entry
253
- ? el.toMeta(ctx)
254
- : el.toMeta(),
255
- ) as ElementMeta
256
- meta.type = type // ensure type is always set, even if toMeta forgot
257
-
258
- // Stamp the page-level disabled state on non-row Actions so the renderer
259
- // greys out the button. Row actions get per-row stamping in dispatchTable.
260
- if (el instanceof Action && el.hasVisibilityRules() && el.getPlacement() !== 'row') {
261
- const evalCtx: { record?: unknown; user?: unknown } = {}
262
- if (ctx.record !== undefined) evalCtx.record = ctx.record
263
- if (ctx.user !== undefined) evalCtx.user = ctx.user
264
- const { disabled } = await el.evaluate(evalCtx)
265
- if (disabled) meta['disabled'] = true
266
- }
267
-
268
- // Same for ActionGroup — stamp disabled when the group's rule says so.
269
- if (el instanceof ActionGroup && el.hasVisibilityRules()) {
270
- const evalCtx: { record?: unknown; user?: unknown } = {}
271
- if (ctx.record !== undefined) evalCtx.record = ctx.record
272
- if (ctx.user !== undefined) evalCtx.user = ctx.user
273
- const { disabled } = await el.evaluate(evalCtx)
274
- if (disabled) meta['disabled'] = true
275
- }
276
-
277
- // Plan #8 — emit `_layout` bag when the element used columnSpan/Start/Order.
278
- // Stamped after toMeta() so subclasses don't have to remember.
279
- const layout = el.getLayoutPositioning()
280
- if (layout) meta._layout = layout
281
-
282
- // Plan #15 — stamp `serverData / id / poll / lazy` on every
283
- // ServerDataElement so the renderer can find its `_widgetData[id]`
284
- // payload. The actual data resolution happens in
285
- // `resolveServerDataElements` (a separate pageData pass) — this just
286
- // marks the element on the wire.
287
- if (isServerDataElement(el)) stampServerDataMeta(el, meta)
288
-
289
- // Plan #14 — Repeater rows. Skip the generic `getChildren()` recurse
290
- // below (the inner schema is rendered per-row, not once on the parent),
291
- // and instead populate `meta.rows` + `meta.template` with row-scoped
292
- // contexts so each child sees its own row's values via `$get` / `row`.
293
- if (el instanceof RepeaterField) {
294
- await resolveRepeaterRows(el, ctx, meta)
295
- return meta
296
- }
297
-
298
- // Plan #14 follow-up — Builder rows. Same row-scoped resolve as
299
- // Repeater, but each row's inner schema is the block matching the
300
- // row's submitted `type`. The picker (`meta.blocks`) was already
301
- // stamped by `BuilderField.toMeta()`.
302
- if (el instanceof BuilderField) {
303
- await resolveBuilderRows(el, ctx, meta)
304
- return meta
305
- }
306
-
307
- // Filament v5 — `RepeatableEntry` rows. Read-only sibling of Repeater:
308
- // the array lives on `ctx.record[name]` (no `ctx.values` involvement —
309
- // entries are display-only) and each row's inner schema resolves with
310
- // its own `record` scoped to that row's data, so inner entries read
311
- // their state via the same `record[childName]` lookup they use anywhere
312
- // else.
313
- if (el instanceof RepeatableEntry) {
314
- await resolveRepeatableRows(el, ctx, meta)
315
- return meta
316
- }
317
-
318
- // Push `inlineLabelDefault` into the child ctx when this element is a
319
- // `Form` or `Section` whose `.inlineLabel(...)` was set. Children inherit
320
- // until another container resets the flag. Field-level `inlineLabel(...)`
321
- // calls always win on read (see `Field.buildMeta`).
322
- const childCtx = deriveChildContext(el, ctx)
323
-
324
- const children = el.getChildren()
325
- if (children && children.length > 0) {
326
- meta.children = await resolveAll(children, childCtx)
327
- }
328
-
329
- // Filament v5 — `Section.afterHeader([Action…])` resolves through the
330
- // standard walker so every Action's `.visible() / .disabled()` rule
331
- // evaluates the same way it does anywhere else. Stamped under
332
- // `meta.afterHeader` so the renderer doesn't confuse it with the
333
- // section's body content (`meta.children`).
334
- if (el instanceof Section) {
335
- const afterHeader = el.getAfterHeader()
336
- if (afterHeader && afterHeader.length > 0) {
337
- meta['afterHeader'] = await resolveAll(afterHeader, ctx)
338
- }
339
- }
340
-
341
- // Filament v5 — `Action.modalContentFooter([Element…])` resolves through
342
- // the standard walker so any inner Actions evaluate `.visible() /
343
- // .disabled()` the same way as anywhere else, and non-Action chrome
344
- // (Alert / Text / Heading / …) flows through the same rendering path.
345
- // Stamped under `meta.modalContentFooter` so the renderer can mount it
346
- // between the form body and the Cancel/Submit footer.
347
- if (el instanceof Action) {
348
- const footer = el.getModalContentFooter()
349
- if (footer && footer.length > 0) {
350
- meta['modalContentFooter'] = await resolveAll(footer, ctx)
351
- }
352
- }
353
-
354
- // `Wizard.submitAction() / nextAction() / previousAction()` — build a
355
- // framework default Action for each slot, run the user's customizer
356
- // (or take the explicit Action), then resolve through the standard
357
- // walker so `.visible() / .disabled()` rules evaluate the same way
358
- // as anywhere else. Stamped under `meta.submitAction / nextAction /
359
- // previousAction`; sparse — absent when the user hasn't customized.
360
- if (el instanceof Wizard) {
361
- const submitC = el.getSubmitAction()
362
- const nextC = el.getNextAction()
363
- const previousC = el.getPreviousAction()
364
- if (submitC) {
365
- const [resolved] = await resolveAll([resolveWizardAction(submitC, 'submit')], ctx)
366
- if (resolved) meta['submitAction'] = resolved
367
- }
368
- if (nextC) {
369
- const [resolved] = await resolveAll([resolveWizardAction(nextC, 'next')], ctx)
370
- if (resolved) meta['nextAction'] = resolved
371
- }
372
- if (previousC) {
373
- const [resolved] = await resolveAll([resolveWizardAction(previousC, 'previous')], ctx)
374
- if (resolved) meta['previousAction'] = resolved
375
- }
376
- }
377
-
378
- // `TextField.prefixAction(Action) / suffixAction(Action)` — resolve the
379
- // bound Actions through `resolveAll` so visibility / disabled rules
380
- // evaluate the same way as anywhere else. Hidden Actions are dropped
381
- // from the slot (returning a sparse single-element array → undefined
382
- // collapses cleanly).
383
- if (el instanceof TextField) {
384
- const pre = el.getPrefixAction()
385
- const suf = el.getSuffixAction()
386
- if (pre) {
387
- const [resolved] = await resolveAll([pre], ctx)
388
- if (resolved) meta['prefixAction'] = resolved
389
- }
390
- if (suf) {
391
- const [resolved] = await resolveAll([suf], ctx)
392
- if (resolved) meta['suffixAction'] = resolved
393
- }
394
- }
395
-
396
- return meta
397
- }
398
-
399
- /**
400
- * Per-row resolution for `RepeaterField`. Reads submitted row values from
401
- * `ctx.values?.[field.name]`, falls back to `defaultItems` empty rows on
402
- * fresh renders, resolves the inner schema once per row with a row-scoped
403
- * `RenderContext`, and stamps `meta.rows` + `meta.template`.
404
- *
405
- * `template` is the empty-row blueprint the client clones when the user
406
- * presses "Add row" — resolved with `values: {}` so any `default()` /
407
- * `defaultValue` on inner fields surfaces correctly.
408
- */
409
- async function resolveRepeaterRows(
410
- field: RepeaterField,
411
- ctx: RenderContext,
412
- meta: ElementMeta,
413
- ): Promise<void> {
414
- const inner = field.getInnerSchema()
415
- const submitted = ctx.values?.[field.name]
416
- // For `simple()` repeaters the loaded record / submitted value is a
417
- // flat `[v1, v2, …]` array. Wrap each primitive entry inline using the
418
- // inner field's name so the rest of the resolver path treats it as a
419
- // standard `[{<innerName>: v}]` row. Object entries pass through (e.g.
420
- // when a coerce/state-update has already produced wrapped rows).
421
- const simpleInner = field.getSimpleInnerField()
422
- const rowsInput: Array<Record<string, unknown>> = Array.isArray(submitted)
423
- ? submitted.map(raw => simpleInner ? coerceSimpleRowValues(raw, simpleInner.name) : coerceRowValues(raw))
424
- : Array.from({ length: field.getDefaultItems() }, () => ({}))
425
-
426
- const labelFn = field.getItemLabel()
427
- const hiddenFn = field.getItemHidden()
428
- const canDeleteFn = field.getItemCanDelete()
429
- const canCloneFn = field.getItemCanClone()
430
- const canReorderFn = field.getItemCanReorder()
431
-
432
- const rows = await Promise.all(rowsInput.map(async (rowValues, index) => {
433
- const siblings = rowsInput.filter((_, i) => i !== index)
434
- const rowCtx: RenderContext = {
435
- ...ctx,
436
- values: rowValues,
437
- $get: (name: string) => rowValues[name],
438
- $set: (name: string, value: unknown) => { rowValues[name] = value },
439
- row: {
440
- index,
441
- $get: (name: string) => rowValues[name],
442
- $set: (name: string, value: unknown) => { rowValues[name] = value },
443
- siblings,
444
- },
445
- }
446
- delete rowCtx.changed // changed key is parent-scoped; not meaningful inside the row resolve
447
- const children = await resolveAll(inner, rowCtx)
448
- const id = readRowId(rowValues, field.name, index)
449
- const row: RepeaterRowMeta = { id, children }
450
- if (labelFn) {
451
- try {
452
- const label = labelFn(rowValues)
453
- if (typeof label === 'string') row.itemLabel = label
454
- } catch (err) {
455
- console.warn(`[pilotiq] itemLabel() on Repeater "${field.name}" threw:`, err)
456
- }
457
- }
458
- // Build the layout context lazily — most repeaters configure none of
459
- // {hidden, canDelete, canClone, canReorder}, so the common path skips
460
- // it entirely.
461
- const needsLayoutCtx = hiddenFn !== undefined
462
- || canDeleteFn !== undefined
463
- || canCloneFn !== undefined
464
- || canReorderFn !== undefined
465
- const layoutCtx = needsLayoutCtx ? buildLayoutContext(rowCtx) : undefined
466
- if (hiddenFn !== undefined && layoutCtx !== undefined) {
467
- const hidden = await evalItemHidden(hiddenFn, layoutCtx, `Repeater "${field.name}"`)
468
- if (hidden) row.hidden = true
469
- }
470
- if (canDeleteFn !== undefined && layoutCtx !== undefined) {
471
- const allowed = await evalItemCan(canDeleteFn, layoutCtx, 'itemCanDelete', `Repeater "${field.name}"`)
472
- if (!allowed) row.canDelete = false
473
- }
474
- if (canCloneFn !== undefined && layoutCtx !== undefined) {
475
- const allowed = await evalItemCan(canCloneFn, layoutCtx, 'itemCanClone', `Repeater "${field.name}"`)
476
- if (!allowed) row.canClone = false
477
- }
478
- if (canReorderFn !== undefined && layoutCtx !== undefined) {
479
- const allowed = await evalItemCan(canReorderFn, layoutCtx, 'itemCanReorder', `Repeater "${field.name}"`)
480
- if (!allowed) row.canReorder = false
481
- }
482
- const extras = field.getExtraItemActions()
483
- if (extras.length > 0) {
484
- const resolved = await resolveExtraItemActions(extras, ctx, rowValues)
485
- if (resolved.length > 0) row.extraActions = resolved
486
- }
487
- return row
488
- }))
489
-
490
- const templateCtx: RenderContext = { ...ctx, values: {} }
491
- delete templateCtx.row
492
- delete templateCtx.changed
493
- const template = await resolveAll(inner, templateCtx)
494
-
495
- meta['rows'] = rows
496
- meta['template'] = template
497
- }
498
-
499
- /**
500
- * Resolve a Repeater/Builder field's `extraItemActions` for one row.
501
- *
502
- * Each action's `.visible() / .hidden() / .disabled()` rules see a
503
- * `ActionVisibilityContext` with the parent record + user (so an action
504
- * can authorize against the page's user) plus the row's `values` and
505
- * `row.{ index, id, blockType? }` (so per-row predicates work).
506
- *
507
- * Visibility-eval throwers fail-closed (the action is dropped from the
508
- * row's strip — same posture as page-level Action visibility).
509
- *
510
- * Disabled stamping mirrors row-placement table actions: visibility =
511
- * include/exclude in the strip; disabled = stamp `disabled: true` on
512
- * the meta so the renderer greys it out.
513
- */
514
- async function resolveExtraItemActions(
515
- actions: Action[],
516
- ctx: RenderContext,
517
- rowValues: Record<string, unknown>,
518
- ): Promise<ActionMeta[]> {
519
- const out: ActionMeta[] = []
520
- for (const action of actions) {
521
- const evalCtx: ActionVisibilityContext = { values: rowValues }
522
- if (ctx.record !== undefined) evalCtx.record = ctx.record
523
- if (ctx.user !== undefined) evalCtx.user = ctx.user
524
- let visible = true
525
- let disabled = false
526
- if (action.hasVisibilityRules()) {
527
- const result = await action.evaluate(evalCtx)
528
- visible = result.visible
529
- disabled = result.disabled
530
- }
531
- if (!visible) continue
532
- const meta = action.toMeta()
533
- if (disabled) meta.disabled = true
534
- out.push(meta)
535
- }
536
- return out
537
- }
538
-
539
- function coerceRowValues(raw: unknown): Record<string, unknown> {
540
- if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
541
- return { ...(raw as Record<string, unknown>) }
542
- }
543
- return {}
544
- }
545
-
546
- /**
547
- * Variant of `coerceRowValues` for `Repeater.simple(field)`. Object rows
548
- * pass through (already wrapped); primitive / null / undefined entries
549
- * are wrapped under the inner field's name so the resolve / validate /
550
- * coerce pipeline keeps using the standard `{ <innerName>: value }`
551
- * shape regardless of whether the caller fed flat `[v1, v2]` (loaded
552
- * record, or `withValues({ name: ['x'] })`) or wrapped `[{name: v1}]`
553
- * (post-coerce, post-state-update).
554
- */
555
- function coerceSimpleRowValues(raw: unknown, innerName: string): Record<string, unknown> {
556
- if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
557
- return { ...(raw as Record<string, unknown>) }
558
- }
559
- if (raw === undefined) return {}
560
- return { [innerName]: raw }
561
- }
562
-
563
- // Fail-closed-as-visible: a throwing predicate keeps the row visible —
564
- // inverse of `Element.evaluateVisibility` because silently hiding data the
565
- // user is editing is the worse failure mode.
566
- async function evalItemHidden(
567
- rule: RepeaterItemHiddenRule,
568
- ctx: LayoutContext,
569
- ownerId: string,
570
- ): Promise<boolean> {
571
- if (typeof rule === 'boolean') return rule
572
- try {
573
- return Boolean(await rule(ctx))
574
- } catch (err) {
575
- console.warn(`[pilotiq] itemHidden() on ${ownerId} threw:`, err)
576
- return false
577
- }
578
- }
579
-
580
- /**
581
- * Stable row id. Prefers a round-tripped `__id` posted from the client
582
- * (string-only — anything else is ignored to keep the meta JSON-clean),
583
- * otherwise generates a deterministic id from the field name + row index
584
- * so server re-renders are idempotent. The client renderer (Step 7)
585
- * upgrades fresh rows to crypto-random UUIDs before persisting them
586
- * through the form-state map.
587
- */
588
- function readRowId(row: Record<string, unknown>, fieldName: string, index: number): string {
589
- const raw = row['__id']
590
- if (typeof raw === 'string' && raw.length > 0) return raw
591
- return `${fieldName}-${index}`
592
- }
593
-
594
- /**
595
- * Per-row resolution for `BuilderField`. Each submitted row is
596
- * `{ __id?, type, data?: {…} }`. We pick the block matching `type`,
597
- * resolve its `schema()` against a row-scoped context where `values`
598
- * is the row's `data` body (so `$get('heading')` reads the row's
599
- * heading, not the parent form's), and stamp `meta.rows`.
600
- *
601
- * Rows whose `type` doesn't match a registered block are shipped with
602
- * `unknownType: true` and empty `children` — the renderer falls back to
603
- * a "missing block" placeholder. Values still round-trip through
604
- * FormData (the renderer keeps the row's `__id` + `type` in hidden
605
- * inputs) so a config-rollback doesn't silently drop data.
606
- *
607
- * No template is shipped (`BuilderFieldMeta` doesn't carry one): the
608
- * client builds fresh rows by reading the picked block's children from
609
- * a server-resolved sample after the user picks the type. v1 ships
610
- * empty `data: {}` and lets the inner schema's `default()` values
611
- * surface on the next live re-resolve.
612
- */
613
- async function resolveBuilderRows(
614
- field: BuilderField,
615
- ctx: RenderContext,
616
- meta: ElementMeta,
617
- ): Promise<void> {
618
- const submitted = ctx.values?.[field.name]
619
- const rowsInput = Array.isArray(submitted) ? submitted.map(coerceBuilderRow) : []
620
-
621
- const labelFn = field.getItemLabel()
622
- const hiddenFn = field.getItemHidden()
623
- const canDeleteFn = field.getItemCanDelete()
624
- const canCloneFn = field.getItemCanClone()
625
- const canReorderFn = field.getItemCanReorder()
626
-
627
- const rows = await Promise.all(rowsInput.map(async (rowEntry, index) => {
628
- const blockName = rowEntry.type
629
- const block = blockName ? field.getBlock(blockName) : undefined
630
- const data = rowEntry.data
631
- const id = readBuilderRowId(rowEntry, field.name, index)
632
-
633
- if (!block) {
634
- const unknown: BuilderRowMeta = {
635
- id,
636
- type: blockName ?? '',
637
- children: [],
638
- unknownType: true,
639
- }
640
- return unknown
641
- }
642
-
643
- // Builder siblings are scoped to same-block-type rows only — the
644
- // option pickers in a `heading` block aren't shadowed by picks in a
645
- // `paragraph` block (different schemas, different `name` keys).
646
- // Each entry is the sibling's `data` map (not the envelope) so the
647
- // field's lookup `siblings[i][fieldName]` works the same as Repeater.
648
- const siblings: Record<string, unknown>[] = []
649
- for (let j = 0; j < rowsInput.length; j++) {
650
- if (j === index) continue
651
- const other = rowsInput[j]!
652
- if (other.type !== blockName) continue
653
- siblings.push(other.data)
654
- }
655
- const rowCtx: RenderContext = {
656
- ...ctx,
657
- values: data,
658
- $get: (name: string) => data[name],
659
- $set: (name: string, value: unknown) => { data[name] = value },
660
- row: {
661
- index,
662
- $get: (name: string) => data[name],
663
- $set: (name: string, value: unknown) => { data[name] = value },
664
- siblings,
665
- blockType: block.name,
666
- },
667
- }
668
- delete rowCtx.changed
669
-
670
- const children = await resolveAll(block.getSchema(), rowCtx)
671
- const row: BuilderRowMeta = { id, type: block.name, children }
672
-
673
- if (labelFn) {
674
- try {
675
- const label = labelFn(data, block.name)
676
- if (typeof label === 'string') row.itemLabel = label
677
- } catch (err) {
678
- console.warn(`[pilotiq] itemLabel() on Builder "${field.name}" threw:`, err)
679
- }
680
- }
681
- const needsLayoutCtx = hiddenFn !== undefined
682
- || canDeleteFn !== undefined
683
- || canCloneFn !== undefined
684
- || canReorderFn !== undefined
685
- const layoutCtx = needsLayoutCtx ? buildLayoutContext(rowCtx) : undefined
686
- if (hiddenFn !== undefined && layoutCtx !== undefined) {
687
- const hidden = await evalItemHidden(hiddenFn, layoutCtx, `Builder "${field.name}"`)
688
- if (hidden) row.hidden = true
689
- }
690
- if (canDeleteFn !== undefined && layoutCtx !== undefined) {
691
- const allowed = await evalItemCan(canDeleteFn, layoutCtx, 'itemCanDelete', `Builder "${field.name}"`)
692
- if (!allowed) row.canDelete = false
693
- }
694
- if (canCloneFn !== undefined && layoutCtx !== undefined) {
695
- const allowed = await evalItemCan(canCloneFn, layoutCtx, 'itemCanClone', `Builder "${field.name}"`)
696
- if (!allowed) row.canClone = false
697
- }
698
- if (canReorderFn !== undefined && layoutCtx !== undefined) {
699
- const allowed = await evalItemCan(canReorderFn, layoutCtx, 'itemCanReorder', `Builder "${field.name}"`)
700
- if (!allowed) row.canReorder = false
701
- }
702
- const extras = field.getExtraItemActions()
703
- if (extras.length > 0) {
704
- const resolved = await resolveExtraItemActions(extras, ctx, data)
705
- if (resolved.length > 0) row.extraActions = resolved
706
- }
707
- return row
708
- }))
709
-
710
- // Resolve a fresh-row template per block (empty values map). The
711
- // client uses these blueprints when the user picks a block from the
712
- // picker, so the new row's inputs render with whatever `default()`
713
- // values + visibility state apply to a blank record. Resolved here
714
- // rather than once-and-cached because conditional `options(fn)` can
715
- // depend on the surrounding `record / user / values` context.
716
- //
717
- // `Block.visible(rule)` is evaluated against an outer (not row-scoped)
718
- // LayoutContext — it's a picker-time gate, not a per-row gate. Hidden
719
- // blocks drop from `meta.blocks` so the picker doesn't show them; rows
720
- // already in `submitted` data of that block-type still render above
721
- // (the for-loop over `rowsInput` is independent of `getBlocks()`).
722
- const pickerLayoutCtx = buildLayoutContext(ctx)
723
- const blocksMetaRaw = await Promise.all(field.getBlocks().map(async block => {
724
- const visible = await block.evaluateVisibility(pickerLayoutCtx)
725
- if (!visible) return null
726
- const templateCtx: RenderContext = { ...ctx, values: {} }
727
- delete templateCtx.row
728
- delete templateCtx.changed
729
- const template = await resolveAll(block.getSchema(), templateCtx)
730
- const m = block.toMeta()
731
- m.template = template
732
- return m
733
- }))
734
- const blocksMeta = blocksMetaRaw.filter((m): m is BlockMeta => m !== null)
735
-
736
- meta['rows'] = rows
737
- meta['blocks'] = blocksMeta
738
- }
739
-
740
- /**
741
- * Per-row resolution for `RepeatableEntry`. Reads the array off
742
- * `ctx.record[name]`, resolves the inner schema once per row with `record`
743
- * scoped to that row's data, and stamps `meta.rows` with the resolved
744
- * children.
745
- *
746
- * Non-array / null / undefined / empty-array values stamp an empty
747
- * `meta.rows` — the renderer falls through to the `default()` placeholder
748
- * (inherited from `Entry.default()`).
749
- */
750
- async function resolveRepeatableRows(
751
- entry: RepeatableEntry,
752
- ctx: RenderContext,
753
- meta: ElementMeta,
754
- ): Promise<void> {
755
- const inner = entry.getInnerSchema()
756
- const fieldName = entry.getName()
757
- const sourceArray = readRecordArray(ctx.record, fieldName)
758
- if (sourceArray === undefined || sourceArray.length === 0 || inner.length === 0) {
759
- meta['rows'] = []
760
- return
761
- }
762
-
763
- const rows = await Promise.all(sourceArray.map(async (raw, index) => {
764
- const rowRecord = coerceRowRecord(raw)
765
- const rowCtx: RenderContext = { ...ctx, record: rowRecord }
766
- delete rowCtx.values
767
- delete rowCtx.$get
768
- delete rowCtx.$set
769
- delete rowCtx.changed
770
- delete rowCtx.row
771
- const children = await resolveAll(inner, rowCtx)
772
- const id = readRepeatableRowId(raw, fieldName, index)
773
- const row: RepeatableEntryRowMeta = { id, children }
774
- return row
775
- }))
776
-
777
- meta['rows'] = rows
778
- }
779
-
780
- /** Read a record's array-shaped property by name. Returns `undefined` when
781
- * absent / null / non-array — callers fall through to the empty-state
782
- * placeholder. */
783
- function readRecordArray(record: unknown, name: string): unknown[] | undefined {
784
- if (record === null || record === undefined || typeof record !== 'object') return undefined
785
- const value = (record as Record<string, unknown>)[name]
786
- return Array.isArray(value) ? value : undefined
787
- }
788
-
789
- /** Coerce an arbitrary array element into a `Record<string, unknown>` for
790
- * the row's `RenderContext.record`. Object rows pass through (clone to
791
- * avoid downstream mutation); primitives stash under a reserved `_value`
792
- * key so an inner `TextEntry.make('_value')` can still target them
793
- * (mirrors the read-only equivalent of Repeater's flat-array fallback). */
794
- function coerceRowRecord(raw: unknown): Record<string, unknown> {
795
- if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
796
- return { ...(raw as Record<string, unknown>) }
797
- }
798
- if (raw === undefined || raw === null) return {}
799
- return { _value: raw }
800
- }
801
-
802
- interface BuilderRowEntry {
803
- __id?: string
804
- type: string
805
- data: Record<string, unknown>
806
- }
807
-
808
- /**
809
- * Normalize an arbitrary submitted row entry into the `{ __id?, type, data }`
810
- * envelope. Robust to JSON bodies (already-shaped) and to flat-fold output
811
- * from `coerceBuilderValue` (top-level `__id`/`type` siblings of `data`).
812
- *
813
- * Returns an entry with a possibly-empty `type` ("" → unknownType row) so
814
- * the resolver still emits a `BuilderRowMeta` for shape stability — the
815
- * renderer drops empty-type rows visually.
816
- */
817
- function coerceBuilderRow(raw: unknown): BuilderRowEntry {
818
- if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
819
- return { type: '', data: {} }
820
- }
821
- const r = raw as Record<string, unknown>
822
- const type = typeof r['type'] === 'string' ? (r['type'] as string) : ''
823
- const dataRaw = r['data']
824
- const data: Record<string, unknown> = (dataRaw && typeof dataRaw === 'object' && !Array.isArray(dataRaw))
825
- ? { ...(dataRaw as Record<string, unknown>) }
826
- : {}
827
- const out: BuilderRowEntry = { type, data }
828
- if (typeof r['__id'] === 'string') out.__id = r['__id'] as string
829
- return out
830
- }
831
-
832
- function readBuilderRowId(row: BuilderRowEntry, fieldName: string, index: number): string {
833
- if (typeof row.__id === 'string' && row.__id.length > 0) return row.__id
834
- return `${fieldName}-${index}`
835
- }
836
-
837
- /**
838
- * Evaluate a per-row capability rule (`itemCanDelete / itemCanClone /
839
- * itemCanReorder`) against a row's `LayoutContext`. Returns the boolean
840
- * "is allowed" — `true` keeps the matching button mounted, `false`
841
- * removes it on this row.
842
- *
843
- * Fail-open: when the predicate throws, the capability stays enabled and
844
- * we log a warning. This mirrors `itemHidden`'s posture — a misbehaving
845
- * gate shouldn't silently lock the user out of editing data. For real
846
- * authorization (vs. UX), gate the parent form's lifecycle hooks.
847
- *
848
- * Shared between Repeater + Builder so the failure mode is identical.
849
- */
850
- async function evalItemCan(
851
- rule: RepeaterItemCanRule,
852
- ctx: LayoutContext,
853
- setter: string,
854
- ownerId: string,
855
- ): Promise<boolean> {
856
- if (typeof rule === 'boolean') return rule
857
- try {
858
- return Boolean(await rule(ctx))
859
- } catch (err) {
860
- console.warn(`[pilotiq] ${setter}() on ${ownerId} threw:`, err)
861
- return true
862
- }
863
- }
864
-
865
- /**
866
- * Build a layout-visibility context from the resolver's `RenderContext`.
867
- * Mirrors the `Field.buildConditionContext` shape so layout `visible(fn)`
868
- * callbacks can destructure the same way as `Field.showWhen` callbacks.
869
- */
870
- /**
871
- * Resolve a `WizardActionCustomizer` against the framework default for
872
- * the given slot. The default carries a stable `name` (`wizardSubmit /
873
- * wizardNext / wizardPrevious`) and sensible chrome — the customizer
874
- * may pass through verbatim, mutate, or replace entirely. Returned
875
- * `Action` is then resolved through the standard walker so visibility
876
- * and disabled rules evaluate the same way as any other Action.
877
- */
878
- function resolveWizardAction(customizer: WizardActionCustomizer, slot: 'submit' | 'next' | 'previous'): Action {
879
- const def = slot === 'submit'
880
- ? Action.make('wizardSubmit').label('Submit').color('primary')
881
- : slot === 'next'
882
- ? Action.make('wizardNext').label('Next').color('primary')
883
- : Action.make('wizardPrevious').label('Back').color('ghost')
884
- return typeof customizer === 'function' ? customizer(def) : customizer
885
- }
886
-
887
- function buildLayoutContext(ctx: RenderContext): LayoutContext {
888
- const out: LayoutContext = {}
889
- if (ctx.record !== undefined) out.record = ctx.record
890
- if (ctx.values !== undefined) out.values = ctx.values
891
- if (ctx.$get) out.$get = ctx.$get
892
- if (ctx.$set) out.$set = ctx.$set
893
- if (ctx.user !== undefined) out.user = ctx.user
894
- if (ctx.row !== undefined) out.row = ctx.row
895
- return out
896
- }
897
-
898
- /**
899
- * Derive the render context that this element's children should see.
900
- * Currently only handles the `inlineLabelDefault` cascade — `Form` /
901
- * `Section` with `.inlineLabel(true|false)` push the value into the
902
- * descendant context so every nested `Field.buildMeta` can read it.
903
- * A nested container that re-sets the flag overrides the outer value
904
- * for its subtree (the current ctx's value is replaced, not merged).
905
- *
906
- * Returns the same `ctx` reference when nothing needs to change so
907
- * call sites that pass it down skip a fresh object allocation.
908
- */
909
- function deriveChildContext(el: Element, ctx: RenderContext): RenderContext {
910
- if (el instanceof Form || el instanceof Section) {
911
- const v = (el as Form | Section).getInlineLabel?.()
912
- if (v !== undefined && v !== ctx.inlineLabelDefault) {
913
- return { ...ctx, inlineLabelDefault: v }
914
- }
915
- }
916
- return ctx
917
- }