@pilotiq/pilotiq 0.23.1 → 0.24.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +91 -0
- package/boost/guidelines.md +566 -0
- package/boost/skills/pilotiq-fields/SKILL.md +47 -0
- package/boost/skills/pilotiq-fields/rules/field-catalog.md +288 -0
- package/boost/skills/pilotiq-fields/rules/reactive-fields.md +199 -0
- package/boost/skills/pilotiq-fields/rules/validation.md +198 -0
- package/boost/skills/pilotiq-relations/SKILL.md +47 -0
- package/boost/skills/pilotiq-relations/rules/relation-managers.md +256 -0
- package/boost/skills/pilotiq-relations/rules/repeater-relationship.md +177 -0
- package/boost/skills/pilotiq-resource/SKILL.md +61 -0
- package/boost/skills/pilotiq-resource/rules/authorization.md +242 -0
- package/boost/skills/pilotiq-resource/rules/defining-resources.md +228 -0
- package/boost/skills/pilotiq-resource/rules/page-overrides.md +296 -0
- package/dist/actions/exportFactory.d.ts +10 -0
- package/dist/actions/exportFactory.d.ts.map +1 -1
- package/dist/actions/exportFactory.js +10 -0
- package/dist/actions/exportFactory.js.map +1 -1
- package/dist/react/CollabRoomContext.d.ts +5 -5
- package/dist/react/index.d.ts +0 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +0 -1
- package/dist/react/index.js.map +1 -1
- package/dist/routes/helpers.d.ts.map +1 -1
- package/dist/routes/helpers.js +6 -2
- package/dist/routes/helpers.js.map +1 -1
- package/dist/routes/relations.d.ts.map +1 -1
- package/dist/routes/relations.js +12 -0
- package/dist/routes/relations.js.map +1 -1
- package/package.json +6 -1
- package/.turbo/turbo-build.log +0 -8
- package/CLAUDE.md +0 -265
- package/dist/react/useCollabSeed.d.ts +0 -23
- package/dist/react/useCollabSeed.d.ts.map +0 -1
- package/dist/react/useCollabSeed.js +0 -82
- package/dist/react/useCollabSeed.js.map +0 -1
- package/src/Cluster.test.ts +0 -283
- package/src/Cluster.ts +0 -83
- package/src/Column.test.ts +0 -199
- package/src/Column.ts +0 -710
- package/src/Global.test.ts +0 -367
- package/src/Global.ts +0 -169
- package/src/Page.test.ts +0 -114
- package/src/Page.ts +0 -208
- package/src/Pilotiq.perf.test.ts +0 -252
- package/src/Pilotiq.test.ts +0 -129
- package/src/Pilotiq.ts +0 -1158
- package/src/PilotiqRegistry.ts +0 -36
- package/src/PilotiqServiceProvider.ts +0 -121
- package/src/RelationManager.test.ts +0 -400
- package/src/RelationManager.ts +0 -527
- package/src/RenderHook.test.ts +0 -252
- package/src/RenderHook.ts +0 -242
- package/src/Resource.test.ts +0 -284
- package/src/Resource.ts +0 -526
- package/src/RightPanel.test.ts +0 -202
- package/src/RightPanel.ts +0 -132
- package/src/Tab.test.ts +0 -91
- package/src/Tab.ts +0 -156
- package/src/UserMenuItem.ts +0 -145
- package/src/actions/Action.test.ts +0 -2526
- package/src/actions/Action.ts +0 -1515
- package/src/actions/ActionGroup.test.ts +0 -112
- package/src/actions/ActionGroup.ts +0 -173
- package/src/actions/attachFactory.ts +0 -172
- package/src/actions/bulkFactories.ts +0 -168
- package/src/actions/crudFactories.ts +0 -220
- package/src/actions/exportFactory.ts +0 -215
- package/src/actions/factoryHelpers.ts +0 -177
- package/src/actions/importFactory.ts +0 -243
- package/src/actions/index.ts +0 -17
- package/src/actions/m2mFactories.ts +0 -193
- package/src/actions/relationFactories.ts +0 -372
- package/src/applyPageHooks.test.ts +0 -463
- package/src/applyPageHooks.ts +0 -330
- package/src/authorization.test.ts +0 -483
- package/src/breadcrumbs.test.ts +0 -238
- package/src/cells/coerce.test.ts +0 -85
- package/src/cells/coerce.ts +0 -84
- package/src/clusterPaths.ts +0 -35
- package/src/columns/BadgeColumn.test.ts +0 -54
- package/src/columns/BadgeColumn.ts +0 -32
- package/src/columns/BooleanColumn.test.ts +0 -41
- package/src/columns/BooleanColumn.ts +0 -18
- package/src/columns/ColorColumn.test.ts +0 -37
- package/src/columns/ColorColumn.ts +0 -38
- package/src/columns/IconColumn.test.ts +0 -54
- package/src/columns/IconColumn.ts +0 -37
- package/src/columns/ImageColumn.test.ts +0 -41
- package/src/columns/ImageColumn.ts +0 -28
- package/src/columns/SelectColumn.ts +0 -98
- package/src/columns/TextColumn.test.ts +0 -190
- package/src/columns/TextColumn.ts +0 -20
- package/src/columns/TextInputColumn.ts +0 -68
- package/src/columns/ToggleColumn.ts +0 -46
- package/src/columns/editableColumns.test.ts +0 -238
- package/src/columns/index.ts +0 -9
- package/src/defaultGlobalPages.ts +0 -95
- package/src/defaultPages.test.ts +0 -634
- package/src/defaultPages.ts +0 -617
- package/src/defaultViewPage.test.ts +0 -147
- package/src/elements/Form.test.ts +0 -223
- package/src/elements/Form.ts +0 -416
- package/src/elements/ListTabs.ts +0 -28
- package/src/elements/Table.test.ts +0 -422
- package/src/elements/Table.ts +0 -850
- package/src/elements/TableGroup.test.ts +0 -260
- package/src/elements/TableGroup.ts +0 -334
- package/src/elements/dispatchAction.test.ts +0 -463
- package/src/elements/dispatchAction.ts +0 -355
- package/src/elements/dispatchForm.test.ts +0 -477
- package/src/elements/dispatchForm.ts +0 -1993
- package/src/elements/dispatchTable.test.ts +0 -1514
- package/src/elements/dispatchTable.ts +0 -745
- package/src/elements/index.ts +0 -21
- package/src/entries/BadgeEntry.ts +0 -39
- package/src/entries/CodeEntry.test.ts +0 -40
- package/src/entries/CodeEntry.ts +0 -52
- package/src/entries/ColorEntry.ts +0 -63
- package/src/entries/ComponentEntry.test.ts +0 -173
- package/src/entries/ComponentEntry.ts +0 -95
- package/src/entries/Entry.ts +0 -304
- package/src/entries/IconEntry.ts +0 -49
- package/src/entries/ImageEntry.ts +0 -61
- package/src/entries/KeyValueEntry.ts +0 -47
- package/src/entries/RepeatableEntry.test.ts +0 -239
- package/src/entries/RepeatableEntry.ts +0 -173
- package/src/entries/TextEntry.test.ts +0 -394
- package/src/entries/TextEntry.ts +0 -60
- package/src/entries/index.ts +0 -12
- package/src/entries/leaves.test.ts +0 -306
- package/src/entries/registry.ts +0 -54
- package/src/fields/BuilderField.test.ts +0 -1188
- package/src/fields/BuilderField.ts +0 -605
- package/src/fields/BuilderRelationship.test.ts +0 -811
- package/src/fields/CheckboxField.test.ts +0 -44
- package/src/fields/CheckboxField.ts +0 -27
- package/src/fields/CheckboxListField.test.ts +0 -99
- package/src/fields/CheckboxListField.ts +0 -66
- package/src/fields/ColorPickerField.test.ts +0 -33
- package/src/fields/ColorPickerField.ts +0 -25
- package/src/fields/DateField.ts +0 -54
- package/src/fields/DateTimeField.test.ts +0 -55
- package/src/fields/EmailField.ts +0 -16
- package/src/fields/Field.test.ts +0 -654
- package/src/fields/Field.ts +0 -817
- package/src/fields/FileUploadField.test.ts +0 -143
- package/src/fields/FileUploadField.ts +0 -159
- package/src/fields/HiddenField.test.ts +0 -27
- package/src/fields/HiddenField.ts +0 -28
- package/src/fields/KeyValueField.test.ts +0 -105
- package/src/fields/KeyValueField.ts +0 -55
- package/src/fields/MarkdownField.test.ts +0 -167
- package/src/fields/MarkdownField.ts +0 -162
- package/src/fields/NumberField.ts +0 -33
- package/src/fields/RadioField.test.ts +0 -94
- package/src/fields/RadioField.ts +0 -67
- package/src/fields/RepeaterField.test.ts +0 -1806
- package/src/fields/RepeaterField.ts +0 -939
- package/src/fields/RepeaterRelationship.test.ts +0 -1923
- package/src/fields/RepeaterSimple.test.ts +0 -248
- package/src/fields/RowButton.test.ts +0 -219
- package/src/fields/RowButton.ts +0 -135
- package/src/fields/SelectField.test.ts +0 -192
- package/src/fields/SelectField.ts +0 -235
- package/src/fields/SliderField.test.ts +0 -50
- package/src/fields/SliderField.ts +0 -53
- package/src/fields/SlugField.ts +0 -24
- package/src/fields/TagsInputField.test.ts +0 -154
- package/src/fields/TagsInputField.ts +0 -133
- package/src/fields/TextField.test.ts +0 -213
- package/src/fields/TextField.ts +0 -177
- package/src/fields/TextareaField.test.ts +0 -58
- package/src/fields/TextareaField.ts +0 -59
- package/src/fields/ToggleButtonsField.test.ts +0 -106
- package/src/fields/ToggleButtonsField.ts +0 -59
- package/src/fields/ToggleField.ts +0 -16
- package/src/fields/disableOptionsWhenSelectedInSiblingRepeaterItems.test.ts +0 -319
- package/src/fields/optionsResolver.ts +0 -95
- package/src/fields/resolveField.ts +0 -28
- package/src/filters/BooleanFilter.ts +0 -35
- package/src/filters/DateRangeFilter.test.ts +0 -194
- package/src/filters/DateRangeFilter.ts +0 -148
- package/src/filters/Filter.test.ts +0 -268
- package/src/filters/Filter.ts +0 -184
- package/src/filters/FormFilter.test.ts +0 -238
- package/src/filters/FormFilter.ts +0 -215
- package/src/filters/MultiSelectFilter.test.ts +0 -119
- package/src/filters/MultiSelectFilter.ts +0 -78
- package/src/filters/QueryBuilderFilter.test.ts +0 -662
- package/src/filters/QueryBuilderFilter.ts +0 -398
- package/src/filters/SelectFilter.ts +0 -46
- package/src/filters/TernaryFilter.test.ts +0 -160
- package/src/filters/TernaryFilter.ts +0 -72
- package/src/filters/TrashedFilter.test.ts +0 -149
- package/src/filters/TrashedFilter.ts +0 -55
- package/src/filters/queryBuilder/BooleanConstraint.ts +0 -31
- package/src/filters/queryBuilder/Constraint.ts +0 -115
- package/src/filters/queryBuilder/DateConstraint.ts +0 -69
- package/src/filters/queryBuilder/NumberConstraint.ts +0 -66
- package/src/filters/queryBuilder/SelectConstraint.ts +0 -72
- package/src/filters/queryBuilder/TextConstraint.ts +0 -64
- package/src/filters/queryBuilder/index.ts +0 -12
- package/src/icons/index.ts +0 -2
- package/src/icons/lucide.ts +0 -204
- package/src/icons/registry.test.ts +0 -56
- package/src/icons/registry.ts +0 -41
- package/src/icons/types.ts +0 -47
- package/src/index.ts +0 -525
- package/src/io/csv.test.ts +0 -142
- package/src/io/csv.ts +0 -170
- package/src/nestedRelationManagerData.test.ts +0 -547
- package/src/notifications/Notification.test.ts +0 -210
- package/src/notifications/Notification.ts +0 -354
- package/src/notifications/broadcast.test.ts +0 -110
- package/src/notifications/broadcast.ts +0 -95
- package/src/notifications/database.test.ts +0 -383
- package/src/notifications/database.ts +0 -398
- package/src/notifications/databaseNotifications.test.ts +0 -187
- package/src/notifications/dispatchNotificationAction.test.ts +0 -341
- package/src/notifications/dispatchNotificationAction.ts +0 -142
- package/src/notifications/flash.test.ts +0 -89
- package/src/notifications/flash.ts +0 -71
- package/src/notifications/index.ts +0 -45
- package/src/notifications/registerBroadcastAuth.test.ts +0 -134
- package/src/notifications/registerBroadcastAuth.ts +0 -100
- package/src/notifications/resolveSavedNotification.test.ts +0 -82
- package/src/notifications/resolveSavedNotification.ts +0 -59
- package/src/notifications/types.ts +0 -93
- package/src/orm/m2mAccessor.ts +0 -66
- package/src/orm/modelDefaults.test.ts +0 -633
- package/src/orm/modelDefaults.ts +0 -666
- package/src/pageData/breadcrumbs.ts +0 -288
- package/src/pageData/forms.ts +0 -578
- package/src/pageData/helpers.ts +0 -857
- package/src/pageData/misc.ts +0 -347
- package/src/pageData/navigation.ts +0 -842
- package/src/pageData/relationPages.ts +0 -1248
- package/src/pageData/relationTabs.ts +0 -286
- package/src/pageData/resourcePages.ts +0 -609
- package/src/pageData.test.ts +0 -1545
- package/src/pageData.ts +0 -341
- package/src/plugins/index.ts +0 -8
- package/src/plugins/themeEditor.test.ts +0 -36
- package/src/plugins/themeEditor.ts +0 -45
- package/src/react/AppShell.tsx +0 -251
- package/src/react/CollabExtensionFactoryRegistry.ts +0 -55
- package/src/react/CollabRoomContext.ts +0 -98
- package/src/react/CollabTextRendererRegistry.ts +0 -102
- package/src/react/CommandPalette.tsx +0 -375
- package/src/react/CurrentUserContext.tsx +0 -50
- package/src/react/CustomPageWrapperGate.tsx +0 -69
- package/src/react/CustomPageWrapperRegistry.ts +0 -45
- package/src/react/FieldFocusReporterRegistry.ts +0 -37
- package/src/react/FieldLabelSlotRegistry.ts +0 -30
- package/src/react/FieldPresenceRegistry.ts +0 -46
- package/src/react/FormCollabBindingRegistry.ts +0 -242
- package/src/react/FormStateContext.tsx +0 -591
- package/src/react/HeadHooks.tsx +0 -126
- package/src/react/MarkdownEditorRegistry.test.ts +0 -38
- package/src/react/MarkdownEditorRegistry.ts +0 -107
- package/src/react/NotificationActionStrip.tsx +0 -263
- package/src/react/NotificationBell.tsx +0 -426
- package/src/react/PendingSuggestionApplierRegistry.test.ts +0 -97
- package/src/react/PendingSuggestionApplierRegistry.ts +0 -98
- package/src/react/PendingSuggestionOverlayRegistry.ts +0 -54
- package/src/react/PendingSuggestionsContext.tsx +0 -172
- package/src/react/RecordWrapperGate.tsx +0 -58
- package/src/react/RecordWrapperRegistry.ts +0 -39
- package/src/react/RenderHookSlot.tsx +0 -32
- package/src/react/RightSidebar.tsx +0 -257
- package/src/react/RightSidebarContext.tsx +0 -234
- package/src/react/RightSidebarTrigger.tsx +0 -53
- package/src/react/RowCoordsContext.tsx +0 -23
- package/src/react/SchemaRenderer.tsx +0 -549
- package/src/react/SearchTrigger.tsx +0 -46
- package/src/react/ThemeProvider.tsx +0 -93
- package/src/react/ThemeSettingsPage.tsx +0 -579
- package/src/react/ThemeToggle.tsx +0 -20
- package/src/react/Toaster.tsx +0 -158
- package/src/react/UserMenu.tsx +0 -196
- package/src/react/WidgetDataContext.tsx +0 -157
- package/src/react/cells/EditableCell.tsx +0 -389
- package/src/react/component-slots.test.ts +0 -103
- package/src/react/component-slots.ts +0 -116
- package/src/react/fieldJsHandler.test.ts +0 -166
- package/src/react/fieldJsHandler.ts +0 -79
- package/src/react/fields/BuilderInput.tsx +0 -1078
- package/src/react/fields/CheckboxInput.tsx +0 -39
- package/src/react/fields/CheckboxListInput.tsx +0 -102
- package/src/react/fields/ColorInput.tsx +0 -71
- package/src/react/fields/DateFieldInput.tsx +0 -70
- package/src/react/fields/DateTimeInput.tsx +0 -62
- package/src/react/fields/FieldShell.tsx +0 -348
- package/src/react/fields/FileUploadInput.tsx +0 -639
- package/src/react/fields/HiddenInput.tsx +0 -17
- package/src/react/fields/KeyValueInput.tsx +0 -230
- package/src/react/fields/MarkdownInput.tsx +0 -560
- package/src/react/fields/RadioInput.tsx +0 -81
- package/src/react/fields/RepeaterInput.test.ts +0 -116
- package/src/react/fields/RepeaterInput.tsx +0 -1420
- package/src/react/fields/SelectFieldInput.tsx +0 -280
- package/src/react/fields/SliderInput.tsx +0 -81
- package/src/react/fields/TagsInput.tsx +0 -283
- package/src/react/fields/TextLikeInput.tsx +0 -256
- package/src/react/fields/ToggleButtonsInput.tsx +0 -60
- package/src/react/fields/ToggleFieldInput.tsx +0 -56
- package/src/react/fields/relationshipRenameDispatch.test.ts +0 -106
- package/src/react/fields/relationshipRenameDispatch.ts +0 -97
- package/src/react/fields/repeaterReconcile.test.ts +0 -114
- package/src/react/fields/repeaterReconcile.ts +0 -104
- package/src/react/fields/rowChromeButton.tsx +0 -336
- package/src/react/fields/rowState.ts +0 -106
- package/src/react/fields/syncRowGates.test.ts +0 -202
- package/src/react/fields/syncRowGates.ts +0 -66
- package/src/react/fields/textInputControls.tsx +0 -238
- package/src/react/fields/useRowReorderDnd.ts +0 -78
- package/src/react/formStateHelpers.test.ts +0 -508
- package/src/react/formStateHelpers.ts +0 -381
- package/src/react/hooks/use-mobile.ts +0 -19
- package/src/react/icon-context.tsx +0 -60
- package/src/react/index.ts +0 -195
- package/src/react/layouts/SidebarLayout.tsx +0 -250
- package/src/react/layouts/TopbarLayout.tsx +0 -258
- package/src/react/navigate.tsx +0 -37
- package/src/react/onProviderSynced.test.ts +0 -90
- package/src/react/parseRecordEditUrl.test.ts +0 -122
- package/src/react/parseRecordEditUrl.ts +0 -94
- package/src/react/persistedState.ts +0 -40
- package/src/react/registry.ts +0 -48
- package/src/react/right-panel-registry.tsx +0 -47
- package/src/react/schemaRenderer/AlertRenderer.tsx +0 -112
- package/src/react/schemaRenderer/EntryRenderer.tsx +0 -501
- package/src/react/schemaRenderer/SectionRenderer.tsx +0 -120
- package/src/react/schemaRenderer/SimpleElements.tsx +0 -306
- package/src/react/schemaRenderer/TabsRenderer.tsx +0 -62
- package/src/react/schemaRenderer/WizardRenderer.tsx +0 -338
- package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +0 -177
- package/src/react/schemaRenderer/action/ActionModalDialog.tsx +0 -273
- package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +0 -61
- package/src/react/schemaRenderer/action/HandlerActionButton.tsx +0 -43
- package/src/react/schemaRenderer/action/MethodActionButton.tsx +0 -64
- package/src/react/schemaRenderer/action/buttons.tsx +0 -99
- package/src/react/schemaRenderer/action/helpers.ts +0 -140
- package/src/react/schemaRenderer/action/renderAction.tsx +0 -245
- package/src/react/schemaRenderer/columnFormat.ts +0 -65
- package/src/react/schemaRenderer/constants.ts +0 -50
- package/src/react/schemaRenderer/form/FormRenderer.tsx +0 -274
- package/src/react/schemaRenderer/form/renderField.tsx +0 -511
- package/src/react/schemaRenderer/helpers.tsx +0 -81
- package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +0 -308
- package/src/react/schemaRenderer/table/TableRenderer.tsx +0 -123
- package/src/react/schemaRenderer/table/TableRendererBody.tsx +0 -974
- package/src/react/schemaRenderer/table/filters.tsx +0 -1233
- package/src/react/schemaRenderer/table/formatCell.tsx +0 -264
- package/src/react/schemaRenderer/table/links.tsx +0 -112
- package/src/react/schemaRenderer/table/renderRowActions.tsx +0 -52
- package/src/react/schemaRenderer/table/url.tsx +0 -143
- package/src/react/theme-preview/apply.ts +0 -99
- package/src/react/theme-preview/build-html.ts +0 -436
- package/src/react/ui/button.tsx +0 -51
- package/src/react/ui/calendar.tsx +0 -67
- package/src/react/ui/checkbox.tsx +0 -29
- package/src/react/ui/dialog.tsx +0 -108
- package/src/react/ui/dropdown-menu.tsx +0 -97
- package/src/react/ui/input.tsx +0 -20
- package/src/react/ui/label.tsx +0 -21
- package/src/react/ui/popover.tsx +0 -50
- package/src/react/ui/select.tsx +0 -169
- package/src/react/ui/separator.tsx +0 -25
- package/src/react/ui/sheet.tsx +0 -136
- package/src/react/ui/sidebar.tsx +0 -723
- package/src/react/ui/skeleton.tsx +0 -13
- package/src/react/ui/slider.tsx +0 -34
- package/src/react/ui/switch.tsx +0 -28
- package/src/react/ui/table.tsx +0 -105
- package/src/react/ui/tabs.tsx +0 -63
- package/src/react/ui/textarea.tsx +0 -18
- package/src/react/ui/tooltip.tsx +0 -64
- package/src/react/useCollabSeed.ts +0 -86
- package/src/react/useResizableWidth.ts +0 -139
- package/src/react/utils.ts +0 -6
- package/src/react/widgetRegistry.test.ts +0 -43
- package/src/react/widgetRegistry.ts +0 -50
- package/src/react/widgets/StatsOverviewRenderer.tsx +0 -232
- package/src/react/widgets/TableWidgetRenderer.tsx +0 -231
- package/src/react/widgets/ViewRenderer.tsx +0 -71
- package/src/relationManagerData.test.ts +0 -1595
- package/src/richtext/index.ts +0 -8
- package/src/richtext/registry.ts +0 -89
- package/src/routes/globals.ts +0 -148
- package/src/routes/guard.test.ts +0 -325
- package/src/routes/helpers.ts +0 -700
- package/src/routes/pages.ts +0 -175
- package/src/routes/panel.ts +0 -204
- package/src/routes/relations.ts +0 -1227
- package/src/routes/resources.ts +0 -781
- package/src/routes/theme.ts +0 -91
- package/src/routes-nested-relations.test.ts +0 -676
- package/src/routes-relations.test.ts +0 -972
- package/src/routes.test.ts +0 -2027
- package/src/routes.ts +0 -303
- package/src/schema/Alert.test.ts +0 -109
- package/src/schema/Alert.ts +0 -131
- package/src/schema/Block.ts +0 -169
- package/src/schema/Breadcrumbs.ts +0 -40
- package/src/schema/Card.ts +0 -35
- package/src/schema/Divider.ts +0 -20
- package/src/schema/Element.ts +0 -219
- package/src/schema/EmptyState.test.ts +0 -37
- package/src/schema/EmptyState.ts +0 -63
- package/src/schema/Fieldset.ts +0 -43
- package/src/schema/Grid.ts +0 -43
- package/src/schema/Group.ts +0 -30
- package/src/schema/Heading.ts +0 -39
- package/src/schema/Html.ts +0 -67
- package/src/schema/Icon.ts +0 -54
- package/src/schema/Image.ts +0 -57
- package/src/schema/LinkTag.ts +0 -41
- package/src/schema/Markdown.ts +0 -85
- package/src/schema/MetaTag.ts +0 -41
- package/src/schema/RelationTabs.ts +0 -71
- package/src/schema/ScriptTag.ts +0 -55
- package/src/schema/Section.ts +0 -160
- package/src/schema/ServerDataElement.test.ts +0 -140
- package/src/schema/ServerDataElement.ts +0 -156
- package/src/schema/SlotComponent.test.ts +0 -77
- package/src/schema/SlotComponent.ts +0 -71
- package/src/schema/Split.ts +0 -50
- package/src/schema/Stat.test.ts +0 -118
- package/src/schema/Stat.ts +0 -154
- package/src/schema/StatsOverview.test.ts +0 -141
- package/src/schema/StatsOverview.ts +0 -119
- package/src/schema/StyleTag.ts +0 -35
- package/src/schema/TableWidget.test.ts +0 -297
- package/src/schema/TableWidget.ts +0 -289
- package/src/schema/Tabs.ts +0 -79
- package/src/schema/Text.ts +0 -58
- package/src/schema/UnorderedList.ts +0 -49
- package/src/schema/View.test.ts +0 -111
- package/src/schema/View.ts +0 -127
- package/src/schema/Wizard.ts +0 -220
- package/src/schema/containers.test.ts +0 -564
- package/src/schema/headTags.test.ts +0 -134
- package/src/schema/index.ts +0 -40
- package/src/schema/primes.test.ts +0 -269
- package/src/schema/resolveSchema.test.ts +0 -379
- package/src/schema/resolveSchema.ts +0 -917
- package/src/schema/sanitize.ts +0 -58
- package/src/search.test.ts +0 -446
- package/src/search.ts +0 -178
- package/src/sessionFilters.test.ts +0 -375
- package/src/sessionFilters.ts +0 -143
- package/src/slot-components/index.ts +0 -10
- package/src/slot-components/registry.ts +0 -56
- package/src/styles/file-upload.css +0 -13
- package/src/summarizers/Summarizer.test.ts +0 -84
- package/src/summarizers/Summarizer.ts +0 -123
- package/src/summarizers/index.ts +0 -11
- package/src/theme/base-colors.ts +0 -68
- package/src/theme/chart-colors.ts +0 -50
- package/src/theme/colors.ts +0 -447
- package/src/theme/generate-css.test.ts +0 -139
- package/src/theme/generate-css.ts +0 -44
- package/src/theme/generate-scale.test.ts +0 -106
- package/src/theme/generate-scale.ts +0 -97
- package/src/theme/icon-map.ts +0 -42
- package/src/theme/index.ts +0 -34
- package/src/theme/migrate.test.ts +0 -178
- package/src/theme/migrate.ts +0 -81
- package/src/theme/presets.ts +0 -135
- package/src/theme/radius.ts +0 -18
- package/src/theme/resolve.test.ts +0 -238
- package/src/theme/resolve.ts +0 -96
- package/src/theme/spacing.ts +0 -18
- package/src/theme/storage.test.ts +0 -126
- package/src/theme/storage.ts +0 -106
- package/src/theme/theme-colors.ts +0 -88
- package/src/theme/types.ts +0 -125
- package/src/uploads/UploadAdapter.ts +0 -35
- package/src/uploads/index.ts +0 -2
- package/src/uploads/localUpload.test.ts +0 -70
- package/src/uploads/localUpload.ts +0 -84
- package/src/validation/Validator.ts +0 -49
- package/src/validation/index.ts +0 -28
- package/src/validation/rules.ts +0 -78
- package/src/validation/runValidators.ts +0 -435
- package/src/validation/uniqueValidator.test.ts +0 -196
- package/src/validation/uniqueValidator.ts +0 -133
- package/src/validation/validators.test.ts +0 -268
- package/src/vite.test.ts +0 -184
- package/src/vite.ts +0 -787
- package/src/widgets/index.ts +0 -10
- package/src/widgets/registry.ts +0 -45
- package/src/widgets.test.ts +0 -592
- package/tsconfig.build.json +0 -11
- package/tsconfig.json +0 -4
- package/tsconfig.test.json +0 -10
- package/views/react/Dashboard.tsx +0 -27
- package/views/react/Resources/Form.tsx +0 -102
- 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
|
-
}
|