@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,745 +0,0 @@
|
|
|
1
|
-
import { Element, type ElementMeta } from '../schema/Element.js'
|
|
2
|
-
import { Table, type TableContext, type SortDirection } from './Table.js'
|
|
3
|
-
import { TableGroup, bucketDateValue, formatDateBucketTitle } from './TableGroup.js'
|
|
4
|
-
import type { Filter } from '../filters/Filter.js'
|
|
5
|
-
import { Action } from '../actions/Action.js'
|
|
6
|
-
import { Column, type ColumnSelectOption } from '../Column.js'
|
|
7
|
-
import { SelectColumn, normalizeSelectOptions, type SelectColumnOptionsResolver } from '../columns/SelectColumn.js'
|
|
8
|
-
import { ListTab } from '../Tab.js'
|
|
9
|
-
import { isRepeaterField } from '../fields/RepeaterField.js'
|
|
10
|
-
import { isBuilderField } from '../fields/BuilderField.js'
|
|
11
|
-
import { tryRenderRichText, getRichTextRenderer } from '../richtext/registry.js'
|
|
12
|
-
import { resolveSchema, type RenderContext } from '../schema/resolveSchema.js'
|
|
13
|
-
import { sanitizeHtml } from '../schema/sanitize.js'
|
|
14
|
-
import { marked } from 'marked'
|
|
15
|
-
import type { SummaryResult } from '../summarizers/Summarizer.js'
|
|
16
|
-
|
|
17
|
-
export interface QueryParams {
|
|
18
|
-
search?: string
|
|
19
|
-
sort?: string // "col:dir" or "col"
|
|
20
|
-
page?: string | number
|
|
21
|
-
perPage?: string | number
|
|
22
|
-
/** Filter values keyed by filter name. Any URL query key not in the
|
|
23
|
-
* reserved set above is treated as a candidate filter value. */
|
|
24
|
-
[key: string]: unknown
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const RESERVED_QUERY_KEYS = new Set(['search', 'sort', 'page', 'perPage', 'group', 'groupKey'])
|
|
28
|
-
|
|
29
|
-
export function prefixedKey(prefix: string | undefined, key: string): string {
|
|
30
|
-
return prefix === undefined || prefix === '' ? key : `${prefix}_${key}`
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function prefixedReservedKeys(prefix: string | undefined): Set<string> {
|
|
34
|
-
if (prefix === undefined || prefix === '') return RESERVED_QUERY_KEYS
|
|
35
|
-
return new Set([...RESERVED_QUERY_KEYS].map(k => `${prefix}_${k}`))
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Optional hooks passed by the caller (`pageData.resourceIndexData`)
|
|
40
|
-
* to plug Resource-level concerns into the table dispatcher without
|
|
41
|
-
* giving it a hard dependency on `Resource`.
|
|
42
|
-
*
|
|
43
|
-
* `canEdit` is consulted once per row when the table has at least one
|
|
44
|
-
* editable column (`TextInputColumn / ToggleColumn / SelectColumn`) —
|
|
45
|
-
* the result gates per-cell edit affordances. Custom-page tables and
|
|
46
|
-
* relation-manager tables (v1) pass `undefined` and skip per-cell edit.
|
|
47
|
-
*/
|
|
48
|
-
export interface LoadTableHooks {
|
|
49
|
-
canEdit?: (
|
|
50
|
-
user: unknown,
|
|
51
|
-
record: Record<string, unknown>,
|
|
52
|
-
) => boolean | Promise<boolean>
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function parseFilterValues(
|
|
56
|
-
query: QueryParams,
|
|
57
|
-
filters: ReadonlyArray<Filter>,
|
|
58
|
-
prefix?: string,
|
|
59
|
-
): Record<string, string> {
|
|
60
|
-
if (filters.length === 0) return {}
|
|
61
|
-
const filterNames = new Set(filters.map(f => f.name))
|
|
62
|
-
const reserved = prefixedReservedKeys(prefix)
|
|
63
|
-
const stripPrefix = prefix !== undefined && prefix !== ''
|
|
64
|
-
? `${prefix}_`
|
|
65
|
-
: ''
|
|
66
|
-
const out: Record<string, string> = {}
|
|
67
|
-
for (const [key, val] of Object.entries(query)) {
|
|
68
|
-
if (reserved.has(key)) continue
|
|
69
|
-
// Strip the table's prefix when present; un-prefixed keys belong to
|
|
70
|
-
// some other namespace on the page so they can't match this table's
|
|
71
|
-
// filters.
|
|
72
|
-
let logicalKey = key
|
|
73
|
-
if (stripPrefix !== '') {
|
|
74
|
-
if (!key.startsWith(stripPrefix)) continue
|
|
75
|
-
logicalKey = key.slice(stripPrefix.length)
|
|
76
|
-
}
|
|
77
|
-
if (!filterNames.has(logicalKey)) continue
|
|
78
|
-
if (typeof val === 'string' && val !== '') out[logicalKey] = val
|
|
79
|
-
}
|
|
80
|
-
return out
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function parseTableQuery(q: QueryParams = {}, prefix?: string): {
|
|
84
|
-
search: string | undefined
|
|
85
|
-
sort: { column: string; direction: SortDirection } | undefined
|
|
86
|
-
page: number | undefined
|
|
87
|
-
perPage: number | undefined
|
|
88
|
-
} {
|
|
89
|
-
const k = (key: string): string => prefixedKey(prefix, key)
|
|
90
|
-
const searchRaw = q[k('search')]
|
|
91
|
-
const search = typeof searchRaw === 'string' && searchRaw.trim() !== ''
|
|
92
|
-
? searchRaw.trim()
|
|
93
|
-
: undefined
|
|
94
|
-
|
|
95
|
-
let sort: { column: string; direction: SortDirection } | undefined
|
|
96
|
-
const sortRaw = q[k('sort')]
|
|
97
|
-
if (typeof sortRaw === 'string' && sortRaw.trim() !== '') {
|
|
98
|
-
const [colRaw, dirRaw] = sortRaw.split(':')
|
|
99
|
-
const column = colRaw?.trim()
|
|
100
|
-
if (column) {
|
|
101
|
-
const direction: SortDirection = dirRaw?.trim() === 'desc' ? 'desc' : 'asc'
|
|
102
|
-
sort = { column, direction }
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const pageRaw = q[k('page')]
|
|
107
|
-
const page = pageRaw !== undefined
|
|
108
|
-
? Math.max(1, Math.floor(Number(pageRaw)) || 1)
|
|
109
|
-
: undefined
|
|
110
|
-
|
|
111
|
-
const perPageRaw = q[k('perPage')]
|
|
112
|
-
const perPage = perPageRaw !== undefined && Number(perPageRaw) > 0
|
|
113
|
-
? Math.floor(Number(perPageRaw))
|
|
114
|
-
: undefined
|
|
115
|
-
|
|
116
|
-
return { search, sort, page, perPage }
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Reconcile the URL `?group=` key against a table's configured
|
|
121
|
-
* `defaultGroup` + `groups([...])` registration. Returns the column
|
|
122
|
-
* the table should band rows by for this request, or `undefined` for
|
|
123
|
-
* "no grouping".
|
|
124
|
-
*
|
|
125
|
-
* Selection rules:
|
|
126
|
-
*
|
|
127
|
-
* `?group=col` → that column, IF it's registered or matches
|
|
128
|
-
* the bare `defaultGroup` (otherwise no grouping).
|
|
129
|
-
* `?group=` (empty) → no grouping (overrides defaultGroup).
|
|
130
|
-
* absent → fall back to `defaultGroup`.
|
|
131
|
-
*/
|
|
132
|
-
export function parseActiveGroup(
|
|
133
|
-
query: QueryParams,
|
|
134
|
-
table: Table,
|
|
135
|
-
prefix?: string,
|
|
136
|
-
): string | undefined {
|
|
137
|
-
const raw = query[prefixedKey(prefix, 'group')]
|
|
138
|
-
if (typeof raw === 'string') {
|
|
139
|
-
if (raw === '') return undefined // explicit clear
|
|
140
|
-
const registered = table.getGroups().map(g => g.getColumn())
|
|
141
|
-
const fallback = table.getDefaultGroup()
|
|
142
|
-
// Trust either an explicit registration OR the bare-column form.
|
|
143
|
-
// Unknown columns → treat as no grouping (silent — stale bookmark).
|
|
144
|
-
if (registered.includes(raw)) return raw
|
|
145
|
-
if (fallback === raw) return raw
|
|
146
|
-
return undefined
|
|
147
|
-
}
|
|
148
|
-
return table.getDefaultGroup()
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Read the drilled-in group key from the URL. Returns the key string when
|
|
153
|
-
* the URL carries a value AND the active group exists AND is `scopable`;
|
|
154
|
-
* `undefined` otherwise (stale bookmark, group dropped, or never set).
|
|
155
|
-
*
|
|
156
|
-
* The active column resolution runs first so the drill-in state can be
|
|
157
|
-
* silently dropped without affecting the parent group's reconciliation —
|
|
158
|
-
* a stale `groupKey` from a renamed-column or non-scopable group falls
|
|
159
|
-
* through to "no drill-in", which is the same as the user not having
|
|
160
|
-
* clicked a heading.
|
|
161
|
-
*/
|
|
162
|
-
export function parseActiveGroupKey(
|
|
163
|
-
query: QueryParams,
|
|
164
|
-
table: Table,
|
|
165
|
-
activeColumn: string | undefined,
|
|
166
|
-
prefix?: string,
|
|
167
|
-
): string | undefined {
|
|
168
|
-
if (activeColumn === undefined) return undefined
|
|
169
|
-
const raw = query[prefixedKey(prefix, 'groupKey')]
|
|
170
|
-
if (typeof raw !== 'string') return undefined
|
|
171
|
-
if (raw === '') return undefined // explicit clear
|
|
172
|
-
const group = table.getGroups().find(g => g.getColumn() === activeColumn)
|
|
173
|
-
// Bare-column groups (synthesized from defaultGroup with no entry in
|
|
174
|
-
// `groups([…])`) can't be scopable — `.scopeQueryByKey()` is a builder
|
|
175
|
-
// call. Drop the drill-in silently.
|
|
176
|
-
if (!group || !group.isScopable()) return undefined
|
|
177
|
-
return raw
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/** Walk an Element tree and return every `Table` instance in document order. */
|
|
181
|
-
export function findTables(elements: ReadonlyArray<Element>): Table[] {
|
|
182
|
-
const tables: Table[] = []
|
|
183
|
-
const walk = (els: ReadonlyArray<Element>): void => {
|
|
184
|
-
for (const el of els) {
|
|
185
|
-
if (el instanceof Table) tables.push(el)
|
|
186
|
-
// Plan #14 — Tables inside Repeater / Builder rows aren't
|
|
187
|
-
// supported in v1. Stop at the array-row boundary so the parent
|
|
188
|
-
// table dispatcher doesn't pick them up.
|
|
189
|
-
if (isRepeaterField(el)) continue
|
|
190
|
-
if (isBuilderField(el)) continue
|
|
191
|
-
const children = el.getChildren()
|
|
192
|
-
if (children && children.length > 0) walk(children)
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
walk(elements)
|
|
196
|
-
return tables
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* For every `Table` on the page that has a `records()` handler, run it
|
|
201
|
-
* with the parsed `TableContext` and seed the table's render-time state
|
|
202
|
-
* (rows, total, sort, search, page).
|
|
203
|
-
*
|
|
204
|
-
* Tables without a `records()` handler are left untouched — `toMeta()`
|
|
205
|
-
* will emit them with no rows, which the renderer falls back to as
|
|
206
|
-
* "No records yet."
|
|
207
|
-
*
|
|
208
|
-
* `pathname` is the absolute route the page lives at (e.g.
|
|
209
|
-
* `/admin/articles`). Sort, search, and pagination links in the rendered
|
|
210
|
-
* table prefix with it so SPA navigation has a real pathname to route
|
|
211
|
-
* against — Vike's client-side router doesn't resolve `?qs`-only
|
|
212
|
-
* relative hrefs against the current URL.
|
|
213
|
-
*/
|
|
214
|
-
export async function loadTableRecords(
|
|
215
|
-
elements: ReadonlyArray<Element>,
|
|
216
|
-
query: QueryParams = {},
|
|
217
|
-
pathname?: string,
|
|
218
|
-
user?: unknown,
|
|
219
|
-
hooks?: LoadTableHooks,
|
|
220
|
-
): Promise<void> {
|
|
221
|
-
const tables = findTables(elements)
|
|
222
|
-
if (tables.length === 0) return
|
|
223
|
-
|
|
224
|
-
await Promise.all(tables.map(async (table) => {
|
|
225
|
-
const prefix = table.getQueryStringIdentifier()
|
|
226
|
-
const { search, sort, page, perPage } = parseTableQuery(query, prefix)
|
|
227
|
-
|
|
228
|
-
// Carry per-table defaults forward when the URL didn't override them.
|
|
229
|
-
// Reorderable tables fall back to `(reorderableColumn, asc)` when no
|
|
230
|
-
// explicit `defaultSort` is set, so the visible order matches the
|
|
231
|
-
// persisted column even before the user clicks a header.
|
|
232
|
-
const reorderFallback = table.isReorderable() && !table.getDefaultSort()
|
|
233
|
-
? { column: table.getReorderableColumn()!, direction: 'asc' as const }
|
|
234
|
-
: undefined
|
|
235
|
-
const effectiveSort = sort ?? table.getDefaultSort() ?? reorderFallback
|
|
236
|
-
const effectivePerPage = perPage ?? table.getPerPage()
|
|
237
|
-
const effectivePage = page ?? 1
|
|
238
|
-
|
|
239
|
-
// Parse filter values from the URL query. Mirror them back onto the
|
|
240
|
-
// Filter elements so the renderer can show the active selection, and
|
|
241
|
-
// pass them through TableContext for the records handler to consume.
|
|
242
|
-
const tableFilters = table.getFilters()
|
|
243
|
-
const filterValues = parseFilterValues(query, tableFilters, prefix)
|
|
244
|
-
for (const filter of tableFilters) {
|
|
245
|
-
const v = filterValues[filter.name]
|
|
246
|
-
if (v !== undefined) filter.withValue(v)
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Thread the active list-page tab + its query modifier through to the
|
|
250
|
-
// records handler. The active tab was marked by `pageData.resolveActiveTab`
|
|
251
|
-
// (`tab.withActive(true)`) before `loadTableRecords` was called, so we
|
|
252
|
-
// just walk the schema for it. User-defined `Table.records(fn)`
|
|
253
|
-
// handlers can branch on `ctx.tab`; the model adapter consults
|
|
254
|
-
// `ctx.tabQuery` to splice the predicate into its ORM query chain.
|
|
255
|
-
const activeTab = findActiveTab(elements)
|
|
256
|
-
|
|
257
|
-
// Reconcile group + drill-in BEFORE building ctx so `groupScope` can
|
|
258
|
-
// be threaded into the records handler (model adapter splices the
|
|
259
|
-
// scoper after filters). A live drill-in also resets the page to 1
|
|
260
|
-
// server-side — clicking a heading should reliably land on the first
|
|
261
|
-
// page of the bucket regardless of where the user was paginated to.
|
|
262
|
-
const activeGroupCol = parseActiveGroup(query, table, prefix)
|
|
263
|
-
// Stamp the reconciled column onto the table early so
|
|
264
|
-
// `getActiveGroupInstance()` can resolve to a registered group (or
|
|
265
|
-
// synthesize a bare-column instance) consistently across the file.
|
|
266
|
-
table.withActiveGroup(activeGroupCol ?? '')
|
|
267
|
-
const activeGroup = table.getActiveGroupInstance()
|
|
268
|
-
const drilledKey = parseActiveGroupKey(query, table, activeGroupCol, prefix)
|
|
269
|
-
const effectivePageWithDrill = drilledKey !== undefined ? 1 : effectivePage
|
|
270
|
-
|
|
271
|
-
const ctx: TableContext = {
|
|
272
|
-
...(search !== undefined ? { search } : {}),
|
|
273
|
-
...(effectiveSort !== undefined ? { sort: effectiveSort } : {}),
|
|
274
|
-
...(effectivePerPage !== undefined ? { perPage: effectivePerPage } : {}),
|
|
275
|
-
...(Object.keys(filterValues).length > 0 ? { filters: filterValues } : {}),
|
|
276
|
-
...(activeTab ? { tab: activeTab.name } : {}),
|
|
277
|
-
...(activeTab?.getQuery() ? { tabQuery: activeTab.getQuery()! } : {}),
|
|
278
|
-
...(user != null ? { user } : {}),
|
|
279
|
-
...(drilledKey !== undefined && activeGroup !== undefined
|
|
280
|
-
? { groupScope: { group: activeGroup, key: drilledKey } }
|
|
281
|
-
: {}),
|
|
282
|
-
page: effectivePageWithDrill,
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Apply the tab's TableContext customizer last (escape hatch — wins
|
|
286
|
-
// over filters/tab/etc. that we just set above).
|
|
287
|
-
const ctxFn = activeTab?.getContextFn()
|
|
288
|
-
const finalCtx = ctxFn ? ctxFn(ctx) : ctx
|
|
289
|
-
|
|
290
|
-
// Skip records + per-row work when the table is deferred (the client
|
|
291
|
-
// refetches via `tableUrl`); URL state still mirrors below so the
|
|
292
|
-
// skeleton frame keeps current sort / search / page accurate.
|
|
293
|
-
const handler = table.getRecords()
|
|
294
|
-
if (handler && !table.isDeferred()) {
|
|
295
|
-
const result = await handler(finalCtx)
|
|
296
|
-
const rawRows = Array.isArray(result) ? result : result.rows
|
|
297
|
-
const total = Array.isArray(result) ? rawRows.length : (result.total ?? rawRows.length)
|
|
298
|
-
|
|
299
|
-
// Per-row visibility evaluation for row-placement actions with rules.
|
|
300
|
-
// Static row actions (no rules) are always visible/enabled, so we
|
|
301
|
-
// skip stamping on rows when none of the table's row actions opt in.
|
|
302
|
-
const rowActionsWithRules = (table.getChildren() ?? [])
|
|
303
|
-
.filter((c): c is Action =>
|
|
304
|
-
c instanceof Action
|
|
305
|
-
&& c.getPlacement() === 'row'
|
|
306
|
-
&& c.hasVisibilityRules(),
|
|
307
|
-
)
|
|
308
|
-
|
|
309
|
-
// Per-row server-side `formatStateUsing` eval. Columns with a
|
|
310
|
-
// formatter (the function isn't serializable) get their result
|
|
311
|
-
// stashed under `row._formatted[columnName]` so the renderer can
|
|
312
|
-
// pick it up without re-running the function client-side.
|
|
313
|
-
const columnsWithFormatter = (table.getChildren() ?? [])
|
|
314
|
-
.filter((c): c is Column => c instanceof Column && c.hasFormatter())
|
|
315
|
-
|
|
316
|
-
// Plain-text columns that may carry Tiptap rich-text content.
|
|
317
|
-
// Auto-rendered by the registered richtext renderer (Phase D —
|
|
318
|
-
// wired by `registerTiptap()`). Conservative: only default-text
|
|
319
|
-
// columns without an explicit `formatStateUsing` / `format` /
|
|
320
|
-
// editable-input override. Without a registered renderer the
|
|
321
|
-
// per-row pass is skipped entirely so non-tiptap apps pay nothing.
|
|
322
|
-
const richTextColumns = getRichTextRenderer() !== undefined
|
|
323
|
-
? (table.getChildren() ?? [])
|
|
324
|
-
.filter((c): c is Column =>
|
|
325
|
-
c instanceof Column
|
|
326
|
-
&& c.getColumnType() === 'text'
|
|
327
|
-
&& !c.hasFormatter()
|
|
328
|
-
&& !c.hasFormat()
|
|
329
|
-
&& !c.isEditable()
|
|
330
|
-
&& !c.isRichText(),
|
|
331
|
-
)
|
|
332
|
-
: []
|
|
333
|
-
|
|
334
|
-
// Markdown / HTML columns — opt-in via `Column.markdown() /
|
|
335
|
-
// Column.html()`. Each row's value gets server-rendered and the
|
|
336
|
-
// resulting HTML stamps `_formatted[col.name]` +
|
|
337
|
-
// `_richtextCells[col.name] = true` so the renderer mounts the
|
|
338
|
-
// existing prose-sm `dangerouslySetInnerHTML` path.
|
|
339
|
-
const explicitRichTextColumns = (table.getChildren() ?? [])
|
|
340
|
-
.filter((c): c is Column => c instanceof Column && c.isRichText())
|
|
341
|
-
|
|
342
|
-
// Columns with their own per-row recordUrl handler — overrides
|
|
343
|
-
// the table-level `Table.recordUrl` for clicks on that column.
|
|
344
|
-
const columnsWithRecordUrl = (table.getChildren() ?? [])
|
|
345
|
-
.filter((c): c is Column => c instanceof Column && c.hasRecordUrlHandler())
|
|
346
|
-
|
|
347
|
-
// Inline-edit columns (`TextInputColumn / ToggleColumn / SelectColumn`).
|
|
348
|
-
// Per-row stamping is gated on a `canEdit` hook — without it the
|
|
349
|
-
// dispatcher has no way to authorize the cell, so we skip stamping
|
|
350
|
-
// entirely and the renderer falls back to the read-only formatter.
|
|
351
|
-
const editableColumns = (table.getChildren() ?? [])
|
|
352
|
-
.filter((c): c is Column => c instanceof Column && c.isEditable())
|
|
353
|
-
const canEditEditableColumns = editableColumns.length > 0 && hooks?.canEdit !== undefined
|
|
354
|
-
|
|
355
|
-
// SelectColumns with a per-row options resolver — pre-filtered so the
|
|
356
|
-
// row loop doesn't re-walk `editableColumns` to find them. Stamps
|
|
357
|
-
// `row._cellSelectOptions[col.name]` with the resolved option list.
|
|
358
|
-
const selectColumnsWithResolver: Array<{ name: string; resolve: SelectColumnOptionsResolver }> =
|
|
359
|
-
editableColumns
|
|
360
|
-
.filter((c): c is SelectColumn => c instanceof SelectColumn)
|
|
361
|
-
.map(c => ({ name: c.name, resolve: c.getOptionsResolver() }))
|
|
362
|
-
.filter((c): c is { name: string; resolve: SelectColumnOptionsResolver } => c.resolve !== undefined)
|
|
363
|
-
|
|
364
|
-
const recordUrlFn = table.getRecordUrl()
|
|
365
|
-
const recordClassesFn = table.getRecordClasses()
|
|
366
|
-
const cardSchemaFn = table.isCardsLayout() ? table.getCardSchema() : undefined
|
|
367
|
-
// `activeGroupCol` + `activeGroup` are reconciled at the top of
|
|
368
|
-
// the table-async callback. Stamp the drilled key back onto the
|
|
369
|
-
// table here so `toMeta()` emits `activeGroupKey` alongside the
|
|
370
|
-
// rest of the render-time state. When drilled in, the rendered
|
|
371
|
-
// set is already filtered to one bucket — the renderer doesn't
|
|
372
|
-
// want heading rows or per-group summaries. `bandingActive`
|
|
373
|
-
// gates every banding-side decision (`_groupValue` stamping,
|
|
374
|
-
// stable-sort, per-group summaries); `activeGroup` stays bound
|
|
375
|
-
// so the chip + scope wiring can still reach the resolved
|
|
376
|
-
// group instance.
|
|
377
|
-
table.withActiveGroupKey(drilledKey)
|
|
378
|
-
const bandingActive = activeGroup !== undefined && drilledKey === undefined
|
|
379
|
-
const groupColumn = bandingActive ? activeGroup!.getColumn() : undefined
|
|
380
|
-
|
|
381
|
-
const needsRowMutation =
|
|
382
|
-
rowActionsWithRules.length > 0 ||
|
|
383
|
-
columnsWithFormatter.length > 0 ||
|
|
384
|
-
columnsWithRecordUrl.length > 0 ||
|
|
385
|
-
recordUrlFn !== undefined ||
|
|
386
|
-
recordClassesFn !== undefined ||
|
|
387
|
-
groupColumn !== undefined ||
|
|
388
|
-
canEditEditableColumns ||
|
|
389
|
-
richTextColumns.length > 0 ||
|
|
390
|
-
explicitRichTextColumns.length > 0 ||
|
|
391
|
-
cardSchemaFn !== undefined
|
|
392
|
-
|
|
393
|
-
const rows = !needsRowMutation
|
|
394
|
-
? rawRows
|
|
395
|
-
: await Promise.all(rawRows.map(async row => {
|
|
396
|
-
const recordObj = row as Record<string, unknown>
|
|
397
|
-
const out: Record<string, unknown> = { ...recordObj }
|
|
398
|
-
|
|
399
|
-
if (rowActionsWithRules.length > 0) {
|
|
400
|
-
const visibleActions: string[] = []
|
|
401
|
-
const disabledActions: string[] = []
|
|
402
|
-
const evals = await Promise.all(
|
|
403
|
-
rowActionsWithRules.map(a =>
|
|
404
|
-
a.evaluate(user !== undefined ? { record: row, user } : { record: row }),
|
|
405
|
-
),
|
|
406
|
-
)
|
|
407
|
-
rowActionsWithRules.forEach((a, i) => {
|
|
408
|
-
const { visible, disabled } = evals[i]!
|
|
409
|
-
if (visible) visibleActions.push(a.name)
|
|
410
|
-
if (disabled) disabledActions.push(a.name)
|
|
411
|
-
})
|
|
412
|
-
out['_visibleActions'] = visibleActions
|
|
413
|
-
out['_disabledActions'] = disabledActions
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
if (columnsWithFormatter.length > 0) {
|
|
417
|
-
const formatted: Record<string, string> = {}
|
|
418
|
-
for (const col of columnsWithFormatter) {
|
|
419
|
-
const fn = col.getFormatStateHandler()
|
|
420
|
-
if (!fn) continue
|
|
421
|
-
try {
|
|
422
|
-
formatted[col.name] = fn(recordObj[col.name], recordObj)
|
|
423
|
-
} catch {
|
|
424
|
-
// Swallow per-row formatter errors; the renderer falls
|
|
425
|
-
// back to the raw value.
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
out['_formatted'] = formatted
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
if (richTextColumns.length > 0) {
|
|
432
|
-
const formatted = (out['_formatted'] as Record<string, string> | undefined) ?? {}
|
|
433
|
-
const rich: Record<string, true> = {}
|
|
434
|
-
for (const col of richTextColumns) {
|
|
435
|
-
// `formatStateUsing` already won — skip when the column
|
|
436
|
-
// had a result stamped in the prior loop (formatStateUsing
|
|
437
|
-
// and richTextColumns are mutually exclusive at filter
|
|
438
|
-
// time, so this is defensive).
|
|
439
|
-
if (formatted[col.name] !== undefined) continue
|
|
440
|
-
const html = tryRenderRichText(recordObj[col.name])
|
|
441
|
-
if (html === null) continue
|
|
442
|
-
formatted[col.name] = html
|
|
443
|
-
rich[col.name] = true
|
|
444
|
-
}
|
|
445
|
-
if (Object.keys(rich).length > 0) {
|
|
446
|
-
out['_formatted'] = formatted
|
|
447
|
-
out['_richtextCells'] = rich
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
if (explicitRichTextColumns.length > 0) {
|
|
452
|
-
const formatted = (out['_formatted'] as Record<string, string> | undefined) ?? {}
|
|
453
|
-
const rich = (out['_richtextCells'] as Record<string, true> | undefined) ?? {}
|
|
454
|
-
for (const col of explicitRichTextColumns) {
|
|
455
|
-
// formatStateUsing wins (declared first by the user — we
|
|
456
|
-
// assume they meant it). markdown()/html() yield only when
|
|
457
|
-
// the explicit per-row formatter already stamped a value.
|
|
458
|
-
if (formatted[col.name] !== undefined) continue
|
|
459
|
-
const raw = recordObj[col.name]
|
|
460
|
-
if (raw === null || raw === undefined || raw === '') continue
|
|
461
|
-
const kind = col.getRichTextKind()
|
|
462
|
-
let html: string
|
|
463
|
-
try {
|
|
464
|
-
html = kind === 'markdown'
|
|
465
|
-
? (marked.parse(String(raw), { gfm: true, breaks: false, async: false }) as string)
|
|
466
|
-
: String(raw)
|
|
467
|
-
} catch {
|
|
468
|
-
// marked errors fall through to a string-cast so a
|
|
469
|
-
// malformed cell still ships *something* to the user.
|
|
470
|
-
html = String(raw)
|
|
471
|
-
}
|
|
472
|
-
const sanitizeOpt = col.getSanitize()
|
|
473
|
-
const finalHtml = sanitizeOpt === false
|
|
474
|
-
? html
|
|
475
|
-
: await sanitizeHtml(html, sanitizeOpt === true ? undefined : sanitizeOpt)
|
|
476
|
-
formatted[col.name] = finalHtml
|
|
477
|
-
rich[col.name] = true
|
|
478
|
-
}
|
|
479
|
-
if (Object.keys(rich).length > 0) {
|
|
480
|
-
out['_formatted'] = formatted
|
|
481
|
-
out['_richtextCells'] = rich
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
if (recordUrlFn !== undefined) {
|
|
486
|
-
try {
|
|
487
|
-
const url = recordUrlFn(row)
|
|
488
|
-
if (url !== undefined) out['_recordUrl'] = url
|
|
489
|
-
} catch {
|
|
490
|
-
// Per-row recordUrl errors stay silent; rows without a URL
|
|
491
|
-
// simply aren't clickable.
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
if (recordClassesFn !== undefined) {
|
|
496
|
-
try {
|
|
497
|
-
const cls = recordClassesFn(row)
|
|
498
|
-
if (cls) out['_recordClasses'] = cls
|
|
499
|
-
} catch {
|
|
500
|
-
// Silent — row falls back to default classes.
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
if (bandingActive) {
|
|
505
|
-
const raw = recordObj[activeGroup!.getColumn()]
|
|
506
|
-
// Date-bucketed groups use `YYYY-MM-DD` as the stable-sort
|
|
507
|
-
// key so all rows from the same day cluster together;
|
|
508
|
-
// unparseable values bucket to '' (bottom).
|
|
509
|
-
const value = activeGroup!.resolveKey(row)
|
|
510
|
-
out['_groupValue'] = value
|
|
511
|
-
|
|
512
|
-
// Per-row resolved title. User-supplied handler wins over
|
|
513
|
-
// the date() default formatter. Throwing handler stays
|
|
514
|
-
// silent — falls back to the raw `_groupValue`.
|
|
515
|
-
const titleFn = activeGroup!.getTitleHandler()
|
|
516
|
-
if (titleFn) {
|
|
517
|
-
try {
|
|
518
|
-
const t = titleFn(row)
|
|
519
|
-
if (t !== undefined) out['_groupTitle'] = String(t)
|
|
520
|
-
} catch { /* silent */ }
|
|
521
|
-
} else if (activeGroup!.isDate() && value !== '') {
|
|
522
|
-
out['_groupTitle'] = formatDateBucketTitle(raw)
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
const descFn = activeGroup!.getDescriptionHandler()
|
|
526
|
-
if (descFn) {
|
|
527
|
-
try {
|
|
528
|
-
const d = descFn(row)
|
|
529
|
-
if (d !== undefined) out['_groupDescription'] = String(d)
|
|
530
|
-
} catch { /* silent */ }
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
if (columnsWithRecordUrl.length > 0) {
|
|
535
|
-
const colUrls: Record<string, string> = {}
|
|
536
|
-
for (const col of columnsWithRecordUrl) {
|
|
537
|
-
const fn = col.getRecordUrlHandler()
|
|
538
|
-
if (!fn) continue
|
|
539
|
-
try {
|
|
540
|
-
const url = fn(recordObj)
|
|
541
|
-
if (url !== undefined) colUrls[col.name] = url
|
|
542
|
-
} catch {
|
|
543
|
-
// Same as table-level: per-cell URL errors stay silent.
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
out['_columnRecordUrls'] = colUrls
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
if (cardSchemaFn !== undefined) {
|
|
550
|
-
try {
|
|
551
|
-
const elements = await cardSchemaFn(row, finalCtx)
|
|
552
|
-
const cardCtx: RenderContext = {
|
|
553
|
-
mode: 'table',
|
|
554
|
-
record: row,
|
|
555
|
-
...(user !== undefined && user !== null
|
|
556
|
-
? { user: user as NonNullable<RenderContext['user']> }
|
|
557
|
-
: {}),
|
|
558
|
-
}
|
|
559
|
-
const children = await resolveSchema(elements, cardCtx)
|
|
560
|
-
out['_cardChildren'] = children as ElementMeta[]
|
|
561
|
-
} catch (err) {
|
|
562
|
-
console.warn(`[pilotiq] cardSchema() threw for row in Table:`, err)
|
|
563
|
-
out['_cardChildren'] = [] as ElementMeta[]
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
if (canEditEditableColumns) {
|
|
568
|
-
// ONE auth call per row regardless of editable column count
|
|
569
|
-
// — same record, same answer. Failures or false → no edit
|
|
570
|
-
// affordance for any column.
|
|
571
|
-
let allowed: boolean
|
|
572
|
-
try { allowed = await hooks!.canEdit!(user, recordObj) }
|
|
573
|
-
catch { allowed = false }
|
|
574
|
-
if (allowed) {
|
|
575
|
-
const editableMap: Record<string, true> = {}
|
|
576
|
-
const disabledMap: Record<string, true> = {}
|
|
577
|
-
for (const col of editableColumns) {
|
|
578
|
-
editableMap[col.name] = true
|
|
579
|
-
if (col.isDisabledFor(recordObj)) disabledMap[col.name] = true
|
|
580
|
-
}
|
|
581
|
-
out['_cellEditable'] = editableMap
|
|
582
|
-
if (Object.keys(disabledMap).length > 0) {
|
|
583
|
-
out['_cellDisabled'] = disabledMap
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
// Per-row select options. Resolvers run in parallel so a
|
|
587
|
-
// table with several SelectColumn resolvers doesn't stall
|
|
588
|
-
// on serial awaits. A throwing resolver leaves the slot
|
|
589
|
-
// unset — the renderer falls back to the column-level
|
|
590
|
-
// static `selectOptions` (or an empty list) so one bad
|
|
591
|
-
// row doesn't break the whole table.
|
|
592
|
-
if (selectColumnsWithResolver.length > 0) {
|
|
593
|
-
const resolvedEntries = await Promise.all(selectColumnsWithResolver.map(async (c) => {
|
|
594
|
-
try {
|
|
595
|
-
const opts = await c.resolve(recordObj)
|
|
596
|
-
return [c.name, normalizeSelectOptions(opts)] as const
|
|
597
|
-
} catch {
|
|
598
|
-
return [c.name, undefined] as const
|
|
599
|
-
}
|
|
600
|
-
}))
|
|
601
|
-
const optionsMap: Record<string, ColumnSelectOption[]> = {}
|
|
602
|
-
for (const [name, opts] of resolvedEntries) {
|
|
603
|
-
if (opts !== undefined) optionsMap[name] = opts
|
|
604
|
-
}
|
|
605
|
-
if (Object.keys(optionsMap).length > 0) {
|
|
606
|
-
out['_cellSelectOptions'] = optionsMap
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
return out
|
|
613
|
-
}))
|
|
614
|
-
|
|
615
|
-
// Group banding: stable-sort by _groupValue so all rows with the
|
|
616
|
-
// same value cluster together. The user's sort still applies
|
|
617
|
-
// (records came in pre-sorted from `records()`); we just shuffle
|
|
618
|
-
// those clusters so groups are contiguous. Empty/null group values
|
|
619
|
-
// bubble to the bottom under the empty-string key. When
|
|
620
|
-
// `TableGroup.orderUsing(comparator)` is set on the active group,
|
|
621
|
-
// the comparator overrides the default alphabetic order.
|
|
622
|
-
const finalRows = groupColumn === undefined
|
|
623
|
-
? rows
|
|
624
|
-
: sortRowsByGroupValue(
|
|
625
|
-
rows as Array<Record<string, unknown>>,
|
|
626
|
-
activeGroup?.getKeyComparator(),
|
|
627
|
-
)
|
|
628
|
-
|
|
629
|
-
// Per-column summaries. Compute over the rows we just rendered
|
|
630
|
-
// (per-page only in v1; cross-page aggregation is deferred). Pulls
|
|
631
|
-
// raw column values — summarizers coerce to numbers internally.
|
|
632
|
-
const columnsWithSummaries = (table.getChildren() ?? [])
|
|
633
|
-
.filter((c): c is Column => c instanceof Column && c.hasSummarizers())
|
|
634
|
-
if (columnsWithSummaries.length > 0) {
|
|
635
|
-
const summaries: Record<string, SummaryResult[]> = {}
|
|
636
|
-
for (const col of columnsWithSummaries) {
|
|
637
|
-
const values = (finalRows as Array<Record<string, unknown>>)
|
|
638
|
-
.map(r => r[col.name])
|
|
639
|
-
summaries[col.name] = col.getSummarizers().map(s => s.toResult(values))
|
|
640
|
-
}
|
|
641
|
-
table.withSummaries(summaries)
|
|
642
|
-
|
|
643
|
-
// Per-group summaries — one summary set per `_groupValue`. Only
|
|
644
|
-
// computed when a group is banded; otherwise the global tfoot
|
|
645
|
-
// is the only summary row. Empty-group bucket ('') still gets
|
|
646
|
-
// its own row when present (mirrors the bucket-to-bottom rule
|
|
647
|
-
// already used for sorting). Drill-in mode (`bandingActive=false`)
|
|
648
|
-
// intentionally skips per-group summaries — the visible rows are
|
|
649
|
-
// already one bucket.
|
|
650
|
-
if (bandingActive) {
|
|
651
|
-
const buckets: Record<string, Array<Record<string, unknown>>> = {}
|
|
652
|
-
for (const r of finalRows as Array<Record<string, unknown>>) {
|
|
653
|
-
const key = String(r['_groupValue'] ?? '')
|
|
654
|
-
if (!buckets[key]) buckets[key] = []
|
|
655
|
-
buckets[key].push(r)
|
|
656
|
-
}
|
|
657
|
-
const groupSummaries: Record<string, Record<string, SummaryResult[]>> = {}
|
|
658
|
-
for (const [groupValue, groupRows] of Object.entries(buckets)) {
|
|
659
|
-
const perCol: Record<string, SummaryResult[]> = {}
|
|
660
|
-
for (const col of columnsWithSummaries) {
|
|
661
|
-
const values = groupRows.map(r => r[col.name])
|
|
662
|
-
perCol[col.name] = col.getSummarizers().map(s => s.toResult(values))
|
|
663
|
-
}
|
|
664
|
-
groupSummaries[groupValue] = perCol
|
|
665
|
-
}
|
|
666
|
-
table.withGroupSummaries(groupSummaries)
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
table.withRows(finalRows as unknown[], total)
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
// Mirror the resolved context back onto the table so the renderer can
|
|
674
|
-
// produce sort/search/page links without re-parsing the URL. Drilled-in
|
|
675
|
-
// requests reset the visible page to 1 — keep the mirror in sync so
|
|
676
|
-
// pagination chrome doesn't claim page N on a single-bucket render.
|
|
677
|
-
if (effectiveSort) table.withSort(effectiveSort.column, effectiveSort.direction)
|
|
678
|
-
if (search !== undefined) table.withSearch(search)
|
|
679
|
-
table.withPage(effectivePageWithDrill)
|
|
680
|
-
if (pathname) table.withCurrentPath(pathname)
|
|
681
|
-
}))
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
/**
|
|
685
|
-
* Locate the currently-active list-page tab in the schema. The active tab
|
|
686
|
-
* is the `ListTab` whose `_active` flag was set by
|
|
687
|
-
* `pageData.resolveActiveTab` during the page-data pass — the tab strip
|
|
688
|
-
* lives in the page schema (above the Table), so this just walks the
|
|
689
|
-
* tree until it finds the matching tab. Returns `undefined` when the
|
|
690
|
-
* page has no ListTabs (most non-resource pages).
|
|
691
|
-
*
|
|
692
|
-
* Uses a structural `getType() === 'listTab'` check rather than
|
|
693
|
-
* `instanceof ListTab` for the same reason as `findForms / findActions`
|
|
694
|
-
* — Vite SSR module-cache duplication can load the package through
|
|
695
|
-
* two paths during a dev session and silently break `instanceof`.
|
|
696
|
-
*/
|
|
697
|
-
/**
|
|
698
|
-
* Stable-sort rows by their stamped `_groupValue` so all rows with the
|
|
699
|
-
* same group are contiguous. Preserves original order within groups
|
|
700
|
-
* (ties broken by original index). Empty / unstamped group values sort
|
|
701
|
-
* to the end so the "ungrouped" band appears last.
|
|
702
|
-
*
|
|
703
|
-
* When a `comparator` is supplied (via `TableGroup.orderUsing(...)`) it
|
|
704
|
-
* replaces the default alphabetic comparison between distinct keys. The
|
|
705
|
-
* empty-bucket-last rule and within-group stability are preserved
|
|
706
|
-
* around the user's comparator — those are structural, not policy.
|
|
707
|
-
*/
|
|
708
|
-
function sortRowsByGroupValue(
|
|
709
|
-
rows: ReadonlyArray<Record<string, unknown>>,
|
|
710
|
-
comparator?: (a: string, b: string) => number,
|
|
711
|
-
): Array<Record<string, unknown>> {
|
|
712
|
-
const indexed = rows.map((r, i) => ({ r, i, key: String(r['_groupValue'] ?? '') }))
|
|
713
|
-
indexed.sort((a, b) => {
|
|
714
|
-
const aEmpty = a.key === ''
|
|
715
|
-
const bEmpty = b.key === ''
|
|
716
|
-
if (aEmpty && !bEmpty) return 1
|
|
717
|
-
if (!aEmpty && bEmpty) return -1
|
|
718
|
-
if (a.key === b.key) return a.i - b.i
|
|
719
|
-
if (comparator) {
|
|
720
|
-
const cmp = comparator(a.key, b.key)
|
|
721
|
-
if (cmp !== 0) return cmp
|
|
722
|
-
// Comparator-tied → fall back to alphabetic so ties don't churn
|
|
723
|
-
// depending on input order (otherwise pinning two keys to the
|
|
724
|
-
// same rank would leave them in arrival order, which surprises
|
|
725
|
-
// users when their `records()` reorders).
|
|
726
|
-
}
|
|
727
|
-
return a.key < b.key ? -1 : a.key > b.key ? 1 : 0
|
|
728
|
-
})
|
|
729
|
-
return indexed.map(({ r }) => r)
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
function findActiveTab(elements: ReadonlyArray<Element>): ListTab | undefined {
|
|
733
|
-
const walk = (els: ReadonlyArray<Element>): ListTab | undefined => {
|
|
734
|
-
for (const el of els) {
|
|
735
|
-
if (el.getType() === 'listTab' && (el as ListTab).isActive()) return el as ListTab
|
|
736
|
-
const children = el.getChildren()
|
|
737
|
-
if (children) {
|
|
738
|
-
const found = walk(children)
|
|
739
|
-
if (found) return found
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
return undefined
|
|
743
|
-
}
|
|
744
|
-
return walk(elements)
|
|
745
|
-
}
|