@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.
Files changed (451) hide show
  1. package/CHANGELOG.md +171 -0
  2. package/README.md +1 -1
  3. package/data/module-store/catalog.json +2 -2
  4. package/dist/attachments/table-view.js +2 -2
  5. package/dist/attachments/table-view.js.map +1 -1
  6. package/dist/automation/actions.d.ts +2 -0
  7. package/dist/automation/actions.js +116 -71
  8. package/dist/automation/actions.js.map +1 -1
  9. package/dist/automation/i18n.d.ts +4 -0
  10. package/dist/automation/i18n.js +19 -0
  11. package/dist/automation/i18n.js.map +1 -0
  12. package/dist/automation/send-notification.d.ts +21 -0
  13. package/dist/automation/send-notification.js +163 -0
  14. package/dist/automation/send-notification.js.map +1 -0
  15. package/dist/automation/triggers.js +19 -25
  16. package/dist/automation/triggers.js.map +1 -1
  17. package/dist/client-origin-cors.d.ts +1 -0
  18. package/dist/client-origin-cors.js +19 -0
  19. package/dist/client-origin-cors.js.map +1 -0
  20. package/dist/config.d.ts +3 -2
  21. package/dist/config.js +5 -7
  22. package/dist/config.js.map +1 -1
  23. package/dist/frontend-module.d.ts +4 -0
  24. package/dist/frontend-module.js +37 -0
  25. package/dist/frontend-module.js.map +1 -0
  26. package/dist/implementations/dms/client-ip.js +1 -1
  27. package/dist/implementations/dms/client-ip.js.map +1 -1
  28. package/dist/implementations/dms/dev-reload.d.ts +5 -0
  29. package/dist/implementations/dms/dev-reload.js +23 -3
  30. package/dist/implementations/dms/dev-reload.js.map +1 -1
  31. package/dist/implementations/dms/frontend-modules.d.ts +2 -4
  32. package/dist/implementations/dms/frontend-modules.js +38 -94
  33. package/dist/implementations/dms/frontend-modules.js.map +1 -1
  34. package/dist/implementations/dms/frontend-ready.d.ts +12 -2
  35. package/dist/implementations/dms/frontend-ready.js +34 -4
  36. package/dist/implementations/dms/frontend-ready.js.map +1 -1
  37. package/dist/implementations/dms/layout-banners.d.ts +2 -1
  38. package/dist/implementations/dms/layout-banners.js +9 -0
  39. package/dist/implementations/dms/layout-banners.js.map +1 -1
  40. package/dist/implementations/dms/manifest.d.ts +7 -2
  41. package/dist/implementations/dms/manifest.js +12 -7
  42. package/dist/implementations/dms/manifest.js.map +1 -1
  43. package/dist/implementations/dms/page.d.ts +4 -3
  44. package/dist/implementations/dms/page.js +49 -18
  45. package/dist/implementations/dms/page.js.map +1 -1
  46. package/dist/implementations/dms/permissions-resolver.d.ts +2 -2
  47. package/dist/implementations/dms/permissions-resolver.js +2 -2
  48. package/dist/implementations/dms/permissions-resolver.js.map +1 -1
  49. package/dist/implementations/dms/realtime.js +1 -0
  50. package/dist/implementations/dms/realtime.js.map +1 -1
  51. package/dist/implementations/dms-base/bulk-selection.d.ts +1 -1
  52. package/dist/implementations/dms-base/bulk-selection.js +23 -1
  53. package/dist/implementations/dms-base/bulk-selection.js.map +1 -1
  54. package/dist/implementations/dms-base/filter-tokens.js +5 -3
  55. package/dist/implementations/dms-base/filter-tokens.js.map +1 -1
  56. package/dist/implementations/dms-base/search-route.d.ts +1 -0
  57. package/dist/implementations/dms-base/search-route.js +35 -13
  58. package/dist/implementations/dms-base/search-route.js.map +1 -1
  59. package/dist/implementations/dms-base/table-view.js +11 -6
  60. package/dist/implementations/dms-base/table-view.js.map +1 -1
  61. package/dist/implementations/dms-notifications/index.d.ts +2 -2
  62. package/dist/implementations/dms-notifications/index.js +5 -5
  63. package/dist/implementations/dms-notifications/index.js.map +1 -1
  64. package/dist/index.js +32 -55
  65. package/dist/index.js.map +1 -1
  66. package/dist/pages/modules/index.d.ts +1 -0
  67. package/dist/pages/modules/index.js +1 -0
  68. package/dist/pages/modules/index.js.map +1 -1
  69. package/dist/pages/modules/modules-store.d.ts +9 -0
  70. package/dist/pages/modules/modules-store.js +30 -0
  71. package/dist/pages/modules/modules-store.js.map +1 -0
  72. package/dist/pages/settings/users/account-credentials.js +2 -2
  73. package/dist/pages/settings/users/account-credentials.js.map +1 -1
  74. package/dist/pages/settings/users/account-data-store.js +25 -8
  75. package/dist/pages/settings/users/account-data-store.js.map +1 -1
  76. package/dist/pages/settings/users/invite-extension-routes.js +9 -3
  77. package/dist/pages/settings/users/invite-extension-routes.js.map +1 -1
  78. package/dist/pages/settings/users/member-management.js +13 -14
  79. package/dist/pages/settings/users/member-management.js.map +1 -1
  80. package/dist/pages/settings/users/profile.d.ts +1 -4
  81. package/dist/pages/settings/users/profile.js +0 -35
  82. package/dist/pages/settings/users/profile.js.map +1 -1
  83. package/dist/pages/settings/users/role-editor-store.js +26 -18
  84. package/dist/pages/settings/users/role-editor-store.js.map +1 -1
  85. package/dist/pages/settings/users/role-editor.d.ts +0 -1
  86. package/dist/pages/settings/users/role-editor.js +1 -2
  87. package/dist/pages/settings/users/role-editor.js.map +1 -1
  88. package/dist/pages/settings/users/role-preview.js +1 -2
  89. package/dist/pages/settings/users/role-preview.js.map +1 -1
  90. package/dist/pages/settings/users/roles.js +2 -2
  91. package/dist/pages/settings/users/roles.js.map +1 -1
  92. package/dist/pages/settings/users/two-factor-operations.js.map +1 -1
  93. package/dist/realtime/broker.d.ts +6 -0
  94. package/dist/realtime/broker.js +8 -1
  95. package/dist/realtime/broker.js.map +1 -1
  96. package/dist/realtime/presence.d.ts +8 -3
  97. package/dist/realtime/presence.js +8 -4
  98. package/dist/realtime/presence.js.map +1 -1
  99. package/dist/realtime/sessions.d.ts +11 -1
  100. package/dist/realtime/sessions.js +21 -3
  101. package/dist/realtime/sessions.js.map +1 -1
  102. package/dist/realtime/table-view.d.ts +2 -2
  103. package/dist/realtime/table-view.js +4 -3
  104. package/dist/realtime/table-view.js.map +1 -1
  105. package/dist/routes/auth/client-ip.d.ts +1 -0
  106. package/dist/routes/auth/client-ip.js +18 -0
  107. package/dist/routes/auth/client-ip.js.map +1 -1
  108. package/dist/routes/auth/oauth/config.d.ts +2 -1
  109. package/dist/routes/auth/oauth/config.js +5 -1
  110. package/dist/routes/auth/oauth/config.js.map +1 -1
  111. package/dist/routes/auth/session-handoff.d.ts +0 -1
  112. package/dist/routes/auth/session-handoff.js +0 -2
  113. package/dist/routes/auth/session-handoff.js.map +1 -1
  114. package/dist/routes/auth.js +3 -2
  115. package/dist/routes/auth.js.map +1 -1
  116. package/dist/routes/index.d.ts +0 -1
  117. package/dist/routes/index.js +0 -1
  118. package/dist/routes/index.js.map +1 -1
  119. package/dist/routes/realtime.d.ts +13 -1
  120. package/dist/routes/realtime.js +114 -29
  121. package/dist/routes/realtime.js.map +1 -1
  122. package/dist/startup-config.d.ts +2 -0
  123. package/dist/startup-config.js +62 -0
  124. package/dist/startup-config.js.map +1 -0
  125. package/dist/store/interfaces.d.ts +3 -2
  126. package/dist/store/interfaces.js +16 -6
  127. package/dist/store/interfaces.js.map +1 -1
  128. package/dist/store/listing.js +25 -15
  129. package/dist/store/listing.js.map +1 -1
  130. package/dist/store/operations.js +5 -2
  131. package/dist/store/operations.js.map +1 -1
  132. package/dist/store/requirements.js +3 -2
  133. package/dist/store/requirements.js.map +1 -1
  134. package/dist/store/runtime.d.ts +10 -1
  135. package/dist/store/runtime.js +33 -6
  136. package/dist/store/runtime.js.map +1 -1
  137. package/dist/store/types.d.ts +10 -0
  138. package/dist/utils/ensure-default-tenant.d.ts +2 -1
  139. package/dist/utils/ensure-default-tenant.js +13 -1
  140. package/dist/utils/ensure-default-tenant.js.map +1 -1
  141. package/dist/utils/export-jobs.js +2 -1
  142. package/dist/utils/export-jobs.js.map +1 -1
  143. package/dist/validation/auth.schema.d.ts +2 -2
  144. package/dist/validation/member-invite.schema.d.ts +2 -10
  145. package/dist/validation/member-invite.schema.js +2 -15
  146. package/dist/validation/member-invite.schema.js.map +1 -1
  147. package/frontend-vue/dms.frontend.ts +24 -5
  148. package/frontend-vue/dms.server.ts +17 -0
  149. package/frontend-vue/layers/dms-auth/server/accounts.ts +3 -4
  150. package/frontend-vue/layers/dms-core/app/build/composables/renderNow.ts +33 -0
  151. package/frontend-vue/layers/dms-core/app/build/composables/user/tablePreferences.ts +5 -1
  152. package/frontend-vue/layers/dms-core/app/build/utils/crossTab.ts +199 -0
  153. package/frontend-vue/layers/dms-core/app/build/utils/sharedRealtimeConnection.ts +410 -0
  154. package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +77 -12
  155. package/frontend-vue/layers/dms-core/app/composables/data-types/useDataType.ts +26 -0
  156. package/frontend-vue/layers/dms-core/app/composables/live/livePauseSource.ts +29 -0
  157. package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +150 -9
  158. package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +6 -4
  159. package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +27 -1
  160. package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +256 -119
  161. package/frontend-vue/layers/dms-core/app/composables/translation/useComposedText.ts +21 -8
  162. package/frontend-vue/layers/dms-core/app/composables/translation/useTranslation.ts +25 -5
  163. package/frontend-vue/layers/dms-core/app/composables/useApiError.ts +3 -5
  164. package/frontend-vue/layers/dms-core/app/composables/useFieldErrors.ts +61 -26
  165. package/frontend-vue/layers/dms-core/app/plugins/dev-error-log.ts +16 -0
  166. package/frontend-vue/layers/dms-core/app/types/user.ts +6 -0
  167. package/frontend-vue/layers/dms-core/app/utils/composedText.ts +7 -2
  168. package/frontend-vue/layers/dms-core/app/utils/formatter.ts +134 -35
  169. package/frontend-vue/layers/dms-core/app/utils/url-interpolation.ts +18 -1
  170. package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +136 -2
  171. package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +136 -2
  172. package/frontend-vue/layers/dms-core/shared/types/app-config.ts +0 -2
  173. package/frontend-vue/layers/dms-layout/app/app.config.ts +0 -1
  174. package/frontend-vue/layers/dms-layout/app/assets/css/main.css +11 -1
  175. package/frontend-vue/layers/dms-layout/app/assets/fonts/Geist-Italic-Variable.woff2 +0 -0
  176. package/frontend-vue/layers/dms-layout/app/assets/fonts/Geist-Variable.woff2 +0 -0
  177. package/frontend-vue/layers/dms-layout/app/assets/fonts/GeistMono-Italic-Variable.woff2 +0 -0
  178. package/frontend-vue/layers/dms-layout/app/assets/fonts/GeistMono-Variable.woff2 +0 -0
  179. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +3 -2
  180. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +41 -21
  181. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardPageBody.vue +6 -2
  182. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +4 -19
  183. package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +11 -107
  184. package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +18 -7
  185. package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +4 -0
  186. package/frontend-vue/layers/dms-layout/app/build/components/layout/RowSidePanel.vue +43 -82
  187. package/frontend-vue/layers/dms-layout/app/build/components/layout/SidebarUserMenu.vue +44 -3
  188. package/frontend-vue/layers/dms-layout/app/build/components/layout/UserIdentity.vue +11 -3
  189. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleCatalogTile.vue +132 -35
  190. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleManageMenu.vue +59 -0
  191. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleSourceLabel.vue +25 -0
  192. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreBanners.vue +73 -0
  193. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreOperation.vue +116 -0
  194. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreRequirements.vue +8 -3
  195. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue +85 -207
  196. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTile.vue +10 -3
  197. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTileSkeleton.vue +68 -0
  198. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +24 -8
  199. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxItem.vue +8 -5
  200. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/region/RegionTimeZoneInput.vue +1 -0
  201. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-permissions.ts +19 -0
  202. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/useRoleEditor.ts +2 -1
  203. package/frontend-vue/layers/dms-layout/app/build/composables/layout/useSidePanelWidth.ts +31 -70
  204. package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen.ts +11 -1
  205. package/frontend-vue/layers/dms-layout/app/build/utils/dev-reload-stream.ts +81 -0
  206. package/frontend-vue/layers/dms-layout/app/build/utils/github-repository.ts +2 -2
  207. package/frontend-vue/layers/dms-layout/app/build/utils/module-store-catalog.ts +232 -36
  208. package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +122 -14
  209. package/frontend-vue/layers/dms-layout/app/composables/layout/useLayoutBanners.ts +32 -3
  210. package/frontend-vue/layers/dms-layout/app/composables/layout/useServedHeaderActions.ts +49 -11
  211. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleHistory.ts +3 -2
  212. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +59 -4
  213. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStoreActions.ts +194 -0
  214. package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +23 -3
  215. package/frontend-vue/layers/dms-layout/app/composables/page/usePageLayout.ts +4 -2
  216. package/frontend-vue/layers/dms-layout/app/composables/page/usePageRecordDeletion.ts +162 -0
  217. package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +6 -1
  218. package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +6 -2
  219. package/frontend-vue/layers/dms-layout/app/custom-layouts/SettingsLayout.vue +6 -2
  220. package/frontend-vue/layers/dms-layout/app/custom-pages/modules-store.vue +291 -0
  221. package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +87 -474
  222. package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +3 -0
  223. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +13 -22
  224. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-navigation.client.ts +11 -10
  225. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-personal-pages.client.ts +22 -52
  226. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-quick-actions.client.ts +7 -7
  227. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-session.client.ts +11 -10
  228. package/frontend-vue/layers/dms-layout/app/plugins/dms-dev-reload.client.ts +14 -9
  229. package/frontend-vue/layers/dms-layout/app/theme/actions.ts +5 -4
  230. package/frontend-vue/layers/dms-layout/app/theme/form-controls.ts +9 -4
  231. package/frontend-vue/layers/dms-layout/app/theme/surfaces.ts +22 -6
  232. package/frontend-vue/layers/dms-layout/app/types/module-store.ts +9 -0
  233. package/frontend-vue/layers/dms-layout/app/types/page.ts +15 -1
  234. package/frontend-vue/layers/dms-layout/app/utils/account-menu.ts +11 -0
  235. package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +57 -0
  236. package/frontend-vue/layers/dms-layout/app/utils/pageQuery.ts +26 -0
  237. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +45 -18
  238. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +44 -17
  239. package/frontend-vue/layers/dms-ui/app/build/components/containers/TargetComponentTree.vue +31 -0
  240. package/frontend-vue/layers/dms-ui/app/build/components/containers/constants.ts +25 -0
  241. package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +244 -15
  242. package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +42 -25
  243. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowHeader.vue +153 -0
  244. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowNotFound.vue +44 -0
  245. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/drawerRowHeader.ts +35 -0
  246. package/frontend-vue/layers/dms-ui/app/build/components/form/FormEntries.vue +44 -9
  247. package/frontend-vue/layers/dms-ui/app/build/components/form/FormErrorSummary.vue +11 -4
  248. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldControl.vue +38 -5
  249. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldNotes.vue +29 -5
  250. package/frontend-vue/layers/dms-ui/app/build/components/form/FormLoadError.vue +5 -1
  251. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSectionNav.vue +3 -1
  252. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSections.vue +106 -27
  253. package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +12 -4
  254. package/frontend-vue/layers/dms-ui/app/build/components/table/Empty.vue +4 -2
  255. package/frontend-vue/layers/dms-ui/app/build/components/table/FiltersRow.vue +48 -1
  256. package/frontend-vue/layers/dms-ui/app/build/components/table/Pagination.vue +55 -54
  257. package/frontend-vue/layers/dms-ui/app/build/components/table/RowSelection.vue +2 -1
  258. package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +396 -288
  259. package/frontend-vue/layers/dms-ui/app/build/components/table/Tabs.vue +2 -1
  260. package/frontend-vue/layers/dms-ui/app/build/components/table/Views.vue +2 -1
  261. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardAvatar.vue +59 -0
  262. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardGridSkeleton.vue +19 -2
  263. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardRowActions.vue +38 -0
  264. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardsDisplay.vue +148 -104
  265. package/frontend-vue/layers/dms-ui/app/build/components/table-view/LiveHoldBanner.vue +17 -2
  266. package/frontend-vue/layers/dms-ui/app/build/composables/actions/bulkSelection.ts +43 -9
  267. package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowComponentOptions.ts +76 -0
  268. package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowNavigation.ts +172 -27
  269. package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +270 -46
  270. package/frontend-vue/layers/dms-ui/app/build/composables/confirm/actionError.ts +6 -1
  271. package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +2 -0
  272. package/frontend-vue/layers/dms-ui/app/build/composables/containers/closingLayers.ts +186 -0
  273. package/frontend-vue/layers/dms-ui/app/build/composables/containers/context.ts +14 -0
  274. package/frontend-vue/layers/dms-ui/app/build/composables/containers/drawerSize.ts +229 -0
  275. package/frontend-vue/layers/dms-ui/app/build/composables/containers/useResizableSize.ts +175 -0
  276. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/metricCells.ts +18 -35
  277. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/numberSuffix.ts +44 -0
  278. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +123 -27
  279. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerFieldTypes.ts +2 -1
  280. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationFilter.ts +75 -0
  281. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationRows.ts +92 -0
  282. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationValue.ts +39 -0
  283. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/twoLineCell.ts +3 -3
  284. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/useColumnValueRenderer.ts +6 -2
  285. package/frontend-vue/layers/dms-ui/app/build/composables/form/createFormFields.ts +15 -0
  286. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldSaved.ts +40 -0
  287. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldValueRule.ts +62 -0
  288. package/frontend-vue/layers/dms-ui/app/build/composables/form/formEntryContext.ts +5 -0
  289. package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +42 -7
  290. package/frontend-vue/layers/dms-ui/app/build/composables/form/formLayout.ts +106 -4
  291. package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +11 -5
  292. package/frontend-vue/layers/dms-ui/app/build/composables/form/formSections.ts +32 -0
  293. package/frontend-vue/layers/dms-ui/app/build/composables/form/formValidation.ts +36 -4
  294. package/frontend-vue/layers/dms-ui/app/build/composables/form/readonlyValue.ts +19 -0
  295. package/frontend-vue/layers/dms-ui/app/build/composables/form/registerFormFunctions.ts +27 -0
  296. package/frontend-vue/layers/dms-ui/app/build/composables/form/repeaterTable.ts +78 -0
  297. package/frontend-vue/layers/dms-ui/app/build/composables/form/serverFieldErrors.ts +49 -0
  298. package/frontend-vue/layers/dms-ui/app/build/composables/form/toasterClearance.ts +118 -0
  299. package/frontend-vue/layers/dms-ui/app/build/composables/form/useInstantForm.ts +12 -1
  300. package/frontend-vue/layers/dms-ui/app/build/composables/live/currentPageLive.ts +41 -3
  301. package/frontend-vue/layers/dms-ui/app/build/composables/live/livePoller.ts +10 -2
  302. package/frontend-vue/layers/dms-ui/app/build/composables/live/pageReadClock.ts +28 -0
  303. package/frontend-vue/layers/dms-ui/app/build/composables/live/shortcuts.ts +1 -1
  304. package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageLiveHost.ts +17 -3
  305. package/frontend-vue/layers/dms-ui/app/build/composables/live/useUpdatedLabel.ts +38 -0
  306. package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/pageToolbarContext.ts +75 -0
  307. package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/toolbarControlHeight.ts +29 -0
  308. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodComparison.ts +52 -0
  309. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +21 -0
  310. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodShortLabels.ts +28 -0
  311. package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodDraft.ts +1 -0
  312. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRelationFilterLabels.ts +97 -0
  313. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRowScrollAnchor.ts +106 -0
  314. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +9 -0
  315. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTableColumns.ts +1 -2
  316. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/archivedCount.ts +31 -0
  317. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/filterEditorValue.ts +38 -0
  318. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/formatFilterValue.ts +33 -2
  319. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/scrollEdges.ts +31 -0
  320. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useCardFields.ts +33 -0
  321. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useMobileDisplay.ts +48 -0
  322. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useScrolledPastTop.ts +41 -18
  323. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +3 -2
  324. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLinkedVisit.ts +78 -0
  325. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLiveHold.ts +135 -22
  326. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +56 -25
  327. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +90 -35
  328. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +86 -10
  329. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/bulkActions.ts +14 -4
  330. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/card.ts +127 -2
  331. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/compareValues.ts +13 -0
  332. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/expandedRows.ts +28 -0
  333. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/groupedRows.ts +114 -1
  334. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/linkedArrival.ts +308 -0
  335. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/locateRow.ts +106 -0
  336. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pageArrival.ts +34 -0
  337. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pinnedRows.ts +17 -0
  338. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/quickFilters.ts +41 -33
  339. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/recordLink.ts +24 -8
  340. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableCounts.ts +113 -0
  341. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableQuery.ts +27 -4
  342. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/views.ts +22 -6
  343. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/writeError.ts +21 -0
  344. package/frontend-vue/layers/dms-ui/app/build/utils/addressSuggestions.ts +49 -0
  345. package/frontend-vue/layers/dms-ui/app/build/utils/codeHighlight.ts +67 -29
  346. package/frontend-vue/layers/dms-ui/app/build/utils/eyebrow.ts +12 -0
  347. package/frontend-vue/layers/dms-ui/app/build/utils/fieldOptionsI18n.ts +51 -11
  348. package/frontend-vue/layers/dms-ui/app/build/utils/htmlGrammar.ts +79 -0
  349. package/frontend-vue/layers/dms-ui/app/build/utils/inputWidth.ts +53 -0
  350. package/frontend-vue/layers/dms-ui/app/build/utils/numberAdjustment.ts +6 -3
  351. package/frontend-vue/layers/dms-ui/app/build/utils/relativeDate.ts +19 -5
  352. package/frontend-vue/layers/dms-ui/app/build/utils/scrollWithin.ts +99 -0
  353. package/frontend-vue/layers/dms-ui/app/build/utils/shellParser.ts +1 -1
  354. package/frontend-vue/layers/dms-ui/app/build/utils/streamHighlight.ts +90 -0
  355. package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +36 -14
  356. package/frontend-vue/layers/dms-ui/app/components/PasswordStrength.vue +1 -1
  357. package/frontend-vue/layers/dms-ui/app/components/RecursiveComponent.vue +9 -1
  358. package/frontend-vue/layers/dms-ui/app/components/blocks/LiveToggleBlock.vue +80 -32
  359. package/frontend-vue/layers/dms-ui/app/components/blocks/PageToolbarBlock.vue +137 -0
  360. package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +5 -0
  361. package/frontend-vue/layers/dms-ui/app/components/chart/ApexChartHost.vue +16 -2
  362. package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +24 -5
  363. package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +41 -23
  364. package/frontend-vue/layers/dms-ui/app/components/code-snippet/CodeSnippet.vue +23 -12
  365. package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +1 -1
  366. package/frontend-vue/layers/dms-ui/app/components/container-footer/ContainerFooter.vue +34 -0
  367. package/frontend-vue/layers/dms-ui/app/components/field-row/FieldRow.vue +25 -2
  368. package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +2 -2
  369. package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +119 -36
  370. package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +53 -22
  371. package/frontend-vue/layers/dms-ui/app/components/form/components/DatePicker.vue +20 -1
  372. package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +13 -1
  373. package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +8 -3
  374. package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +53 -5
  375. package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +29 -5
  376. package/frontend-vue/layers/dms-ui/app/components/form/components/InputNumber.vue +45 -8
  377. package/frontend-vue/layers/dms-ui/app/components/form/components/InputPercentage.vue +28 -5
  378. package/frontend-vue/layers/dms-ui/app/components/form/components/InputSecret.vue +5 -0
  379. package/frontend-vue/layers/dms-ui/app/components/form/components/InputText.vue +19 -2
  380. package/frontend-vue/layers/dms-ui/app/components/form/components/InputTime.vue +2 -1
  381. package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +226 -65
  382. package/frontend-vue/layers/dms-ui/app/components/form/components/Repeater.vue +80 -30
  383. package/frontend-vue/layers/dms-ui/app/components/form/components/RepeaterTable.vue +210 -0
  384. package/frontend-vue/layers/dms-ui/app/components/form/components/Select.vue +4 -2
  385. package/frontend-vue/layers/dms-ui/app/components/grid/GridRow.vue +11 -2
  386. package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +79 -70
  387. package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +117 -21
  388. package/frontend-vue/layers/dms-ui/app/components/meter/Meter.vue +2 -1
  389. package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +113 -34
  390. package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +14 -2
  391. package/frontend-vue/layers/dms-ui/app/components/section-header/Eyebrow.vue +6 -1
  392. package/frontend-vue/layers/dms-ui/app/components/segmented/Segmented.vue +13 -0
  393. package/frontend-vue/layers/dms-ui/app/components/stat-group/StatGroup.vue +52 -3
  394. package/frontend-vue/layers/dms-ui/app/components/tab/Tab.vue +2 -1
  395. package/frontend-vue/layers/dms-ui/app/components/table-view/CascaderPath.vue +74 -25
  396. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +48 -44
  397. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanDisplay.vue +4 -1
  398. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +426 -97
  399. package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +33 -5
  400. package/frontend-vue/layers/dms-ui/app/composables/chart/apexCategoryLabels.ts +68 -0
  401. package/frontend-vue/layers/dms-ui/app/composables/chart/apexDatetimeBuckets.ts +306 -0
  402. package/frontend-vue/layers/dms-ui/app/composables/chart/formatValue.ts +4 -3
  403. package/frontend-vue/layers/dms-ui/app/composables/chart/heatmapColors.ts +93 -0
  404. package/frontend-vue/layers/dms-ui/app/composables/chart/localizeSeries.ts +62 -0
  405. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.ts +116 -8
  406. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.types.ts +7 -0
  407. package/frontend-vue/layers/dms-ui/app/composables/containers/types.ts +84 -1
  408. package/frontend-vue/layers/dms-ui/app/composables/containers/useDrawer.ts +69 -8
  409. package/frontend-vue/layers/dms-ui/app/composables/form/types/field-saved.ts +11 -0
  410. package/frontend-vue/layers/dms-ui/app/composables/form/types/field.ts +16 -0
  411. package/frontend-vue/layers/dms-ui/app/composables/form/types/functions.ts +1 -0
  412. package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +189 -28
  413. package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +29 -12
  414. package/frontend-vue/layers/dms-ui/app/composables/page-toolbar/usePageToolbarSize.ts +30 -0
  415. package/frontend-vue/layers/dms-ui/app/composables/table-view/kanban.ts +4 -2
  416. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +22 -3
  417. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +4 -0
  418. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/display.ts +24 -1
  419. package/frontend-vue/layers/dms-ui/app/plugins/closing-layers.client.ts +10 -0
  420. package/frontend-vue/layers/dms-ui/app/plugins/live-pause-source.ts +15 -0
  421. package/frontend-vue/layers/dms-ui/app/plugins/register.ts +2 -0
  422. package/frontend-vue/layers/dms-ui/app/types/input-number.ts +3 -2
  423. package/frontend-vue/layers/dms-ui/app/types/page-toolbar.ts +7 -0
  424. package/frontend-vue/layers/dms-ui/app/types/row-action.ts +0 -2
  425. package/frontend-vue/layers/dms-ui/app/types/row-navigation.ts +17 -0
  426. package/frontend-vue/layers/dms-ui/app/types/table/query.ts +2 -1
  427. package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +3 -1
  428. package/frontend-vue/layers/dms-ui/app/utils/cascader.ts +94 -3
  429. package/frontend-vue/layers/dms-ui/app/utils/componentTree.ts +24 -0
  430. package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +20 -5
  431. package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +20 -5
  432. package/frontend-vue/package.json +6 -2
  433. package/package.json +11 -10
  434. package/skills/dms/SKILL.md +1 -1
  435. package/skills/dms-auth/SKILL.md +5 -3
  436. package/skills/dms-best-practices/REFERENCE.md +2 -0
  437. package/skills/dms-best-practices/SKILL.md +4 -0
  438. package/skills/dms-module/SKILL.md +3 -2
  439. package/skills/dms-pages/REFERENCE.md +129 -16
  440. package/skills/dms-pages/SKILL.md +1 -1
  441. package/skills/dms-project/DEV-SERVERS.md +23 -1
  442. package/skills/dms-project/REFERENCE.md +1 -1
  443. package/skills/dms-project/SKILL.md +4 -1
  444. package/skills/dms-project/scripts/dms-dev.sh +205 -17
  445. package/dist/hooks/tenant-instances.d.ts +0 -1
  446. package/dist/hooks/tenant-instances.js +0 -13
  447. package/dist/hooks/tenant-instances.js.map +0 -1
  448. package/dist/routes/startup-gate.d.ts +0 -7
  449. package/dist/routes/startup-gate.js +0 -39
  450. package/dist/routes/startup-gate.js.map +0 -1
  451. package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +0 -42
