@antelopejs/dms 0.9.7 → 0.10.0
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 +114 -0
- package/README.md +1 -1
- package/data/module-store/catalog.json +2 -2
- package/dist/attachments/table-view.js +2 -2
- package/dist/attachments/table-view.js.map +1 -1
- package/dist/automation/actions.d.ts +2 -0
- package/dist/automation/actions.js +116 -71
- package/dist/automation/actions.js.map +1 -1
- package/dist/automation/i18n.d.ts +4 -0
- package/dist/automation/i18n.js +19 -0
- package/dist/automation/i18n.js.map +1 -0
- package/dist/automation/send-notification.d.ts +21 -0
- package/dist/automation/send-notification.js +163 -0
- package/dist/automation/send-notification.js.map +1 -0
- package/dist/automation/triggers.js +19 -25
- package/dist/automation/triggers.js.map +1 -1
- package/dist/client-origin-cors.d.ts +1 -0
- package/dist/client-origin-cors.js +19 -0
- package/dist/client-origin-cors.js.map +1 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +5 -3
- package/dist/config.js.map +1 -1
- package/dist/frontend-module.d.ts +4 -0
- package/dist/frontend-module.js +37 -0
- package/dist/frontend-module.js.map +1 -0
- package/dist/implementations/dms/client-ip.js +1 -1
- package/dist/implementations/dms/client-ip.js.map +1 -1
- package/dist/implementations/dms/dev-reload.d.ts +5 -0
- package/dist/implementations/dms/dev-reload.js +23 -3
- package/dist/implementations/dms/dev-reload.js.map +1 -1
- package/dist/implementations/dms/frontend-modules.d.ts +2 -4
- package/dist/implementations/dms/frontend-modules.js +22 -89
- package/dist/implementations/dms/frontend-modules.js.map +1 -1
- package/dist/implementations/dms/frontend-ready.d.ts +12 -2
- package/dist/implementations/dms/frontend-ready.js +34 -4
- package/dist/implementations/dms/frontend-ready.js.map +1 -1
- package/dist/implementations/dms/layout-banners.d.ts +2 -1
- package/dist/implementations/dms/layout-banners.js +9 -0
- package/dist/implementations/dms/layout-banners.js.map +1 -1
- package/dist/implementations/dms/manifest.d.ts +1 -2
- package/dist/implementations/dms/manifest.js +2 -0
- package/dist/implementations/dms/manifest.js.map +1 -1
- package/dist/implementations/dms/page.d.ts +4 -3
- package/dist/implementations/dms/page.js +49 -18
- package/dist/implementations/dms/page.js.map +1 -1
- package/dist/implementations/dms/permissions-resolver.d.ts +2 -2
- package/dist/implementations/dms/permissions-resolver.js +2 -2
- package/dist/implementations/dms/permissions-resolver.js.map +1 -1
- package/dist/implementations/dms/realtime.js +1 -0
- package/dist/implementations/dms/realtime.js.map +1 -1
- package/dist/implementations/dms-base/bulk-selection.d.ts +1 -1
- package/dist/implementations/dms-base/bulk-selection.js +23 -1
- package/dist/implementations/dms-base/bulk-selection.js.map +1 -1
- package/dist/implementations/dms-base/filter-tokens.js +5 -3
- package/dist/implementations/dms-base/filter-tokens.js.map +1 -1
- package/dist/implementations/dms-base/search-route.d.ts +1 -0
- package/dist/implementations/dms-base/search-route.js +35 -13
- package/dist/implementations/dms-base/search-route.js.map +1 -1
- package/dist/implementations/dms-base/table-view.js +11 -6
- package/dist/implementations/dms-base/table-view.js.map +1 -1
- package/dist/implementations/dms-notifications/index.d.ts +2 -2
- package/dist/implementations/dms-notifications/index.js +5 -5
- package/dist/implementations/dms-notifications/index.js.map +1 -1
- package/dist/index.js +28 -45
- package/dist/index.js.map +1 -1
- package/dist/pages/modules/index.d.ts +1 -0
- package/dist/pages/modules/index.js +1 -0
- package/dist/pages/modules/index.js.map +1 -1
- package/dist/pages/modules/modules-store.d.ts +9 -0
- package/dist/pages/modules/modules-store.js +30 -0
- package/dist/pages/modules/modules-store.js.map +1 -0
- package/dist/pages/settings/users/account-credentials.js +2 -2
- package/dist/pages/settings/users/account-credentials.js.map +1 -1
- package/dist/pages/settings/users/account-data-store.js +25 -8
- package/dist/pages/settings/users/account-data-store.js.map +1 -1
- package/dist/pages/settings/users/invite-extension-routes.js +9 -3
- package/dist/pages/settings/users/invite-extension-routes.js.map +1 -1
- package/dist/pages/settings/users/member-management.js +13 -14
- package/dist/pages/settings/users/member-management.js.map +1 -1
- package/dist/pages/settings/users/profile.d.ts +1 -4
- package/dist/pages/settings/users/profile.js +0 -35
- package/dist/pages/settings/users/profile.js.map +1 -1
- package/dist/pages/settings/users/role-editor-store.js +26 -18
- package/dist/pages/settings/users/role-editor-store.js.map +1 -1
- package/dist/pages/settings/users/role-editor.d.ts +0 -1
- package/dist/pages/settings/users/role-editor.js +1 -2
- package/dist/pages/settings/users/role-editor.js.map +1 -1
- package/dist/pages/settings/users/role-preview.js +1 -2
- package/dist/pages/settings/users/role-preview.js.map +1 -1
- package/dist/pages/settings/users/roles.js +2 -2
- package/dist/pages/settings/users/roles.js.map +1 -1
- package/dist/pages/settings/users/two-factor-operations.js.map +1 -1
- package/dist/realtime/broker.d.ts +6 -0
- package/dist/realtime/broker.js +8 -1
- package/dist/realtime/broker.js.map +1 -1
- package/dist/realtime/presence.d.ts +8 -3
- package/dist/realtime/presence.js +8 -4
- package/dist/realtime/presence.js.map +1 -1
- package/dist/realtime/sessions.d.ts +11 -1
- package/dist/realtime/sessions.js +21 -3
- package/dist/realtime/sessions.js.map +1 -1
- package/dist/realtime/table-view.d.ts +2 -2
- package/dist/realtime/table-view.js +4 -3
- package/dist/realtime/table-view.js.map +1 -1
- package/dist/routes/auth/client-ip.d.ts +1 -0
- package/dist/routes/auth/client-ip.js +18 -0
- package/dist/routes/auth/client-ip.js.map +1 -1
- package/dist/routes/auth/oauth/config.d.ts +2 -1
- package/dist/routes/auth/oauth/config.js +5 -1
- package/dist/routes/auth/oauth/config.js.map +1 -1
- package/dist/routes/auth/session-handoff.d.ts +0 -1
- package/dist/routes/auth/session-handoff.js +0 -2
- package/dist/routes/auth/session-handoff.js.map +1 -1
- package/dist/routes/auth.js +3 -2
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/realtime.d.ts +13 -1
- package/dist/routes/realtime.js +114 -29
- package/dist/routes/realtime.js.map +1 -1
- package/dist/startup-config.d.ts +2 -0
- package/dist/startup-config.js +62 -0
- package/dist/startup-config.js.map +1 -0
- package/dist/store/interfaces.d.ts +3 -2
- package/dist/store/interfaces.js +16 -6
- package/dist/store/interfaces.js.map +1 -1
- package/dist/store/listing.js +24 -13
- package/dist/store/listing.js.map +1 -1
- package/dist/store/operations.js +4 -0
- package/dist/store/operations.js.map +1 -1
- package/dist/store/requirements.js +3 -2
- package/dist/store/requirements.js.map +1 -1
- package/dist/store/runtime.d.ts +10 -1
- package/dist/store/runtime.js +33 -6
- package/dist/store/runtime.js.map +1 -1
- package/dist/store/types.d.ts +10 -0
- package/dist/validation/auth.schema.d.ts +2 -2
- package/dist/validation/member-invite.schema.d.ts +2 -10
- package/dist/validation/member-invite.schema.js +2 -15
- package/dist/validation/member-invite.schema.js.map +1 -1
- package/frontend-vue/dms.frontend.ts +24 -5
- package/frontend-vue/dms.server.ts +17 -0
- package/frontend-vue/layers/dms-auth/server/accounts.ts +3 -4
- package/frontend-vue/layers/dms-core/app/build/composables/renderNow.ts +34 -0
- package/frontend-vue/layers/dms-core/app/build/composables/user/tablePreferences.ts +5 -1
- package/frontend-vue/layers/dms-core/app/build/utils/crossTab.ts +199 -0
- package/frontend-vue/layers/dms-core/app/build/utils/sharedRealtimeConnection.ts +410 -0
- package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +77 -12
- package/frontend-vue/layers/dms-core/app/composables/data-types/useDataType.ts +26 -0
- package/frontend-vue/layers/dms-core/app/composables/hydration/isHydratingComponent.ts +29 -0
- package/frontend-vue/layers/dms-core/app/composables/live/livePauseSource.ts +29 -0
- package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +150 -9
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +6 -4
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +27 -1
- package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +256 -119
- package/frontend-vue/layers/dms-core/app/composables/translation/useComposedText.ts +21 -8
- package/frontend-vue/layers/dms-core/app/composables/translation/useTranslation.ts +22 -5
- package/frontend-vue/layers/dms-core/app/plugins/dev-error-log.ts +16 -0
- package/frontend-vue/layers/dms-core/app/types/user.ts +6 -0
- package/frontend-vue/layers/dms-core/app/utils/composedText.ts +7 -2
- package/frontend-vue/layers/dms-core/app/utils/formatter.ts +166 -35
- package/frontend-vue/layers/dms-core/app/utils/url-interpolation.ts +18 -1
- package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +134 -2
- package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +134 -2
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +3 -2
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +41 -21
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardPageBody.vue +9 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +4 -19
- package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +11 -3
- package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +18 -7
- package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionBar.vue +8 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +14 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/RowSidePanel.vue +43 -82
- package/frontend-vue/layers/dms-layout/app/build/components/layout/SidebarUserMenu.vue +44 -3
- package/frontend-vue/layers/dms-layout/app/build/components/layout/UserIdentity.vue +11 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleCatalogTile.vue +132 -35
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleManageMenu.vue +59 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleSourceLabel.vue +25 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreBanners.vue +73 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreOperation.vue +116 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreRequirements.vue +8 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue +85 -207
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTile.vue +10 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTileSkeleton.vue +68 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +24 -8
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxItem.vue +8 -5
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/region/RegionTimeZoneInput.vue +1 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-permissions.ts +19 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/useRoleEditor.ts +2 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/layout/useSidePanelWidth.ts +31 -70
- package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen.ts +11 -1
- package/frontend-vue/layers/dms-layout/app/build/utils/dev-reload-stream.ts +81 -0
- package/frontend-vue/layers/dms-layout/app/build/utils/module-store-catalog.ts +232 -36
- package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +122 -14
- package/frontend-vue/layers/dms-layout/app/composables/layout/useLayoutBanners.ts +32 -3
- package/frontend-vue/layers/dms-layout/app/composables/layout/usePageRecord.ts +50 -0
- package/frontend-vue/layers/dms-layout/app/composables/layout/useServedHeaderActions.ts +25 -7
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleHistory.ts +3 -2
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +51 -4
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStoreActions.ts +194 -0
- package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +23 -3
- package/frontend-vue/layers/dms-layout/app/composables/page/usePageLayout.ts +4 -2
- package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +6 -1
- package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +9 -0
- package/frontend-vue/layers/dms-layout/app/custom-layouts/SettingsLayout.vue +9 -0
- package/frontend-vue/layers/dms-layout/app/custom-pages/modules-store.vue +291 -0
- package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +87 -474
- package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +3 -0
- package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +6 -22
- package/frontend-vue/layers/dms-layout/app/plugins/command-palette-navigation.client.ts +2 -1
- package/frontend-vue/layers/dms-layout/app/plugins/command-palette-personal-pages.client.ts +14 -44
- package/frontend-vue/layers/dms-layout/app/plugins/dms-dev-reload.client.ts +14 -9
- package/frontend-vue/layers/dms-layout/app/theme/form-controls.ts +9 -4
- package/frontend-vue/layers/dms-layout/app/theme/surfaces.ts +22 -6
- package/frontend-vue/layers/dms-layout/app/types/module-store.ts +9 -0
- package/frontend-vue/layers/dms-layout/app/types/page.ts +15 -1
- package/frontend-vue/layers/dms-layout/app/utils/account-menu.ts +11 -0
- package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +57 -0
- package/frontend-vue/layers/dms-layout/app/utils/pageQuery.ts +26 -0
- package/frontend-vue/layers/dms-layout/app/utils/pageRecord.ts +71 -0
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +39 -14
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +38 -13
- package/frontend-vue/layers/dms-ui/app/build/components/containers/TargetComponentTree.vue +31 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/constants.ts +25 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +244 -15
- package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +42 -25
- package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowHeader.vue +153 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowNotFound.vue +44 -0
- package/frontend-vue/layers/dms-ui/app/build/components/containers/row/drawerRowHeader.ts +35 -0
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormEntries.vue +44 -9
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormErrorSummary.vue +11 -4
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldControl.vue +38 -5
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldNotes.vue +29 -5
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormSectionNav.vue +3 -1
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormSections.vue +106 -27
- package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +12 -4
- package/frontend-vue/layers/dms-ui/app/build/components/table/Empty.vue +4 -2
- package/frontend-vue/layers/dms-ui/app/build/components/table/FiltersRow.vue +38 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table/Pagination.vue +55 -54
- package/frontend-vue/layers/dms-ui/app/build/components/table/RowSelection.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +396 -288
- package/frontend-vue/layers/dms-ui/app/build/components/table/Tabs.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table/Views.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardAvatar.vue +59 -0
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardGridSkeleton.vue +19 -2
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardRowActions.vue +38 -0
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardsDisplay.vue +148 -104
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/LiveHoldBanner.vue +17 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/bulkSelection.ts +43 -9
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowComponentOptions.ts +76 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowNavigation.ts +172 -27
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +270 -46
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/containers/closingLayers.ts +186 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/containers/context.ts +14 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/containers/drawerSize.ts +229 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/containers/useResizableSize.ts +175 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/metricCells.ts +18 -35
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/numberSuffix.ts +47 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +99 -24
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerFieldTypes.ts +2 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationFilter.ts +75 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationRows.ts +71 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationValue.ts +39 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/twoLineCell.ts +3 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/useColumnValueRenderer.ts +6 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/form/createFormFields.ts +15 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldSaved.ts +40 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldValueRule.ts +62 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formEntryContext.ts +5 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +42 -7
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formLayout.ts +106 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formSections.ts +32 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/readonlyValue.ts +19 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/registerFormFunctions.ts +27 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/repeaterTable.ts +65 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/serverFieldErrors.ts +49 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/toasterClearance.ts +118 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/useInstantForm.ts +12 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/live/currentPageLive.ts +41 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/live/livePoller.ts +10 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/live/pageReadClock.ts +28 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/live/shortcuts.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageLiveHost.ts +17 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/live/useUpdatedLabel.ts +38 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/pageToolbarContext.ts +75 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/toolbarControlHeight.ts +29 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodComparison.ts +52 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +21 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodShortLabels.ts +28 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodDraft.ts +1 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useRelationFilterLabels.ts +73 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useRowScrollAnchor.ts +106 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +9 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useTableColumns.ts +1 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/archivedCount.ts +31 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/filterEditorValue.ts +38 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/formatFilterValue.ts +33 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/scrollEdges.ts +31 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useCardFields.ts +33 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useMobileDisplay.ts +48 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useScrolledPastTop.ts +41 -18
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +3 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLinkedVisit.ts +78 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLiveHold.ts +135 -22
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +56 -25
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +77 -32
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +86 -10
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/bulkActions.ts +14 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/card.ts +127 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/compareValues.ts +13 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/expandedRows.ts +28 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/groupedRows.ts +114 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/linkedArrival.ts +308 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/locateRow.ts +106 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pageArrival.ts +34 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pinnedRows.ts +17 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/quickFilters.ts +41 -33
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/recordLink.ts +24 -8
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableCounts.ts +113 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableQuery.ts +27 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/views.ts +22 -6
- package/frontend-vue/layers/dms-ui/app/build/utils/addressSuggestions.ts +49 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/eyebrow.ts +12 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/fieldOptionsI18n.ts +51 -11
- package/frontend-vue/layers/dms-ui/app/build/utils/inputWidth.ts +53 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/numberAdjustment.ts +6 -3
- package/frontend-vue/layers/dms-ui/app/build/utils/relativeDate.ts +19 -5
- package/frontend-vue/layers/dms-ui/app/build/utils/scrollWithin.ts +99 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +36 -14
- package/frontend-vue/layers/dms-ui/app/components/PasswordStrength.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/RecursiveComponent.vue +9 -1
- package/frontend-vue/layers/dms-ui/app/components/blocks/LiveToggleBlock.vue +80 -32
- package/frontend-vue/layers/dms-ui/app/components/blocks/PageToolbarBlock.vue +137 -0
- package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +5 -0
- package/frontend-vue/layers/dms-ui/app/components/chart/ApexChartHost.vue +16 -2
- package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +24 -5
- package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +41 -23
- package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/container-footer/ContainerFooter.vue +34 -0
- package/frontend-vue/layers/dms-ui/app/components/field-row/FieldRow.vue +25 -2
- package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +2 -2
- package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +118 -36
- package/frontend-vue/layers/dms-ui/app/components/form/components/DatePicker.vue +20 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +13 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +8 -3
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +53 -5
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +29 -5
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputNumber.vue +45 -8
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputPercentage.vue +28 -5
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputSecret.vue +5 -0
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputText.vue +19 -2
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputTime.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +205 -65
- package/frontend-vue/layers/dms-ui/app/components/form/components/Repeater.vue +80 -30
- package/frontend-vue/layers/dms-ui/app/components/form/components/RepeaterTable.vue +203 -0
- package/frontend-vue/layers/dms-ui/app/components/form/components/Select.vue +4 -2
- package/frontend-vue/layers/dms-ui/app/components/grid/GridRow.vue +11 -2
- package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +79 -70
- package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +117 -21
- package/frontend-vue/layers/dms-ui/app/components/meter/Meter.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +113 -34
- package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +14 -2
- package/frontend-vue/layers/dms-ui/app/components/section-header/Eyebrow.vue +6 -1
- package/frontend-vue/layers/dms-ui/app/components/segmented/Segmented.vue +13 -0
- package/frontend-vue/layers/dms-ui/app/components/stat-group/StatGroup.vue +52 -3
- package/frontend-vue/layers/dms-ui/app/components/tab/Tab.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +45 -35
- package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanDisplay.vue +4 -1
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +410 -96
- package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +33 -5
- package/frontend-vue/layers/dms-ui/app/composables/chart/apexCategoryLabels.ts +68 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/apexDatetimeBuckets.ts +306 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/formatValue.ts +4 -3
- package/frontend-vue/layers/dms-ui/app/composables/chart/heatmapColors.ts +93 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/localizeSeries.ts +62 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.ts +116 -8
- package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.types.ts +7 -0
- package/frontend-vue/layers/dms-ui/app/composables/containers/types.ts +84 -1
- package/frontend-vue/layers/dms-ui/app/composables/containers/useDrawer.ts +69 -8
- package/frontend-vue/layers/dms-ui/app/composables/form/types/field-saved.ts +11 -0
- package/frontend-vue/layers/dms-ui/app/composables/form/types/field.ts +4 -0
- package/frontend-vue/layers/dms-ui/app/composables/form/types/functions.ts +1 -0
- package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +142 -21
- package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +11 -4
- package/frontend-vue/layers/dms-ui/app/composables/page-toolbar/usePageToolbarSize.ts +30 -0
- package/frontend-vue/layers/dms-ui/app/composables/table-view/kanban.ts +4 -2
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +22 -3
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +4 -0
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/display.ts +24 -1
- package/frontend-vue/layers/dms-ui/app/plugins/closing-layers.client.ts +10 -0
- package/frontend-vue/layers/dms-ui/app/plugins/live-pause-source.ts +15 -0
- package/frontend-vue/layers/dms-ui/app/plugins/register.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/types/input-number.ts +3 -2
- package/frontend-vue/layers/dms-ui/app/types/page-toolbar.ts +7 -0
- package/frontend-vue/layers/dms-ui/app/types/row-action.ts +0 -2
- package/frontend-vue/layers/dms-ui/app/types/row-navigation.ts +17 -0
- package/frontend-vue/layers/dms-ui/app/types/table/query.ts +2 -1
- package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +3 -1
- package/frontend-vue/layers/dms-ui/app/utils/componentTree.ts +24 -0
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +16 -4
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +16 -4
- package/frontend-vue/package.json +2 -2
- package/package.json +7 -6
- package/skills/dms/SKILL.md +1 -1
- package/skills/dms-auth/SKILL.md +5 -3
- package/skills/dms-best-practices/REFERENCE.md +2 -0
- package/skills/dms-best-practices/SKILL.md +4 -0
- package/skills/dms-module/SKILL.md +3 -2
- package/skills/dms-pages/REFERENCE.md +125 -16
- package/skills/dms-pages/SKILL.md +1 -1
- package/skills/dms-project/DEV-SERVERS.md +23 -1
- package/skills/dms-project/REFERENCE.md +1 -1
- package/skills/dms-project/SKILL.md +4 -1
- package/skills/dms-project/scripts/dms-dev.sh +205 -17
- package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +0 -42
package/frontend-vue/layers/dms-ui/app/build/composables/data-types/useColumnValueRenderer.ts
CHANGED
|
@@ -20,12 +20,14 @@ export interface RenderableColumn {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
// v2 cell anatomy: amounts are right-aligned mono figures in the ink color,
|
|
23
|
-
// dates 12px muted mono.
|
|
23
|
+
// dates 12px muted mono. Where a record is read (the detail surface), amounts
|
|
24
|
+
// keep the form's own font with tabular figures, as its read-only values do. A data type can give cells a shorter rendering
|
|
24
25
|
// through a `cell` formatter (the date type does); others use `default`.
|
|
25
26
|
export const NUMERIC_TYPE_IDS = new Set(["number", "price", "percentage"]);
|
|
26
27
|
const DATE_TYPE_IDS = new Set(["date"]);
|
|
27
28
|
const NUMERIC_CELL_CLASS =
|
|
28
29
|
"block text-right font-mono tabular-nums text-highlighted";
|
|
30
|
+
const DETAIL_NUMERIC_CELL_CLASS = "block text-right tabular-nums";
|
|
29
31
|
const NUMERIC_EMPTY_CELL_CLASS = "block text-right text-dimmed";
|
|
30
32
|
const DATE_CELL_CLASS = "font-mono text-xs text-muted tabular-nums";
|
|
31
33
|
const DEFAULT_FALLBACK = "-";
|
|
@@ -101,7 +103,9 @@ export function useColumnValueRenderer() {
|
|
|
101
103
|
return formatted as VNodeChild;
|
|
102
104
|
}
|
|
103
105
|
if (isNumeric) {
|
|
104
|
-
|
|
106
|
+
const numericClass =
|
|
107
|
+
surface === "detail" ? DETAIL_NUMERIC_CELL_CLASS : NUMERIC_CELL_CLASS;
|
|
108
|
+
return h("span", { class: numericClass }, String(formatted));
|
|
105
109
|
}
|
|
106
110
|
if (!column.display && typeId && DATE_TYPE_IDS.has(typeId)) {
|
|
107
111
|
return h("span", { class: DATE_CELL_CLASS }, String(formatted));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { FormField } from "../../../composables/form/types/field";
|
|
2
|
+
import { isEmptyFormValue } from "../unsaved-changes/formValue";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Whether a form creating a record leaves a field out: a read-only one that
|
|
6
|
+
* holds nothing yet (a number, a date, a total the server sets once the
|
|
7
|
+
* record exists) would only read "—". It shows once it holds a value (a
|
|
8
|
+
* default, one a rule computed), and a field declaring `showEmptyOnCreate`
|
|
9
|
+
* keeps its place regardless.
|
|
10
|
+
*/
|
|
11
|
+
export function isHiddenOnCreate(field: FormField, value: unknown): boolean {
|
|
12
|
+
if (field.showEmptyOnCreate) return false;
|
|
13
|
+
if (!field.disabled && !field.readonly) return false;
|
|
14
|
+
return isEmptyFormValue(value);
|
|
15
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Ref } from "vue";
|
|
2
|
+
import type { MarkFieldSaved } from "../../../composables/form/types/field-saved";
|
|
3
|
+
import type {
|
|
4
|
+
FormData,
|
|
5
|
+
FormFieldValue,
|
|
6
|
+
} from "../../../composables/form/types/value";
|
|
7
|
+
import { cloneFormValue } from "../../../composables/form/useForm";
|
|
8
|
+
import type { UseFormDirtyReturn } from "../../../composables/unsaved-changes/useFormDirty";
|
|
9
|
+
import type { InstantForm } from "./useInstantForm";
|
|
10
|
+
|
|
11
|
+
type FormValues = Record<string, unknown>;
|
|
12
|
+
|
|
13
|
+
/** What a field the server saved on its own updates in its form. */
|
|
14
|
+
export interface FieldSavedTargets {
|
|
15
|
+
/** Whether the form has the field: another control's id is left alone. */
|
|
16
|
+
hasField: (id: string) => boolean;
|
|
17
|
+
/** The values "Discard" puts back. */
|
|
18
|
+
initialValues: Ref<FormData>;
|
|
19
|
+
/** The values the form reads as unchanged. */
|
|
20
|
+
dirty: Pick<UseFormDirtyReturn<FormValues>, "baseline" | "markClean">;
|
|
21
|
+
/** The instant save of a form saving as it goes. */
|
|
22
|
+
instantSave?: Pick<InstantForm, "confirmField">;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The form's answer to a field whose value the server stored on its own (a
|
|
27
|
+
* rotated secret): that value becomes the one the field is compared with and
|
|
28
|
+
* the one "Discard" puts back, and an instant form does not save it again.
|
|
29
|
+
*/
|
|
30
|
+
export function fieldSavedMarker(targets: FieldSavedTargets): MarkFieldSaved {
|
|
31
|
+
return (id, value) => {
|
|
32
|
+
if (!targets.hasField(id)) return;
|
|
33
|
+
targets.initialValues.value = {
|
|
34
|
+
...targets.initialValues.value,
|
|
35
|
+
[id]: cloneFormValue(value as FormFieldValue) as FormFieldValue,
|
|
36
|
+
};
|
|
37
|
+
targets.dirty.markClean({ ...targets.dirty.baseline.value, [id]: value });
|
|
38
|
+
targets.instantSave?.confirmField(id, value);
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** What a `SetFieldValue` watch writes, and where. */
|
|
2
|
+
export interface SetFieldValueParams {
|
|
3
|
+
targetField: string;
|
|
4
|
+
value?: unknown;
|
|
5
|
+
valueMap?: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** The payload of a form's `FieldChange` event. */
|
|
9
|
+
export interface FieldChangePayload {
|
|
10
|
+
fieldId?: string;
|
|
11
|
+
value?: unknown;
|
|
12
|
+
/** The field still holds the value the form started from. */
|
|
13
|
+
initial?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** A value to write, or nothing to write. */
|
|
17
|
+
export type FieldValueWrite = { value: unknown } | undefined;
|
|
18
|
+
|
|
19
|
+
type MapKey = string | number | boolean;
|
|
20
|
+
|
|
21
|
+
function isMapKey(value: unknown): value is MapKey {
|
|
22
|
+
return ["string", "number", "boolean"].includes(typeof value);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* What a `SetFieldValue` watch writes for a field change: its `value`, or the
|
|
27
|
+
* entry of its `valueMap` named by the changed field's value. Nothing on a
|
|
28
|
+
* value the form started from — the record keeps what it stored — nor for a
|
|
29
|
+
* value the map has no entry for.
|
|
30
|
+
*/
|
|
31
|
+
export function resolveFieldValueWrite(
|
|
32
|
+
params: SetFieldValueParams,
|
|
33
|
+
payload: FieldChangePayload | undefined,
|
|
34
|
+
): FieldValueWrite {
|
|
35
|
+
if (payload?.initial) return undefined;
|
|
36
|
+
if (params.valueMap) {
|
|
37
|
+
const key = payload?.value;
|
|
38
|
+
if (!isMapKey(key) || !Object.hasOwn(params.valueMap, String(key))) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
return { value: params.valueMap[String(key)] };
|
|
42
|
+
}
|
|
43
|
+
if (!Object.hasOwn(params, "value")) return undefined;
|
|
44
|
+
return { value: params.value };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Tells, for each `FieldChange` a form sends, whether the field still holds
|
|
49
|
+
* the value it started from and never held another: once a field changed,
|
|
50
|
+
* coming back to that value is a change too.
|
|
51
|
+
*/
|
|
52
|
+
export function createStartingValueTracker(
|
|
53
|
+
isStartingValue: (fieldId: string, value: unknown) => boolean,
|
|
54
|
+
): (fieldId: string, value: unknown) => boolean {
|
|
55
|
+
const changedFieldIds = new Set<string>();
|
|
56
|
+
return (fieldId, value) => {
|
|
57
|
+
if (changedFieldIds.has(fieldId)) return false;
|
|
58
|
+
if (isStartingValue(fieldId, value)) return true;
|
|
59
|
+
changedFieldIds.add(fieldId);
|
|
60
|
+
return false;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -20,6 +20,11 @@ export interface FormEntryContext {
|
|
|
20
20
|
isFieldHidden: (field: FormField) => boolean;
|
|
21
21
|
isFieldDisabled: (field: FormField) => boolean;
|
|
22
22
|
isFieldRequired: (field: FormField) => boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Whether nothing in the form can be changed (a record's details): its
|
|
25
|
+
* rows show values without the descriptions that tell how to fill them.
|
|
26
|
+
*/
|
|
27
|
+
isReadOnly: Readonly<Ref<boolean>>;
|
|
23
28
|
/**
|
|
24
29
|
* Whether a field points out a change waiting to be saved: not in an
|
|
25
30
|
* instant form (nothing waits) nor in an action form (nothing was saved).
|
|
@@ -3,15 +3,19 @@
|
|
|
3
3
|
*
|
|
4
4
|
* - `record` (default): a record, a settings page, a table view's form. It
|
|
5
5
|
* keeps its values once saved; Cancel while there is nothing to save when
|
|
6
|
-
* it has somewhere to go back to, then "Unsaved changes" with
|
|
7
|
-
*
|
|
6
|
+
* it has somewhere to go back to, then "Unsaved changes" with Save and,
|
|
7
|
+
* beside it, Cancel again (discard and leave) or, with nowhere to go back
|
|
8
|
+
* to, Discard.
|
|
9
|
+
* - `create`: a form that makes a new record (a table view's add form, the
|
|
10
|
+
* duplicate it opens). Its submit from the start, no "Unsaved changes"
|
|
11
|
+
* list, since all it holds is new.
|
|
8
12
|
* - `action`: a form that does something each time (send a notification,
|
|
9
13
|
* invite someone, run a job). Its own submit label, always ready: what it
|
|
10
14
|
* holds untouched, its defaults included, is a payload too. Beside it,
|
|
11
15
|
* Reset once a value changed, Cancel before that in a drawer or a modal.
|
|
12
16
|
* It empties after a successful submit.
|
|
13
17
|
*/
|
|
14
|
-
export type FormKind = "record" | "action";
|
|
18
|
+
export type FormKind = "record" | "create" | "action";
|
|
15
19
|
|
|
16
20
|
/** How a form offers to save (interface-dms `FormSaveMode`). */
|
|
17
21
|
export type FormSaveMode = "bar" | "footer" | "none" | "instant";
|
|
@@ -39,7 +43,10 @@ export interface SaveBarContext {
|
|
|
39
43
|
resettable: boolean;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* The button beside the submit: Discard / Reset (values back, the form
|
|
48
|
+
* stays), or Cancel (values back, the form is left).
|
|
49
|
+
*/
|
|
43
50
|
export type SaveBarSecondary = "discard" | "cancel";
|
|
44
51
|
|
|
45
52
|
/** What a form's save bar shows. */
|
|
@@ -67,11 +74,20 @@ const HIDDEN_BAR: SaveBarState = {
|
|
|
67
74
|
};
|
|
68
75
|
|
|
69
76
|
/**
|
|
70
|
-
* A record form's bar: "Unsaved changes"
|
|
71
|
-
*
|
|
77
|
+
* A record form's bar: "Unsaved changes" and Save once a value changed, Cancel
|
|
78
|
+
* before that when it has somewhere to go back to. Beside Save, a form with
|
|
79
|
+
* somewhere to go back to keeps Cancel, which drops the changes and leaves,
|
|
80
|
+
* the same as before any change: one click, never a reverted form left open.
|
|
81
|
+
* A form with nowhere to go back to offers Discard.
|
|
72
82
|
*/
|
|
73
83
|
function recordSaveBar(context: SaveBarContext): SaveBarState {
|
|
74
|
-
if (context.dirty)
|
|
84
|
+
if (context.dirty) {
|
|
85
|
+
return {
|
|
86
|
+
...HIDDEN_BAR,
|
|
87
|
+
secondary: context.cancellable ? "cancel" : "discard",
|
|
88
|
+
isHidden: false,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
75
91
|
if (!context.cancellable) return HIDDEN_BAR;
|
|
76
92
|
return {
|
|
77
93
|
showsStatus: false,
|
|
@@ -82,6 +98,24 @@ function recordSaveBar(context: SaveBarContext): SaveBarState {
|
|
|
82
98
|
};
|
|
83
99
|
}
|
|
84
100
|
|
|
101
|
+
/**
|
|
102
|
+
* A create form's bar: its submit from the start, since a duplicate (or a
|
|
103
|
+
* row its defaults fill) is saved as it is, with no "Unsaved changes" list,
|
|
104
|
+
* since all it holds is new. Beside it, Cancel when it has somewhere to go
|
|
105
|
+
* back to, Discard once a value changed otherwise.
|
|
106
|
+
*/
|
|
107
|
+
function createSaveBar(context: SaveBarContext): SaveBarState {
|
|
108
|
+
const bar: SaveBarState = {
|
|
109
|
+
showsStatus: false,
|
|
110
|
+
showsSubmit: true,
|
|
111
|
+
isSubmitHeld: false,
|
|
112
|
+
isHidden: false,
|
|
113
|
+
};
|
|
114
|
+
if (context.cancellable) return { ...bar, secondary: "cancel" };
|
|
115
|
+
if (context.dirty) return { ...bar, secondary: "discard" };
|
|
116
|
+
return bar;
|
|
117
|
+
}
|
|
118
|
+
|
|
85
119
|
/**
|
|
86
120
|
* An action form's bar: its submit, never held, so a pre-filled form sends as
|
|
87
121
|
* it is. Beside it, Reset once a value changed; before that, Cancel in a
|
|
@@ -106,6 +140,7 @@ function actionSaveBar(context: SaveBarContext): SaveBarState {
|
|
|
106
140
|
|
|
107
141
|
const SAVE_BARS: Record<FormKind, (context: SaveBarContext) => SaveBarState> = {
|
|
108
142
|
record: recordSaveBar,
|
|
143
|
+
create: createSaveBar,
|
|
109
144
|
action: actionSaveBar,
|
|
110
145
|
};
|
|
111
146
|
|
|
@@ -2,6 +2,11 @@ import type {
|
|
|
2
2
|
FieldRowLayout,
|
|
3
3
|
FieldRowSpacing,
|
|
4
4
|
} from "../../../components/field-row/FieldRow.vue";
|
|
5
|
+
import {
|
|
6
|
+
type FormField,
|
|
7
|
+
type FormFieldOrGroup,
|
|
8
|
+
isFieldGroup,
|
|
9
|
+
} from "../../../composables/form/types/field";
|
|
5
10
|
|
|
6
11
|
export type FormOrientation = "horizontal" | "vertical";
|
|
7
12
|
export type FormSurface = "card" | "container" | "section" | "sections";
|
|
@@ -41,6 +46,25 @@ export const FORM_LAYOUT_CLASSES: Record<FormOrientation, FormLayoutClasses> = {
|
|
|
41
46
|
},
|
|
42
47
|
};
|
|
43
48
|
|
|
49
|
+
// A form in a drawer or a modal: the same rows on a tighter rhythm, the
|
|
50
|
+
// container's width being scarcer than a page's. A row with one line of
|
|
51
|
+
// value is 57px beside its label; stacked (a phone, a narrow drawer) the
|
|
52
|
+
// label sits 6px above its control. Read-only details and editable forms
|
|
53
|
+
// share the rows, so both read alike.
|
|
54
|
+
export const CONTAINER_LAYOUT_CLASSES: Record<
|
|
55
|
+
FormOrientation,
|
|
56
|
+
FormLayoutClasses
|
|
57
|
+
> = {
|
|
58
|
+
horizontal: {
|
|
59
|
+
rows: "flex flex-col",
|
|
60
|
+
row: { layout: "form", inset: false, spacing: "dense" },
|
|
61
|
+
},
|
|
62
|
+
vertical: {
|
|
63
|
+
rows: "flex flex-col gap-3 py-3",
|
|
64
|
+
row: { layout: "stack", inset: false, spacing: "list" },
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
44
68
|
// A card form draws its own head band and sticky footer band; inside a
|
|
45
69
|
// modal or drawer the container is the surface and already pads the body.
|
|
46
70
|
// The footer sticks to the bottom of the scroll area while the form runs
|
|
@@ -82,10 +106,88 @@ export const SECTION_LAYOUT_CLASSES: FormLayoutClasses = {
|
|
|
82
106
|
row: { layout: "form", inset: true, spacing: "row" },
|
|
83
107
|
};
|
|
84
108
|
|
|
85
|
-
// Grouped controls
|
|
86
|
-
//
|
|
87
|
-
//
|
|
109
|
+
// Grouped controls share the row while each keeps a usable width (11rem: a
|
|
110
|
+
// date, a select showing its value), and wrap onto the next line once they
|
|
111
|
+
// would shrink below it, so a group of five in a modal, a drawer or on a
|
|
112
|
+
// phone wraps instead of drawing its controls over one another. A member
|
|
113
|
+
// alone on its line takes the whole width; `min(100%, ...)` keeps a row
|
|
114
|
+
// narrower than the minimum from overflowing.
|
|
88
115
|
export const GROUP_FIELDS_CLASSES: Record<FormOrientation, string> = {
|
|
89
|
-
horizontal:
|
|
116
|
+
horizontal:
|
|
117
|
+
"grid grid-cols-[repeat(auto-fit,minmax(min(100%,11rem),1fr))] gap-x-3 gap-y-2.5",
|
|
90
118
|
vertical: "flex flex-col gap-2.5",
|
|
91
119
|
};
|
|
120
|
+
|
|
121
|
+
// Fields whose value is a block of its own (a table of rows, pairs, code,
|
|
122
|
+
// formatted text, a gallery): beside a 240px label they get what is left of
|
|
123
|
+
// a drawer, where a repeater falls into its stacked cards. They take the
|
|
124
|
+
// row's full width under their label instead, whatever the form's layout.
|
|
125
|
+
const WIDE_FIELD_COMPONENTS: ReadonlySet<string> = new Set([
|
|
126
|
+
"dms-repeater",
|
|
127
|
+
"dms-key-value",
|
|
128
|
+
"dms-input-code",
|
|
129
|
+
"dms-rich-text",
|
|
130
|
+
]);
|
|
131
|
+
// The same values shown read-only, drawn from the field's type.
|
|
132
|
+
const WIDE_FIELD_TYPES: ReadonlySet<string> = new Set([
|
|
133
|
+
"array",
|
|
134
|
+
"key_value",
|
|
135
|
+
"code",
|
|
136
|
+
"rich_text",
|
|
137
|
+
]);
|
|
138
|
+
// Files and images drop their list or gallery under the control when
|
|
139
|
+
// several are allowed.
|
|
140
|
+
const MULTIPLE_FILE_COMPONENTS: ReadonlySet<string> = new Set([
|
|
141
|
+
"dms-file",
|
|
142
|
+
"dms-image",
|
|
143
|
+
]);
|
|
144
|
+
// A textarea taller than the browser's three rows is a long text.
|
|
145
|
+
const SHORT_TEXTAREA_ROWS = 3;
|
|
146
|
+
|
|
147
|
+
/** The options of a field's control that make it wide. */
|
|
148
|
+
interface WideFieldOptions {
|
|
149
|
+
multiple?: boolean;
|
|
150
|
+
rows?: number;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Whether a field takes the row's full width, its label above it. */
|
|
154
|
+
export function isWideField(field: FormField): boolean {
|
|
155
|
+
if (field.type && WIDE_FIELD_TYPES.has(field.type)) return true;
|
|
156
|
+
const name = field.component.componentName ?? "";
|
|
157
|
+
if (WIDE_FIELD_COMPONENTS.has(name)) return true;
|
|
158
|
+
const options: WideFieldOptions = field.component.options ?? {};
|
|
159
|
+
if (MULTIPLE_FILE_COMPONENTS.has(name)) return options.multiple === true;
|
|
160
|
+
if (name !== "dms-textarea") return false;
|
|
161
|
+
return (options.rows ?? 0) > SHORT_TEXTAREA_ROWS;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* How a form row is drawn: a wide field, or a group holding one, stacks its
|
|
166
|
+
* label above it where the form would set it beside.
|
|
167
|
+
*/
|
|
168
|
+
export function entryRowLayout(
|
|
169
|
+
row: FormRowLayout,
|
|
170
|
+
fields: readonly FormField[],
|
|
171
|
+
): FormRowLayout {
|
|
172
|
+
if (row.layout !== "form" || !fields.some(isWideField)) return row;
|
|
173
|
+
return { ...row, layout: "stack" };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** The fields a form entry draws: its own, or its group's. */
|
|
177
|
+
export function entryFields(entry: FormFieldOrGroup): FormField[] {
|
|
178
|
+
return isFieldGroup(entry) ? entry.fields : [entry];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Classes of a grouped field: its own label above its control. */
|
|
182
|
+
export interface GroupMemberClasses {
|
|
183
|
+
root: string;
|
|
184
|
+
label: string;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// A member's label is a step below the group's (FieldRow's 13px semibold
|
|
188
|
+
// label), which heads the cluster.
|
|
189
|
+
export const GROUP_MEMBER_CLASSES: GroupMemberClasses = {
|
|
190
|
+
root: "grid min-w-0 flex-1 content-start gap-1.5",
|
|
191
|
+
label:
|
|
192
|
+
"flex flex-wrap items-center gap-1 text-[12.5px] font-medium text-toned",
|
|
193
|
+
};
|
|
@@ -133,3 +133,35 @@ export function sectionState(
|
|
|
133
133
|
invalidCount: section.fieldIds.filter((id) => invalid.has(id)).length,
|
|
134
134
|
};
|
|
135
135
|
}
|
|
136
|
+
|
|
137
|
+
/** Where a section's card starts, from the top of the visible area. */
|
|
138
|
+
export interface SectionOffset {
|
|
139
|
+
id: string;
|
|
140
|
+
top: number;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** What the side list reads to tell which section is being read. */
|
|
144
|
+
export interface SectionSpyView {
|
|
145
|
+
/** The sections, in order. */
|
|
146
|
+
sections: SectionOffset[];
|
|
147
|
+
/** Offset of the reading line from the top of the visible area. */
|
|
148
|
+
readingLine: number;
|
|
149
|
+
/** Scrolled to the end: nothing below can rise to the reading line. */
|
|
150
|
+
isAtEnd: boolean;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* The section being read: the last one whose top has passed the reading
|
|
155
|
+
* line, the first before any has, and the last once the form is scrolled to
|
|
156
|
+
* its end — the sections at the bottom of a long form never rise that far,
|
|
157
|
+
* and would otherwise never be current. One section at a time.
|
|
158
|
+
*/
|
|
159
|
+
export function currentSectionId(view: SectionSpyView): string | undefined {
|
|
160
|
+
if (view.isAtEnd) return view.sections.at(-1)?.id;
|
|
161
|
+
let current = view.sections[0]?.id;
|
|
162
|
+
for (const section of view.sections) {
|
|
163
|
+
if (section.top > view.readingLine) break;
|
|
164
|
+
current = section.id;
|
|
165
|
+
}
|
|
166
|
+
return current;
|
|
167
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DataType } from "#dms-core/app/composables/data-types/useDataType";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What a read-only field shows: for a type whose form state is mapped from
|
|
5
|
+
* the value the server sent (a relation loaded as its row, held as its id),
|
|
6
|
+
* that value while the state still references it, so the field reads as the
|
|
7
|
+
* table cell does — the row's label and avatar rather than its id. Any other
|
|
8
|
+
* value, or one the form changed, is shown as the state holds it.
|
|
9
|
+
*/
|
|
10
|
+
export function readonlyFieldValue(
|
|
11
|
+
value: unknown,
|
|
12
|
+
loaded: unknown,
|
|
13
|
+
dataType: DataType | undefined,
|
|
14
|
+
options?: unknown,
|
|
15
|
+
): unknown {
|
|
16
|
+
if (!dataType?.beforeStateMapper || !dataType.isSameValue) return value;
|
|
17
|
+
if (loaded === undefined || loaded === null) return value;
|
|
18
|
+
return dataType.isSameValue(value, loaded, options) ? loaded : value;
|
|
19
|
+
}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type FieldChangePayload,
|
|
3
|
+
type SetFieldValueParams,
|
|
4
|
+
resolveFieldValueWrite,
|
|
5
|
+
} from "./fieldValueRule";
|
|
6
|
+
|
|
1
7
|
type FieldSetStateKey = "disabledFields" | "hiddenFields" | "requiredFields";
|
|
2
8
|
|
|
3
9
|
function createFieldSetMutator(stateKey: FieldSetStateKey, flagKey: string) {
|
|
@@ -23,6 +29,25 @@ function createFieldSetMutator(stateKey: FieldSetStateKey, flagKey: string) {
|
|
|
23
29
|
};
|
|
24
30
|
}
|
|
25
31
|
|
|
32
|
+
function setFieldValue(
|
|
33
|
+
action: WatchAction,
|
|
34
|
+
eventData: ComponentEventData,
|
|
35
|
+
componentState: Ref<Record<string, unknown>>,
|
|
36
|
+
) {
|
|
37
|
+
const params = action.params as unknown as SetFieldValueParams;
|
|
38
|
+
const write = resolveFieldValueWrite(
|
|
39
|
+
params,
|
|
40
|
+
eventData.data as FieldChangePayload | undefined,
|
|
41
|
+
);
|
|
42
|
+
if (!write) return;
|
|
43
|
+
// A copy: a map entry holding a list or an object is shared by every write
|
|
44
|
+
// (and may reach here as a reactive proxy, which structuredClone refuses).
|
|
45
|
+
componentState.value[params.targetField] =
|
|
46
|
+
write.value === undefined
|
|
47
|
+
? undefined
|
|
48
|
+
: JSON.parse(JSON.stringify(write.value));
|
|
49
|
+
}
|
|
50
|
+
|
|
26
51
|
export function registerFormFunctions() {
|
|
27
52
|
const { registerFunction } = useDefinedFunctions();
|
|
28
53
|
|
|
@@ -40,4 +65,6 @@ export function registerFormFunctions() {
|
|
|
40
65
|
FormFunctions.SET_FIELD_REQUIRED,
|
|
41
66
|
createFieldSetMutator("requiredFields", "setRequired"),
|
|
42
67
|
);
|
|
68
|
+
|
|
69
|
+
registerFunction(FormFunctions.SET_FIELD_VALUE, setFieldValue);
|
|
43
70
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DataType,
|
|
3
|
+
DataTypeSelectSource,
|
|
4
|
+
} from "#dms-core/app/composables/data-types/useDataType";
|
|
5
|
+
import type { RenderableColumn } from "../data-types/useColumnValueRenderer";
|
|
6
|
+
|
|
7
|
+
/** A column of a read-only list: a field of its rows. */
|
|
8
|
+
export interface RepeaterTableColumn {
|
|
9
|
+
id: string;
|
|
10
|
+
label?: string;
|
|
11
|
+
type?: string;
|
|
12
|
+
component: ComponentInfo;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Where a relation column reads the rows its values name. */
|
|
16
|
+
export interface RelationLabelSource extends DataTypeSelectSource {
|
|
17
|
+
multiple: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The column as the grid's cell renderer reads it: its data type, with the
|
|
22
|
+
* options of its control (a select's items, a relation's key mapping).
|
|
23
|
+
*/
|
|
24
|
+
export function renderableColumn(
|
|
25
|
+
column: RepeaterTableColumn,
|
|
26
|
+
): RenderableColumn {
|
|
27
|
+
const type: DataTypeConfig = {
|
|
28
|
+
id: column.type ?? "",
|
|
29
|
+
compareModes: [],
|
|
30
|
+
filterComponents: {},
|
|
31
|
+
// The column came from the form's JSON: its options are JSON values.
|
|
32
|
+
inputComponent: column.component as ComponentInfo<ComponentOptionsData>,
|
|
33
|
+
};
|
|
34
|
+
return { id: column.id, type };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The rows a relation column names its values from, if its data type reads
|
|
39
|
+
* its labels from a `select` route ({@link DataType.selectSource}).
|
|
40
|
+
*/
|
|
41
|
+
export function relationLabelSource(
|
|
42
|
+
column: RepeaterTableColumn,
|
|
43
|
+
dataType: Pick<DataType, "selectSource"> | undefined,
|
|
44
|
+
): RelationLabelSource | undefined {
|
|
45
|
+
const options = column.component.options as
|
|
46
|
+
| { multiple?: unknown }
|
|
47
|
+
| undefined;
|
|
48
|
+
const source = dataType?.selectSource?.(options);
|
|
49
|
+
return source ? { ...source, multiple: !!options?.multiple } : undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A relation value as its formatter draws it: the related row in place of
|
|
54
|
+
* each id it holds. An id no row matches (a deleted one) stays as it is; a
|
|
55
|
+
* value the server already joined is kept.
|
|
56
|
+
*/
|
|
57
|
+
export function joinRelationValue(
|
|
58
|
+
value: unknown,
|
|
59
|
+
rowsByValue: ReadonlyMap<unknown, Record<string, unknown>>,
|
|
60
|
+
multiple: boolean,
|
|
61
|
+
): unknown {
|
|
62
|
+
const join = (item: unknown) => rowsByValue.get(item) ?? item;
|
|
63
|
+
if (multiple && Array.isArray(value)) return value.map(join);
|
|
64
|
+
return join(value);
|
|
65
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A server error as UForm holds it once placed: on the field, or on the part
|
|
3
|
+
* of it the error names (`address.street`).
|
|
4
|
+
*/
|
|
5
|
+
export interface PlacedServerError {
|
|
6
|
+
name?: string;
|
|
7
|
+
message: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** A refused submit's errors a field shows, and the value they were raised against. */
|
|
11
|
+
export interface KeptServerError {
|
|
12
|
+
/** The field's value when the server refused it, as JSON. */
|
|
13
|
+
value: string;
|
|
14
|
+
errors: PlacedServerError[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** The value of a field as a kept error compares it. */
|
|
18
|
+
export function serverErrorValue(value: unknown): string {
|
|
19
|
+
return JSON.stringify(value) ?? "";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The fields whose value moved off the one their kept error was raised against. */
|
|
23
|
+
export function changedServerErrorFields(
|
|
24
|
+
kept: ReadonlyMap<string, KeptServerError>,
|
|
25
|
+
state: Readonly<Record<string, unknown>>,
|
|
26
|
+
): string[] {
|
|
27
|
+
return [...kept]
|
|
28
|
+
.filter(([name, entry]) => serverErrorValue(state[name]) !== entry.value)
|
|
29
|
+
.map(([name]) => name);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The kept server errors, as UForm's `validate` answers them. UForm runs it
|
|
34
|
+
* with its schema on every validation and replaces a field's errors with what
|
|
35
|
+
* both answer: a number input commits its value as it loses focus, and the
|
|
36
|
+
* input event, landing 300 ms later, re-validates the field after a refused
|
|
37
|
+
* submit placed its error. A field still holding the refused value keeps it.
|
|
38
|
+
* A submit validates without them: the server judges the values again.
|
|
39
|
+
*/
|
|
40
|
+
export function keptServerErrors(
|
|
41
|
+
kept: ReadonlyMap<string, KeptServerError>,
|
|
42
|
+
state: Readonly<Record<string, unknown>>,
|
|
43
|
+
isSubmitting: boolean,
|
|
44
|
+
): PlacedServerError[] {
|
|
45
|
+
if (isSubmitting) return [];
|
|
46
|
+
return [...kept]
|
|
47
|
+
.filter(([name, entry]) => serverErrorValue(state[name]) === entry.value)
|
|
48
|
+
.flatMap(([, entry]) => entry.errors);
|
|
49
|
+
}
|