@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,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
- }