@pilotiq/pilotiq 0.24.1 → 0.24.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -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/package.json +6 -1
- package/.turbo/turbo-build.log +0 -8
- package/CLAUDE.md +0 -265
- 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 -225
- 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 -194
- 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/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 -704
- package/src/routes/pages.ts +0 -175
- package/src/routes/panel.ts +0 -204
- package/src/routes/relations.ts +0 -1243
- 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,245 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import type { ElementMeta } from '../../../schema/Element.js'
|
|
3
|
-
import { withTooltip } from '../helpers.js'
|
|
4
|
-
import {
|
|
5
|
-
actionButtonClass,
|
|
6
|
-
renderActionBadge,
|
|
7
|
-
renderActionIcon,
|
|
8
|
-
type RenderActionOptions,
|
|
9
|
-
} from './buttons.js'
|
|
10
|
-
import { ActionGroupTrigger } from './ActionGroupTrigger.js'
|
|
11
|
-
import { ActionModalDialog } from './ActionModalDialog.js'
|
|
12
|
-
import { ConfirmActionDialog } from './ConfirmActionDialog.js'
|
|
13
|
-
import { HandlerActionButton } from './HandlerActionButton.js'
|
|
14
|
-
import { MethodActionButton } from './MethodActionButton.js'
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Function-prop deps for action rendering — injected from
|
|
18
|
-
* `SchemaRenderer.tsx`'s top-level dispatch. `renderElement` is needed
|
|
19
|
-
* for slot-component pass-through and modal-content-footer; `renderFormChild`
|
|
20
|
-
* is needed by `ActionModalDialog` for its form-body fields.
|
|
21
|
-
*/
|
|
22
|
-
export interface ActionRendererDeps {
|
|
23
|
-
renderElement: (el: ElementMeta, index: number) => React.ReactNode
|
|
24
|
-
renderFormChild: (child: ElementMeta, index: number, values: Record<string, unknown>, errors: Record<string, string[]>) => React.ReactNode
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Render either a single Action or an ActionGroup based on `el.type`.
|
|
28
|
-
* Used by callsites that accept both (table header / bulk toolbars,
|
|
29
|
-
* heading actions, container schemas). */
|
|
30
|
-
export function renderActionLike(
|
|
31
|
-
el: ElementMeta,
|
|
32
|
-
index: number,
|
|
33
|
-
opts: RenderActionOptions,
|
|
34
|
-
deps: ActionRendererDeps,
|
|
35
|
-
): React.ReactNode {
|
|
36
|
-
if (el.type === 'slotComponent') {
|
|
37
|
-
// Plugin-contributed React mount — render through the main element
|
|
38
|
-
// dispatcher, which looks up the registered component and forwards
|
|
39
|
-
// its serialised props bag. Keeps every action-row slot (heading
|
|
40
|
-
// children, alert footer, empty-state footer, table-toolbar bulk
|
|
41
|
-
// strip) usable as a plugin extension point.
|
|
42
|
-
return deps.renderElement(el, index)
|
|
43
|
-
}
|
|
44
|
-
if (el.type === 'actionGroup') {
|
|
45
|
-
return (
|
|
46
|
-
<ActionGroupTrigger
|
|
47
|
-
key={index}
|
|
48
|
-
el={el}
|
|
49
|
-
ids={opts.ids ?? []}
|
|
50
|
-
renderFormChild={deps.renderFormChild}
|
|
51
|
-
renderElement={deps.renderElement}
|
|
52
|
-
/>
|
|
53
|
-
)
|
|
54
|
-
}
|
|
55
|
-
return renderAction(el, index, opts, deps)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Render a single `Action` element. Dispatches on the Action's mode
|
|
59
|
-
* (submit / href / method / handler) and chrome (confirm, modal). */
|
|
60
|
-
export function renderAction(
|
|
61
|
-
el: ElementMeta,
|
|
62
|
-
index: number,
|
|
63
|
-
opts: RenderActionOptions,
|
|
64
|
-
deps: ActionRendererDeps,
|
|
65
|
-
): React.ReactNode {
|
|
66
|
-
const name = String(el['name'] ?? '')
|
|
67
|
-
const label = String(el['label'] ?? name)
|
|
68
|
-
const destructive = Boolean(el['destructive'])
|
|
69
|
-
const href = el['href'] as string | undefined
|
|
70
|
-
const method = el['method'] as 'post' | 'put' | 'patch' | 'delete' | undefined
|
|
71
|
-
const actionUrl = el['action'] as string | undefined
|
|
72
|
-
const dispatchUrl = el['dispatchUrl'] as string | undefined
|
|
73
|
-
const submit = Boolean(el['submit'])
|
|
74
|
-
const confirm = el['confirm'] as { title?: string; message: string } | undefined
|
|
75
|
-
const tooltip = el['tooltip'] as string | undefined
|
|
76
|
-
const iconOnly = Boolean(el['iconOnly'])
|
|
77
|
-
const isDisabled = Boolean(el['disabled'])
|
|
78
|
-
|
|
79
|
-
const className = actionButtonClass(el, opts) + (isDisabled ? ' opacity-50 cursor-not-allowed pointer-events-none' : '')
|
|
80
|
-
const icon = renderActionIcon(el)
|
|
81
|
-
const badge = renderActionBadge(el)
|
|
82
|
-
// Icon-only buttons hide the label visually but expose it via aria-label.
|
|
83
|
-
const ariaLabel = iconOnly ? label : undefined
|
|
84
|
-
const inner = iconOnly ? <>{icon}{badge}</> : <>{icon}<span>{label}</span>{badge}</>
|
|
85
|
-
|
|
86
|
-
// Submit-style action — renders as <button type="submit">. Optionally
|
|
87
|
-
// targets a specific form via the HTML `form="<id>"` attribute so the
|
|
88
|
-
// button can submit a form it lives outside of (e.g. a page-header
|
|
89
|
-
// Save button driving a form below). When `formField` is set, the
|
|
90
|
-
// button posts a sentinel name/value pair (e.g. `_continueCreate=1`)
|
|
91
|
-
// so the server can branch on which submit was clicked.
|
|
92
|
-
if (submit) {
|
|
93
|
-
const formTarget = el['form'] as string | undefined
|
|
94
|
-
const formField = el['formField'] as { name: string; value: string } | undefined
|
|
95
|
-
if (confirm) {
|
|
96
|
-
// Confirm-gated submit: render as type="button" so click opens the
|
|
97
|
-
// dialog instead of submitting; on confirm, programmatically submit
|
|
98
|
-
// the targeted form (or the closest enclosing form if no formTarget).
|
|
99
|
-
// `formField` is intentionally not threaded here — programmatic
|
|
100
|
-
// `requestSubmit()` has no submitter, so the name/value pair would
|
|
101
|
-
// be lost anyway. Pair `.confirm()` with a hidden input on the form
|
|
102
|
-
// if you need a sentinel under a confirm flow.
|
|
103
|
-
return (
|
|
104
|
-
<ConfirmActionDialog
|
|
105
|
-
key={index}
|
|
106
|
-
title={confirm.title}
|
|
107
|
-
message={confirm.message}
|
|
108
|
-
destructive={destructive}
|
|
109
|
-
onConfirm={() => {
|
|
110
|
-
if (typeof document === 'undefined') return
|
|
111
|
-
const form = formTarget
|
|
112
|
-
? document.getElementById(formTarget) as HTMLFormElement | null
|
|
113
|
-
: document.querySelector<HTMLFormElement>('form')
|
|
114
|
-
form?.requestSubmit()
|
|
115
|
-
}}
|
|
116
|
-
trigger={(open) => withTooltip(
|
|
117
|
-
<button
|
|
118
|
-
type="button"
|
|
119
|
-
onClick={open}
|
|
120
|
-
className={className}
|
|
121
|
-
data-action-name={name}
|
|
122
|
-
aria-label={ariaLabel}
|
|
123
|
-
>
|
|
124
|
-
{inner}
|
|
125
|
-
</button>,
|
|
126
|
-
tooltip,
|
|
127
|
-
)}
|
|
128
|
-
/>
|
|
129
|
-
)
|
|
130
|
-
}
|
|
131
|
-
return withTooltip(
|
|
132
|
-
<button
|
|
133
|
-
key={index}
|
|
134
|
-
type="submit"
|
|
135
|
-
form={formTarget}
|
|
136
|
-
className={className}
|
|
137
|
-
data-action-name={name}
|
|
138
|
-
aria-label={ariaLabel}
|
|
139
|
-
{...(formField ? { name: formField.name, value: formField.value } : {})}
|
|
140
|
-
>
|
|
141
|
-
{inner}
|
|
142
|
-
</button>,
|
|
143
|
-
tooltip,
|
|
144
|
-
)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Substitute the `:id` placeholder with the current row id when this
|
|
148
|
-
// action is rendered in a row context. Lets row-level link/form actions
|
|
149
|
-
// ship a single template URL like `/admin/articles/:id/edit`.
|
|
150
|
-
const rowId = opts.ids?.length === 1 ? opts.ids[0]! : undefined
|
|
151
|
-
const resolveTemplate = (s: string | undefined): string | undefined =>
|
|
152
|
-
s && rowId ? s.replace(':id', rowId) : s
|
|
153
|
-
|
|
154
|
-
// Link-style action.
|
|
155
|
-
if (href) {
|
|
156
|
-
return withTooltip(
|
|
157
|
-
<a
|
|
158
|
-
key={index}
|
|
159
|
-
href={resolveTemplate(href)}
|
|
160
|
-
className={className}
|
|
161
|
-
data-action-name={name}
|
|
162
|
-
aria-label={ariaLabel}
|
|
163
|
-
>
|
|
164
|
-
{inner}
|
|
165
|
-
</a>,
|
|
166
|
-
tooltip,
|
|
167
|
-
)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Form-style action (POST/PUT/PATCH/DELETE) — fetch + JSON, no full reload.
|
|
171
|
-
if (method) {
|
|
172
|
-
const resolvedUrl = resolveTemplate(actionUrl)
|
|
173
|
-
return (
|
|
174
|
-
<MethodActionButton
|
|
175
|
-
key={index}
|
|
176
|
-
url={resolvedUrl}
|
|
177
|
-
method={method}
|
|
178
|
-
confirm={confirm}
|
|
179
|
-
destructive={destructive}
|
|
180
|
-
className={className}
|
|
181
|
-
name={name}
|
|
182
|
-
ariaLabel={ariaLabel}
|
|
183
|
-
tooltip={tooltip}
|
|
184
|
-
inner={inner}
|
|
185
|
-
/>
|
|
186
|
-
)
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Handler-style action — fetch + JSON dispatch with `ids[]` body.
|
|
190
|
-
if (dispatchUrl) {
|
|
191
|
-
const ids = opts.ids ?? []
|
|
192
|
-
const modal = el['modal']
|
|
193
|
-
if (confirm || modal) {
|
|
194
|
-
return (
|
|
195
|
-
<ActionModalDialog
|
|
196
|
-
key={index}
|
|
197
|
-
meta={el}
|
|
198
|
-
ids={ids}
|
|
199
|
-
trigger={(open) => withTooltip(
|
|
200
|
-
<button
|
|
201
|
-
type="button"
|
|
202
|
-
onClick={open}
|
|
203
|
-
className={className}
|
|
204
|
-
data-action-name={name}
|
|
205
|
-
aria-label={ariaLabel}
|
|
206
|
-
>
|
|
207
|
-
{inner}
|
|
208
|
-
</button>,
|
|
209
|
-
tooltip,
|
|
210
|
-
)}
|
|
211
|
-
renderFormChild={deps.renderFormChild}
|
|
212
|
-
renderElement={deps.renderElement}
|
|
213
|
-
/>
|
|
214
|
-
)
|
|
215
|
-
}
|
|
216
|
-
return (
|
|
217
|
-
<HandlerActionButton
|
|
218
|
-
key={index}
|
|
219
|
-
url={dispatchUrl}
|
|
220
|
-
ids={ids}
|
|
221
|
-
className={className}
|
|
222
|
-
name={name}
|
|
223
|
-
ariaLabel={ariaLabel}
|
|
224
|
-
tooltip={tooltip}
|
|
225
|
-
inner={inner}
|
|
226
|
-
/>
|
|
227
|
-
)
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// No dispatch wired (no href / method / dispatchUrl). Render a disabled
|
|
231
|
-
// placeholder so the user sees the button, but it does nothing.
|
|
232
|
-
return withTooltip(
|
|
233
|
-
<button
|
|
234
|
-
key={index}
|
|
235
|
-
type="button"
|
|
236
|
-
disabled
|
|
237
|
-
className={className + ' opacity-50 cursor-not-allowed'}
|
|
238
|
-
data-action-name={name}
|
|
239
|
-
aria-label={ariaLabel}
|
|
240
|
-
>
|
|
241
|
-
{inner}
|
|
242
|
-
</button>,
|
|
243
|
-
tooltip,
|
|
244
|
-
)
|
|
245
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/** Apply a built-in `ColumnFormat` to a raw value; returns a string.
|
|
2
|
-
*
|
|
3
|
-
* Used by both `formatCell` (table layer) and `renderEntry` (infolist
|
|
4
|
-
* Phase #16). Hoisted out of the table block so EntryRenderer doesn't
|
|
5
|
-
* need to import across phases. */
|
|
6
|
-
export function applyColumnFormat(value: unknown, format: { kind: string; [k: string]: unknown }): string {
|
|
7
|
-
if (value === null || value === undefined || value === '') return ''
|
|
8
|
-
switch (format['kind']) {
|
|
9
|
-
case 'dateTime': {
|
|
10
|
-
const d = value instanceof Date ? value : new Date(String(value))
|
|
11
|
-
if (isNaN(d.getTime())) return String(value)
|
|
12
|
-
// Default — locale-aware short date+time. Custom patterns aren't
|
|
13
|
-
// supported (no date-fns dep); pattern is kept on meta for future use.
|
|
14
|
-
return d.toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' })
|
|
15
|
-
}
|
|
16
|
-
case 'since': {
|
|
17
|
-
const d = value instanceof Date ? value : new Date(String(value))
|
|
18
|
-
if (isNaN(d.getTime())) return String(value)
|
|
19
|
-
const seconds = Math.round((Date.now() - d.getTime()) / 1000)
|
|
20
|
-
const abs = Math.abs(seconds)
|
|
21
|
-
const past = seconds >= 0
|
|
22
|
-
const fmt = (n: number, unit: string): string =>
|
|
23
|
-
past ? `${n} ${unit}${n === 1 ? '' : 's'} ago` : `in ${n} ${unit}${n === 1 ? '' : 's'}`
|
|
24
|
-
if (abs < 60) return past ? 'just now' : 'in a moment'
|
|
25
|
-
if (abs < 3600) return fmt(Math.floor(abs / 60), 'minute')
|
|
26
|
-
if (abs < 86400) return fmt(Math.floor(abs / 3600), 'hour')
|
|
27
|
-
if (abs < 2592000) return fmt(Math.floor(abs / 86400), 'day')
|
|
28
|
-
if (abs < 31536000) return fmt(Math.floor(abs / 2592000), 'month')
|
|
29
|
-
return fmt(Math.floor(abs / 31536000), 'year')
|
|
30
|
-
}
|
|
31
|
-
case 'money': {
|
|
32
|
-
const n = typeof value === 'number' ? value : Number(value)
|
|
33
|
-
if (isNaN(n)) return String(value)
|
|
34
|
-
const currency = String(format['currency'] ?? 'USD')
|
|
35
|
-
const locale = format['locale'] as string | undefined
|
|
36
|
-
return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(n)
|
|
37
|
-
}
|
|
38
|
-
case 'numeric': {
|
|
39
|
-
const n = typeof value === 'number' ? value : Number(value)
|
|
40
|
-
if (isNaN(n)) return String(value)
|
|
41
|
-
const decimals = format['decimals'] as number | undefined
|
|
42
|
-
const locale = format['locale'] as string | undefined
|
|
43
|
-
const opts: Intl.NumberFormatOptions = {}
|
|
44
|
-
if (decimals !== undefined) {
|
|
45
|
-
opts.minimumFractionDigits = decimals
|
|
46
|
-
opts.maximumFractionDigits = decimals
|
|
47
|
-
}
|
|
48
|
-
return new Intl.NumberFormat(locale, opts).format(n)
|
|
49
|
-
}
|
|
50
|
-
case 'limit': {
|
|
51
|
-
const s = String(value)
|
|
52
|
-
const n = format['chars'] as number
|
|
53
|
-
return s.length > n ? s.slice(0, n) + '…' : s
|
|
54
|
-
}
|
|
55
|
-
case 'words': {
|
|
56
|
-
const s = String(value).trim()
|
|
57
|
-
if (s.length === 0) return s
|
|
58
|
-
const tokens = s.split(/\s+/)
|
|
59
|
-
const n = format['words'] as number
|
|
60
|
-
return tokens.length > n ? tokens.slice(0, n).join(' ') + '…' : s
|
|
61
|
-
}
|
|
62
|
-
default:
|
|
63
|
-
return String(value)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
// Tailwind-class lookup tables shared across SchemaRenderer's element
|
|
2
|
-
// dispatchers. Pure data — no React, no DOM.
|
|
3
|
-
|
|
4
|
-
export const TEXT_COLOR_CLASSES: Record<string, string> = {
|
|
5
|
-
default: '',
|
|
6
|
-
muted: 'text-muted-foreground',
|
|
7
|
-
primary: 'text-primary',
|
|
8
|
-
destructive: 'text-destructive',
|
|
9
|
-
success: 'text-emerald-600 dark:text-emerald-400',
|
|
10
|
-
warning: 'text-amber-600 dark:text-amber-400',
|
|
11
|
-
info: 'text-blue-600 dark:text-blue-400',
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const TEXT_SIZE_CLASSES: Record<string, string> = {
|
|
15
|
-
xs: 'text-xs',
|
|
16
|
-
sm: 'text-sm',
|
|
17
|
-
base: 'text-base',
|
|
18
|
-
lg: 'text-lg',
|
|
19
|
-
xl: 'text-xl',
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export const TEXT_WEIGHT_CLASSES: Record<string, string> = {
|
|
23
|
-
normal: 'font-normal',
|
|
24
|
-
medium: 'font-medium',
|
|
25
|
-
semibold: 'font-semibold',
|
|
26
|
-
bold: 'font-bold',
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// `ColumnColor` / `ColumnWeight` mirror the text-side scales 1:1. Kept as
|
|
30
|
-
// named aliases so column-cell call sites still read distinctly, but a
|
|
31
|
-
// single source-of-truth means adding a new color / weight only touches
|
|
32
|
-
// the text map.
|
|
33
|
-
export const COLUMN_COLOR_CLASSES = TEXT_COLOR_CLASSES
|
|
34
|
-
export const COLUMN_WEIGHT_CLASSES = TEXT_WEIGHT_CLASSES
|
|
35
|
-
|
|
36
|
-
export const BADGE_COLOR_CLASSES: Record<string, string> = {
|
|
37
|
-
gray: 'bg-muted text-muted-foreground',
|
|
38
|
-
primary: 'bg-primary/10 text-primary',
|
|
39
|
-
success: 'bg-emerald-100 text-emerald-800 dark:bg-emerald-900/40 dark:text-emerald-200',
|
|
40
|
-
warning: 'bg-amber-100 text-amber-800 dark:bg-amber-900/40 dark:text-amber-200',
|
|
41
|
-
destructive: 'bg-destructive/10 text-destructive',
|
|
42
|
-
info: 'bg-blue-100 text-blue-800 dark:bg-blue-900/40 dark:text-blue-200',
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export const alertStyles: Record<string, string> = {
|
|
46
|
-
info: 'border-blue-200 bg-blue-50 text-blue-800 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-200',
|
|
47
|
-
warning: 'border-amber-200 bg-amber-50 text-amber-800 dark:border-amber-800 dark:bg-amber-950 dark:text-amber-200',
|
|
48
|
-
success: 'border-green-200 bg-green-50 text-green-800 dark:border-green-800 dark:bg-green-950 dark:text-green-200',
|
|
49
|
-
danger: 'border-red-200 bg-red-50 text-red-800 dark:border-red-800 dark:bg-red-950 dark:text-red-200',
|
|
50
|
-
}
|
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
import React, { useRef, useState } from 'react'
|
|
2
|
-
import type { ElementMeta } from '../../../schema/Element.js'
|
|
3
|
-
import type { NotificationMeta } from '../../../notifications/Notification.js'
|
|
4
|
-
import { FormIdContext, FormStateProvider, useFormState } from '../../FormStateContext.js'
|
|
5
|
-
import { useCollabRoom } from '../../CollabRoomContext.js'
|
|
6
|
-
import { getFormCollabBinding } from '../../FormCollabBindingRegistry.js'
|
|
7
|
-
import { useNavigate } from '../../navigate.js'
|
|
8
|
-
import { useToast } from '../../Toaster.js'
|
|
9
|
-
import { markSubmitForReconcile } from '../../fields/repeaterReconcile.js'
|
|
10
|
-
import {
|
|
11
|
-
applyRelationshipRenames,
|
|
12
|
-
type RelationshipRenameEntry,
|
|
13
|
-
} from '../../fields/relationshipRenameDispatch.js'
|
|
14
|
-
import { renderField } from './renderField.js'
|
|
15
|
-
|
|
16
|
-
// ─── Form ───────────────────────────────────────────────────
|
|
17
|
-
|
|
18
|
-
type RenderElement = (el: ElementMeta, index: number) => React.ReactNode
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Top-level `<form>` element. Owns:
|
|
22
|
-
* - HTML form chrome (action, method, _method spoof, hidden formId)
|
|
23
|
-
* - Fetch + JSON submission with `Accept: application/json` (so the
|
|
24
|
-
* server can return 422 with field errors instead of re-rendering)
|
|
25
|
-
* - Inline error stamping (`_form` banner + per-field error strings)
|
|
26
|
-
* - `FormStateProvider` mount when the form has any reactive field
|
|
27
|
-
* (`live()` or `afterStateUpdatedJs`) and a `stateUrl` was stamped.
|
|
28
|
-
*
|
|
29
|
-
* `renderElement` is injected for non-field children inside the form
|
|
30
|
-
* body (cards / dividers / fieldsets / etc).
|
|
31
|
-
*/
|
|
32
|
-
export function FormRenderer({
|
|
33
|
-
el,
|
|
34
|
-
renderElement,
|
|
35
|
-
}: {
|
|
36
|
-
el: ElementMeta
|
|
37
|
-
renderElement: RenderElement
|
|
38
|
-
}) {
|
|
39
|
-
const formId = String(el['formId'] ?? '')
|
|
40
|
-
const method = String(el['method'] ?? 'post').toLowerCase()
|
|
41
|
-
const action = el['action'] ? String(el['action']) : undefined
|
|
42
|
-
const stateUrl = el['stateUrl'] ? String(el['stateUrl']) : undefined
|
|
43
|
-
const serverValues = (el['values'] as Record<string, unknown> | undefined) ?? {}
|
|
44
|
-
const serverErrors = (el['errors'] as Record<string, string[]> | undefined) ?? {}
|
|
45
|
-
|
|
46
|
-
// Phase F2 — the controlled-form path also activates when we're
|
|
47
|
-
// inside a `<RecordCollabRoom>` AND a plugin (e.g.
|
|
48
|
-
// `@pilotiq-pro/collab`) registered a `FormCollabBinding` factory.
|
|
49
|
-
// Without this, forms with no `live()` fields stayed uncontrolled
|
|
50
|
-
// and the collab binding never wired up.
|
|
51
|
-
const collabRoom = useCollabRoom()
|
|
52
|
-
const collabFactory = getFormCollabBinding()
|
|
53
|
-
const collabActive = !!(collabRoom && collabFactory && formId)
|
|
54
|
-
const useControlled = !!stateUrl || collabActive
|
|
55
|
-
|
|
56
|
-
// Methods other than GET/POST are spoofed via _method, mirroring Laravel.
|
|
57
|
-
const httpMethod = method === 'get' ? 'get' : 'post'
|
|
58
|
-
const spoofedMethod = method !== 'get' && method !== 'post' ? method : undefined
|
|
59
|
-
|
|
60
|
-
const navigate = useNavigate()
|
|
61
|
-
const { notify } = useToast()
|
|
62
|
-
|
|
63
|
-
// Client-side errors override server-rendered ones after a fetch-mode
|
|
64
|
-
// 422 response. Field values stay uncontrolled — the inputs in the DOM
|
|
65
|
-
// still hold whatever the user typed, so we don't need to mirror them.
|
|
66
|
-
const [clientErrors, setClientErrors] = useState<Record<string, string[]> | null>(null)
|
|
67
|
-
const [submitting, setSubmitting] = useState(false)
|
|
68
|
-
const errors = clientErrors ?? serverErrors
|
|
69
|
-
|
|
70
|
-
// Plan #14 — formRef is threaded into FormStateProvider so live triggers
|
|
71
|
-
// can snapshot the form's full DOM state via FormData (captures
|
|
72
|
-
// uncontrolled inner-Repeater inputs that don't participate in the
|
|
73
|
-
// controlled values map).
|
|
74
|
-
const formRef = useRef<HTMLFormElement | null>(null)
|
|
75
|
-
|
|
76
|
-
const formErrors = errors['_form'] ?? []
|
|
77
|
-
const hasFieldErrors = Object.keys(errors).some(k => k !== '_form')
|
|
78
|
-
|
|
79
|
-
const onSubmit = async (e: React.FormEvent<HTMLFormElement>): Promise<void> => {
|
|
80
|
-
if (!action) return // no action URL → fall through to native submit
|
|
81
|
-
e.preventDefault()
|
|
82
|
-
if (submitting) return
|
|
83
|
-
setSubmitting(true)
|
|
84
|
-
setClientErrors(null)
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
// Thread `event.submitter` so the clicked submit button's
|
|
88
|
-
// name/value pair lands in the FormData. Without this, secondary
|
|
89
|
-
// submits like "Create & create another" can't signal which
|
|
90
|
-
// button fired through the body. Supported in all evergreen
|
|
91
|
-
// browsers since 2022; cast through `as any` because TS lib.dom
|
|
92
|
-
// hasn't picked up the optional submitter argument on every
|
|
93
|
-
// version.
|
|
94
|
-
const submitter = (e.nativeEvent as SubmitEvent).submitter as HTMLElement | null
|
|
95
|
-
const fd = new (FormData as any)(e.currentTarget, submitter ?? undefined) as FormData
|
|
96
|
-
const res = await fetch(action, {
|
|
97
|
-
method: 'POST',
|
|
98
|
-
headers: { 'Accept': 'application/json' },
|
|
99
|
-
body: fd,
|
|
100
|
-
})
|
|
101
|
-
const data = await res.json().catch(() => ({}))
|
|
102
|
-
|
|
103
|
-
if (res.status === 422) {
|
|
104
|
-
const next = (data as { errors?: Record<string, string[]> }).errors ?? {}
|
|
105
|
-
setClientErrors(next)
|
|
106
|
-
// Surface a banner-level message if no field errors were returned
|
|
107
|
-
// — the form-level _form key lights up the existing banner.
|
|
108
|
-
setSubmitting(false)
|
|
109
|
-
return
|
|
110
|
-
}
|
|
111
|
-
if (!res.ok) {
|
|
112
|
-
const message = String((data as { error?: string }).error ?? `Request failed (${res.status})`)
|
|
113
|
-
notify({ type: 'error', title: 'Save failed', body: message })
|
|
114
|
-
setSubmitting(false)
|
|
115
|
-
return
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Success — drain notifications and SPA-navigate to the redirect.
|
|
119
|
-
//
|
|
120
|
-
// Apply PK-switch Phase B renames against the active form's collab
|
|
121
|
-
// binding FIRST (synchronous Yjs transact under the hood). The
|
|
122
|
-
// server emits `relationshipRenames: { field, old, new }[]` for
|
|
123
|
-
// every relationship-backed row that persisted under a new PK; we
|
|
124
|
-
// forward each through the formId-keyed dispatch so the binding's
|
|
125
|
-
// `renameRow` runs before the navigate destroys the provider that
|
|
126
|
-
// owns it. No-op when no binding is registered (no collab plugin)
|
|
127
|
-
// OR when the array is empty (no relationship-backed creates this
|
|
128
|
-
// submit). The submitter-only Phase A reconciler still fires below
|
|
129
|
-
// — Phase B closes the multi-peer gap WITHOUT obviating the
|
|
130
|
-
// single-peer cleanup path.
|
|
131
|
-
const renames = (data as { relationshipRenames?: RelationshipRenameEntry[] })
|
|
132
|
-
.relationshipRenames
|
|
133
|
-
applyRelationshipRenames(formId, renames)
|
|
134
|
-
|
|
135
|
-
// Before navigating, mark this tab for the relationship-backed
|
|
136
|
-
// Repeater/Builder PK-switch reconciler. The next mount of any
|
|
137
|
-
// child Repeater/Builder under this formId will run a one-shot
|
|
138
|
-
// CRDT reconcile to drop orphan UUIDs whose rows just persisted
|
|
139
|
-
// under a fresh DB PK. See
|
|
140
|
-
// `pilotiq-pro/docs/plans/repeater-relationship-pk-switch.md`
|
|
141
|
-
// (Phase A).
|
|
142
|
-
markSubmitForReconcile(formId)
|
|
143
|
-
const notifs = (data as { notifications?: NotificationMeta[] }).notifications
|
|
144
|
-
if (notifs && notifs.length > 0) for (const n of notifs) notify(n)
|
|
145
|
-
const redirect = String((data as { redirect?: string }).redirect ?? '')
|
|
146
|
-
// The server may force a navigate even when the redirect equals
|
|
147
|
-
// the current URL — used by "Create & create another" so the
|
|
148
|
-
// form remounts with empty defaults instead of preserving the
|
|
149
|
-
// just-submitted values. Otherwise: skip navigate when the
|
|
150
|
-
// redirect matches the current URL, since re-fetching the same
|
|
151
|
-
// page would force a form remount and reset scroll.
|
|
152
|
-
const force = Boolean((data as { force?: boolean }).force)
|
|
153
|
-
const currentUrl = typeof window !== 'undefined'
|
|
154
|
-
? window.location.pathname + window.location.search
|
|
155
|
-
: ''
|
|
156
|
-
if (redirect && (force || redirect !== currentUrl)) {
|
|
157
|
-
navigate(redirect)
|
|
158
|
-
// Don't reset submitting on success — the navigation will unmount us.
|
|
159
|
-
} else {
|
|
160
|
-
setSubmitting(false)
|
|
161
|
-
}
|
|
162
|
-
} catch (err) {
|
|
163
|
-
notify({ type: 'error', title: 'Save failed', body: err instanceof Error ? err.message : String(err) })
|
|
164
|
-
setSubmitting(false)
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return (
|
|
169
|
-
<form
|
|
170
|
-
ref={formRef}
|
|
171
|
-
id={formId || undefined}
|
|
172
|
-
data-form-id={formId || undefined}
|
|
173
|
-
method={httpMethod}
|
|
174
|
-
action={action}
|
|
175
|
-
onSubmit={onSubmit}
|
|
176
|
-
className="flex flex-col gap-6"
|
|
177
|
-
>
|
|
178
|
-
{formId && <input type="hidden" name="_formId" value={formId} />}
|
|
179
|
-
{spoofedMethod && <input type="hidden" name="_method" value={spoofedMethod} />}
|
|
180
|
-
{(formErrors.length > 0 || hasFieldErrors) && (
|
|
181
|
-
<div className="rounded-lg border border-destructive/40 bg-destructive/5 text-destructive p-3 text-sm">
|
|
182
|
-
{formErrors.length > 0 ? (
|
|
183
|
-
<ul className="list-disc pl-4">
|
|
184
|
-
{formErrors.map((msg, i) => <li key={i}>{msg}</li>)}
|
|
185
|
-
</ul>
|
|
186
|
-
) : (
|
|
187
|
-
'Please correct the errors below.'
|
|
188
|
-
)}
|
|
189
|
-
</div>
|
|
190
|
-
)}
|
|
191
|
-
<FormIdContext.Provider value={formId}>
|
|
192
|
-
{useControlled ? (
|
|
193
|
-
<FormStateProvider initialMeta={el} initialErrors={errors} formRef={formRef}>
|
|
194
|
-
<FormBody
|
|
195
|
-
fallbackChildren={el.children ?? []}
|
|
196
|
-
fallbackValues={serverValues}
|
|
197
|
-
fallbackErrors={errors}
|
|
198
|
-
renderElement={renderElement}
|
|
199
|
-
/>
|
|
200
|
-
</FormStateProvider>
|
|
201
|
-
) : (
|
|
202
|
-
(el.children ?? []).map((child, i) => renderFormChild(child, i, serverValues, errors, renderElement))
|
|
203
|
-
)}
|
|
204
|
-
</FormIdContext.Provider>
|
|
205
|
-
</form>
|
|
206
|
-
)
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Renders the controlled-form's children, sourcing them from the
|
|
211
|
-
* `FormStateProvider`'s current `formMeta` (which gets replaced after
|
|
212
|
-
* each live POST). Falls back to the props if (somehow) used outside a
|
|
213
|
-
* provider — the shell only mounts this when `stateUrl` is set so the
|
|
214
|
-
* fallback path is dead code in practice, but keeping it defensive.
|
|
215
|
-
*/
|
|
216
|
-
function FormBody({
|
|
217
|
-
fallbackChildren, fallbackValues, fallbackErrors, renderElement,
|
|
218
|
-
}: {
|
|
219
|
-
fallbackChildren: ElementMeta[]
|
|
220
|
-
fallbackValues: Record<string, unknown>
|
|
221
|
-
fallbackErrors: Record<string, string[]>
|
|
222
|
-
renderElement: RenderElement
|
|
223
|
-
}): React.ReactElement {
|
|
224
|
-
const ctx = useFormState()
|
|
225
|
-
if (!ctx) {
|
|
226
|
-
return <>{fallbackChildren.map((child, i) => renderFormChild(child, i, fallbackValues, fallbackErrors, renderElement))}</>
|
|
227
|
-
}
|
|
228
|
-
const children = (ctx.formMeta.children ?? []) as ElementMeta[]
|
|
229
|
-
return <>{children.map((child, i) => renderFormChild(child, i, ctx.values, ctx.errors, renderElement))}</>
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Render one child of a form's resolved schema with per-field values + errors.
|
|
234
|
-
*
|
|
235
|
-
* Field elements wrap in error-stamp chrome; non-field children fall
|
|
236
|
-
* through to `renderElement` so the form body can host cards / dividers /
|
|
237
|
-
* fieldsets / etc.
|
|
238
|
-
*/
|
|
239
|
-
export function renderFormChild(
|
|
240
|
-
child: ElementMeta,
|
|
241
|
-
index: number,
|
|
242
|
-
values: Record<string, unknown>,
|
|
243
|
-
errors: Record<string, string[]>,
|
|
244
|
-
renderElement: RenderElement,
|
|
245
|
-
): React.ReactNode {
|
|
246
|
-
if (child.type === 'field') {
|
|
247
|
-
const name = String(child['name'] ?? '')
|
|
248
|
-
const fieldErrors = errors[name] ?? []
|
|
249
|
-
const value = values[name]
|
|
250
|
-
return (
|
|
251
|
-
<div key={index} className="flex flex-col gap-1">
|
|
252
|
-
{renderFieldWithValue(child, index, value, renderElement)}
|
|
253
|
-
{fieldErrors.map((msg, i) => (
|
|
254
|
-
<p key={i} className="text-xs text-destructive">{msg}</p>
|
|
255
|
-
))}
|
|
256
|
-
</div>
|
|
257
|
-
)
|
|
258
|
-
}
|
|
259
|
-
return renderElement(child, index)
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
function renderFieldWithValue(
|
|
263
|
-
el: ElementMeta,
|
|
264
|
-
index: number,
|
|
265
|
-
value: unknown,
|
|
266
|
-
renderElement: RenderElement,
|
|
267
|
-
): React.ReactNode {
|
|
268
|
-
// The form-state value (from `withValues` / record-fill) wins when present;
|
|
269
|
-
// otherwise the meta's own `defaultValue` (Plan #6 `Field.default()`) survives.
|
|
270
|
-
const enriched: ElementMeta = value !== undefined
|
|
271
|
-
? { ...el, defaultValue: value }
|
|
272
|
-
: el
|
|
273
|
-
return renderField(enriched, index, renderElement)
|
|
274
|
-
}
|