@antelopejs/dms 0.9.8 → 0.11.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 +171 -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 -2
- package/dist/config.js +5 -7
- 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 +38 -94
- 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 +7 -2
- package/dist/implementations/dms/manifest.js +12 -7
- 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 +32 -55
- 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/index.d.ts +0 -1
- package/dist/routes/index.js +0 -1
- package/dist/routes/index.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 +25 -15
- package/dist/store/listing.js.map +1 -1
- package/dist/store/operations.js +5 -2
- 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/utils/ensure-default-tenant.d.ts +2 -1
- package/dist/utils/ensure-default-tenant.js +13 -1
- package/dist/utils/ensure-default-tenant.js.map +1 -1
- package/dist/utils/export-jobs.js +2 -1
- package/dist/utils/export-jobs.js.map +1 -1
- 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 +33 -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/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 +25 -5
- package/frontend-vue/layers/dms-core/app/composables/useApiError.ts +3 -5
- package/frontend-vue/layers/dms-core/app/composables/useFieldErrors.ts +61 -26
- 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 +134 -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 +136 -2
- package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +136 -2
- package/frontend-vue/layers/dms-core/shared/types/app-config.ts +0 -2
- package/frontend-vue/layers/dms-layout/app/app.config.ts +0 -1
- package/frontend-vue/layers/dms-layout/app/assets/css/main.css +11 -1
- package/frontend-vue/layers/dms-layout/app/assets/fonts/Geist-Italic-Variable.woff2 +0 -0
- package/frontend-vue/layers/dms-layout/app/assets/fonts/Geist-Variable.woff2 +0 -0
- package/frontend-vue/layers/dms-layout/app/assets/fonts/GeistMono-Italic-Variable.woff2 +0 -0
- package/frontend-vue/layers/dms-layout/app/assets/fonts/GeistMono-Variable.woff2 +0 -0
- 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 +6 -2
- 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 -107
- package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +18 -7
- package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +4 -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/github-repository.ts +2 -2
- 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/useServedHeaderActions.ts +49 -11
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleHistory.ts +3 -2
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +59 -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/usePageRecordDeletion.ts +162 -0
- package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +6 -1
- package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +6 -2
- package/frontend-vue/layers/dms-layout/app/custom-layouts/SettingsLayout.vue +6 -2
- 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 +13 -22
- package/frontend-vue/layers/dms-layout/app/plugins/command-palette-navigation.client.ts +11 -10
- package/frontend-vue/layers/dms-layout/app/plugins/command-palette-personal-pages.client.ts +22 -52
- package/frontend-vue/layers/dms-layout/app/plugins/command-palette-quick-actions.client.ts +7 -7
- package/frontend-vue/layers/dms-layout/app/plugins/command-palette-session.client.ts +11 -10
- package/frontend-vue/layers/dms-layout/app/plugins/dms-dev-reload.client.ts +14 -9
- package/frontend-vue/layers/dms-layout/app/theme/actions.ts +5 -4
- 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/i18n/locales/layout-en-GB.json +45 -18
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +44 -17
- 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/FormLoadError.vue +5 -1
- 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 +48 -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/actionError.ts +6 -1
- 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 +44 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +123 -27
- 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 +92 -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/formRecordLoad.ts +11 -5
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formSections.ts +32 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formValidation.ts +36 -4
- 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 +78 -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 +97 -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 +90 -35
- 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/composables/table-view/utils/writeError.ts +21 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/addressSuggestions.ts +49 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/codeHighlight.ts +67 -29
- 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/htmlGrammar.ts +79 -0
- 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/shellParser.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/build/utils/streamHighlight.ts +90 -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/code-snippet/CodeSnippet.vue +23 -12
- 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 +119 -36
- package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +53 -22
- 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 +226 -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 +210 -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/CascaderPath.vue +74 -25
- package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +48 -44
- 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 +426 -97
- 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 +16 -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 +189 -28
- package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +29 -12
- 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/cascader.ts +94 -3
- package/frontend-vue/layers/dms-ui/app/utils/componentTree.ts +24 -0
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +20 -5
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +20 -5
- package/frontend-vue/package.json +6 -2
- package/package.json +11 -10
- 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 +129 -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/dist/hooks/tenant-instances.d.ts +0 -1
- package/dist/hooks/tenant-instances.js +0 -13
- package/dist/hooks/tenant-instances.js.map +0 -1
- package/dist/routes/startup-gate.d.ts +0 -7
- package/dist/routes/startup-gate.js +0 -39
- package/dist/routes/startup-gate.js.map +0 -1
- package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +0 -42
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { TableFilter } from "../../../components/table/Table.vue";
|
|
2
|
+
import type { TableCountRequest } from "../useTableListData";
|
|
3
|
+
import { buildTableQuery, isFilterEffective } from "./tableQuery";
|
|
4
|
+
|
|
5
|
+
/** A view's counter shares the tab counters' request, under its own id. */
|
|
6
|
+
export const VIEW_COUNT_ID_PREFIX = "view:";
|
|
7
|
+
/**
|
|
8
|
+
* So does the navigation badge of a tab while anything narrows its counter:
|
|
9
|
+
* the badge stands for the whole page, the counter for what the table shows.
|
|
10
|
+
*/
|
|
11
|
+
export const NAV_BADGE_COUNT_ID_PREFIX = "nav:";
|
|
12
|
+
|
|
13
|
+
/** A tab that filters the table (a link tab counts its own list). */
|
|
14
|
+
export interface CountedTab {
|
|
15
|
+
id: string;
|
|
16
|
+
filters: TableFilter[];
|
|
17
|
+
hasNavBadge: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** A view with a counter, as opening it sets the table. */
|
|
21
|
+
export interface CountedView {
|
|
22
|
+
id: string;
|
|
23
|
+
filters: TableFilter[];
|
|
24
|
+
search?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** What the table lists, besides its tab. */
|
|
28
|
+
export interface TableCountState {
|
|
29
|
+
/** The filters the page sets: its URL query and route parameters. */
|
|
30
|
+
scopeFilters: TableFilter[];
|
|
31
|
+
/** The open tab's filter. */
|
|
32
|
+
tabFilters: TableFilter[];
|
|
33
|
+
/** The filters on screen: the open view's, as the user edited them. */
|
|
34
|
+
columnFilters: TableFilter[];
|
|
35
|
+
search?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface TableCountQueriesInput extends TableCountState {
|
|
39
|
+
tabs: CountedTab[];
|
|
40
|
+
views: CountedView[];
|
|
41
|
+
activeViewId?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const countQuery = (
|
|
45
|
+
columnFilters: TableFilter[],
|
|
46
|
+
hiddenFilters: TableFilter[],
|
|
47
|
+
search?: string,
|
|
48
|
+
) =>
|
|
49
|
+
buildTableQuery({
|
|
50
|
+
pagination: { pageIndex: 0, pageSize: 0 },
|
|
51
|
+
sorting: [],
|
|
52
|
+
columnFilters,
|
|
53
|
+
hiddenFilters,
|
|
54
|
+
globalFilter: search,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/** Whether a tab's counter counts fewer rows than its whole page holds. */
|
|
58
|
+
export const isCountNarrowed = (state: TableCountState): boolean =>
|
|
59
|
+
!!state.search ||
|
|
60
|
+
state.scopeFilters.some(isFilterEffective) ||
|
|
61
|
+
state.columnFilters.some(isFilterEffective);
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The counters of a table's tabs and views, in one request: each counts what
|
|
65
|
+
* a click on it would list, the rest of the table's state kept. A tab counts
|
|
66
|
+
* under the filters and search on screen; a view under the tab open, with its
|
|
67
|
+
* own filters and search, which opening it applies; the open view counts the
|
|
68
|
+
* rows listed. The page's URL filters apply to all. A `navBadge` tab also gets
|
|
69
|
+
* a count of its filter alone, for the menu, while anything narrows its own.
|
|
70
|
+
*/
|
|
71
|
+
export function buildTableCountQueries(
|
|
72
|
+
input: TableCountQueriesInput,
|
|
73
|
+
): TableCountRequest[] {
|
|
74
|
+
const { scopeFilters, tabFilters, columnFilters, search } = input;
|
|
75
|
+
const viewScope = [...scopeFilters, ...tabFilters];
|
|
76
|
+
const isNarrowed = isCountNarrowed(input);
|
|
77
|
+
return [
|
|
78
|
+
...input.tabs.map((tab) => ({
|
|
79
|
+
id: tab.id,
|
|
80
|
+
query: countQuery(
|
|
81
|
+
columnFilters,
|
|
82
|
+
[...scopeFilters, ...tab.filters],
|
|
83
|
+
search,
|
|
84
|
+
),
|
|
85
|
+
})),
|
|
86
|
+
...(isNarrowed ? input.tabs : [])
|
|
87
|
+
.filter((tab) => tab.hasNavBadge)
|
|
88
|
+
.map((tab) => ({
|
|
89
|
+
id: `${NAV_BADGE_COUNT_ID_PREFIX}${tab.id}`,
|
|
90
|
+
query: countQuery([], tab.filters),
|
|
91
|
+
})),
|
|
92
|
+
...input.views.map((view) => ({
|
|
93
|
+
id: `${VIEW_COUNT_ID_PREFIX}${view.id}`,
|
|
94
|
+
query:
|
|
95
|
+
view.id === input.activeViewId
|
|
96
|
+
? countQuery(columnFilters, viewScope, search)
|
|
97
|
+
: countQuery(view.filters, viewScope, view.search),
|
|
98
|
+
})),
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The menu badge of a `navBadge` tab, from the counts answered: its global
|
|
104
|
+
* count once asked for, its own counter while nothing narrows it, else none
|
|
105
|
+
* until the global count arrives.
|
|
106
|
+
*/
|
|
107
|
+
export const navBadgeCount = (
|
|
108
|
+
counts: Record<string, number> | null | undefined,
|
|
109
|
+
tabId: string,
|
|
110
|
+
isNarrowed: boolean,
|
|
111
|
+
): number | undefined =>
|
|
112
|
+
counts?.[`${NAV_BADGE_COUNT_ID_PREFIX}${tabId}`] ??
|
|
113
|
+
(isNarrowed ? undefined : counts?.[tabId]);
|
|
@@ -47,16 +47,39 @@ const buildFiltersQuery = (filters: TableFilter[]): Record<string, string> => {
|
|
|
47
47
|
);
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* The column filters (the filters row, a saved view) laid over the hidden
|
|
52
|
+
* ones (a tab, a route or query parameter). On a column both filter, both
|
|
53
|
+
* are sent: the list route keeps the rows passing every filter of a column
|
|
54
|
+
* (AND), so a tab never lists rows it excludes, and two filters no row can
|
|
55
|
+
* pass list nothing.
|
|
56
|
+
*/
|
|
57
|
+
const combineFilters = (
|
|
58
|
+
hidden: Record<string, string>,
|
|
59
|
+
column: Record<string, string>,
|
|
60
|
+
): Record<string, string | string[]> => {
|
|
61
|
+
const combined: Record<string, string | string[]> = { ...hidden };
|
|
62
|
+
for (const [key, value] of Object.entries(column)) {
|
|
63
|
+
const hiddenValue = hidden[key];
|
|
64
|
+
combined[key] =
|
|
65
|
+
hiddenValue === undefined || hiddenValue === value
|
|
66
|
+
? value
|
|
67
|
+
: [hiddenValue, value];
|
|
68
|
+
}
|
|
69
|
+
return combined;
|
|
70
|
+
};
|
|
71
|
+
|
|
50
72
|
export const buildTableQuery = (config: QueryConfig): TableQueryParams => {
|
|
51
73
|
const pageSize = config.pagination.pageSize;
|
|
52
74
|
const activeSort = config.sorting[0];
|
|
53
75
|
|
|
54
|
-
const
|
|
55
|
-
|
|
76
|
+
const filters = combineFilters(
|
|
77
|
+
buildFiltersQuery(config.hiddenFilters || []),
|
|
78
|
+
buildFiltersQuery(config.columnFilters),
|
|
79
|
+
);
|
|
56
80
|
|
|
57
81
|
return {
|
|
58
|
-
...
|
|
59
|
-
...columnFilters,
|
|
82
|
+
...filters,
|
|
60
83
|
search: config.globalFilter ?? undefined,
|
|
61
84
|
offset: config.pagination.pageIndex * pageSize,
|
|
62
85
|
limit: pageSize,
|
|
@@ -53,7 +53,11 @@ export const newUserViewId = (): string =>
|
|
|
53
53
|
.toString(ID_RADIX)
|
|
54
54
|
.slice(ID_RANDOM_START)}`;
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
/**
|
|
57
|
+
* The filters opening `view` sets: the table's pinned filters, at the view's
|
|
58
|
+
* value for those it names, then the view's other filters.
|
|
59
|
+
*/
|
|
60
|
+
export function viewFilters(
|
|
57
61
|
view: TableViewViewStateConfig,
|
|
58
62
|
pinned: TableFilter[],
|
|
59
63
|
): TableFilter[] {
|
|
@@ -179,6 +183,8 @@ export interface TableUrlScope {
|
|
|
179
183
|
/** What reading a key off the URL found. */
|
|
180
184
|
export interface UrlKeyReading {
|
|
181
185
|
value?: string;
|
|
186
|
+
/** The key is there, empty (`?view=`): it names nothing, explicitly. */
|
|
187
|
+
isCleared?: boolean;
|
|
182
188
|
/** A short key named something the table may not claim. */
|
|
183
189
|
ignoredShortKey?: boolean;
|
|
184
190
|
}
|
|
@@ -208,14 +214,24 @@ export function writeTableUrlKey(
|
|
|
208
214
|
const scopedKey = tableUrlKey(scope, key);
|
|
209
215
|
if (value) url.searchParams.set(scopedKey, value);
|
|
210
216
|
else url.searchParams.delete(scopedKey);
|
|
217
|
+
// The only table reads its prefixed key first (`readTableUrlKey`): a link
|
|
218
|
+
// naming it (`content.tab=`) would otherwise outlive the short key written
|
|
219
|
+
// over it, and win again on reload.
|
|
220
|
+
if (scope.tableId && scopedKey === key) {
|
|
221
|
+
url.searchParams.delete(`${scope.tableId}.${key}`);
|
|
222
|
+
}
|
|
211
223
|
window.history.replaceState(window.history.state, "", url);
|
|
212
224
|
return url;
|
|
213
225
|
}
|
|
214
226
|
|
|
227
|
+
const readUrlValue = (value: string): UrlKeyReading =>
|
|
228
|
+
value ? { value } : { isCleared: true };
|
|
229
|
+
|
|
215
230
|
/**
|
|
216
231
|
* The value the URL gives `key` (`view`, `tab`) for this table: under its
|
|
217
232
|
* prefixed key (`content.view`) or, when it is its page's only table, the
|
|
218
|
-
* short one. A short key on a page with several tables is ignored.
|
|
233
|
+
* short one. A short key on a page with several tables is ignored. A key
|
|
234
|
+
* left empty reads as cleared, not as absent.
|
|
219
235
|
*/
|
|
220
236
|
export function readTableUrlKey(
|
|
221
237
|
query: Record<string, unknown>,
|
|
@@ -223,9 +239,9 @@ export function readTableUrlKey(
|
|
|
223
239
|
key: string,
|
|
224
240
|
): UrlKeyReading {
|
|
225
241
|
const prefixed = scope.tableId ? query[`${scope.tableId}.${key}`] : undefined;
|
|
226
|
-
if (typeof prefixed === "string"
|
|
242
|
+
if (typeof prefixed === "string") return readUrlValue(prefixed);
|
|
227
243
|
const short = query[key];
|
|
228
|
-
if (typeof short !== "string"
|
|
229
|
-
if (scope.isSoleTableView) return
|
|
230
|
-
return { ignoredShortKey: true };
|
|
244
|
+
if (typeof short !== "string") return {};
|
|
245
|
+
if (scope.isSoleTableView) return readUrlValue(short);
|
|
246
|
+
return short ? { ignoredShortKey: true } : {};
|
|
231
247
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { I18nTranslate } from "#dms-core/app/composables/translation/useTranslation";
|
|
2
|
+
import type { TableViewColumn } from "../../../../composables/table-view/types/column";
|
|
3
|
+
import { resolveActionError } from "../../confirm/actionError";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Why a table view's write failed (a card moved on the board, a row moved
|
|
7
|
+
* by hand, a row action), for its toast: the server's reason, a field the
|
|
8
|
+
* data API refused named by its column's label ("Loss reason is
|
|
9
|
+
* required."). Never the raw ofetch error line.
|
|
10
|
+
*/
|
|
11
|
+
export function describeTableWriteError(
|
|
12
|
+
error: unknown,
|
|
13
|
+
translate: I18nTranslate,
|
|
14
|
+
columns: ReadonlyArray<Pick<TableViewColumn, "accessorKey" | "header">>,
|
|
15
|
+
processI18n: (text: string) => string,
|
|
16
|
+
): string {
|
|
17
|
+
return resolveActionError(error, translate, (field) => {
|
|
18
|
+
const column = columns.find((entry) => entry.accessorKey === field);
|
|
19
|
+
return column ? processI18n(column.header) : undefined;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// The geocoder answers the whole world in its own order, and the same place
|
|
2
|
+
// several times (one entry per OSM object: a square, its bus stop, its
|
|
3
|
+
// parking). The list a user picks from holds each place once, the ones in
|
|
4
|
+
// the address's country first.
|
|
5
|
+
|
|
6
|
+
/** How many results are asked for per suggestion shown, to rank from. */
|
|
7
|
+
export const SUGGESTION_POOL_FACTOR = 4;
|
|
8
|
+
/** The most results asked for at once. */
|
|
9
|
+
export const SUGGESTION_POOL_MAX = 20;
|
|
10
|
+
|
|
11
|
+
/** A suggestion as the address field lists it. */
|
|
12
|
+
export interface RankableSuggestion {
|
|
13
|
+
label: string;
|
|
14
|
+
/** ISO 3166-1 alpha-2 code of the suggestion's country, upper case. */
|
|
15
|
+
countryCode?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** How many results to ask the geocoder for, to show `limit`. */
|
|
19
|
+
export function suggestionPoolSize(limit: number): number {
|
|
20
|
+
return Math.min(SUGGESTION_POOL_MAX, limit * SUGGESTION_POOL_FACTOR);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The suggestions to list: each label once, those in `country` (when given)
|
|
25
|
+
* first and in the geocoder's order otherwise, at most `limit`.
|
|
26
|
+
*
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
export function rankAddressSuggestions<T extends RankableSuggestion>(
|
|
30
|
+
suggestions: readonly T[],
|
|
31
|
+
country: string | undefined,
|
|
32
|
+
limit: number,
|
|
33
|
+
): T[] {
|
|
34
|
+
const seen = new Set<string>();
|
|
35
|
+
const unique = suggestions.filter((suggestion) => {
|
|
36
|
+
const key = suggestion.label.trim().toLowerCase();
|
|
37
|
+
if (seen.has(key)) return false;
|
|
38
|
+
seen.add(key);
|
|
39
|
+
return true;
|
|
40
|
+
});
|
|
41
|
+
const preferred = country?.toUpperCase();
|
|
42
|
+
if (!preferred) return unique.slice(0, limit);
|
|
43
|
+
const isPreferred = (suggestion: T) =>
|
|
44
|
+
suggestion.countryCode?.toUpperCase() === preferred;
|
|
45
|
+
return [
|
|
46
|
+
...unique.filter(isPreferred),
|
|
47
|
+
...unique.filter((suggestion) => !isPreferred(suggestion)),
|
|
48
|
+
].slice(0, limit);
|
|
49
|
+
}
|
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
// Read-only highlighting: the code parsed by the editor's
|
|
2
|
-
// languages and cut into styled tokens, without
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
javascriptLanguage,
|
|
8
|
-
typescriptLanguage,
|
|
9
|
-
} from "@codemirror/lang-javascript";
|
|
10
|
-
import { jsonLanguage } from "@codemirror/lang-json";
|
|
11
|
-
import { StandardSQL } from "@codemirror/lang-sql";
|
|
12
|
-
import { StreamLanguage } from "@codemirror/language";
|
|
1
|
+
// Read-only highlighting: the code parsed by the grammars the editor's
|
|
2
|
+
// CodeMirror languages are built on, and cut into styled tokens, without
|
|
3
|
+
// mounting an editor. Each language's grammar loads on demand and alone: a
|
|
4
|
+
// snippet needs neither the editor's view nor its state, which would triple
|
|
5
|
+
// what a page showing a JSON payload downloads. SQL is the exception: its
|
|
6
|
+
// grammar only ships inside `@codemirror/lang-sql`.
|
|
13
7
|
import type { Parser } from "@lezer/common";
|
|
14
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
type Highlighter,
|
|
10
|
+
highlightTree,
|
|
11
|
+
tagHighlighter,
|
|
12
|
+
} from "@lezer/highlight";
|
|
15
13
|
import { CODE_PALETTE, type CodeTokenStyle } from "./codePalette";
|
|
16
|
-
import {
|
|
14
|
+
import type { HighlightedRange } from "./streamHighlight";
|
|
17
15
|
|
|
18
16
|
/** A run of code drawn in one style; unstyled text has no `style`. */
|
|
19
17
|
export interface CodeToken {
|
|
@@ -21,13 +19,42 @@ export interface CodeToken {
|
|
|
21
19
|
style?: CodeTokenStyle;
|
|
22
20
|
}
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
/** Cuts code of the language it was loaded for into tokens. */
|
|
23
|
+
export type CodeHighlighter = (code: string) => CodeToken[];
|
|
24
|
+
|
|
25
|
+
/** Calls `put` for each styled range of `code`, in order. */
|
|
26
|
+
type RangeReader = (
|
|
27
|
+
code: string,
|
|
28
|
+
highlighter: Highlighter,
|
|
29
|
+
put: HighlightedRange,
|
|
30
|
+
) => void;
|
|
31
|
+
|
|
32
|
+
const readWith =
|
|
33
|
+
(parser: Parser): RangeReader =>
|
|
34
|
+
(code, highlighter, put) =>
|
|
35
|
+
highlightTree(parser.parse(code), highlighter, put);
|
|
36
|
+
|
|
37
|
+
// One dynamic import per language, so a page loads the grammar it shows.
|
|
38
|
+
const READERS: Record<string, () => Promise<RangeReader>> = {
|
|
39
|
+
json: async () => readWith((await import("@lezer/json")).parser),
|
|
40
|
+
javascript: async () => readWith((await import("@lezer/javascript")).parser),
|
|
41
|
+
typescript: async () =>
|
|
42
|
+
readWith(
|
|
43
|
+
(await import("@lezer/javascript")).parser.configure({ dialect: "ts" }),
|
|
44
|
+
),
|
|
45
|
+
html: async () => readWith((await import("./htmlGrammar")).htmlParser),
|
|
46
|
+
sql: async () =>
|
|
47
|
+
readWith(
|
|
48
|
+
(await import("@codemirror/lang-sql")).StandardSQL.language.parser,
|
|
49
|
+
),
|
|
50
|
+
shell: async () => {
|
|
51
|
+
const [{ highlightStream }, { shellParser }] = await Promise.all([
|
|
52
|
+
import("./streamHighlight"),
|
|
53
|
+
import("./shellParser"),
|
|
54
|
+
]);
|
|
55
|
+
return (code, highlighter, put) =>
|
|
56
|
+
highlightStream(shellParser, code, highlighter, put);
|
|
57
|
+
},
|
|
31
58
|
};
|
|
32
59
|
|
|
33
60
|
// Each palette entry's position is its class, read back into its style.
|
|
@@ -40,16 +67,11 @@ function styleOf(classes: string): CodeTokenStyle {
|
|
|
40
67
|
return { color, fontStyle };
|
|
41
68
|
}
|
|
42
69
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
* `resolveCodeLanguage`); a language without a parser is one plain token.
|
|
46
|
-
*/
|
|
47
|
-
export function highlightCode(code: string, language: string): CodeToken[] {
|
|
48
|
-
const parser = PARSERS[language];
|
|
49
|
-
if (!parser || !code) return [{ text: code }];
|
|
70
|
+
function tokensOf(code: string, read: RangeReader): CodeToken[] {
|
|
71
|
+
if (!code) return [{ text: code }];
|
|
50
72
|
const tokens: CodeToken[] = [];
|
|
51
73
|
let cursor = 0;
|
|
52
|
-
|
|
74
|
+
read(code, HIGHLIGHTER, (from, to, classes) => {
|
|
53
75
|
if (from > cursor) tokens.push({ text: code.slice(cursor, from) });
|
|
54
76
|
tokens.push({ text: code.slice(from, to), style: styleOf(classes) });
|
|
55
77
|
cursor = to;
|
|
@@ -57,3 +79,19 @@ export function highlightCode(code: string, language: string): CodeToken[] {
|
|
|
57
79
|
if (cursor < code.length) tokens.push({ text: code.slice(cursor) });
|
|
58
80
|
return tokens;
|
|
59
81
|
}
|
|
82
|
+
|
|
83
|
+
const plain: CodeHighlighter = (code) => [{ text: code }];
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Loads the grammar of `language` (a resolved name, see
|
|
87
|
+
* `resolveCodeLanguage`) and resolves to what cuts its code into styled
|
|
88
|
+
* tokens; a language without a grammar shows each code as one plain token.
|
|
89
|
+
*/
|
|
90
|
+
export async function loadCodeHighlighter(
|
|
91
|
+
language: string,
|
|
92
|
+
): Promise<CodeHighlighter> {
|
|
93
|
+
const load = READERS[language];
|
|
94
|
+
if (!load) return plain;
|
|
95
|
+
const read = await load();
|
|
96
|
+
return (code) => tokensOf(code, read);
|
|
97
|
+
}
|
|
@@ -5,3 +5,15 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export const EYEBROW_CLASS =
|
|
7
7
|
"font-mono text-[10.5px] font-semibold tracking-[0.12em] uppercase";
|
|
8
|
+
|
|
9
|
+
/** U+2060: no line break on either side, and no glyph to draw. */
|
|
10
|
+
const WORD_JOINER = "";
|
|
11
|
+
const INNER_HYPHEN = /(?<=\p{L})-(?=\p{L})/gu;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* `label` with each hyphenated word kept on one line: a short label that wraps
|
|
15
|
+
* on a phone breaks between words, never as "MOIS-" over "CI".
|
|
16
|
+
*/
|
|
17
|
+
export function keepHyphenatedWords(label: string): string {
|
|
18
|
+
return label.replace(INNER_HYPHEN, `-${WORD_JOINER}`);
|
|
19
|
+
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
/** An input component, with the options its field declares. */
|
|
2
|
+
interface ComponentWithOptions {
|
|
3
|
+
options?: object;
|
|
4
|
+
}
|
|
5
|
+
|
|
1
6
|
/** A field whose input component carries options. */
|
|
2
7
|
interface FieldWithOptions {
|
|
3
|
-
component:
|
|
8
|
+
component: ComponentWithOptions;
|
|
4
9
|
}
|
|
5
10
|
|
|
6
11
|
type Translate = (key: string) => string;
|
|
@@ -23,6 +28,9 @@ const ENTRY_TEXTS = ["label", "description"] as const;
|
|
|
23
28
|
|
|
24
29
|
const isText = (value: unknown): value is string => typeof value === "string";
|
|
25
30
|
|
|
31
|
+
const isOptions = (value: unknown): value is Options =>
|
|
32
|
+
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33
|
+
|
|
26
34
|
function translateTexts(
|
|
27
35
|
source: Options,
|
|
28
36
|
keys: readonly string[],
|
|
@@ -36,25 +44,57 @@ function translateTexts(
|
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* its options
|
|
47
|
+
* An entry holding a field of its own (a repeater's column, a key-value
|
|
48
|
+
* editor's value) has the options of that field's component translated too,
|
|
49
|
+
* so a select inside a repeater shows its options in the reader's language.
|
|
42
50
|
*/
|
|
43
|
-
|
|
44
|
-
|
|
51
|
+
function translateNestedField(entry: Options, processI18n: Translate): Options {
|
|
52
|
+
if (!isOptions(entry.component)) return entry;
|
|
53
|
+
return {
|
|
54
|
+
...entry,
|
|
55
|
+
component: translateComponent(entry.component, processI18n),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function translateComponent<C extends ComponentWithOptions>(
|
|
60
|
+
component: C,
|
|
45
61
|
processI18n: Translate,
|
|
46
|
-
):
|
|
47
|
-
const opts =
|
|
48
|
-
if (!opts) return
|
|
62
|
+
): C {
|
|
63
|
+
const opts = component.options as Options | undefined;
|
|
64
|
+
if (!opts) return component;
|
|
49
65
|
|
|
50
66
|
const options = translateTexts(opts, TEXT_OPTIONS, processI18n);
|
|
51
67
|
for (const key of LIST_OPTIONS) {
|
|
52
68
|
const list = opts[key];
|
|
53
69
|
if (!Array.isArray(list)) continue;
|
|
54
70
|
options[key] = list.map((entry: Options) =>
|
|
55
|
-
|
|
71
|
+
translateNestedField(
|
|
72
|
+
translateTexts(entry, ENTRY_TEXTS, processI18n),
|
|
73
|
+
processI18n,
|
|
74
|
+
),
|
|
56
75
|
);
|
|
57
76
|
}
|
|
77
|
+
if (isOptions(opts.value)) {
|
|
78
|
+
options.value = translateNestedField(opts.value, processI18n);
|
|
79
|
+
}
|
|
58
80
|
|
|
59
|
-
return { ...
|
|
81
|
+
return { ...component, options };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A field with the texts of its input component's options translated
|
|
86
|
+
* (`$`-prefixed i18n keys): placeholder, labels, descriptions, and those of
|
|
87
|
+
* its options and columns, the way forms show them. The fields nested in it
|
|
88
|
+
* (a repeater's columns, a key-value editor's value) are translated the same
|
|
89
|
+
* way, at any depth.
|
|
90
|
+
*/
|
|
91
|
+
export function processFieldI18n<F extends FieldWithOptions>(
|
|
92
|
+
field: F,
|
|
93
|
+
processI18n: Translate,
|
|
94
|
+
): F {
|
|
95
|
+
if (!field.component.options) return field;
|
|
96
|
+
return {
|
|
97
|
+
...field,
|
|
98
|
+
component: translateComponent(field.component, processI18n),
|
|
99
|
+
};
|
|
60
100
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// HTML as the editor's language reads it (`@codemirror/lang-html`): scripts,
|
|
2
|
+
// styles, `style` attributes and event handlers parsed in their own
|
|
3
|
+
// languages, from the lezer grammars alone. The nesting mirrors lang-html's
|
|
4
|
+
// defaults, so a snippet colours HTML exactly like the editor. A module of its
|
|
5
|
+
// own because HTML is the one grammar composed of others.
|
|
6
|
+
import { parser as cssParser } from "@lezer/css";
|
|
7
|
+
import { configureNesting, parser } from "@lezer/html";
|
|
8
|
+
import { parser as javascriptParser } from "@lezer/javascript";
|
|
9
|
+
|
|
10
|
+
// What lezer read of a tag's attributes; an absent one is undefined.
|
|
11
|
+
type Attributes = Readonly<Record<string, string | undefined>>;
|
|
12
|
+
|
|
13
|
+
const typescriptParser = javascriptParser.configure({ dialect: "ts" });
|
|
14
|
+
// A JSON script holds one value, read as JavaScript reads it.
|
|
15
|
+
const jsonScriptParser = javascriptParser.configure({
|
|
16
|
+
top: "SingleExpression",
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const JSON_SCRIPT_TYPE =
|
|
20
|
+
/^(importmap|speculationrules|application\/(.+\+)?json)$/i;
|
|
21
|
+
const JAVASCRIPT_TYPE =
|
|
22
|
+
/^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i;
|
|
23
|
+
const CSS_TYPE = /^(text\/)?(x-)?(stylesheet|css)$/i;
|
|
24
|
+
|
|
25
|
+
const NESTED_TAGS = [
|
|
26
|
+
{
|
|
27
|
+
tag: "script",
|
|
28
|
+
attrs: (attrs: Attributes) =>
|
|
29
|
+
attrs.type === "text/typescript" || attrs.lang === "ts",
|
|
30
|
+
parser: typescriptParser,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
tag: "script",
|
|
34
|
+
attrs: (attrs: Attributes) =>
|
|
35
|
+
attrs.type === "text/babel" || attrs.type === "text/jsx",
|
|
36
|
+
parser: javascriptParser.configure({ dialect: "jsx" }),
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
tag: "script",
|
|
40
|
+
attrs: (attrs: Attributes) => attrs.type === "text/typescript-jsx",
|
|
41
|
+
parser: javascriptParser.configure({ dialect: "jsx ts" }),
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
tag: "script",
|
|
45
|
+
attrs: (attrs: Attributes) => JSON_SCRIPT_TYPE.test(attrs.type ?? ""),
|
|
46
|
+
parser: jsonScriptParser,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
tag: "script",
|
|
50
|
+
attrs: (attrs: Attributes) =>
|
|
51
|
+
!attrs.type || JAVASCRIPT_TYPE.test(attrs.type),
|
|
52
|
+
parser: javascriptParser,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
tag: "style",
|
|
56
|
+
attrs: (attrs: Attributes) =>
|
|
57
|
+
(!attrs.lang || attrs.lang === "css") &&
|
|
58
|
+
(!attrs.type || CSS_TYPE.test(attrs.type)),
|
|
59
|
+
parser: cssParser,
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
const EVENT_ATTRIBUTES = (
|
|
64
|
+
"beforeunload copy cut dragstart dragover dragleave dragenter dragend " +
|
|
65
|
+
"drag paste focus blur change click load mousedown mouseenter mouseleave " +
|
|
66
|
+
"mouseup keydown keyup resize scroll unload"
|
|
67
|
+
)
|
|
68
|
+
.split(" ")
|
|
69
|
+
.map((name) => `on${name}`);
|
|
70
|
+
|
|
71
|
+
const NESTED_ATTRIBUTES = [
|
|
72
|
+
{ name: "style", parser: cssParser.configure({ top: "Styles" }) },
|
|
73
|
+
...EVENT_ATTRIBUTES.map((name) => ({ name, parser: javascriptParser })),
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
/** The HTML grammar, with its embedded languages. */
|
|
77
|
+
export const htmlParser = parser.configure({
|
|
78
|
+
wrap: configureNesting(NESTED_TAGS, NESTED_ATTRIBUTES),
|
|
79
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How wide a single-line input sits in its row: `"compact"` for a short
|
|
3
|
+
* value, `"full"` to fill the row. Left out, the input's bounds decide.
|
|
4
|
+
*/
|
|
5
|
+
export type InputWidth = "compact" | "full";
|
|
6
|
+
|
|
7
|
+
/** The widest number, in digits, a number input sizes compact by itself. */
|
|
8
|
+
const COMPACT_NUMBER_DIGITS = 6;
|
|
9
|
+
/** The longest `maxLength` a text input sizes compact by itself. */
|
|
10
|
+
const COMPACT_TEXT_LENGTH = 16;
|
|
11
|
+
|
|
12
|
+
interface NumberWidthOptions {
|
|
13
|
+
width?: InputWidth;
|
|
14
|
+
min?: number;
|
|
15
|
+
max?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface TextWidthOptions {
|
|
19
|
+
width?: InputWidth;
|
|
20
|
+
maxLength?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function integerDigits(value: number): number {
|
|
24
|
+
return String(Math.trunc(Math.abs(value))).length;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Whether a number input takes a compact width: asked for, or left out with
|
|
29
|
+
* a `max` (and `min`) that keep the value within a few digits. Without a
|
|
30
|
+
* `max` nothing bounds the value, so it fills the row.
|
|
31
|
+
*/
|
|
32
|
+
export function isCompactNumberInput({
|
|
33
|
+
width,
|
|
34
|
+
min,
|
|
35
|
+
max,
|
|
36
|
+
}: NumberWidthOptions): boolean {
|
|
37
|
+
if (width) return width === "compact";
|
|
38
|
+
if (max === undefined || !Number.isFinite(max)) return false;
|
|
39
|
+
const bounds = min === undefined ? [max] : [min, max];
|
|
40
|
+
return Math.max(...bounds.map(integerDigits)) <= COMPACT_NUMBER_DIGITS;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Whether a text input takes a compact width: asked for, or left out with a
|
|
45
|
+
* `maxLength` short enough for a code or an abbreviation.
|
|
46
|
+
*/
|
|
47
|
+
export function isCompactTextInput({
|
|
48
|
+
width,
|
|
49
|
+
maxLength,
|
|
50
|
+
}: TextWidthOptions): boolean {
|
|
51
|
+
if (width) return width === "compact";
|
|
52
|
+
return maxLength !== undefined && maxLength <= COMPACT_TEXT_LENGTH;
|
|
53
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readableNumberFormat } from "#dms-core/app/utils/formatter";
|
|
1
2
|
import type {
|
|
2
3
|
InputNumberAdjustment,
|
|
3
4
|
InputNumberAdjustmentReason,
|
|
@@ -15,7 +16,7 @@ const WHITESPACE = /\s/g;
|
|
|
15
16
|
const NOT_NUMERIC = /[^\d.+-]/g;
|
|
16
17
|
|
|
17
18
|
function separatorsOf(locale: string): { group?: string; decimal: string } {
|
|
18
|
-
const parts =
|
|
19
|
+
const parts = readableNumberFormat(locale).formatToParts(SEPARATOR_SAMPLE);
|
|
19
20
|
return {
|
|
20
21
|
group: parts.find((part) => part.type === "group")?.value,
|
|
21
22
|
decimal: parts.find((part) => part.type === "decimal")?.value ?? ".",
|
|
@@ -23,8 +24,10 @@ function separatorsOf(locale: string): { group?: string; decimal: string } {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
|
-
* A number as typed in a field written in `locale` ("1,020", "1 020,5")
|
|
27
|
-
*
|
|
27
|
+
* A number as typed in a field written in `locale` ("1,020", "1 020,5"),
|
|
28
|
+
* its digits grouped by any space (the narrow one the field draws in French,
|
|
29
|
+
* the no-break one the cells write, a typed one): `null` when nothing is
|
|
30
|
+
* typed or it does not read as a number.
|
|
28
31
|
*/
|
|
29
32
|
export function parseTypedNumber(text: string, locale: string): number | null {
|
|
30
33
|
const { group, decimal } = separatorsOf(locale);
|