@@ -12,9 +12,11 @@ import { readFetchNotFound } from "../../build/utils/fetchNotFound";
12
12
  import DmsBlockInlineStatus from "../../build/components/blocks/BlockInlineStatus.vue";
13
13
  import {
14
14
  ABSENT_VALUE_PARTS,
15
+ formatValue,
15
16
  formatValueParts,
16
17
  } from "../../composables/chart/formatValue";
17
18
  import { resolveSparklineAccent } from "../../composables/chart/resolveSparklineAccent";
19
+ import { usePeriodComparisonDisplay } from "../../build/composables/period/periodComparison";
18
20
  import type {
19
21
  KpiCardResponse,
20
22
  ValueFormat,
@@ -34,16 +36,21 @@ interface Props extends DefaultComponentProps {
34
36
  fetchUrl?: string;
35
37
  fetchUrlMethod?: string;
36
38
  periodScope?: string;
39
+ /** Topics whose events make the card read `fetchUrl` again. */
40
+ realtimeTopic?: string | string[];
37
41
  valueFormat?: ValueFormat;
38
42
  currencyCode?: string;
39
43
  valuePrecision?: ValuePrecision;
40
44
  showDelta?: boolean;
45
+ /** "vs 1 234" next to the change, when the data carries `previousValue`. */
46
+ showPreviousValue?: boolean;
41
47
  showSparkline?: boolean;
42
48
  sparklineAccent?: string;
43
49
  invert?: boolean;
44
50
  compareLabel?: string;
45
51
  staticValue?: number;
46
52
  staticDelta?: number;
53
+ staticPreviousValue?: number;
47
54
  staticSparkline?: number[];
48
55
  }
49
56
 
@@ -54,6 +61,7 @@ const POSITIVE_SPARKLINE_ACCENT = "primary";
54
61
  const props = withDefaults(defineProps<Props>(), {
55
62
  variant: "default",
56
63
  showDelta: true,
64
+ showPreviousValue: true,
57
65
  showSparkline: false,
58
66
  valueFormat: "compact",
59
67
  sparklineAccent: "auto",
@@ -74,6 +82,7 @@ const staticData = computed<KpiCardResponse | null>(() => {
74
82
  return {
75
83
  value: props.staticValue,
76
84
  delta: props.staticDelta,
85
+ previousValue: props.staticPreviousValue,
77
86
  sparkline: props.staticSparkline,
78
87
  };
79
88
  });
@@ -83,12 +92,21 @@ const { data, isLoading, error, refresh } = useChartFetch<KpiCardResponse>({
83
92
  fetchUrlMethod: props.fetchUrlMethod,
84
93
  routeParams: () => props.routeParams,
85
94
  periodScope: props.periodScope,
95
+ realtimeTopic: props.realtimeTopic,
86
96
  staticData: () => staticData.value,
87
97
  watchSource: () => watchKey.value,
88
98
  });
89
99
 
100
+ // With no comparison selected on the scope, a change the data still carries
101
+ // (a static one, a route ignoring `comparison`) is not shown either.
102
+ const comparison = usePeriodComparisonDisplay(props.periodScope);
90
103
  const value = computed(() => data.value?.value);
91
- const delta = computed(() => data.value?.delta ?? null);
104
+ const delta = computed(() =>
105
+ comparison.isOff.value ? null : (data.value?.delta ?? null),
106
+ );
107
+ const previousValue = computed(() =>
108
+ comparison.isOff.value ? undefined : data.value?.previousValue,
109
+ );
92
110
  const sparkline = computed(() => data.value?.sparkline ?? []);
93
111
 
94
112
  const formattedParts = computed(() =>
@@ -103,6 +121,19 @@ const formattedParts = computed(() =>
103
121
  ),
104
122
  );
105
123
 
124
+ // Formatted as the value is, unit and currency included: "vs €42,900".
125
+ const previousText = computed(() => {
126
+ if (!props.showPreviousValue || previousValue.value === undefined) return "";
127
+ const formatted = formatValue(
128
+ previousValue.value,
129
+ props.valueFormat,
130
+ locale.value,
131
+ props.currencyCode,
132
+ props.valuePrecision,
133
+ );
134
+ return t("dms.chart.versus_value", { value: formatted });
135
+ });
136
+
106
137
  const isFirstLoad = computed(() => isLoading.value && data.value === null);
107
138
  // v2 error state: a failed first load shows a message and a retry button in
108
139
  // place of the value; a failed refetch keeps the last value.
@@ -116,13 +147,37 @@ const notFound = computed(() =>
116
147
  // A refetch keeps the stale value on screen, faded, instead of a skeleton.
117
148
  const isRefreshing = computed(() => isLoading.value && data.value !== null);
118
149
  const showTrend = computed(() => props.showDelta && delta.value !== null);
150
+ // The trend's line is held while the change is still to come, so toggling a
151
+ // comparison never moves the footnote, and dropped with no comparison at all.
152
+ const showTrendLine = computed(
153
+ () => props.showDelta && !comparison.isOff.value,
154
+ );
119
155
  const hasSparkline = computed(
120
156
  () => props.showSparkline && sparkline.value.length > 0,
121
157
  );
158
+ // The comparison label names the period the change is measured against, so
159
+ // it shows with the change only: an explicit `compareLabel` wins, then the
160
+ // description, then the label of the comparison the scope selects. With no
161
+ // change (no comparison selected, or none in the data) only the description.
122
162
  const footnote = computed(() => {
123
- const text = props.compareLabel || props.description;
124
- return text ? processI18n(text) : "";
163
+ const isComparing = delta.value !== null;
164
+ const text = (isComparing && props.compareLabel) || props.description;
165
+ if (text) return processI18n(text);
166
+ return isComparing ? (comparison.label.value ?? "") : "";
125
167
  });
168
+ // Known before any data: the skeleton draws it, and the loaded card holds it
169
+ // empty while the change is still to come, so toggling a comparison moves
170
+ // nothing. With no comparison selected, the trend's line goes and only a
171
+ // description keeps this one.
172
+ const hasFootnoteLine = computed(
173
+ () =>
174
+ !!props.description ||
175
+ (!comparison.isOff.value &&
176
+ !!(props.compareLabel || comparison.label.value)),
177
+ );
178
+ const holdsFootnoteLine = computed(
179
+ () => !footnote.value && hasFootnoteLine.value,
180
+ );
126
181
 
127
182
  const resolvedSparklineAccent = computed(() =>
128
183
  resolveSparklineAccent(
@@ -160,23 +215,42 @@ const isStat = computed(() => props.variant === "stat");
160
215
  {{ formattedParts.unit }}
161
216
  </span>
162
217
  </template>
163
- <!-- With no change to show (no comparison period), the trend's line
164
- keeps its place: toggling a comparison never resizes the card. -->
165
- <template v-if="showDelta || hasSparkline" #aside>
218
+ <!-- While the change is still to come, the trend's line keeps its
219
+ place: toggling a comparison never resizes the card. With no
220
+ comparison selected it goes. So does the sparkline's box keep its
221
+ place, from the first load on: the aside is taller than the value
222
+ column with it, and the card would grow. -->
223
+ <template v-if="showTrendLine || previousText || showSparkline" #aside>
166
224
  <div class="ml-auto grid shrink-0 justify-items-end gap-1">
167
- <DmsTrendBadge
168
- v-if="showTrend"
169
- :delta="delta"
170
- :invert="invert"
171
- variant="text"
172
- />
173
- <span v-else-if="showDelta" class="h-4" aria-hidden="true" />
174
- <div v-if="hasSparkline" class="h-6 w-16">
225
+ <div
226
+ v-if="showTrendLine || previousText"
227
+ class="flex h-4 items-center gap-1.5"
228
+ >
229
+ <DmsTrendBadge
230
+ v-if="showTrend"
231
+ :delta="delta"
232
+ :invert="invert"
233
+ variant="text"
234
+ />
235
+ <span
236
+ v-if="previousText"
237
+ class="text-dimmed text-xs whitespace-nowrap tabular-nums"
238
+ >
239
+ {{ previousText }}
240
+ </span>
241
+ </div>
242
+ <div v-if="showSparkline" class="h-6 w-16">
175
243
  <DmsSparkline
244
+ v-if="hasSparkline"
176
245
  :values="sparkline"
177
246
  :accent="resolvedSparklineAccent"
178
247
  :aria-label="processI18n(title)"
179
248
  />
249
+ <USkeleton
250
+ v-else-if="isFirstLoad"
251
+ :aria-label="t('dms.a11y.loading')"
252
+ class="size-full"
253
+ />
180
254
  </div>
181
255
  </div>
182
256
  </template>
@@ -236,14 +310,14 @@ const isStat = computed(() => props.variant === "stat");
236
310
  <!-- The trend (a 24px line) and the footnote (16px) as the loaded card
237
311
  lays them out; the footnote is a prop, so it is known up front. -->
238
312
  <div
239
- v-if="isFirstLoad && (showDelta || footnote || showSparkline)"
313
+ v-if="isFirstLoad && (showTrendLine || hasFootnoteLine || showSparkline)"
240
314
  class="mt-auto flex items-end gap-2"
241
315
  >
242
316
  <div class="grid">
243
- <div v-if="showDelta" class="flex h-6 items-center">
317
+ <div v-if="showTrendLine" class="flex h-6 items-center">
244
318
  <USkeleton :aria-label="t('dms.a11y.loading')" class="h-3 w-[58px]" />
245
319
  </div>
246
- <div v-if="footnote" class="flex h-4 items-center">
320
+ <div v-if="hasFootnoteLine" class="flex h-4 items-center">
247
321
  <USkeleton :aria-label="t('dms.a11y.loading')" class="h-2.5 w-28" />
248
322
  </div>
249
323
  </div>
@@ -254,20 +328,37 @@ const isStat = computed(() => props.variant === "stat");
254
328
  />
255
329
  </div>
256
330
  <!-- The trend's line holds its place, as in the skeleton, while there is
257
- no change to show (no comparison period): no "0%" stand-in, and
258
- toggling a comparison never moves the footnote. -->
331
+ no change to show yet: no "0%" stand-in, and toggling a comparison
332
+ never moves the footnote. With no comparison selected on the scope,
333
+ the line goes, and the comparison label with it. -->
259
334
  <div
260
- v-else-if="!hasError && (showDelta || footnote || hasSparkline)"
335
+ v-else-if="
336
+ !hasError &&
337
+ (showTrendLine ||
338
+ previousText ||
339
+ footnote ||
340
+ holdsFootnoteLine ||
341
+ hasSparkline)
342
+ "
261
343
  class="mt-auto flex items-end gap-2"
262
344
  >
263
345
  <div class="min-w-0">
264
- <div v-if="showDelta" class="flex h-6 items-center">
346
+ <div
347
+ v-if="showTrendLine || previousText"
348
+ class="flex h-6 min-w-0 items-center gap-2"
349
+ >
265
350
  <DmsTrendBadge
266
351
  v-if="showTrend"
267
352
  :delta="delta"
268
353
  :invert="invert"
269
354
  variant="text"
270
355
  />
356
+ <span
357
+ v-if="previousText"
358
+ class="text-dimmed truncate text-xs tabular-nums"
359
+ >
360
+ {{ previousText }}
361
+ </span>
271
362
  </div>
272
363
  <p
273
364
  v-if="footnote"
@@ -275,6 +366,11 @@ const isStat = computed(() => props.variant === "stat");
275
366
  >
276
367
  {{ footnote }}
277
368
  </p>
369
+ <span
370
+ v-else-if="holdsFootnoteLine"
371
+ class="block h-4"
372
+ aria-hidden="true"
373
+ />
278
374
  </div>
279
375
  <div
280
376
  v-if="hasSparkline"
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { readableNumberFormat } from "#dms-core/app/utils/formatter";
2
3
  import { computed } from "vue";
3
4
  import { tv } from "tailwind-variants";
4
5
 
@@ -176,7 +177,7 @@ const legendEntries = computed(() =>
176
177
  );
177
178
 
178
179
  const formatNumber = (amount: number): string =>
179
- new Intl.NumberFormat(locale.value).format(amount);
180
+ readableNumberFormat(locale.value).format(amount);
180
181
 
181
182
  const valueText = computed(() => {
182
183
  if (props.valueLabel) return props.valueLabel;
@@ -25,6 +25,7 @@ import {
25
25
  type CalendarRange,
26
26
  type CalendarRangeAnchor,
27
27
  calendarToRange,
28
+ formatPeriodDays,
28
29
  formatPeriodRange,
29
30
  formatRelativeInstant,
30
31
  limitCalendarRange,
@@ -35,6 +36,9 @@ import PeriodRangeCalendar from "../../build/components/period/PeriodRangeCalend
35
36
  import { useUserRegionalPreferences } from "#dms-core/app/composables/user/useUserRegionalPreferences";
36
37
  import { usePageLive } from "../../composables/live/usePageLive";
37
38
  import { useLivePeriodSpan } from "../../build/composables/live/useLivePeriodSpan";
39
+ import { usePageToolbar } from "../../build/composables/page-toolbar/pageToolbarContext";
40
+ import { toolbarControlHeightClass } from "../../build/composables/page-toolbar/toolbarControlHeight";
41
+ import { PRESET_SHORT_LABELS } from "../../build/composables/period/periodShortLabels";
38
42
 
39
43
  interface Props extends DefaultComponentProps {
40
44
  id: string;
@@ -64,9 +68,12 @@ const RELATIVE_RANGE_LABEL: NonNullable<Props["rangeLabel"]> = "relative";
64
68
  // Two months side by side from this width; one below (design @container 760px).
65
69
  const TWO_MONTHS_QUERY = "(min-width: 768px)";
66
70
 
71
+ type PeriodSelectorSize = NonNullable<Props["size"]>;
72
+ const DEFAULT_SIZE: PeriodSelectorSize = "sm";
73
+
67
74
  const props = withDefaults(defineProps<Props>(), {
68
75
  align: "right",
69
- size: "sm",
76
+ size: undefined,
70
77
  showRangeLabel: true,
71
78
  rangeLabel: "absolute",
72
79
  variant: "default",
@@ -78,16 +85,14 @@ const ALIGN_CLASSES: Record<NonNullable<Props["align"]>, string> = {
78
85
  right: "justify-end",
79
86
  };
80
87
 
81
- /** Short mono codes, as trails in the preset list and segmented labels. */
82
- const PRESET_SHORT_LABELS: Partial<Record<PeriodPreset, string>> = {
83
- "last-hour": "1H",
84
- "last-24h": "24H",
85
- today: "1D",
86
- "last-7-days": "7D",
87
- "last-30-days": "30D",
88
- "last-90-days": "90D",
89
- "this-quarter": "QTD",
90
- ytd: "YTD",
88
+ // The window beside the controls, at each size of a toolbar holding the
89
+ // selector; outside one it keeps its size whatever the controls'.
90
+ const RANGE_LABEL_SIZE_CLASSES: Record<PeriodSelectorSize, string> = {
91
+ xs: "text-[11px]",
92
+ sm: "text-[11.5px]",
93
+ md: "text-[12.5px]",
94
+ lg: "text-[13px]",
95
+ xl: "text-sm",
91
96
  };
92
97
 
93
98
  /** Rolling windows first, then calendar periods (design preset groups). */
@@ -113,6 +118,19 @@ useWatch(props.watchActions || [], props.componentId);
113
118
 
114
119
  const { t, locale } = useI18n();
115
120
 
121
+ // In a toolbar, the selector takes its size, and its window scales with it.
122
+ const toolbar = usePageToolbar();
123
+ const size = computed<PeriodSelectorSize>(
124
+ () => props.size ?? toolbar?.size.value ?? DEFAULT_SIZE,
125
+ );
126
+ const rangeLabelSizeClass = computed(
127
+ () => RANGE_LABEL_SIZE_CLASSES[toolbar ? size.value : DEFAULT_SIZE],
128
+ );
129
+ // In a toolbar, its buttons stand as tall as the segments and the live pill.
130
+ const controlHeightClass = computed(() =>
131
+ toolbar ? toolbarControlHeightClass(size.value) : "",
132
+ );
133
+
116
134
  // A paused live page keeps its period where it was: a relative preset
117
135
  // sliding every minute would refetch every block reading it.
118
136
  const { isPaused: isPagePaused } = usePageLive();
@@ -171,6 +189,12 @@ function presetLabel(preset: PeriodPreset): string {
171
189
  );
172
190
  }
173
191
 
192
+ /** The preset's short code in the user's language; custom has none. */
193
+ function presetShortLabel(preset: PeriodPreset): string | undefined {
194
+ const fallback = PRESET_SHORT_LABELS[preset];
195
+ return fallback && t(`dms.period.short_presets.${preset}`, fallback);
196
+ }
197
+
174
198
  function comparisonLabel(comparison: PeriodComparison): string {
175
199
  return (
176
200
  props.comparisonLabels?.[comparison] ??
@@ -201,7 +225,7 @@ interface PresetEntry {
201
225
 
202
226
  function presetEntry(preset: PeriodPreset): PresetEntry {
203
227
  const label = presetLabel(preset);
204
- const short = PRESET_SHORT_LABELS[preset];
228
+ const short = presetShortLabel(preset);
205
229
  return { preset, label, short: short === label ? undefined : short };
206
230
  }
207
231
 
@@ -414,11 +438,15 @@ function comparisonKeyClass(active: boolean): string {
414
438
  return active ? COMPARISON_KEY_ACTIVE : COMPARISON_KEY_NONE;
415
439
  }
416
440
 
417
- function compareRangeText(compare: PeriodRange, main: PeriodRange): string {
418
- // In the reader's calendar, on the server as in the browser.
441
+ // The year of a comparison range is noise when it is the main one's, in the
442
+ // reader's calendar, on the server as in the browser.
443
+ function isSameYear(compare: PeriodRange, main: PeriodRange): boolean {
419
444
  const clock = readerClock();
420
- const sameYear = clock.wall(compare.to).year === clock.wall(main.to).year;
421
- return formatPeriodRange(compare, locale.value, !sameYear);
445
+ return clock.wall(compare.to).year === clock.wall(main.to).year;
446
+ }
447
+
448
+ function compareRangeText(compare: PeriodRange, main: PeriodRange): string {
449
+ return formatPeriodRange(compare, locale.value, !isSameYear(compare, main));
422
450
  }
423
451
 
424
452
  const compareRangeLabel = computed(() => {
@@ -426,40 +454,82 @@ const compareRangeLabel = computed(() => {
426
454
  return compareRange && range ? compareRangeText(compareRange, range) : null;
427
455
  });
428
456
 
457
+ // A menu names the compared window itself ("vs 16 – 17 Oct"), so the range
458
+ // label beside it keeps the current window only: said twice, the comparison
459
+ // wrapped the toolbar. The option's name moves to its tooltip and its menu.
460
+ const rangeLabelCompare = computed(() =>
461
+ isComparisonMenu.value ? null : compareRangeLabel.value,
462
+ );
463
+
464
+ const menuCompareDays = computed(() => {
465
+ const { compareRange, range } = period.state.value;
466
+ if (!compareRange || !range) return null;
467
+ return formatPeriodDays(
468
+ compareRange,
469
+ locale.value,
470
+ !isSameYear(compareRange, range),
471
+ );
472
+ });
473
+
474
+ const comparisonMenuLabel = computed(() => {
475
+ const days = menuCompareDays.value;
476
+ if (!hasComparison.value || !days) {
477
+ return comparisonLabel(period.comparison.value);
478
+ }
479
+ return `${t("dms.period.popover.vs", "vs")} ${days}`;
480
+ });
481
+
482
+ const comparisonMenuAria = computed(() => {
483
+ const name = `${t("dms.period.comparison_aria", "Comparison")}: ${comparisonLabel(period.comparison.value)}`;
484
+ const days = hasComparison.value ? menuCompareDays.value : null;
485
+ return days ? `${name}, ${days}` : name;
486
+ });
487
+
429
488
  // ── Segmented variant ──────────────────────────────────────────────────
430
489
  function segmentLabel(preset: PeriodPreset): string {
431
490
  return (
432
491
  props.presetLabels?.[preset] ??
433
- PRESET_SHORT_LABELS[preset] ??
492
+ presetShortLabel(preset) ??
434
493
  presetLabel(preset)
435
494
  );
436
495
  }
437
496
 
438
497
  interface SegmentItem {
439
498
  label: string;
499
+ ariaLabel: string;
440
500
  value: string;
441
501
  icon?: string;
442
502
  }
443
503
 
444
- function customSegmentLabel(): string {
504
+ function customRangeText(): string | null {
445
505
  const { range } = period.state.value;
446
506
  return isCustomActive.value && range
447
507
  ? formatPeriodRange(range, locale.value, false)
448
- : presetLabel(CUSTOM_PRESET);
508
+ : null;
509
+ }
510
+
511
+ // In a toolbar the custom segment is its calendar icon alone, like the short
512
+ // codes beside it: the window beside them writes the range once chosen.
513
+ // Named in full either way.
514
+ function customSegment(): SegmentItem {
515
+ const range = customRangeText();
516
+ const name = presetLabel(CUSTOM_PRESET);
517
+ const visible = toolbar ? "" : (range ?? name);
518
+ return {
519
+ label: visible,
520
+ ariaLabel: range ? `${name}: ${range}` : name,
521
+ value: CUSTOM_PRESET,
522
+ icon: CALENDAR_ICON,
523
+ };
449
524
  }
450
525
 
451
526
  const segmentedItems = computed<SegmentItem[]>(() => {
452
527
  const items: SegmentItem[] = listedPresets.value.map((preset) => ({
453
528
  label: segmentLabel(preset),
529
+ ariaLabel: presetLabel(preset),
454
530
  value: preset,
455
531
  }));
456
- if (isCustomEnabled.value) {
457
- items.push({
458
- label: customSegmentLabel(),
459
- value: CUSTOM_PRESET,
460
- icon: CALENDAR_ICON,
461
- });
462
- }
532
+ if (isCustomEnabled.value) items.push(customSegment());
463
533
  return items;
464
534
  });
465
535
 
@@ -509,7 +579,11 @@ function onSegmentedClick(event: MouseEvent) {
509
579
  :icon="CALENDAR_ICON"
510
580
  :trailing-icon="isOpen ? CARET_UP_ICON : CARET_DOWN_ICON"
511
581
  :aria-label="$t('dms.period.preset_aria', 'Preset')"
512
- :class="[triggerOrder, isOpen && OPEN_TRIGGER_CLASS]"
582
+ :class="[
583
+ triggerOrder,
584
+ controlHeightClass,
585
+ isOpen && OPEN_TRIGGER_CLASS,
586
+ ]"
513
587
  :ui="{ trailingIcon: 'text-dimmed -me-0.5' }"
514
588
  >
515
589
  <span
@@ -729,7 +803,10 @@ function onSegmentedClick(event: MouseEvent) {
729
803
  :aria-pressed="hasComparison"
730
804
  :disabled="isComparisonDisabled"
731
805
  class="order-2"
732
- :class="hasComparison ? 'text-toned' : 'text-dimmed'"
806
+ :class="[
807
+ controlHeightClass,
808
+ hasComparison ? 'text-toned' : 'text-dimmed',
809
+ ]"
733
810
  @click="toggleComparison"
734
811
  >
735
812
  <template #leading>
@@ -754,8 +831,10 @@ function onSegmentedClick(event: MouseEvent) {
754
831
  :size="size"
755
832
  :disabled="isComparisonDisabled"
756
833
  :trailing-icon="CARET_DOWN_ICON"
757
- :aria-label="$t('dms.period.comparison_aria', 'Comparison')"
834
+ :aria-label="comparisonMenuAria"
835
+ :title="comparisonLabel(period.comparison.value)"
758
836
  class="text-muted order-2"
837
+ :class="controlHeightClass"
759
838
  :ui="{ trailingIcon: 'text-dimmed' }"
760
839
  >
761
840
  <template #leading>
@@ -765,7 +844,7 @@ function onSegmentedClick(event: MouseEvent) {
765
844
  aria-hidden="true"
766
845
  />
767
846
  </template>
768
- {{ comparisonLabel(period.comparison.value) }}
847
+ {{ comparisonMenuLabel }}
769
848
  </UButton>
770
849
  </UDropdownMenu>
771
850
 
@@ -775,16 +854,16 @@ function onSegmentedClick(event: MouseEvent) {
775
854
  <span
776
855
  v-if="showRangeLabel && rangeLabelText"
777
856
  data-allow-mismatch="text"
778
- class="text-dimmed hidden font-mono text-[11.5px] font-medium whitespace-nowrap tabular-nums sm:inline"
779
- :class="rangeLabelOrder"
857
+ class="dms-period-selector__range text-dimmed hidden font-mono font-medium whitespace-nowrap tabular-nums sm:inline"
858
+ :class="[rangeLabelOrder, rangeLabelSizeClass]"
780
859
  >
781
860
  <b class="text-toned font-[550]">{{ rangeLabelText }}</b>
782
- <template v-if="compareRangeLabel">
861
+ <template v-if="rangeLabelCompare">
783
862
  ·
784
863
  <span class="text-(--dms-chart-2)">
785
864
  {{ $t("dms.period.popover.vs", "vs") }}
786
865
  </span>
787
- {{ compareRangeLabel }}
866
+ {{ rangeLabelCompare }}
788
867
  </template>
789
868
  </span>
790
869
  </div>
@@ -1,11 +1,12 @@
1
1
  <script setup lang="ts">
2
- import { computed } from "vue";
2
+ import { computed, useTemplateRef } from "vue";
3
3
  import { tv } from "tailwind-variants";
4
4
  import {
5
5
  type FormKind,
6
6
  saveBarState,
7
7
  } from "../../build/composables/form/formFooter";
8
8
  import { trackFloatingSaveBar } from "../../build/composables/form/floatingSaveBars";
9
+ import { trackToasterClearance } from "../../build/composables/form/toasterClearance";
9
10
  import DmsUnsavedStatus from "../../build/components/form/UnsavedStatus.vue";
10
11
 
11
12
  /**
@@ -64,6 +65,7 @@ const emit = defineEmits<{
64
65
 
65
66
  const SECONDARY_LABELS: Record<FormKind, string> = {
66
67
  record: "dms.save_bar.discard",
68
+ create: "dms.save_bar.discard",
67
69
  action: "dms.button.reset",
68
70
  };
69
71
 
@@ -136,6 +138,15 @@ const buttons = computed(() => BUTTONS[props.variant]);
136
138
  trackFloatingSaveBar(
137
139
  () => props.variant === "floating" && !state.value.isHidden,
138
140
  );
141
+ // Toasts stack in the bottom corner the floating bar and a drawer's footer
142
+ // band reach: they rise above the bar rather than cover its buttons. The
143
+ // band's surface is its root, the floating card its inner bar.
144
+ const rootElement = useTemplateRef<HTMLElement>("root");
145
+ const barElement = useTemplateRef<HTMLElement>("bar");
146
+ trackToasterClearance(
147
+ () => (props.variant === "floating" ? barElement.value : rootElement.value),
148
+ () => !state.value.isHidden,
149
+ );
139
150
 
140
151
  // Empty while there is nothing to save: the hidden bar keeps its one line.
141
152
  const statusText = computed(() => {
@@ -154,11 +165,12 @@ const saveText = computed(() => {
154
165
 
155
166
  <template>
156
167
  <div
168
+ ref="root"
157
169
  :class="ui.root()"
158
170
  :inert="state.isHidden || undefined"
159
171
  :aria-hidden="state.isHidden || undefined"
160
172
  >
161
- <div :class="ui.bar()">
173
+ <div ref="bar" :class="ui.bar()">
162
174
  <DmsUnsavedStatus
163
175
  v-if="state.showsStatus"
164
176
  :text="statusText"
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { computed } from "vue";
3
3
  import { tv } from "tailwind-variants";
4
+ import { keepHyphenatedWords } from "../../build/utils/eyebrow";
4
5
 
5
6
  export type EyebrowTone = "dimmed" | "muted" | "primary" | "error";
6
7
  export type EyebrowSize = "xs" | "sm";
@@ -56,6 +57,10 @@ const theme = tv({
56
57
  },
57
58
  });
58
59
 
60
+ const text = computed(() =>
61
+ props.label === undefined ? undefined : keepHyphenatedWords(props.label),
62
+ );
63
+
59
64
  const classes = computed(() =>
60
65
  theme({ tone: props.tone, size: props.size, truncate: props.truncate }),
61
66
  );
@@ -63,6 +68,6 @@ const classes = computed(() =>
63
68
 
64
69
  <template>
65
70
  <component :is="props.as" :class="classes">
66
- <slot>{{ props.label }}</slot>
71
+ <slot>{{ text }}</slot>
67
72
  </component>
68
73
  </template>
@@ -7,7 +7,13 @@ import { computed, ref } from "vue";
7
7
  // Single-select, v-model'd. Implements the radiogroup keyboard pattern
8
8
  // (roving tabindex + arrow/Home/End, skipping disabled segments).
9
9
  interface SegItem {
10
+ /** The visible text; empty for an icon-only segment. */
10
11
  label: string;
12
+ /**
13
+ * The segment's accessible name when the visible label does not say it
14
+ * all: a short code ("24H" for "Last 24 hours") or an icon alone.
15
+ */
16
+ ariaLabel?: string;
11
17
  value: string | number;
12
18
  icon?: string;
13
19
  /** Small mono counter after the label. */
@@ -149,6 +155,12 @@ function onKeydown(event: KeyboardEvent, index: number) {
149
155
  }
150
156
 
151
157
  // A track wider than its container (a phone, a narrow column) scrolls
158
+ // Every segment is named: its `ariaLabel`, else its label, else its value
159
+ // (an icon-only segment given neither still reads as something).
160
+ function itemAriaLabel(item: SegItem): string {
161
+ return item.ariaLabel || item.label || String(item.value);
162
+ }
163
+
152
164
  // sideways inside its pill, or wraps with `overflow="wrap"`; the focus ring
153
165
  // sits inside the segment so the scroll box never clips it. (No root-level
154
166
  // template comment: it would break the class fallthrough.)
@@ -184,6 +196,7 @@ function itemClass(item: SegItem, index: number): (string | false)[] {
184
196
  type="button"
185
197
  role="radio"
186
198
  :aria-checked="model === item.value"
199
+ :aria-label="itemAriaLabel(item)"
187
200
  :disabled="isDisabled(index)"
188
201
  :tabindex="index === focusIndex ? 0 : -1"
189
202
  class="focus-visible:outline-primary inline-flex shrink-0 items-center gap-1.5 rounded-[6px] whitespace-nowrap transition-colors focus-visible:outline-2 focus-visible:-outline-offset-2"