@pilotiq/pilotiq 0.23.1 → 0.24.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +91 -0
- package/boost/guidelines.md +566 -0
- package/boost/skills/pilotiq-fields/SKILL.md +47 -0
- package/boost/skills/pilotiq-fields/rules/field-catalog.md +288 -0
- package/boost/skills/pilotiq-fields/rules/reactive-fields.md +199 -0
- package/boost/skills/pilotiq-fields/rules/validation.md +198 -0
- package/boost/skills/pilotiq-relations/SKILL.md +47 -0
- package/boost/skills/pilotiq-relations/rules/relation-managers.md +256 -0
- package/boost/skills/pilotiq-relations/rules/repeater-relationship.md +177 -0
- package/boost/skills/pilotiq-resource/SKILL.md +61 -0
- package/boost/skills/pilotiq-resource/rules/authorization.md +242 -0
- package/boost/skills/pilotiq-resource/rules/defining-resources.md +228 -0
- package/boost/skills/pilotiq-resource/rules/page-overrides.md +296 -0
- package/dist/actions/exportFactory.d.ts +10 -0
- package/dist/actions/exportFactory.d.ts.map +1 -1
- package/dist/actions/exportFactory.js +10 -0
- package/dist/actions/exportFactory.js.map +1 -1
- package/dist/react/CollabRoomContext.d.ts +5 -5
- package/dist/react/index.d.ts +0 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +0 -1
- package/dist/react/index.js.map +1 -1
- package/dist/routes/helpers.d.ts.map +1 -1
- package/dist/routes/helpers.js +6 -2
- package/dist/routes/helpers.js.map +1 -1
- package/dist/routes/relations.d.ts.map +1 -1
- package/dist/routes/relations.js +12 -0
- package/dist/routes/relations.js.map +1 -1
- package/package.json +6 -1
- package/.turbo/turbo-build.log +0 -8
- package/CLAUDE.md +0 -265
- package/dist/react/useCollabSeed.d.ts +0 -23
- package/dist/react/useCollabSeed.d.ts.map +0 -1
- package/dist/react/useCollabSeed.js +0 -82
- package/dist/react/useCollabSeed.js.map +0 -1
- package/src/Cluster.test.ts +0 -283
- package/src/Cluster.ts +0 -83
- package/src/Column.test.ts +0 -199
- package/src/Column.ts +0 -710
- package/src/Global.test.ts +0 -367
- package/src/Global.ts +0 -169
- package/src/Page.test.ts +0 -114
- package/src/Page.ts +0 -208
- package/src/Pilotiq.perf.test.ts +0 -252
- package/src/Pilotiq.test.ts +0 -129
- package/src/Pilotiq.ts +0 -1158
- package/src/PilotiqRegistry.ts +0 -36
- package/src/PilotiqServiceProvider.ts +0 -121
- package/src/RelationManager.test.ts +0 -400
- package/src/RelationManager.ts +0 -527
- package/src/RenderHook.test.ts +0 -252
- package/src/RenderHook.ts +0 -242
- package/src/Resource.test.ts +0 -284
- package/src/Resource.ts +0 -526
- package/src/RightPanel.test.ts +0 -202
- package/src/RightPanel.ts +0 -132
- package/src/Tab.test.ts +0 -91
- package/src/Tab.ts +0 -156
- package/src/UserMenuItem.ts +0 -145
- package/src/actions/Action.test.ts +0 -2526
- package/src/actions/Action.ts +0 -1515
- package/src/actions/ActionGroup.test.ts +0 -112
- package/src/actions/ActionGroup.ts +0 -173
- package/src/actions/attachFactory.ts +0 -172
- package/src/actions/bulkFactories.ts +0 -168
- package/src/actions/crudFactories.ts +0 -220
- package/src/actions/exportFactory.ts +0 -215
- package/src/actions/factoryHelpers.ts +0 -177
- package/src/actions/importFactory.ts +0 -243
- package/src/actions/index.ts +0 -17
- package/src/actions/m2mFactories.ts +0 -193
- package/src/actions/relationFactories.ts +0 -372
- package/src/applyPageHooks.test.ts +0 -463
- package/src/applyPageHooks.ts +0 -330
- package/src/authorization.test.ts +0 -483
- package/src/breadcrumbs.test.ts +0 -238
- package/src/cells/coerce.test.ts +0 -85
- package/src/cells/coerce.ts +0 -84
- package/src/clusterPaths.ts +0 -35
- package/src/columns/BadgeColumn.test.ts +0 -54
- package/src/columns/BadgeColumn.ts +0 -32
- package/src/columns/BooleanColumn.test.ts +0 -41
- package/src/columns/BooleanColumn.ts +0 -18
- package/src/columns/ColorColumn.test.ts +0 -37
- package/src/columns/ColorColumn.ts +0 -38
- package/src/columns/IconColumn.test.ts +0 -54
- package/src/columns/IconColumn.ts +0 -37
- package/src/columns/ImageColumn.test.ts +0 -41
- package/src/columns/ImageColumn.ts +0 -28
- package/src/columns/SelectColumn.ts +0 -98
- package/src/columns/TextColumn.test.ts +0 -190
- package/src/columns/TextColumn.ts +0 -20
- package/src/columns/TextInputColumn.ts +0 -68
- package/src/columns/ToggleColumn.ts +0 -46
- package/src/columns/editableColumns.test.ts +0 -238
- package/src/columns/index.ts +0 -9
- package/src/defaultGlobalPages.ts +0 -95
- package/src/defaultPages.test.ts +0 -634
- package/src/defaultPages.ts +0 -617
- package/src/defaultViewPage.test.ts +0 -147
- package/src/elements/Form.test.ts +0 -223
- package/src/elements/Form.ts +0 -416
- package/src/elements/ListTabs.ts +0 -28
- package/src/elements/Table.test.ts +0 -422
- package/src/elements/Table.ts +0 -850
- package/src/elements/TableGroup.test.ts +0 -260
- package/src/elements/TableGroup.ts +0 -334
- package/src/elements/dispatchAction.test.ts +0 -463
- package/src/elements/dispatchAction.ts +0 -355
- package/src/elements/dispatchForm.test.ts +0 -477
- package/src/elements/dispatchForm.ts +0 -1993
- package/src/elements/dispatchTable.test.ts +0 -1514
- package/src/elements/dispatchTable.ts +0 -745
- package/src/elements/index.ts +0 -21
- package/src/entries/BadgeEntry.ts +0 -39
- package/src/entries/CodeEntry.test.ts +0 -40
- package/src/entries/CodeEntry.ts +0 -52
- package/src/entries/ColorEntry.ts +0 -63
- package/src/entries/ComponentEntry.test.ts +0 -173
- package/src/entries/ComponentEntry.ts +0 -95
- package/src/entries/Entry.ts +0 -304
- package/src/entries/IconEntry.ts +0 -49
- package/src/entries/ImageEntry.ts +0 -61
- package/src/entries/KeyValueEntry.ts +0 -47
- package/src/entries/RepeatableEntry.test.ts +0 -239
- package/src/entries/RepeatableEntry.ts +0 -173
- package/src/entries/TextEntry.test.ts +0 -394
- package/src/entries/TextEntry.ts +0 -60
- package/src/entries/index.ts +0 -12
- package/src/entries/leaves.test.ts +0 -306
- package/src/entries/registry.ts +0 -54
- package/src/fields/BuilderField.test.ts +0 -1188
- package/src/fields/BuilderField.ts +0 -605
- package/src/fields/BuilderRelationship.test.ts +0 -811
- package/src/fields/CheckboxField.test.ts +0 -44
- package/src/fields/CheckboxField.ts +0 -27
- package/src/fields/CheckboxListField.test.ts +0 -99
- package/src/fields/CheckboxListField.ts +0 -66
- package/src/fields/ColorPickerField.test.ts +0 -33
- package/src/fields/ColorPickerField.ts +0 -25
- package/src/fields/DateField.ts +0 -54
- package/src/fields/DateTimeField.test.ts +0 -55
- package/src/fields/EmailField.ts +0 -16
- package/src/fields/Field.test.ts +0 -654
- package/src/fields/Field.ts +0 -817
- package/src/fields/FileUploadField.test.ts +0 -143
- package/src/fields/FileUploadField.ts +0 -159
- package/src/fields/HiddenField.test.ts +0 -27
- package/src/fields/HiddenField.ts +0 -28
- package/src/fields/KeyValueField.test.ts +0 -105
- package/src/fields/KeyValueField.ts +0 -55
- package/src/fields/MarkdownField.test.ts +0 -167
- package/src/fields/MarkdownField.ts +0 -162
- package/src/fields/NumberField.ts +0 -33
- package/src/fields/RadioField.test.ts +0 -94
- package/src/fields/RadioField.ts +0 -67
- package/src/fields/RepeaterField.test.ts +0 -1806
- package/src/fields/RepeaterField.ts +0 -939
- package/src/fields/RepeaterRelationship.test.ts +0 -1923
- package/src/fields/RepeaterSimple.test.ts +0 -248
- package/src/fields/RowButton.test.ts +0 -219
- package/src/fields/RowButton.ts +0 -135
- package/src/fields/SelectField.test.ts +0 -192
- package/src/fields/SelectField.ts +0 -235
- package/src/fields/SliderField.test.ts +0 -50
- package/src/fields/SliderField.ts +0 -53
- package/src/fields/SlugField.ts +0 -24
- package/src/fields/TagsInputField.test.ts +0 -154
- package/src/fields/TagsInputField.ts +0 -133
- package/src/fields/TextField.test.ts +0 -213
- package/src/fields/TextField.ts +0 -177
- package/src/fields/TextareaField.test.ts +0 -58
- package/src/fields/TextareaField.ts +0 -59
- package/src/fields/ToggleButtonsField.test.ts +0 -106
- package/src/fields/ToggleButtonsField.ts +0 -59
- package/src/fields/ToggleField.ts +0 -16
- package/src/fields/disableOptionsWhenSelectedInSiblingRepeaterItems.test.ts +0 -319
- package/src/fields/optionsResolver.ts +0 -95
- package/src/fields/resolveField.ts +0 -28
- package/src/filters/BooleanFilter.ts +0 -35
- package/src/filters/DateRangeFilter.test.ts +0 -194
- package/src/filters/DateRangeFilter.ts +0 -148
- package/src/filters/Filter.test.ts +0 -268
- package/src/filters/Filter.ts +0 -184
- package/src/filters/FormFilter.test.ts +0 -238
- package/src/filters/FormFilter.ts +0 -215
- package/src/filters/MultiSelectFilter.test.ts +0 -119
- package/src/filters/MultiSelectFilter.ts +0 -78
- package/src/filters/QueryBuilderFilter.test.ts +0 -662
- package/src/filters/QueryBuilderFilter.ts +0 -398
- package/src/filters/SelectFilter.ts +0 -46
- package/src/filters/TernaryFilter.test.ts +0 -160
- package/src/filters/TernaryFilter.ts +0 -72
- package/src/filters/TrashedFilter.test.ts +0 -149
- package/src/filters/TrashedFilter.ts +0 -55
- package/src/filters/queryBuilder/BooleanConstraint.ts +0 -31
- package/src/filters/queryBuilder/Constraint.ts +0 -115
- package/src/filters/queryBuilder/DateConstraint.ts +0 -69
- package/src/filters/queryBuilder/NumberConstraint.ts +0 -66
- package/src/filters/queryBuilder/SelectConstraint.ts +0 -72
- package/src/filters/queryBuilder/TextConstraint.ts +0 -64
- package/src/filters/queryBuilder/index.ts +0 -12
- package/src/icons/index.ts +0 -2
- package/src/icons/lucide.ts +0 -204
- package/src/icons/registry.test.ts +0 -56
- package/src/icons/registry.ts +0 -41
- package/src/icons/types.ts +0 -47
- package/src/index.ts +0 -525
- package/src/io/csv.test.ts +0 -142
- package/src/io/csv.ts +0 -170
- package/src/nestedRelationManagerData.test.ts +0 -547
- package/src/notifications/Notification.test.ts +0 -210
- package/src/notifications/Notification.ts +0 -354
- package/src/notifications/broadcast.test.ts +0 -110
- package/src/notifications/broadcast.ts +0 -95
- package/src/notifications/database.test.ts +0 -383
- package/src/notifications/database.ts +0 -398
- package/src/notifications/databaseNotifications.test.ts +0 -187
- package/src/notifications/dispatchNotificationAction.test.ts +0 -341
- package/src/notifications/dispatchNotificationAction.ts +0 -142
- package/src/notifications/flash.test.ts +0 -89
- package/src/notifications/flash.ts +0 -71
- package/src/notifications/index.ts +0 -45
- package/src/notifications/registerBroadcastAuth.test.ts +0 -134
- package/src/notifications/registerBroadcastAuth.ts +0 -100
- package/src/notifications/resolveSavedNotification.test.ts +0 -82
- package/src/notifications/resolveSavedNotification.ts +0 -59
- package/src/notifications/types.ts +0 -93
- package/src/orm/m2mAccessor.ts +0 -66
- package/src/orm/modelDefaults.test.ts +0 -633
- package/src/orm/modelDefaults.ts +0 -666
- package/src/pageData/breadcrumbs.ts +0 -288
- package/src/pageData/forms.ts +0 -578
- package/src/pageData/helpers.ts +0 -857
- package/src/pageData/misc.ts +0 -347
- package/src/pageData/navigation.ts +0 -842
- package/src/pageData/relationPages.ts +0 -1248
- package/src/pageData/relationTabs.ts +0 -286
- package/src/pageData/resourcePages.ts +0 -609
- package/src/pageData.test.ts +0 -1545
- package/src/pageData.ts +0 -341
- package/src/plugins/index.ts +0 -8
- package/src/plugins/themeEditor.test.ts +0 -36
- package/src/plugins/themeEditor.ts +0 -45
- package/src/react/AppShell.tsx +0 -251
- package/src/react/CollabExtensionFactoryRegistry.ts +0 -55
- package/src/react/CollabRoomContext.ts +0 -98
- package/src/react/CollabTextRendererRegistry.ts +0 -102
- package/src/react/CommandPalette.tsx +0 -375
- package/src/react/CurrentUserContext.tsx +0 -50
- package/src/react/CustomPageWrapperGate.tsx +0 -69
- package/src/react/CustomPageWrapperRegistry.ts +0 -45
- package/src/react/FieldFocusReporterRegistry.ts +0 -37
- package/src/react/FieldLabelSlotRegistry.ts +0 -30
- package/src/react/FieldPresenceRegistry.ts +0 -46
- package/src/react/FormCollabBindingRegistry.ts +0 -242
- package/src/react/FormStateContext.tsx +0 -591
- package/src/react/HeadHooks.tsx +0 -126
- package/src/react/MarkdownEditorRegistry.test.ts +0 -38
- package/src/react/MarkdownEditorRegistry.ts +0 -107
- package/src/react/NotificationActionStrip.tsx +0 -263
- package/src/react/NotificationBell.tsx +0 -426
- package/src/react/PendingSuggestionApplierRegistry.test.ts +0 -97
- package/src/react/PendingSuggestionApplierRegistry.ts +0 -98
- package/src/react/PendingSuggestionOverlayRegistry.ts +0 -54
- package/src/react/PendingSuggestionsContext.tsx +0 -172
- package/src/react/RecordWrapperGate.tsx +0 -58
- package/src/react/RecordWrapperRegistry.ts +0 -39
- package/src/react/RenderHookSlot.tsx +0 -32
- package/src/react/RightSidebar.tsx +0 -257
- package/src/react/RightSidebarContext.tsx +0 -234
- package/src/react/RightSidebarTrigger.tsx +0 -53
- package/src/react/RowCoordsContext.tsx +0 -23
- package/src/react/SchemaRenderer.tsx +0 -549
- package/src/react/SearchTrigger.tsx +0 -46
- package/src/react/ThemeProvider.tsx +0 -93
- package/src/react/ThemeSettingsPage.tsx +0 -579
- package/src/react/ThemeToggle.tsx +0 -20
- package/src/react/Toaster.tsx +0 -158
- package/src/react/UserMenu.tsx +0 -196
- package/src/react/WidgetDataContext.tsx +0 -157
- package/src/react/cells/EditableCell.tsx +0 -389
- package/src/react/component-slots.test.ts +0 -103
- package/src/react/component-slots.ts +0 -116
- package/src/react/fieldJsHandler.test.ts +0 -166
- package/src/react/fieldJsHandler.ts +0 -79
- package/src/react/fields/BuilderInput.tsx +0 -1078
- package/src/react/fields/CheckboxInput.tsx +0 -39
- package/src/react/fields/CheckboxListInput.tsx +0 -102
- package/src/react/fields/ColorInput.tsx +0 -71
- package/src/react/fields/DateFieldInput.tsx +0 -70
- package/src/react/fields/DateTimeInput.tsx +0 -62
- package/src/react/fields/FieldShell.tsx +0 -348
- package/src/react/fields/FileUploadInput.tsx +0 -639
- package/src/react/fields/HiddenInput.tsx +0 -17
- package/src/react/fields/KeyValueInput.tsx +0 -230
- package/src/react/fields/MarkdownInput.tsx +0 -560
- package/src/react/fields/RadioInput.tsx +0 -81
- package/src/react/fields/RepeaterInput.test.ts +0 -116
- package/src/react/fields/RepeaterInput.tsx +0 -1420
- package/src/react/fields/SelectFieldInput.tsx +0 -280
- package/src/react/fields/SliderInput.tsx +0 -81
- package/src/react/fields/TagsInput.tsx +0 -283
- package/src/react/fields/TextLikeInput.tsx +0 -256
- package/src/react/fields/ToggleButtonsInput.tsx +0 -60
- package/src/react/fields/ToggleFieldInput.tsx +0 -56
- package/src/react/fields/relationshipRenameDispatch.test.ts +0 -106
- package/src/react/fields/relationshipRenameDispatch.ts +0 -97
- package/src/react/fields/repeaterReconcile.test.ts +0 -114
- package/src/react/fields/repeaterReconcile.ts +0 -104
- package/src/react/fields/rowChromeButton.tsx +0 -336
- package/src/react/fields/rowState.ts +0 -106
- package/src/react/fields/syncRowGates.test.ts +0 -202
- package/src/react/fields/syncRowGates.ts +0 -66
- package/src/react/fields/textInputControls.tsx +0 -238
- package/src/react/fields/useRowReorderDnd.ts +0 -78
- package/src/react/formStateHelpers.test.ts +0 -508
- package/src/react/formStateHelpers.ts +0 -381
- package/src/react/hooks/use-mobile.ts +0 -19
- package/src/react/icon-context.tsx +0 -60
- package/src/react/index.ts +0 -195
- package/src/react/layouts/SidebarLayout.tsx +0 -250
- package/src/react/layouts/TopbarLayout.tsx +0 -258
- package/src/react/navigate.tsx +0 -37
- package/src/react/onProviderSynced.test.ts +0 -90
- package/src/react/parseRecordEditUrl.test.ts +0 -122
- package/src/react/parseRecordEditUrl.ts +0 -94
- package/src/react/persistedState.ts +0 -40
- package/src/react/registry.ts +0 -48
- package/src/react/right-panel-registry.tsx +0 -47
- package/src/react/schemaRenderer/AlertRenderer.tsx +0 -112
- package/src/react/schemaRenderer/EntryRenderer.tsx +0 -501
- package/src/react/schemaRenderer/SectionRenderer.tsx +0 -120
- package/src/react/schemaRenderer/SimpleElements.tsx +0 -306
- package/src/react/schemaRenderer/TabsRenderer.tsx +0 -62
- package/src/react/schemaRenderer/WizardRenderer.tsx +0 -338
- package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +0 -177
- package/src/react/schemaRenderer/action/ActionModalDialog.tsx +0 -273
- package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +0 -61
- package/src/react/schemaRenderer/action/HandlerActionButton.tsx +0 -43
- package/src/react/schemaRenderer/action/MethodActionButton.tsx +0 -64
- package/src/react/schemaRenderer/action/buttons.tsx +0 -99
- package/src/react/schemaRenderer/action/helpers.ts +0 -140
- package/src/react/schemaRenderer/action/renderAction.tsx +0 -245
- package/src/react/schemaRenderer/columnFormat.ts +0 -65
- package/src/react/schemaRenderer/constants.ts +0 -50
- package/src/react/schemaRenderer/form/FormRenderer.tsx +0 -274
- package/src/react/schemaRenderer/form/renderField.tsx +0 -511
- package/src/react/schemaRenderer/helpers.tsx +0 -81
- package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +0 -308
- package/src/react/schemaRenderer/table/TableRenderer.tsx +0 -123
- package/src/react/schemaRenderer/table/TableRendererBody.tsx +0 -974
- package/src/react/schemaRenderer/table/filters.tsx +0 -1233
- package/src/react/schemaRenderer/table/formatCell.tsx +0 -264
- package/src/react/schemaRenderer/table/links.tsx +0 -112
- package/src/react/schemaRenderer/table/renderRowActions.tsx +0 -52
- package/src/react/schemaRenderer/table/url.tsx +0 -143
- package/src/react/theme-preview/apply.ts +0 -99
- package/src/react/theme-preview/build-html.ts +0 -436
- package/src/react/ui/button.tsx +0 -51
- package/src/react/ui/calendar.tsx +0 -67
- package/src/react/ui/checkbox.tsx +0 -29
- package/src/react/ui/dialog.tsx +0 -108
- package/src/react/ui/dropdown-menu.tsx +0 -97
- package/src/react/ui/input.tsx +0 -20
- package/src/react/ui/label.tsx +0 -21
- package/src/react/ui/popover.tsx +0 -50
- package/src/react/ui/select.tsx +0 -169
- package/src/react/ui/separator.tsx +0 -25
- package/src/react/ui/sheet.tsx +0 -136
- package/src/react/ui/sidebar.tsx +0 -723
- package/src/react/ui/skeleton.tsx +0 -13
- package/src/react/ui/slider.tsx +0 -34
- package/src/react/ui/switch.tsx +0 -28
- package/src/react/ui/table.tsx +0 -105
- package/src/react/ui/tabs.tsx +0 -63
- package/src/react/ui/textarea.tsx +0 -18
- package/src/react/ui/tooltip.tsx +0 -64
- package/src/react/useCollabSeed.ts +0 -86
- package/src/react/useResizableWidth.ts +0 -139
- package/src/react/utils.ts +0 -6
- package/src/react/widgetRegistry.test.ts +0 -43
- package/src/react/widgetRegistry.ts +0 -50
- package/src/react/widgets/StatsOverviewRenderer.tsx +0 -232
- package/src/react/widgets/TableWidgetRenderer.tsx +0 -231
- package/src/react/widgets/ViewRenderer.tsx +0 -71
- package/src/relationManagerData.test.ts +0 -1595
- package/src/richtext/index.ts +0 -8
- package/src/richtext/registry.ts +0 -89
- package/src/routes/globals.ts +0 -148
- package/src/routes/guard.test.ts +0 -325
- package/src/routes/helpers.ts +0 -700
- package/src/routes/pages.ts +0 -175
- package/src/routes/panel.ts +0 -204
- package/src/routes/relations.ts +0 -1227
- package/src/routes/resources.ts +0 -781
- package/src/routes/theme.ts +0 -91
- package/src/routes-nested-relations.test.ts +0 -676
- package/src/routes-relations.test.ts +0 -972
- package/src/routes.test.ts +0 -2027
- package/src/routes.ts +0 -303
- package/src/schema/Alert.test.ts +0 -109
- package/src/schema/Alert.ts +0 -131
- package/src/schema/Block.ts +0 -169
- package/src/schema/Breadcrumbs.ts +0 -40
- package/src/schema/Card.ts +0 -35
- package/src/schema/Divider.ts +0 -20
- package/src/schema/Element.ts +0 -219
- package/src/schema/EmptyState.test.ts +0 -37
- package/src/schema/EmptyState.ts +0 -63
- package/src/schema/Fieldset.ts +0 -43
- package/src/schema/Grid.ts +0 -43
- package/src/schema/Group.ts +0 -30
- package/src/schema/Heading.ts +0 -39
- package/src/schema/Html.ts +0 -67
- package/src/schema/Icon.ts +0 -54
- package/src/schema/Image.ts +0 -57
- package/src/schema/LinkTag.ts +0 -41
- package/src/schema/Markdown.ts +0 -85
- package/src/schema/MetaTag.ts +0 -41
- package/src/schema/RelationTabs.ts +0 -71
- package/src/schema/ScriptTag.ts +0 -55
- package/src/schema/Section.ts +0 -160
- package/src/schema/ServerDataElement.test.ts +0 -140
- package/src/schema/ServerDataElement.ts +0 -156
- package/src/schema/SlotComponent.test.ts +0 -77
- package/src/schema/SlotComponent.ts +0 -71
- package/src/schema/Split.ts +0 -50
- package/src/schema/Stat.test.ts +0 -118
- package/src/schema/Stat.ts +0 -154
- package/src/schema/StatsOverview.test.ts +0 -141
- package/src/schema/StatsOverview.ts +0 -119
- package/src/schema/StyleTag.ts +0 -35
- package/src/schema/TableWidget.test.ts +0 -297
- package/src/schema/TableWidget.ts +0 -289
- package/src/schema/Tabs.ts +0 -79
- package/src/schema/Text.ts +0 -58
- package/src/schema/UnorderedList.ts +0 -49
- package/src/schema/View.test.ts +0 -111
- package/src/schema/View.ts +0 -127
- package/src/schema/Wizard.ts +0 -220
- package/src/schema/containers.test.ts +0 -564
- package/src/schema/headTags.test.ts +0 -134
- package/src/schema/index.ts +0 -40
- package/src/schema/primes.test.ts +0 -269
- package/src/schema/resolveSchema.test.ts +0 -379
- package/src/schema/resolveSchema.ts +0 -917
- package/src/schema/sanitize.ts +0 -58
- package/src/search.test.ts +0 -446
- package/src/search.ts +0 -178
- package/src/sessionFilters.test.ts +0 -375
- package/src/sessionFilters.ts +0 -143
- package/src/slot-components/index.ts +0 -10
- package/src/slot-components/registry.ts +0 -56
- package/src/styles/file-upload.css +0 -13
- package/src/summarizers/Summarizer.test.ts +0 -84
- package/src/summarizers/Summarizer.ts +0 -123
- package/src/summarizers/index.ts +0 -11
- package/src/theme/base-colors.ts +0 -68
- package/src/theme/chart-colors.ts +0 -50
- package/src/theme/colors.ts +0 -447
- package/src/theme/generate-css.test.ts +0 -139
- package/src/theme/generate-css.ts +0 -44
- package/src/theme/generate-scale.test.ts +0 -106
- package/src/theme/generate-scale.ts +0 -97
- package/src/theme/icon-map.ts +0 -42
- package/src/theme/index.ts +0 -34
- package/src/theme/migrate.test.ts +0 -178
- package/src/theme/migrate.ts +0 -81
- package/src/theme/presets.ts +0 -135
- package/src/theme/radius.ts +0 -18
- package/src/theme/resolve.test.ts +0 -238
- package/src/theme/resolve.ts +0 -96
- package/src/theme/spacing.ts +0 -18
- package/src/theme/storage.test.ts +0 -126
- package/src/theme/storage.ts +0 -106
- package/src/theme/theme-colors.ts +0 -88
- package/src/theme/types.ts +0 -125
- package/src/uploads/UploadAdapter.ts +0 -35
- package/src/uploads/index.ts +0 -2
- package/src/uploads/localUpload.test.ts +0 -70
- package/src/uploads/localUpload.ts +0 -84
- package/src/validation/Validator.ts +0 -49
- package/src/validation/index.ts +0 -28
- package/src/validation/rules.ts +0 -78
- package/src/validation/runValidators.ts +0 -435
- package/src/validation/uniqueValidator.test.ts +0 -196
- package/src/validation/uniqueValidator.ts +0 -133
- package/src/validation/validators.test.ts +0 -268
- package/src/vite.test.ts +0 -184
- package/src/vite.ts +0 -787
- package/src/widgets/index.ts +0 -10
- package/src/widgets/registry.ts +0 -45
- package/src/widgets.test.ts +0 -592
- package/tsconfig.build.json +0 -11
- package/tsconfig.json +0 -4
- package/tsconfig.test.json +0 -10
- package/views/react/Dashboard.tsx +0 -27
- package/views/react/Resources/Form.tsx +0 -102
- package/views/react/Resources/Index.tsx +0 -49
package/src/react/Toaster.tsx
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'
|
|
2
|
-
import {
|
|
3
|
-
CheckCircle2Icon, AlertTriangleIcon, AlertCircleIcon, InfoIcon, XIcon,
|
|
4
|
-
type LucideIcon,
|
|
5
|
-
} from 'lucide-react'
|
|
6
|
-
import type { NotificationMeta, NotificationType } from '../notifications/Notification.js'
|
|
7
|
-
import { NotificationActionStrip } from './NotificationActionStrip.js'
|
|
8
|
-
|
|
9
|
-
interface ToasterContextValue {
|
|
10
|
-
/** Add a notification to the stack. The id is auto-generated when
|
|
11
|
-
* absent so the same `Notification.make().toMeta()` can be re-used. */
|
|
12
|
-
notify: (n: NotificationMeta | Omit<NotificationMeta, 'id'>) => void
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const ToasterContext = createContext<ToasterContextValue>({ notify: () => {} })
|
|
16
|
-
|
|
17
|
-
const TYPE_ICONS: Record<NotificationType, LucideIcon> = {
|
|
18
|
-
info: InfoIcon,
|
|
19
|
-
success: CheckCircle2Icon,
|
|
20
|
-
warning: AlertTriangleIcon,
|
|
21
|
-
error: AlertCircleIcon,
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const TYPE_CLASSES: Record<NotificationType, string> = {
|
|
25
|
-
info: 'border-blue-200 bg-blue-50 text-blue-900 dark:border-blue-900 dark:bg-blue-950/40 dark:text-blue-100',
|
|
26
|
-
success: 'border-emerald-200 bg-emerald-50 text-emerald-900 dark:border-emerald-900 dark:bg-emerald-950/40 dark:text-emerald-100',
|
|
27
|
-
warning: 'border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-900 dark:bg-amber-950/40 dark:text-amber-100',
|
|
28
|
-
error: 'border-red-200 bg-red-50 text-red-900 dark:border-red-900 dark:bg-red-950/40 dark:text-red-100',
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const TYPE_ICON_CLASSES: Record<NotificationType, string> = {
|
|
32
|
-
info: 'text-blue-600 dark:text-blue-300',
|
|
33
|
-
success: 'text-emerald-600 dark:text-emerald-300',
|
|
34
|
-
warning: 'text-amber-600 dark:text-amber-300',
|
|
35
|
-
error: 'text-red-600 dark:text-red-300',
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
let _clientIdSeq = 0
|
|
39
|
-
function clientId(): string {
|
|
40
|
-
_clientIdSeq += 1
|
|
41
|
-
return `c-${_clientIdSeq}`
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface ToastItemProps {
|
|
45
|
-
item: NotificationMeta
|
|
46
|
-
onDismiss: () => void
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function ToastItem({ item, onDismiss }: ToastItemProps) {
|
|
50
|
-
const Icon = TYPE_ICONS[item.type]
|
|
51
|
-
const cls = TYPE_CLASSES[item.type]
|
|
52
|
-
const iconCls = TYPE_ICON_CLASSES[item.type]
|
|
53
|
-
|
|
54
|
-
// Auto-dismiss timer. Skip when duration === 0 (persistent until
|
|
55
|
-
// manually dismissed). Toasts with actions auto-extend to 0
|
|
56
|
-
// (persistent) — auto-dismissal would race the user's click.
|
|
57
|
-
useEffect(() => {
|
|
58
|
-
const hasActions = item.actions && item.actions.length > 0
|
|
59
|
-
const ms = item.duration ?? (hasActions ? 0 : 5000)
|
|
60
|
-
if (ms <= 0) return
|
|
61
|
-
const t = setTimeout(onDismiss, ms)
|
|
62
|
-
return () => clearTimeout(t)
|
|
63
|
-
}, [item.duration, item.actions, onDismiss])
|
|
64
|
-
|
|
65
|
-
return (
|
|
66
|
-
<div
|
|
67
|
-
role="status"
|
|
68
|
-
aria-live="polite"
|
|
69
|
-
className={`pointer-events-auto flex items-start gap-3 rounded-lg border px-4 py-3 shadow-lg ${cls}`}
|
|
70
|
-
>
|
|
71
|
-
<Icon className={`size-5 mt-0.5 shrink-0 ${iconCls}`} aria-hidden />
|
|
72
|
-
<div className="flex-1 min-w-0">
|
|
73
|
-
<p className="text-sm font-semibold leading-snug">{item.title}</p>
|
|
74
|
-
{item.body && (
|
|
75
|
-
<p className="text-xs leading-snug mt-0.5 opacity-90">{item.body}</p>
|
|
76
|
-
)}
|
|
77
|
-
{item.actions && item.actions.length > 0 && (
|
|
78
|
-
<NotificationActionStrip
|
|
79
|
-
actions={item.actions}
|
|
80
|
-
onAfterClick={onDismiss}
|
|
81
|
-
/>
|
|
82
|
-
)}
|
|
83
|
-
</div>
|
|
84
|
-
<button
|
|
85
|
-
type="button"
|
|
86
|
-
aria-label="Dismiss"
|
|
87
|
-
onClick={onDismiss}
|
|
88
|
-
className="shrink-0 rounded p-0.5 opacity-60 hover:opacity-100 transition"
|
|
89
|
-
>
|
|
90
|
-
<XIcon className="size-4" />
|
|
91
|
-
</button>
|
|
92
|
-
</div>
|
|
93
|
-
)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Provider that mounts the toast stack and exposes `useToast()` to descendants.
|
|
98
|
-
* Wrap your app once near the root (the AppShell does this automatically).
|
|
99
|
-
*
|
|
100
|
-
* `initialNotifications` is the server-flashed list — the AppShell reads
|
|
101
|
-
* it from `viewProps.notifications` and forwards it here. They flash on
|
|
102
|
-
* first mount and then disappear; subsequent re-renders don't re-flash.
|
|
103
|
-
*/
|
|
104
|
-
export function ToasterProvider({
|
|
105
|
-
children,
|
|
106
|
-
initialNotifications,
|
|
107
|
-
}: {
|
|
108
|
-
children: React.ReactNode
|
|
109
|
-
initialNotifications?: NotificationMeta[]
|
|
110
|
-
}) {
|
|
111
|
-
const [items, setItems] = useState<NotificationMeta[]>([])
|
|
112
|
-
// Ref instead of useState: React 18 Strict Mode (dev) intentionally
|
|
113
|
-
// runs effects twice on the same component instance to surface mount-
|
|
114
|
-
// time bugs. With a useState flag the second run sees the stale closure
|
|
115
|
-
// value (state setters are async; the second effect's closure was
|
|
116
|
-
// captured at the same render as the first), so it would re-add the
|
|
117
|
-
// notifications and the user gets duplicate toasts. A ref updates
|
|
118
|
-
// synchronously and is shared across both effect invocations of the
|
|
119
|
-
// same render.
|
|
120
|
-
const flashedRef = useRef(false)
|
|
121
|
-
|
|
122
|
-
// Flash server-provided notifications on first mount only.
|
|
123
|
-
useEffect(() => {
|
|
124
|
-
if (flashedRef.current) return
|
|
125
|
-
flashedRef.current = true
|
|
126
|
-
if (initialNotifications && initialNotifications.length > 0) {
|
|
127
|
-
setItems(prev => [...prev, ...initialNotifications])
|
|
128
|
-
}
|
|
129
|
-
}, [initialNotifications])
|
|
130
|
-
|
|
131
|
-
const notify = useCallback<ToasterContextValue['notify']>((n) => {
|
|
132
|
-
const id = (n as NotificationMeta).id ?? clientId()
|
|
133
|
-
setItems(prev => [...prev, { ...(n as NotificationMeta), id }])
|
|
134
|
-
}, [])
|
|
135
|
-
|
|
136
|
-
const dismiss = useCallback((id: string) => {
|
|
137
|
-
setItems(prev => prev.filter(x => x.id !== id))
|
|
138
|
-
}, [])
|
|
139
|
-
|
|
140
|
-
return (
|
|
141
|
-
<ToasterContext.Provider value={{ notify }}>
|
|
142
|
-
{children}
|
|
143
|
-
{/* Stack — fixed bottom-right, click-through unless toast hovered. */}
|
|
144
|
-
<div className="pointer-events-none fixed bottom-4 right-4 z-[100] flex w-[calc(100vw-2rem)] max-w-sm flex-col gap-2">
|
|
145
|
-
{items.map(item => (
|
|
146
|
-
<ToastItem key={item.id} item={item} onDismiss={() => dismiss(item.id)} />
|
|
147
|
-
))}
|
|
148
|
-
</div>
|
|
149
|
-
</ToasterContext.Provider>
|
|
150
|
-
)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/** Programmatic notify hook for client-side triggers (form submits,
|
|
154
|
-
* client-only handlers, etc.). Falls through silently when no
|
|
155
|
-
* ToasterProvider is mounted. */
|
|
156
|
-
export function useToast(): ToasterContextValue {
|
|
157
|
-
return useContext(ToasterContext)
|
|
158
|
-
}
|
package/src/react/UserMenu.tsx
DELETED
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import React from 'react'
|
|
4
|
-
import {
|
|
5
|
-
DropdownMenu,
|
|
6
|
-
DropdownMenuContent,
|
|
7
|
-
DropdownMenuItem,
|
|
8
|
-
DropdownMenuLabel,
|
|
9
|
-
DropdownMenuSeparator,
|
|
10
|
-
DropdownMenuTrigger,
|
|
11
|
-
} from './ui/dropdown-menu.js'
|
|
12
|
-
import { useIconFor } from './icon-context.js'
|
|
13
|
-
import type { UserMenuMeta } from '../pageData.js'
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Top-right user dropdown — avatar / initials trigger that opens to
|
|
17
|
-
* the configured user-menu items + a separator + the optional sign-out
|
|
18
|
-
* entry. Renders nothing when `userMenu` is null (no logged-in user).
|
|
19
|
-
*
|
|
20
|
-
* Sign-out renders as a `<form>` so CSRF middleware downstream can
|
|
21
|
-
* verify the request. `method: 'GET'` falls back to a plain `<a>`-style
|
|
22
|
-
* link when the app's logout endpoint is purely redirect-based.
|
|
23
|
-
*/
|
|
24
|
-
export function UserMenu({
|
|
25
|
-
userMenu,
|
|
26
|
-
before,
|
|
27
|
-
after,
|
|
28
|
-
}: {
|
|
29
|
-
userMenu: UserMenuMeta | null | undefined
|
|
30
|
-
/** Render-hook slot mounted at the top of the dropdown (above the
|
|
31
|
-
* user-identity label). Pass `<RenderHookSlot
|
|
32
|
-
* name="panels::user-menu.before" />`. */
|
|
33
|
-
before?: React.ReactNode
|
|
34
|
-
/** Render-hook slot mounted at the bottom of the dropdown (above the
|
|
35
|
-
* sign-out separator when present). Pass `<RenderHookSlot
|
|
36
|
-
* name="panels::user-menu.after" />`. */
|
|
37
|
-
after?: React.ReactNode
|
|
38
|
-
}) {
|
|
39
|
-
if (!userMenu) return null
|
|
40
|
-
const { user, items, signOut } = userMenu
|
|
41
|
-
|
|
42
|
-
const displayName = user.name ?? user.email ?? 'Account'
|
|
43
|
-
const initials = computeInitials(user.name ?? user.email)
|
|
44
|
-
|
|
45
|
-
const avatarProps: { avatar?: string; initials: string; alt: string } = { initials, alt: displayName }
|
|
46
|
-
if (user.avatar !== undefined) avatarProps.avatar = user.avatar
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<DropdownMenu>
|
|
50
|
-
<DropdownMenuTrigger
|
|
51
|
-
className="inline-flex items-center gap-2 rounded-md p-1 text-sm hover:bg-accent hover:text-accent-foreground transition-colors focus:outline-none"
|
|
52
|
-
aria-label="User menu"
|
|
53
|
-
>
|
|
54
|
-
<Avatar {...avatarProps} />
|
|
55
|
-
</DropdownMenuTrigger>
|
|
56
|
-
<DropdownMenuContent align="end" className="min-w-56">
|
|
57
|
-
{before}
|
|
58
|
-
<DropdownMenuLabel className="flex flex-col gap-0.5">
|
|
59
|
-
{user.name && <span className="font-medium">{user.name}</span>}
|
|
60
|
-
{user.email && (
|
|
61
|
-
<span className="text-xs text-muted-foreground truncate">{user.email}</span>
|
|
62
|
-
)}
|
|
63
|
-
{!user.name && !user.email && (
|
|
64
|
-
<span className="font-medium">Account</span>
|
|
65
|
-
)}
|
|
66
|
-
</DropdownMenuLabel>
|
|
67
|
-
{items.length > 0 && <DropdownMenuSeparator />}
|
|
68
|
-
{items.map(item => (
|
|
69
|
-
<UserMenuItemRow key={item.name} item={item} />
|
|
70
|
-
))}
|
|
71
|
-
{after}
|
|
72
|
-
{signOut && (
|
|
73
|
-
<>
|
|
74
|
-
<DropdownMenuSeparator />
|
|
75
|
-
<SignOutItem signOut={signOut} />
|
|
76
|
-
</>
|
|
77
|
-
)}
|
|
78
|
-
</DropdownMenuContent>
|
|
79
|
-
</DropdownMenu>
|
|
80
|
-
)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function UserMenuItemRow({ item }: { item: NonNullable<UserMenuMeta['items']>[number] }) {
|
|
84
|
-
const Icon = useIconFor(item.icon)
|
|
85
|
-
const destructive = item.color === 'destructive'
|
|
86
|
-
const target = item.openInNewTab ? '_blank' : undefined
|
|
87
|
-
const rel = item.openInNewTab ? 'noopener noreferrer' : undefined
|
|
88
|
-
|
|
89
|
-
// No URL → render as a plain disabled-looking row (rare; usually
|
|
90
|
-
// every item has a destination). Items without a URL still appear
|
|
91
|
-
// for consumers who want a label-only entry (e.g. environment hint).
|
|
92
|
-
if (!item.url) {
|
|
93
|
-
return (
|
|
94
|
-
<DropdownMenuItem destructive={destructive} disabled>
|
|
95
|
-
{Icon && <Icon aria-hidden="true" />}
|
|
96
|
-
<span>{item.label}</span>
|
|
97
|
-
</DropdownMenuItem>
|
|
98
|
-
)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return (
|
|
102
|
-
<DropdownMenuItem
|
|
103
|
-
destructive={destructive}
|
|
104
|
-
render={<a href={item.url} target={target} rel={rel} />}
|
|
105
|
-
>
|
|
106
|
-
{Icon && <Icon aria-hidden="true" />}
|
|
107
|
-
<span>{item.label}</span>
|
|
108
|
-
</DropdownMenuItem>
|
|
109
|
-
)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function SignOutItem({ signOut }: { signOut: NonNullable<UserMenuMeta['signOut']> }) {
|
|
113
|
-
const formRef = React.useRef<HTMLFormElement | null>(null)
|
|
114
|
-
|
|
115
|
-
if (signOut.method === 'GET') {
|
|
116
|
-
return (
|
|
117
|
-
<DropdownMenuItem destructive render={<a href={signOut.url} />}>
|
|
118
|
-
<SignOutIcon />
|
|
119
|
-
<span>{signOut.label}</span>
|
|
120
|
-
</DropdownMenuItem>
|
|
121
|
-
)
|
|
122
|
-
}
|
|
123
|
-
// POST: render a hidden form alongside the menu item; the item's
|
|
124
|
-
// click submits it. CSRF middleware sees a real POST navigation.
|
|
125
|
-
// `closeOnClick: false` is unnecessary — Base UI closes after the
|
|
126
|
-
// click handler runs, but the synchronous `requestSubmit()` triggers
|
|
127
|
-
// a full-page navigation which trumps the close animation.
|
|
128
|
-
return (
|
|
129
|
-
<>
|
|
130
|
-
<form ref={formRef} method="post" action={signOut.url} className="hidden" />
|
|
131
|
-
<DropdownMenuItem
|
|
132
|
-
destructive
|
|
133
|
-
onClick={() => formRef.current?.requestSubmit()}
|
|
134
|
-
>
|
|
135
|
-
<SignOutIcon />
|
|
136
|
-
<span>{signOut.label}</span>
|
|
137
|
-
</DropdownMenuItem>
|
|
138
|
-
</>
|
|
139
|
-
)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function Avatar({ avatar, initials, alt }: { avatar?: string; initials: string; alt: string }) {
|
|
143
|
-
if (avatar) {
|
|
144
|
-
return (
|
|
145
|
-
<img
|
|
146
|
-
src={avatar}
|
|
147
|
-
alt={alt}
|
|
148
|
-
className="size-7 rounded-full object-cover"
|
|
149
|
-
/>
|
|
150
|
-
)
|
|
151
|
-
}
|
|
152
|
-
return (
|
|
153
|
-
<span
|
|
154
|
-
aria-hidden="true"
|
|
155
|
-
className="inline-flex size-7 items-center justify-center rounded-full bg-primary text-primary-foreground text-xs font-medium"
|
|
156
|
-
>
|
|
157
|
-
{initials}
|
|
158
|
-
</span>
|
|
159
|
-
)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function SignOutIcon() {
|
|
163
|
-
return (
|
|
164
|
-
<svg
|
|
165
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
166
|
-
width="16"
|
|
167
|
-
height="16"
|
|
168
|
-
viewBox="0 0 24 24"
|
|
169
|
-
fill="none"
|
|
170
|
-
stroke="currentColor"
|
|
171
|
-
strokeWidth="2"
|
|
172
|
-
strokeLinecap="round"
|
|
173
|
-
strokeLinejoin="round"
|
|
174
|
-
aria-hidden="true"
|
|
175
|
-
>
|
|
176
|
-
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
|
|
177
|
-
<polyline points="16 17 21 12 16 7" />
|
|
178
|
-
<line x1="21" x2="9" y1="12" y2="12" />
|
|
179
|
-
</svg>
|
|
180
|
-
)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function computeInitials(source: string | undefined): string {
|
|
184
|
-
if (!source) return '?'
|
|
185
|
-
const trimmed = source.trim()
|
|
186
|
-
if (!trimmed) return '?'
|
|
187
|
-
// Email → first two letters of the local-part.
|
|
188
|
-
if (trimmed.includes('@')) {
|
|
189
|
-
return trimmed.slice(0, 2).toUpperCase()
|
|
190
|
-
}
|
|
191
|
-
// Name → first letter of the first two whitespace-split tokens.
|
|
192
|
-
const parts = trimmed.split(/\s+/).filter(Boolean)
|
|
193
|
-
if (parts.length === 0) return '?'
|
|
194
|
-
if (parts.length === 1) return parts[0]!.slice(0, 2).toUpperCase()
|
|
195
|
-
return (parts[0]!.charAt(0) + parts[1]!.charAt(0)).toUpperCase()
|
|
196
|
-
}
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Plan #15 — runtime-side context for the per-widget data map shipped
|
|
5
|
-
* alongside `schemaData`. SchemaRenderer wraps its children in
|
|
6
|
-
* `WidgetDataProvider` so widget renderers can look up their initial
|
|
7
|
-
* payload by id (`useInitialWidgetData(id)`) and run polling fetches
|
|
8
|
-
* (`useWidgetData(meta)`).
|
|
9
|
-
*
|
|
10
|
-
* The "initial payload" is whatever the server stamped under
|
|
11
|
-
* `_widgetData[id]`:
|
|
12
|
-
* - lazy widgets stamp `null` (renderer renders skeleton + fetches),
|
|
13
|
-
* - eager widgets stamp the resolved payload directly,
|
|
14
|
-
* - per-widget hook errors stamp `{ error: '<message>' }`.
|
|
15
|
-
*
|
|
16
|
-
* `useWidgetData(meta)` is the high-level hook that owns:
|
|
17
|
-
* - reading the initial payload,
|
|
18
|
-
* - kicking off the lazy-mount fetch when the initial is `null`,
|
|
19
|
-
* - setInterval polling when `meta.poll` is set,
|
|
20
|
-
* - pause polling while the document is hidden,
|
|
21
|
-
* - latest-wins race handling so a slow request can't overwrite a
|
|
22
|
-
* fresh one (mirrors the form-state seq pattern).
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
type WidgetDataMap = Record<string, unknown>
|
|
26
|
-
|
|
27
|
-
const WidgetDataContext = createContext<WidgetDataMap>({})
|
|
28
|
-
|
|
29
|
-
export interface WidgetDataProviderProps {
|
|
30
|
-
data?: WidgetDataMap
|
|
31
|
-
children: React.ReactNode
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function WidgetDataProvider({ data, children }: WidgetDataProviderProps) {
|
|
35
|
-
return (
|
|
36
|
-
<WidgetDataContext.Provider value={data ?? {}}>
|
|
37
|
-
{children}
|
|
38
|
-
</WidgetDataContext.Provider>
|
|
39
|
-
)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Read the server-stamped initial payload for one widget. Returns
|
|
43
|
-
* `undefined` outside a provider — callers fall back to lazy-loading. */
|
|
44
|
-
export function useInitialWidgetData(id: string): unknown {
|
|
45
|
-
return useContext(WidgetDataContext)[id]
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** Wire-shape of the polling-endpoint response. */
|
|
49
|
-
interface WidgetFetchResponse {
|
|
50
|
-
ok: true
|
|
51
|
-
data: unknown
|
|
52
|
-
timestamp?: number
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
interface WidgetFetchFailure {
|
|
56
|
-
ok: false
|
|
57
|
-
error?: string
|
|
58
|
-
status?: number
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export interface WidgetState {
|
|
62
|
-
/** Resolved payload — same shape as whatever `getStats` / `getData`
|
|
63
|
-
* returned. `null` means "not yet loaded" (still in skeleton). */
|
|
64
|
-
data: unknown
|
|
65
|
-
/** Latest error message from the polling endpoint. Cleared on a
|
|
66
|
-
* successful fetch. */
|
|
67
|
-
error: string | null
|
|
68
|
-
/** Convenience flag for skeleton rendering — true while we have no
|
|
69
|
-
* data and no error yet. */
|
|
70
|
-
isLoading: boolean
|
|
71
|
-
/** Manual re-fetch (used by per-widget filter dropdowns later). */
|
|
72
|
-
refetch: (filter?: string) => void
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export interface WidgetMetaLike {
|
|
76
|
-
id?: unknown
|
|
77
|
-
lazy?: unknown
|
|
78
|
-
poll?: unknown
|
|
79
|
-
widgetUrl?: unknown
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Manage one widget's data lifecycle from `meta`.
|
|
84
|
-
*
|
|
85
|
-
* - Initial paint: read `_widgetData[id]` from context.
|
|
86
|
-
* - Lazy mount fetch: when initial is `null` and we have a `widgetUrl`,
|
|
87
|
-
* fire a one-shot POST.
|
|
88
|
-
* - Polling: when `meta.poll` is set, setInterval at that cadence.
|
|
89
|
-
* Skip ticks while `document.visibilityState !== 'visible'` so an
|
|
90
|
-
* inactive tab doesn't hammer the server.
|
|
91
|
-
* - Latest-wins: each fetch increments `seqRef`; stale responses drop.
|
|
92
|
-
*/
|
|
93
|
-
export function useWidgetData(meta: WidgetMetaLike): WidgetState {
|
|
94
|
-
const id = String(meta.id ?? '')
|
|
95
|
-
const lazy = Boolean(meta.lazy)
|
|
96
|
-
const poll = typeof meta.poll === 'number' ? meta.poll : undefined
|
|
97
|
-
const url = typeof meta.widgetUrl === 'string' ? meta.widgetUrl : undefined
|
|
98
|
-
|
|
99
|
-
const initial = useInitialWidgetData(id)
|
|
100
|
-
// Treat the server-stamped error sentinel as a recoverable error
|
|
101
|
-
// rather than rendered data: the client can re-fetch and the error
|
|
102
|
-
// banner clears.
|
|
103
|
-
const initialError =
|
|
104
|
-
initial && typeof initial === 'object' && initial !== null && 'error' in initial
|
|
105
|
-
? String((initial as { error: unknown }).error)
|
|
106
|
-
: null
|
|
107
|
-
|
|
108
|
-
const [data, setData] = useState<unknown>(initialError ? null : initial)
|
|
109
|
-
const [error, setError] = useState<string | null>(initialError)
|
|
110
|
-
const seqRef = useRef(0)
|
|
111
|
-
|
|
112
|
-
const fetchOnce = useCallback(async (filter?: string) => {
|
|
113
|
-
if (!url) return
|
|
114
|
-
const seq = ++seqRef.current
|
|
115
|
-
try {
|
|
116
|
-
const res = await fetch(url, {
|
|
117
|
-
method: 'POST',
|
|
118
|
-
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
119
|
-
body: JSON.stringify(filter !== undefined ? { filter } : {}),
|
|
120
|
-
})
|
|
121
|
-
const json = await res.json() as WidgetFetchResponse | WidgetFetchFailure
|
|
122
|
-
if (seq !== seqRef.current) return // raced — drop stale response
|
|
123
|
-
if (json.ok) {
|
|
124
|
-
setData(json.data)
|
|
125
|
-
setError(null)
|
|
126
|
-
} else {
|
|
127
|
-
setError(json.error ?? `Request failed with status ${res.status}`)
|
|
128
|
-
}
|
|
129
|
-
} catch (e) {
|
|
130
|
-
if (seq !== seqRef.current) return
|
|
131
|
-
setError(e instanceof Error ? e.message : 'Widget failed to load')
|
|
132
|
-
}
|
|
133
|
-
}, [url])
|
|
134
|
-
|
|
135
|
-
// Lazy first-paint fetch.
|
|
136
|
-
useEffect(() => {
|
|
137
|
-
if (lazy && data === null && url && !error) fetchOnce()
|
|
138
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- run once on mount
|
|
139
|
-
}, [])
|
|
140
|
-
|
|
141
|
-
// Polling — re-fetch on the configured interval, paused when tab hidden.
|
|
142
|
-
useEffect(() => {
|
|
143
|
-
if (!poll || !url) return
|
|
144
|
-
const handle = window.setInterval(() => {
|
|
145
|
-
if (typeof document !== 'undefined' && document.visibilityState !== 'visible') return
|
|
146
|
-
fetchOnce()
|
|
147
|
-
}, poll * 1000)
|
|
148
|
-
return () => window.clearInterval(handle)
|
|
149
|
-
}, [poll, url, fetchOnce])
|
|
150
|
-
|
|
151
|
-
return {
|
|
152
|
-
data,
|
|
153
|
-
error,
|
|
154
|
-
isLoading: data === null && !error,
|
|
155
|
-
refetch: fetchOnce,
|
|
156
|
-
}
|
|
157
|
-
}
|