@pilotiq/pilotiq 0.24.1 → 0.24.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -0
- package/boost/guidelines.md +571 -0
- package/boost/skills/pilotiq-actions/SKILL.md +49 -0
- package/boost/skills/pilotiq-actions/rules/dispatch-modes.md +177 -0
- package/boost/skills/pilotiq-actions/rules/factories.md +130 -0
- package/boost/skills/pilotiq-actions/rules/visibility-and-authorization.md +125 -0
- package/boost/skills/pilotiq-fields/SKILL.md +47 -0
- package/boost/skills/pilotiq-fields/rules/field-catalog.md +288 -0
- package/boost/skills/pilotiq-fields/rules/reactive-fields.md +199 -0
- package/boost/skills/pilotiq-fields/rules/validation.md +198 -0
- package/boost/skills/pilotiq-relations/SKILL.md +47 -0
- package/boost/skills/pilotiq-relations/rules/relation-managers.md +256 -0
- package/boost/skills/pilotiq-relations/rules/repeater-relationship.md +177 -0
- package/boost/skills/pilotiq-resource/SKILL.md +61 -0
- package/boost/skills/pilotiq-resource/rules/authorization.md +242 -0
- package/boost/skills/pilotiq-resource/rules/defining-resources.md +228 -0
- package/boost/skills/pilotiq-resource/rules/page-overrides.md +296 -0
- package/dist/Pilotiq.d.ts +31 -0
- package/dist/Pilotiq.d.ts.map +1 -1
- package/dist/Pilotiq.js +3 -1
- package/dist/Pilotiq.js.map +1 -1
- package/dist/PilotiqRegistry.d.ts +13 -0
- package/dist/PilotiqRegistry.d.ts.map +1 -1
- package/dist/PilotiqRegistry.js +15 -0
- package/dist/PilotiqRegistry.js.map +1 -1
- package/dist/pageData/misc.d.ts.map +1 -1
- package/dist/pageData/misc.js +6 -0
- package/dist/pageData/misc.js.map +1 -1
- package/dist/pageData/navigation.d.ts +1 -0
- package/dist/pageData/navigation.d.ts.map +1 -1
- package/dist/pageData/navigation.js +3 -0
- package/dist/pageData/navigation.js.map +1 -1
- package/dist/pageData/relationPages.d.ts.map +1 -1
- package/dist/pageData/relationPages.js +3 -0
- package/dist/pageData/relationPages.js.map +1 -1
- package/dist/pageData/resourcePages.d.ts.map +1 -1
- package/dist/pageData/resourcePages.js +8 -0
- package/dist/pageData/resourcePages.js.map +1 -1
- package/dist/react/AppShell.d.ts +8 -0
- package/dist/react/AppShell.d.ts.map +1 -1
- package/dist/react/AppShell.js.map +1 -1
- package/dist/react/layouts/SidebarLayout.d.ts.map +1 -1
- package/dist/react/layouts/SidebarLayout.js +10 -2
- package/dist/react/layouts/SidebarLayout.js.map +1 -1
- package/dist/react/widgets/StatsOverviewRenderer.d.ts.map +1 -1
- package/dist/react/widgets/StatsOverviewRenderer.js +32 -18
- package/dist/react/widgets/StatsOverviewRenderer.js.map +1 -1
- package/dist/routes/relations.d.ts.map +1 -1
- package/dist/routes/relations.js +25 -18
- package/dist/routes/relations.js.map +1 -1
- package/dist/routes/resources.js.map +1 -1
- package/package.json +10 -5
- 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,260 +0,0 @@
|
|
|
1
|
-
import { describe, it } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
|
|
4
|
-
import { TableGroup, bucketDateValue, formatDateBucketTitle, orderByKeys } from './TableGroup.js'
|
|
5
|
-
|
|
6
|
-
describe('TableGroup', () => {
|
|
7
|
-
describe('fluent setters', () => {
|
|
8
|
-
it('label() round-trips on toMeta()', () => {
|
|
9
|
-
const meta = TableGroup.make('status').label('Status').toMeta()
|
|
10
|
-
assert.equal(meta.column, 'status')
|
|
11
|
-
assert.equal(meta.label, 'Status')
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
it('falls back to column name when no label is set', () => {
|
|
15
|
-
const meta = TableGroup.make('author_id').toMeta()
|
|
16
|
-
assert.equal(meta.label, 'author_id')
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it('collapsible() flips the meta flag', () => {
|
|
20
|
-
assert.equal(TableGroup.make('s').toMeta().collapsible, undefined)
|
|
21
|
-
assert.equal(TableGroup.make('s').collapsible().toMeta().collapsible, true)
|
|
22
|
-
assert.equal(TableGroup.make('s').collapsible(false).toMeta().collapsible, undefined)
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it('collapsed() flips the meta flag', () => {
|
|
26
|
-
assert.equal(TableGroup.make('s').toMeta().collapsed, undefined)
|
|
27
|
-
assert.equal(TableGroup.make('s').collapsed().toMeta().collapsed, true)
|
|
28
|
-
assert.equal(TableGroup.make('s').collapsed(false).toMeta().collapsed, undefined)
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('date() flips the meta flag', () => {
|
|
32
|
-
assert.equal(TableGroup.make('createdAt').toMeta().date, undefined)
|
|
33
|
-
assert.equal(TableGroup.make('createdAt').date().toMeta().date, true)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it('chains multiple setters', () => {
|
|
37
|
-
const meta = TableGroup.make('status')
|
|
38
|
-
.label('Status')
|
|
39
|
-
.collapsible()
|
|
40
|
-
.collapsed()
|
|
41
|
-
.toMeta()
|
|
42
|
-
assert.deepEqual(meta, {
|
|
43
|
-
column: 'status',
|
|
44
|
-
label: 'Status',
|
|
45
|
-
collapsible: true,
|
|
46
|
-
collapsed: true,
|
|
47
|
-
})
|
|
48
|
-
})
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
describe('record-derived title + description', () => {
|
|
52
|
-
it('getTitleHandler is invoked with the full record', () => {
|
|
53
|
-
const g = TableGroup.make<{ status: string }>('status')
|
|
54
|
-
.getTitleFromRecordUsing(r => r.status === 'draft' ? 'Drafts' : 'Published')
|
|
55
|
-
const fn = g.getTitleHandler()!
|
|
56
|
-
assert.equal(fn({ status: 'draft' }), 'Drafts')
|
|
57
|
-
assert.equal(fn({ status: 'live' }), 'Published')
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('getDescriptionHandler is invoked with the full record', () => {
|
|
61
|
-
const g = TableGroup.make<{ count: number }>('status')
|
|
62
|
-
.getDescriptionFromRecordUsing(r => `${r.count} records`)
|
|
63
|
-
const fn = g.getDescriptionHandler()!
|
|
64
|
-
assert.equal(fn({ count: 12 }), '12 records')
|
|
65
|
-
})
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
describe('bucketDateValue', () => {
|
|
69
|
-
it('buckets ISO strings to YYYY-MM-DD', () => {
|
|
70
|
-
assert.equal(bucketDateValue('2026-05-04T13:24:00.000Z'), '2026-05-04')
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
it('buckets Date instances', () => {
|
|
74
|
-
assert.equal(bucketDateValue(new Date(Date.UTC(2026, 0, 9))), '2026-01-09')
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
it('buckets epoch numbers', () => {
|
|
78
|
-
assert.equal(bucketDateValue(0), '1970-01-01')
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('returns empty string for null/undefined/empty', () => {
|
|
82
|
-
assert.equal(bucketDateValue(null), '')
|
|
83
|
-
assert.equal(bucketDateValue(undefined), '')
|
|
84
|
-
assert.equal(bucketDateValue(''), '')
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
it('returns empty string for unparseable inputs', () => {
|
|
88
|
-
assert.equal(bucketDateValue('not a date'), '')
|
|
89
|
-
})
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
describe('orderUsing + orderByKeys', () => {
|
|
93
|
-
it('default — comparator unset', () => {
|
|
94
|
-
const g = TableGroup.make('status')
|
|
95
|
-
assert.equal(g.getKeyComparator(), undefined)
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
it('orderUsing() stores the comparator', () => {
|
|
99
|
-
const cmp = (a: string, b: string): number => a.length - b.length
|
|
100
|
-
const g = TableGroup.make('status').orderUsing(cmp)
|
|
101
|
-
assert.equal(g.getKeyComparator(), cmp)
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
it('orderByKeys ranks listed keys in order', () => {
|
|
105
|
-
const cmp = orderByKeys(['draft', 'published', 'archived'])
|
|
106
|
-
// Listed keys come back in declaration order, regardless of input.
|
|
107
|
-
const sorted = ['archived', 'draft', 'published'].slice().sort(cmp)
|
|
108
|
-
assert.deepEqual(sorted, ['draft', 'published', 'archived'])
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
it('orderByKeys puts unknown keys after the listed ones', () => {
|
|
112
|
-
const cmp = orderByKeys(['draft', 'published'])
|
|
113
|
-
// 'pending' isn't in the list — must sort after both listed keys.
|
|
114
|
-
const sorted = ['pending', 'draft', 'published'].slice().sort(cmp)
|
|
115
|
-
assert.deepEqual(sorted, ['draft', 'published', 'pending'])
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
it('orderByKeys breaks ties between unlisted keys alphabetically', () => {
|
|
119
|
-
const cmp = orderByKeys(['draft'])
|
|
120
|
-
const sorted = ['zeta', 'alpha', 'draft', 'beta'].slice().sort(cmp)
|
|
121
|
-
assert.deepEqual(sorted, ['draft', 'alpha', 'beta', 'zeta'])
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
it('orderByKeys empty list collapses to alphabetic', () => {
|
|
125
|
-
const cmp = orderByKeys([])
|
|
126
|
-
const sorted = ['c', 'a', 'b'].slice().sort(cmp)
|
|
127
|
-
assert.deepEqual(sorted, ['a', 'b', 'c'])
|
|
128
|
-
})
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
describe('formatDateBucketTitle', () => {
|
|
132
|
-
it('formats a parseable date as locale text', () => {
|
|
133
|
-
// We don't pin to an exact locale string — just confirm it's
|
|
134
|
-
// human-readable and contains the year.
|
|
135
|
-
const out = formatDateBucketTitle('2026-05-04T00:00:00.000Z')
|
|
136
|
-
assert.match(out, /2026/)
|
|
137
|
-
assert.match(out, /May/)
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
it('returns empty string for null/empty', () => {
|
|
141
|
-
assert.equal(formatDateBucketTitle(null), '')
|
|
142
|
-
assert.equal(formatDateBucketTitle(''), '')
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
it('returns the raw value for unparseable inputs', () => {
|
|
146
|
-
assert.equal(formatDateBucketTitle('blob'), 'blob')
|
|
147
|
-
})
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
describe('scopable + scopeQueryByKey + getKeyFromRecordUsing', () => {
|
|
151
|
-
it('default — not scopable, no meta emit', () => {
|
|
152
|
-
assert.equal(TableGroup.make('status').isScopable(), false)
|
|
153
|
-
assert.equal(TableGroup.make('status').toMeta().scopable, undefined)
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
it('scopable() flips the meta flag', () => {
|
|
157
|
-
assert.equal(TableGroup.make('s').scopable().toMeta().scopable, true)
|
|
158
|
-
assert.equal(TableGroup.make('s').scopable(false).toMeta().scopable, undefined)
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
it('scopeQueryByKey() auto-arms scopable', () => {
|
|
162
|
-
const g = TableGroup.make('status').scopeQueryByKey((q, _k) => q)
|
|
163
|
-
assert.equal(g.isScopable(), true)
|
|
164
|
-
assert.equal(g.toMeta().scopable, true)
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
it('getKeyFromRecordUsing() auto-arms scopable', () => {
|
|
168
|
-
const g = TableGroup.make<{ s: string }>('status')
|
|
169
|
-
.getKeyFromRecordUsing(r => r.s.toUpperCase())
|
|
170
|
-
assert.equal(g.isScopable(), true)
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
it('scopable(false) after auto-arm opts back out', () => {
|
|
174
|
-
const g = TableGroup.make('status').scopeQueryByKey((q, _k) => q).scopable(false)
|
|
175
|
-
assert.equal(g.isScopable(), false)
|
|
176
|
-
assert.equal(g.toMeta().scopable, undefined)
|
|
177
|
-
})
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
describe('resolveKey', () => {
|
|
181
|
-
it('default — raw column value as string', () => {
|
|
182
|
-
const g = TableGroup.make('status')
|
|
183
|
-
assert.equal(g.resolveKey({ status: 'draft' }), 'draft')
|
|
184
|
-
assert.equal(g.resolveKey({ status: 42 }), '42')
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
it('empty / null collapses to ""', () => {
|
|
188
|
-
const g = TableGroup.make('status')
|
|
189
|
-
assert.equal(g.resolveKey({ status: null }), '')
|
|
190
|
-
assert.equal(g.resolveKey({ status: undefined }), '')
|
|
191
|
-
assert.equal(g.resolveKey({ status: '' }), '')
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
it('date() returns the YYYY-MM-DD bucket', () => {
|
|
195
|
-
const g = TableGroup.make('createdAt').date()
|
|
196
|
-
assert.equal(g.resolveKey({ createdAt: '2026-05-04T12:00:00.000Z' }), '2026-05-04')
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
it('user handler wins', () => {
|
|
200
|
-
const g = TableGroup.make<{ s: string }>('status')
|
|
201
|
-
.getKeyFromRecordUsing(r => `K_${r.s}`)
|
|
202
|
-
assert.equal(g.resolveKey({ s: 'a' }), 'K_a')
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
it('handler returning undefined collapses to ""', () => {
|
|
206
|
-
const g = TableGroup.make<{ s: string | undefined }>('status')
|
|
207
|
-
.getKeyFromRecordUsing(r => r.s)
|
|
208
|
-
assert.equal(g.resolveKey({ s: undefined }), '')
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
it('throwing handler fails soft to ""', () => {
|
|
212
|
-
const g = TableGroup.make('status')
|
|
213
|
-
.getKeyFromRecordUsing(() => { throw new Error('boom') })
|
|
214
|
-
assert.equal(g.resolveKey({ status: 'draft' }), '')
|
|
215
|
-
})
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
describe('resolveScoper defaults', () => {
|
|
219
|
-
it('plain group — exact match where(col, "=", key)', () => {
|
|
220
|
-
const calls: Array<[string, string, unknown]> = []
|
|
221
|
-
const q = { where: (col: string, op: string, val: unknown) => { calls.push([col, op, val]); return q } }
|
|
222
|
-
const g = TableGroup.make('status')
|
|
223
|
-
const scoper = g.resolveScoper<typeof q>()
|
|
224
|
-
scoper(q, 'draft')
|
|
225
|
-
assert.deepEqual(calls, [['status', '=', 'draft']])
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
it('date group — whole-day range', () => {
|
|
229
|
-
const calls: Array<[string, string, unknown]> = []
|
|
230
|
-
const q = { where: (col: string, op: string, val: unknown) => { calls.push([col, op, val]); return q } }
|
|
231
|
-
const g = TableGroup.make('createdAt').date()
|
|
232
|
-
const scoper = g.resolveScoper<typeof q>()
|
|
233
|
-
scoper(q, '2026-05-04')
|
|
234
|
-
assert.deepEqual(calls, [
|
|
235
|
-
['createdAt', '>=', '2026-05-04 00:00:00'],
|
|
236
|
-
['createdAt', '<=', '2026-05-04 23:59:59'],
|
|
237
|
-
])
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
it('date group — empty key is a no-op (no where call)', () => {
|
|
241
|
-
let calls = 0
|
|
242
|
-
const q = { where: (..._args: unknown[]) => { calls++; return q } }
|
|
243
|
-
const g = TableGroup.make('createdAt').date()
|
|
244
|
-
const scoper = g.resolveScoper<typeof q>()
|
|
245
|
-
scoper(q, '')
|
|
246
|
-
assert.equal(calls, 0)
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
it('user scoper wins over date() default', () => {
|
|
250
|
-
const calls: string[] = []
|
|
251
|
-
const q = { where: (col: string, op: string, val: unknown) => { calls.push(`${col} ${op} ${val}`); return q } }
|
|
252
|
-
const g = TableGroup.make<unknown>('createdAt')
|
|
253
|
-
.date()
|
|
254
|
-
.scopeQueryByKey<typeof q>((qq, key) => qq.where('createdAt', '=', key))
|
|
255
|
-
const scoper = g.resolveScoper<typeof q>()
|
|
256
|
-
scoper(q, '2026-05-04')
|
|
257
|
-
assert.deepEqual(calls, ['createdAt = 2026-05-04'])
|
|
258
|
-
})
|
|
259
|
-
})
|
|
260
|
-
})
|
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Table grouping option. Owned by `Table` (not an `Element` — doesn't
|
|
3
|
-
* sit in the schema tree). Each `TableGroup` defines one column the
|
|
4
|
-
* table can band rows by, plus optional chrome (label, collapsibility,
|
|
5
|
-
* record-derived title and description, date bucketing).
|
|
6
|
-
*
|
|
7
|
-
* Multiple `TableGroup`s can live on a single table via `Table.groups([…])`
|
|
8
|
-
* — the renderer mounts a "Group by" dropdown above the table when more
|
|
9
|
-
* than one is registered. The active selection round-trips through the
|
|
10
|
-
* URL via the reserved `?group=` key.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
export type TableGroupTitleHandler<R = unknown> = (
|
|
14
|
-
record: R,
|
|
15
|
-
) => string | undefined
|
|
16
|
-
|
|
17
|
-
export type TableGroupDescriptionHandler<R = unknown> = (
|
|
18
|
-
record: R,
|
|
19
|
-
) => string | undefined
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Per-record key resolver. The returned string is what `scopeQueryByKey`
|
|
23
|
-
* receives at drill-in time. Default resolution = the raw column value
|
|
24
|
-
* cast to string (or `YYYY-MM-DD` when `.date()` is on). Override when the
|
|
25
|
-
* stable bucket key differs from what the column literally stores —
|
|
26
|
-
* e.g. when grouping by an enum object whose `.value` is the persisted
|
|
27
|
-
* column.
|
|
28
|
-
*/
|
|
29
|
-
export type TableGroupKeyHandler<R = unknown> = (
|
|
30
|
-
record: R,
|
|
31
|
-
) => string | undefined
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Query scoper applied when the user clicks a group heading to drill into
|
|
35
|
-
* a single group. Receives the raw model query and the resolved group key
|
|
36
|
-
* (the same value `getKeyFromRecordUsing` produced). Should narrow the
|
|
37
|
-
* query to records belonging to that group. Default narrows by exact-match
|
|
38
|
-
* `where(column, '=', key)`; date groups install a whole-day range default.
|
|
39
|
-
*/
|
|
40
|
-
export type TableGroupQueryScoper<Q = unknown> = (
|
|
41
|
-
query: Q,
|
|
42
|
-
key: string,
|
|
43
|
-
) => Q
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Comparator on resolved group keys. Receives the same string values that
|
|
47
|
-
* the dispatcher stamps onto `_groupValue` (so for `date()` groups the keys
|
|
48
|
-
* are `YYYY-MM-DD`, not raw timestamps). Return < 0 to put `a` first, > 0
|
|
49
|
-
* to put `b` first, 0 to keep insertion order. The empty-bucket-last rule
|
|
50
|
-
* is still applied AFTER your comparator — the empty bucket stays at the
|
|
51
|
-
* bottom regardless of what you return for it.
|
|
52
|
-
*/
|
|
53
|
-
export type TableGroupKeyComparator = (a: string, b: string) => number
|
|
54
|
-
|
|
55
|
-
export interface TableGroupMeta {
|
|
56
|
-
column: string
|
|
57
|
-
label: string
|
|
58
|
-
collapsible?: true
|
|
59
|
-
collapsed?: true
|
|
60
|
-
/** Server-side date bucketing is on (column read as a date, grouped
|
|
61
|
-
* by day). Renderer doesn't need to do anything special — `_groupValue`
|
|
62
|
-
* already arrives as `YYYY-MM-DD` and `_groupTitle` carries the
|
|
63
|
-
* formatted display text. */
|
|
64
|
-
date?: true
|
|
65
|
-
/** Heading is clickable — renderer wraps the title text in a real
|
|
66
|
-
* `<a href>` that sets `?<prefix>groupKey=<value>` to drill into a
|
|
67
|
-
* single group. Sparse: omitted unless the user opted in (directly
|
|
68
|
-
* via `.scopable(true)` or implicitly by calling `.scopeQueryByKey()`
|
|
69
|
-
* / `.getKeyFromRecordUsing()`). */
|
|
70
|
-
scopable?: true
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export class TableGroup<R = unknown> {
|
|
74
|
-
private _column: string
|
|
75
|
-
private _label?: string
|
|
76
|
-
private _collapsible = false
|
|
77
|
-
private _collapsed = false
|
|
78
|
-
private _titleFn?: TableGroupTitleHandler<R>
|
|
79
|
-
private _descriptionFn?: TableGroupDescriptionHandler<R>
|
|
80
|
-
private _date = false
|
|
81
|
-
private _keyComparator?: TableGroupKeyComparator
|
|
82
|
-
private _scopable = false
|
|
83
|
-
private _scopeFn?: TableGroupQueryScoper<unknown>
|
|
84
|
-
private _keyFn?: TableGroupKeyHandler<R>
|
|
85
|
-
|
|
86
|
-
private constructor(column: string) {
|
|
87
|
-
this._column = column
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
static make<R = unknown>(column: string): TableGroup<R> {
|
|
91
|
-
return new TableGroup<R>(column)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** Display label in the group selector dropdown. Falls back to the
|
|
95
|
-
* column name when omitted. */
|
|
96
|
-
label(text: string): this { this._label = text; return this }
|
|
97
|
-
|
|
98
|
-
/** Allow individual groups to be folded. Adds a chevron in the heading row. */
|
|
99
|
-
collapsible(v: boolean = true): this { this._collapsible = v; return this }
|
|
100
|
-
|
|
101
|
-
/** Start collapsed by default. Per-group state is persisted client-
|
|
102
|
-
* side in localStorage; this just seeds the initial state. */
|
|
103
|
-
collapsed(v: boolean = true): this { this._collapsed = v; return this }
|
|
104
|
-
|
|
105
|
-
/** Custom heading text per row. Receives the full record. Falls back
|
|
106
|
-
* to the raw column value when omitted. Stamped per row server-side
|
|
107
|
-
* as `_groupTitle`. */
|
|
108
|
-
getTitleFromRecordUsing(fn: TableGroupTitleHandler<R>): this {
|
|
109
|
-
this._titleFn = fn
|
|
110
|
-
return this
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/** Subtitle below the title. Stamped per row as `_groupDescription`. */
|
|
114
|
-
getDescriptionFromRecordUsing(fn: TableGroupDescriptionHandler<R>): this {
|
|
115
|
-
this._descriptionFn = fn
|
|
116
|
-
return this
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/** Sugar for grouping by day — the column is read as a date and
|
|
120
|
-
* bucketed to `YYYY-MM-DD`. Uses `_groupValue` for the bucket key
|
|
121
|
-
* (so stable-sort still works) and a default title formatter ("May
|
|
122
|
-
* 4, 2026"). User-supplied title formatter wins. */
|
|
123
|
-
date(v: boolean = true): this { this._date = v; return this }
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Override the alphabetic ordering of group buckets with a custom
|
|
127
|
-
* comparator on resolved group keys. Useful for pinning enums in a
|
|
128
|
-
* meaningful order (e.g. `'draft' → 'published' → 'archived'`).
|
|
129
|
-
*
|
|
130
|
-
* The empty-bucket-last rule still runs AFTER this comparator — rows
|
|
131
|
-
* with no group value always sort to the bottom regardless of what
|
|
132
|
-
* the comparator returns for the empty key. Pass an array of keys
|
|
133
|
-
* for the convenient case via the `orderByKeys` helper below.
|
|
134
|
-
*
|
|
135
|
-
* Example:
|
|
136
|
-
* ```ts
|
|
137
|
-
* TableGroup.make('status').orderUsing(orderByKeys([
|
|
138
|
-
* 'draft', 'published', 'archived',
|
|
139
|
-
* ]))
|
|
140
|
-
* ```
|
|
141
|
-
*/
|
|
142
|
-
orderUsing(comparator: TableGroupKeyComparator): this {
|
|
143
|
-
this._keyComparator = comparator
|
|
144
|
-
return this
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Make the group heading clickable — clicking it drills the table into
|
|
149
|
-
* just that group's rows, suppressing the banded layout. Opt-in: most
|
|
150
|
-
* tables stay in the banded view. Auto-armed whenever the user calls
|
|
151
|
-
* `.scopeQueryByKey(fn)` or `.getKeyFromRecordUsing(fn)` since neither
|
|
152
|
-
* is meaningful without the drill-in affordance; pass `.scopable(false)`
|
|
153
|
-
* explicitly to opt back out after the fact.
|
|
154
|
-
*/
|
|
155
|
-
scopable(v: boolean = true): this {
|
|
156
|
-
this._scopable = v
|
|
157
|
-
return this
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Narrow the query to a single group's rows when the user drills in.
|
|
162
|
-
* Receives the raw model query and the resolved group key (same value
|
|
163
|
-
* `getKeyFromRecordUsing` produces). Default narrows by exact-match
|
|
164
|
-
* `where(column, '=', key)`; date groups install a whole-day range
|
|
165
|
-
* default. Auto-arms `.scopable(true)` since a custom scoper without
|
|
166
|
-
* a clickable heading would never fire.
|
|
167
|
-
*
|
|
168
|
-
* ```ts
|
|
169
|
-
* TableGroup.make('status').scopeQueryByKey((q, key) =>
|
|
170
|
-
* q.where('status', '=', key).where('archived', '=', false),
|
|
171
|
-
* )
|
|
172
|
-
* ```
|
|
173
|
-
*/
|
|
174
|
-
scopeQueryByKey<Q = unknown>(fn: TableGroupQueryScoper<Q>): this {
|
|
175
|
-
this._scopeFn = fn as TableGroupQueryScoper<unknown>
|
|
176
|
-
this._scopable = true
|
|
177
|
-
return this
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Override the per-record key resolver. The returned string is the
|
|
182
|
-
* stable bucket key — it round-trips through `?<prefix>groupKey=` on
|
|
183
|
-
* drill-in and lands as the second arg of `scopeQueryByKey`. Default
|
|
184
|
-
* = the raw column value cast to string (or `YYYY-MM-DD` when
|
|
185
|
-
* `.date()` is on). Auto-arms `.scopable(true)`.
|
|
186
|
-
*/
|
|
187
|
-
getKeyFromRecordUsing(fn: TableGroupKeyHandler<R>): this {
|
|
188
|
-
this._keyFn = fn
|
|
189
|
-
this._scopable = true
|
|
190
|
-
return this
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// ─── Getters ──────────────────────────────────────────
|
|
194
|
-
|
|
195
|
-
getColumn(): string { return this._column }
|
|
196
|
-
/** Resolved label — `.label(text)` if set, else the column name. */
|
|
197
|
-
getLabel(): string { return this._label ?? this._column }
|
|
198
|
-
isCollapsible(): boolean { return this._collapsible }
|
|
199
|
-
isCollapsed(): boolean { return this._collapsed }
|
|
200
|
-
getTitleHandler(): TableGroupTitleHandler<R> | undefined { return this._titleFn }
|
|
201
|
-
getDescriptionHandler(): TableGroupDescriptionHandler<R> | undefined { return this._descriptionFn }
|
|
202
|
-
isDate(): boolean { return this._date }
|
|
203
|
-
getKeyComparator(): TableGroupKeyComparator | undefined { return this._keyComparator }
|
|
204
|
-
isScopable(): boolean { return this._scopable }
|
|
205
|
-
getKeyHandler(): TableGroupKeyHandler<R> | undefined { return this._keyFn }
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Resolve the active scoper. Returns the user-supplied function when
|
|
209
|
-
* set; otherwise installs a default:
|
|
210
|
-
*
|
|
211
|
-
* - **Date groups** (`.date()`): whole-day range over the column —
|
|
212
|
-
* `(q, key) => q.where(col, '>=', key + ' 00:00:00').where(col, '<=', key + ' 23:59:59')`.
|
|
213
|
-
* Strings are picked over `Date` instances so the default composes
|
|
214
|
-
* with ORMs that accept date-string literals; consumers wanting
|
|
215
|
-
* sub-day buckets / timezone-aware ranges supply their own scoper.
|
|
216
|
-
* - **Plain groups**: exact-match — `(q, key) => q.where(col, '=', key)`.
|
|
217
|
-
*
|
|
218
|
-
* Note: the default uses `where(col, '>=', …)` 3-arg form. Adapter that
|
|
219
|
-
* only supports 2-arg `where(col, value)` need to detect the comparison
|
|
220
|
-
* argument shape — every ORM pilotiq ships against today supports the
|
|
221
|
-
* 3-arg form via `ModelQuery.where`.
|
|
222
|
-
*/
|
|
223
|
-
resolveScoper<Q = unknown>(): TableGroupQueryScoper<Q> {
|
|
224
|
-
if (this._scopeFn) return this._scopeFn as TableGroupQueryScoper<Q>
|
|
225
|
-
const col = this._column
|
|
226
|
-
if (this._date) {
|
|
227
|
-
return ((q: { where: (...args: unknown[]) => unknown }, key: string) => {
|
|
228
|
-
// Empty key clears the bucket — fall back to a no-op so a stale
|
|
229
|
-
// groupKey doesn't accidentally filter the table to zero rows.
|
|
230
|
-
if (key === '') return q
|
|
231
|
-
return (q
|
|
232
|
-
.where(col, '>=', `${key} 00:00:00`) as typeof q)
|
|
233
|
-
.where(col, '<=', `${key} 23:59:59`)
|
|
234
|
-
}) as unknown as TableGroupQueryScoper<Q>
|
|
235
|
-
}
|
|
236
|
-
return ((q: { where: (...args: unknown[]) => unknown }, key: string) =>
|
|
237
|
-
q.where(col, '=', key)) as unknown as TableGroupQueryScoper<Q>
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Derive the stable bucket key for a record. User-supplied handler
|
|
242
|
-
* wins; otherwise falls back to the column's raw value cast to string
|
|
243
|
-
* (or the `YYYY-MM-DD` bucket when `.date()` is on). Unparseable /
|
|
244
|
-
* null values resolve to `''` so they cluster under the empty bucket.
|
|
245
|
-
*/
|
|
246
|
-
resolveKey(record: R): string {
|
|
247
|
-
if (this._keyFn) {
|
|
248
|
-
try {
|
|
249
|
-
const k = this._keyFn(record)
|
|
250
|
-
return k === undefined ? '' : String(k)
|
|
251
|
-
} catch {
|
|
252
|
-
return ''
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
const raw = (record as Record<string, unknown>)[this._column]
|
|
256
|
-
if (this._date) return bucketDateValue(raw)
|
|
257
|
-
return raw == null || raw === '' ? '' : String(raw)
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
toMeta(): TableGroupMeta {
|
|
261
|
-
return {
|
|
262
|
-
column: this._column,
|
|
263
|
-
label: this.getLabel(),
|
|
264
|
-
...(this._collapsible ? { collapsible: true as const } : {}),
|
|
265
|
-
...(this._collapsed ? { collapsed: true as const } : {}),
|
|
266
|
-
...(this._date ? { date: true as const } : {}),
|
|
267
|
-
...(this._scopable ? { scopable: true as const } : {}),
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Bucket a date-like value to a `YYYY-MM-DD` key. Used by `TableGroup.date()`
|
|
274
|
-
* to compute the stable-sort key for `_groupValue`. Returns `''` for
|
|
275
|
-
* unparseable inputs so they cluster under the empty bucket (which the
|
|
276
|
-
* dispatcher already pushes to the bottom).
|
|
277
|
-
*/
|
|
278
|
-
export function bucketDateValue(raw: unknown): string {
|
|
279
|
-
if (raw == null || raw === '') return ''
|
|
280
|
-
const d = raw instanceof Date ? raw : new Date(raw as string | number)
|
|
281
|
-
if (Number.isNaN(d.getTime())) return ''
|
|
282
|
-
// ISO date in UTC. Tables that need timezone-aware buckets should
|
|
283
|
-
// override via `.getTitleFromRecordUsing` and roll their own bucket.
|
|
284
|
-
const y = d.getUTCFullYear()
|
|
285
|
-
const m = String(d.getUTCMonth() + 1).padStart(2, '0')
|
|
286
|
-
const day = String(d.getUTCDate()).padStart(2, '0')
|
|
287
|
-
return `${y}-${m}-${day}`
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Convenience comparator factory for `TableGroup.orderUsing(...)`. Returns
|
|
292
|
-
* a comparator that sorts the supplied keys in declaration order, with any
|
|
293
|
-
* unknown keys falling through to alphabetic ordering AFTER the listed ones.
|
|
294
|
-
*
|
|
295
|
-
* Use it to pin enum-like values without writing a manual switch:
|
|
296
|
-
*
|
|
297
|
-
* ```ts
|
|
298
|
-
* TableGroup.make('status').orderUsing(orderByKeys(['draft', 'published']))
|
|
299
|
-
* ```
|
|
300
|
-
*
|
|
301
|
-
* The empty bucket is unaffected — `dispatchTable` always sinks empty groups
|
|
302
|
-
* to the bottom regardless of what your comparator returns for them.
|
|
303
|
-
*/
|
|
304
|
-
export function orderByKeys(keys: ReadonlyArray<string>): TableGroupKeyComparator {
|
|
305
|
-
const rank = new Map<string, number>()
|
|
306
|
-
keys.forEach((k, i) => rank.set(k, i))
|
|
307
|
-
return (a, b) => {
|
|
308
|
-
const ra = rank.get(a)
|
|
309
|
-
const rb = rank.get(b)
|
|
310
|
-
if (ra !== undefined && rb !== undefined) return ra - rb
|
|
311
|
-
if (ra !== undefined) return -1
|
|
312
|
-
if (rb !== undefined) return 1
|
|
313
|
-
return a < b ? -1 : a > b ? 1 : 0
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Default title formatter used when `TableGroup.date()` is on but the
|
|
319
|
-
* user didn't supply `getTitleFromRecordUsing`. Returns "May 4, 2026"-
|
|
320
|
-
* style text, or the raw bucket key if the date is unparseable.
|
|
321
|
-
*/
|
|
322
|
-
export function formatDateBucketTitle(raw: unknown): string {
|
|
323
|
-
if (raw == null || raw === '') return ''
|
|
324
|
-
const d = raw instanceof Date ? raw : new Date(raw as string | number)
|
|
325
|
-
if (Number.isNaN(d.getTime())) return String(raw)
|
|
326
|
-
try {
|
|
327
|
-
return new Intl.DateTimeFormat('en-US', {
|
|
328
|
-
year: 'numeric', month: 'long', day: 'numeric',
|
|
329
|
-
timeZone: 'UTC',
|
|
330
|
-
}).format(d)
|
|
331
|
-
} catch {
|
|
332
|
-
return bucketDateValue(raw)
|
|
333
|
-
}
|
|
334
|
-
}
|