@antelopejs/dms 0.9.8 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (420) hide show
  1. package/CHANGELOG.md +102 -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 -0
  21. package/dist/config.js +5 -3
  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 +22 -89
  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 +1 -2
  41. package/dist/implementations/dms/manifest.js +2 -0
  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 +28 -48
  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 +24 -13
  129. package/dist/store/listing.js.map +1 -1
  130. package/dist/store/operations.js +4 -0
  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/validation/auth.schema.d.ts +2 -2
  139. package/dist/validation/member-invite.schema.d.ts +2 -10
  140. package/dist/validation/member-invite.schema.js +2 -15
  141. package/dist/validation/member-invite.schema.js.map +1 -1
  142. package/frontend-vue/dms.frontend.ts +24 -5
  143. package/frontend-vue/dms.server.ts +17 -0
  144. package/frontend-vue/layers/dms-auth/server/accounts.ts +3 -4
  145. package/frontend-vue/layers/dms-core/app/build/composables/renderNow.ts +34 -0
  146. package/frontend-vue/layers/dms-core/app/build/composables/user/tablePreferences.ts +5 -1
  147. package/frontend-vue/layers/dms-core/app/build/utils/crossTab.ts +199 -0
  148. package/frontend-vue/layers/dms-core/app/build/utils/sharedRealtimeConnection.ts +410 -0
  149. package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +77 -12
  150. package/frontend-vue/layers/dms-core/app/composables/data-types/useDataType.ts +26 -0
  151. package/frontend-vue/layers/dms-core/app/composables/hydration/isHydratingComponent.ts +29 -0
  152. package/frontend-vue/layers/dms-core/app/composables/live/livePauseSource.ts +29 -0
  153. package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +150 -9
  154. package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +6 -4
  155. package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +27 -1
  156. package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +256 -119
  157. package/frontend-vue/layers/dms-core/app/composables/translation/useComposedText.ts +21 -8
  158. package/frontend-vue/layers/dms-core/app/composables/translation/useTranslation.ts +22 -5
  159. package/frontend-vue/layers/dms-core/app/plugins/dev-error-log.ts +16 -0
  160. package/frontend-vue/layers/dms-core/app/types/user.ts +6 -0
  161. package/frontend-vue/layers/dms-core/app/utils/composedText.ts +7 -2
  162. package/frontend-vue/layers/dms-core/app/utils/formatter.ts +166 -35
  163. package/frontend-vue/layers/dms-core/app/utils/url-interpolation.ts +18 -1
  164. package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +134 -2
  165. package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +134 -2
  166. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +3 -2
  167. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +41 -21
  168. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardPageBody.vue +9 -0
  169. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +4 -19
  170. package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +11 -3
  171. package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +18 -7
  172. package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionBar.vue +8 -0
  173. package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +14 -0
  174. package/frontend-vue/layers/dms-layout/app/build/components/layout/RowSidePanel.vue +43 -82
  175. package/frontend-vue/layers/dms-layout/app/build/components/layout/SidebarUserMenu.vue +44 -3
  176. package/frontend-vue/layers/dms-layout/app/build/components/layout/UserIdentity.vue +11 -3
  177. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleCatalogTile.vue +132 -35
  178. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleManageMenu.vue +59 -0
  179. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleSourceLabel.vue +25 -0
  180. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreBanners.vue +73 -0
  181. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreOperation.vue +116 -0
  182. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreRequirements.vue +8 -3
  183. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue +85 -207
  184. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTile.vue +10 -3
  185. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTileSkeleton.vue +68 -0
  186. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +24 -8
  187. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxItem.vue +8 -5
  188. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/region/RegionTimeZoneInput.vue +1 -0
  189. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-permissions.ts +19 -0
  190. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/useRoleEditor.ts +2 -1
  191. package/frontend-vue/layers/dms-layout/app/build/composables/layout/useSidePanelWidth.ts +31 -70
  192. package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen.ts +11 -1
  193. package/frontend-vue/layers/dms-layout/app/build/utils/dev-reload-stream.ts +81 -0
  194. package/frontend-vue/layers/dms-layout/app/build/utils/module-store-catalog.ts +232 -36
  195. package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +122 -14
  196. package/frontend-vue/layers/dms-layout/app/composables/layout/useLayoutBanners.ts +32 -3
  197. package/frontend-vue/layers/dms-layout/app/composables/layout/usePageRecord.ts +50 -0
  198. package/frontend-vue/layers/dms-layout/app/composables/layout/useServedHeaderActions.ts +25 -7
  199. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleHistory.ts +3 -2
  200. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +51 -4
  201. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStoreActions.ts +194 -0
  202. package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +23 -3
  203. package/frontend-vue/layers/dms-layout/app/composables/page/usePageLayout.ts +4 -2
  204. package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +6 -1
  205. package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +9 -0
  206. package/frontend-vue/layers/dms-layout/app/custom-layouts/SettingsLayout.vue +9 -0
  207. package/frontend-vue/layers/dms-layout/app/custom-pages/modules-store.vue +291 -0
  208. package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +87 -474
  209. package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +3 -0
  210. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +6 -22
  211. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-navigation.client.ts +2 -1
  212. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-personal-pages.client.ts +14 -44
  213. package/frontend-vue/layers/dms-layout/app/plugins/dms-dev-reload.client.ts +14 -9
  214. package/frontend-vue/layers/dms-layout/app/theme/form-controls.ts +9 -4
  215. package/frontend-vue/layers/dms-layout/app/theme/surfaces.ts +22 -6
  216. package/frontend-vue/layers/dms-layout/app/types/module-store.ts +9 -0
  217. package/frontend-vue/layers/dms-layout/app/types/page.ts +15 -1
  218. package/frontend-vue/layers/dms-layout/app/utils/account-menu.ts +11 -0
  219. package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +57 -0
  220. package/frontend-vue/layers/dms-layout/app/utils/pageQuery.ts +26 -0
  221. package/frontend-vue/layers/dms-layout/app/utils/pageRecord.ts +71 -0
  222. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +39 -14
  223. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +38 -13
  224. package/frontend-vue/layers/dms-ui/app/build/components/containers/TargetComponentTree.vue +31 -0
  225. package/frontend-vue/layers/dms-ui/app/build/components/containers/constants.ts +25 -0
  226. package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +244 -15
  227. package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +42 -25
  228. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowHeader.vue +153 -0
  229. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowNotFound.vue +44 -0
  230. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/drawerRowHeader.ts +35 -0
  231. package/frontend-vue/layers/dms-ui/app/build/components/form/FormEntries.vue +44 -9
  232. package/frontend-vue/layers/dms-ui/app/build/components/form/FormErrorSummary.vue +11 -4
  233. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldControl.vue +38 -5
  234. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldNotes.vue +29 -5
  235. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSectionNav.vue +3 -1
  236. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSections.vue +106 -27
  237. package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +12 -4
  238. package/frontend-vue/layers/dms-ui/app/build/components/table/Empty.vue +4 -2
  239. package/frontend-vue/layers/dms-ui/app/build/components/table/FiltersRow.vue +38 -1
  240. package/frontend-vue/layers/dms-ui/app/build/components/table/Pagination.vue +55 -54
  241. package/frontend-vue/layers/dms-ui/app/build/components/table/RowSelection.vue +2 -1
  242. package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +396 -288
  243. package/frontend-vue/layers/dms-ui/app/build/components/table/Tabs.vue +2 -1
  244. package/frontend-vue/layers/dms-ui/app/build/components/table/Views.vue +2 -1
  245. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardAvatar.vue +59 -0
  246. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardGridSkeleton.vue +19 -2
  247. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardRowActions.vue +38 -0
  248. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardsDisplay.vue +148 -104
  249. package/frontend-vue/layers/dms-ui/app/build/components/table-view/LiveHoldBanner.vue +17 -2
  250. package/frontend-vue/layers/dms-ui/app/build/composables/actions/bulkSelection.ts +43 -9
  251. package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowComponentOptions.ts +76 -0
  252. package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowNavigation.ts +172 -27
  253. package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +270 -46
  254. package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +2 -0
  255. package/frontend-vue/layers/dms-ui/app/build/composables/containers/closingLayers.ts +186 -0
  256. package/frontend-vue/layers/dms-ui/app/build/composables/containers/context.ts +14 -0
  257. package/frontend-vue/layers/dms-ui/app/build/composables/containers/drawerSize.ts +229 -0
  258. package/frontend-vue/layers/dms-ui/app/build/composables/containers/useResizableSize.ts +175 -0
  259. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/metricCells.ts +18 -35
  260. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/numberSuffix.ts +47 -0
  261. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +99 -24
  262. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerFieldTypes.ts +2 -1
  263. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationFilter.ts +75 -0
  264. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationRows.ts +71 -0
  265. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationValue.ts +39 -0
  266. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/twoLineCell.ts +3 -3
  267. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/useColumnValueRenderer.ts +6 -2
  268. package/frontend-vue/layers/dms-ui/app/build/composables/form/createFormFields.ts +15 -0
  269. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldSaved.ts +40 -0
  270. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldValueRule.ts +62 -0
  271. package/frontend-vue/layers/dms-ui/app/build/composables/form/formEntryContext.ts +5 -0
  272. package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +42 -7
  273. package/frontend-vue/layers/dms-ui/app/build/composables/form/formLayout.ts +106 -4
  274. package/frontend-vue/layers/dms-ui/app/build/composables/form/formSections.ts +32 -0
  275. package/frontend-vue/layers/dms-ui/app/build/composables/form/readonlyValue.ts +19 -0
  276. package/frontend-vue/layers/dms-ui/app/build/composables/form/registerFormFunctions.ts +27 -0
  277. package/frontend-vue/layers/dms-ui/app/build/composables/form/repeaterTable.ts +65 -0
  278. package/frontend-vue/layers/dms-ui/app/build/composables/form/serverFieldErrors.ts +49 -0
  279. package/frontend-vue/layers/dms-ui/app/build/composables/form/toasterClearance.ts +118 -0
  280. package/frontend-vue/layers/dms-ui/app/build/composables/form/useInstantForm.ts +12 -1
  281. package/frontend-vue/layers/dms-ui/app/build/composables/live/currentPageLive.ts +41 -3
  282. package/frontend-vue/layers/dms-ui/app/build/composables/live/livePoller.ts +10 -2
  283. package/frontend-vue/layers/dms-ui/app/build/composables/live/pageReadClock.ts +28 -0
  284. package/frontend-vue/layers/dms-ui/app/build/composables/live/shortcuts.ts +1 -1
  285. package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageLiveHost.ts +17 -3
  286. package/frontend-vue/layers/dms-ui/app/build/composables/live/useUpdatedLabel.ts +38 -0
  287. package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/pageToolbarContext.ts +75 -0
  288. package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/toolbarControlHeight.ts +29 -0
  289. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodComparison.ts +52 -0
  290. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +21 -0
  291. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodShortLabels.ts +28 -0
  292. package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodDraft.ts +1 -0
  293. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRelationFilterLabels.ts +73 -0
  294. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRowScrollAnchor.ts +106 -0
  295. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +9 -0
  296. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTableColumns.ts +1 -2
  297. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/archivedCount.ts +31 -0
  298. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/filterEditorValue.ts +38 -0
  299. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/formatFilterValue.ts +33 -2
  300. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/scrollEdges.ts +31 -0
  301. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useCardFields.ts +33 -0
  302. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useMobileDisplay.ts +48 -0
  303. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useScrolledPastTop.ts +41 -18
  304. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +3 -2
  305. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLinkedVisit.ts +78 -0
  306. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLiveHold.ts +135 -22
  307. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +56 -25
  308. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +77 -32
  309. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +86 -10
  310. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/bulkActions.ts +14 -4
  311. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/card.ts +127 -2
  312. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/compareValues.ts +13 -0
  313. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/expandedRows.ts +28 -0
  314. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/groupedRows.ts +114 -1
  315. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/linkedArrival.ts +308 -0
  316. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/locateRow.ts +106 -0
  317. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pageArrival.ts +34 -0
  318. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pinnedRows.ts +17 -0
  319. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/quickFilters.ts +41 -33
  320. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/recordLink.ts +24 -8
  321. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableCounts.ts +113 -0
  322. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableQuery.ts +27 -4
  323. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/views.ts +22 -6
  324. package/frontend-vue/layers/dms-ui/app/build/utils/addressSuggestions.ts +49 -0
  325. package/frontend-vue/layers/dms-ui/app/build/utils/eyebrow.ts +12 -0
  326. package/frontend-vue/layers/dms-ui/app/build/utils/fieldOptionsI18n.ts +51 -11
  327. package/frontend-vue/layers/dms-ui/app/build/utils/inputWidth.ts +53 -0
  328. package/frontend-vue/layers/dms-ui/app/build/utils/numberAdjustment.ts +6 -3
  329. package/frontend-vue/layers/dms-ui/app/build/utils/relativeDate.ts +19 -5
  330. package/frontend-vue/layers/dms-ui/app/build/utils/scrollWithin.ts +99 -0
  331. package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +36 -14
  332. package/frontend-vue/layers/dms-ui/app/components/PasswordStrength.vue +1 -1
  333. package/frontend-vue/layers/dms-ui/app/components/RecursiveComponent.vue +9 -1
  334. package/frontend-vue/layers/dms-ui/app/components/blocks/LiveToggleBlock.vue +80 -32
  335. package/frontend-vue/layers/dms-ui/app/components/blocks/PageToolbarBlock.vue +137 -0
  336. package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +5 -0
  337. package/frontend-vue/layers/dms-ui/app/components/chart/ApexChartHost.vue +16 -2
  338. package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +24 -5
  339. package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +41 -23
  340. package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +1 -1
  341. package/frontend-vue/layers/dms-ui/app/components/container-footer/ContainerFooter.vue +34 -0
  342. package/frontend-vue/layers/dms-ui/app/components/field-row/FieldRow.vue +25 -2
  343. package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +2 -2
  344. package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +118 -36
  345. package/frontend-vue/layers/dms-ui/app/components/form/components/DatePicker.vue +20 -1
  346. package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +13 -1
  347. package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +8 -3
  348. package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +53 -5
  349. package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +29 -5
  350. package/frontend-vue/layers/dms-ui/app/components/form/components/InputNumber.vue +45 -8
  351. package/frontend-vue/layers/dms-ui/app/components/form/components/InputPercentage.vue +28 -5
  352. package/frontend-vue/layers/dms-ui/app/components/form/components/InputSecret.vue +5 -0
  353. package/frontend-vue/layers/dms-ui/app/components/form/components/InputText.vue +19 -2
  354. package/frontend-vue/layers/dms-ui/app/components/form/components/InputTime.vue +2 -1
  355. package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +205 -65
  356. package/frontend-vue/layers/dms-ui/app/components/form/components/Repeater.vue +80 -30
  357. package/frontend-vue/layers/dms-ui/app/components/form/components/RepeaterTable.vue +203 -0
  358. package/frontend-vue/layers/dms-ui/app/components/form/components/Select.vue +4 -2
  359. package/frontend-vue/layers/dms-ui/app/components/grid/GridRow.vue +11 -2
  360. package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +79 -70
  361. package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +117 -21
  362. package/frontend-vue/layers/dms-ui/app/components/meter/Meter.vue +2 -1
  363. package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +113 -34
  364. package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +14 -2
  365. package/frontend-vue/layers/dms-ui/app/components/section-header/Eyebrow.vue +6 -1
  366. package/frontend-vue/layers/dms-ui/app/components/segmented/Segmented.vue +13 -0
  367. package/frontend-vue/layers/dms-ui/app/components/stat-group/StatGroup.vue +52 -3
  368. package/frontend-vue/layers/dms-ui/app/components/tab/Tab.vue +2 -1
  369. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +45 -35
  370. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanDisplay.vue +4 -1
  371. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +410 -96
  372. package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +33 -5
  373. package/frontend-vue/layers/dms-ui/app/composables/chart/apexCategoryLabels.ts +68 -0
  374. package/frontend-vue/layers/dms-ui/app/composables/chart/apexDatetimeBuckets.ts +306 -0
  375. package/frontend-vue/layers/dms-ui/app/composables/chart/formatValue.ts +4 -3
  376. package/frontend-vue/layers/dms-ui/app/composables/chart/heatmapColors.ts +93 -0
  377. package/frontend-vue/layers/dms-ui/app/composables/chart/localizeSeries.ts +62 -0
  378. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.ts +116 -8
  379. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.types.ts +7 -0
  380. package/frontend-vue/layers/dms-ui/app/composables/containers/types.ts +84 -1
  381. package/frontend-vue/layers/dms-ui/app/composables/containers/useDrawer.ts +69 -8
  382. package/frontend-vue/layers/dms-ui/app/composables/form/types/field-saved.ts +11 -0
  383. package/frontend-vue/layers/dms-ui/app/composables/form/types/field.ts +4 -0
  384. package/frontend-vue/layers/dms-ui/app/composables/form/types/functions.ts +1 -0
  385. package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +142 -21
  386. package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +11 -4
  387. package/frontend-vue/layers/dms-ui/app/composables/page-toolbar/usePageToolbarSize.ts +30 -0
  388. package/frontend-vue/layers/dms-ui/app/composables/table-view/kanban.ts +4 -2
  389. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +22 -3
  390. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +4 -0
  391. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/display.ts +24 -1
  392. package/frontend-vue/layers/dms-ui/app/plugins/closing-layers.client.ts +10 -0
  393. package/frontend-vue/layers/dms-ui/app/plugins/live-pause-source.ts +15 -0
  394. package/frontend-vue/layers/dms-ui/app/plugins/register.ts +2 -0
  395. package/frontend-vue/layers/dms-ui/app/types/input-number.ts +3 -2
  396. package/frontend-vue/layers/dms-ui/app/types/page-toolbar.ts +7 -0
  397. package/frontend-vue/layers/dms-ui/app/types/row-action.ts +0 -2
  398. package/frontend-vue/layers/dms-ui/app/types/row-navigation.ts +17 -0
  399. package/frontend-vue/layers/dms-ui/app/types/table/query.ts +2 -1
  400. package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +3 -1
  401. package/frontend-vue/layers/dms-ui/app/utils/componentTree.ts +24 -0
  402. package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +16 -4
  403. package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +16 -4
  404. package/frontend-vue/package.json +2 -2
  405. package/package.json +7 -6
  406. package/skills/dms/SKILL.md +1 -1
  407. package/skills/dms-auth/SKILL.md +5 -3
  408. package/skills/dms-best-practices/REFERENCE.md +2 -0
  409. package/skills/dms-best-practices/SKILL.md +4 -0
  410. package/skills/dms-module/SKILL.md +3 -2
  411. package/skills/dms-pages/REFERENCE.md +125 -16
  412. package/skills/dms-pages/SKILL.md +1 -1
  413. package/skills/dms-project/DEV-SERVERS.md +23 -1
  414. package/skills/dms-project/REFERENCE.md +1 -1
  415. package/skills/dms-project/SKILL.md +4 -1
  416. package/skills/dms-project/scripts/dms-dev.sh +205 -17
  417. package/dist/routes/startup-gate.d.ts +0 -7
  418. package/dist/routes/startup-gate.js +0 -39
  419. package/dist/routes/startup-gate.js.map +0 -1
  420. package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +0 -42
@@ -0,0 +1,118 @@
1
+ import { onBeforeUnmount, onMounted, watch } from "vue";
2
+
3
+ /**
4
+ * The CSS variable the toaster's bottom offset adds (theme/surfaces.ts): how
5
+ * far above the viewport's bottom edge a bar anchored there reaches.
6
+ */
7
+ export const TOASTER_CLEARANCE_VAR = "--dms-toaster-clearance";
8
+
9
+ /**
10
+ * How close to the viewport's bottom edge a bar has to end to count as
11
+ * anchored there: the floating save bar floats 16px off it. A bar that ends
12
+ * higher (a short form's bar, a card's footer band mid-page) leaves the
13
+ * toaster's corner free.
14
+ */
15
+ const BOTTOM_EDGE_REACH = 48;
16
+
17
+ /** The part of a bar's bounding box the clearance reads. */
18
+ export interface BarBox {
19
+ top: number;
20
+ bottom: number;
21
+ height: number;
22
+ }
23
+
24
+ /**
25
+ * The room the toaster leaves above the bars anchored to the bottom of a
26
+ * viewport `viewportHeight` tall: the distance from that edge to the highest
27
+ * such bar's top, 0 when none is.
28
+ */
29
+ export function toasterClearance(
30
+ boxes: BarBox[],
31
+ viewportHeight: number,
32
+ ): number {
33
+ let clearance = 0;
34
+ for (const box of boxes) {
35
+ const isAnchored =
36
+ box.height > 0 &&
37
+ box.top < viewportHeight &&
38
+ box.bottom >= viewportHeight - BOTTOM_EDGE_REACH;
39
+ if (isAnchored)
40
+ clearance = Math.max(clearance, Math.ceil(viewportHeight - box.top));
41
+ }
42
+ return clearance;
43
+ }
44
+
45
+ type BarElement = () => HTMLElement | null | undefined;
46
+
47
+ // Only mounted, shown bars count, so a server render never adds to it.
48
+ const shownBars = new Map<symbol, BarElement>();
49
+ let frame = 0;
50
+
51
+ function applyClearance(): void {
52
+ frame = 0;
53
+ const boxes = [...shownBars.values()]
54
+ .map((element) => element()?.getBoundingClientRect())
55
+ .filter((box): box is DOMRect => !!box);
56
+ const clearance = toasterClearance(boxes, window.innerHeight);
57
+ const { style } = document.documentElement;
58
+ if (clearance > 0) style.setProperty(TOASTER_CLEARANCE_VAR, `${clearance}px`);
59
+ else style.removeProperty(TOASTER_CLEARANCE_VAR);
60
+ }
61
+
62
+ // A bar moves with the scroll (a sticky bar reaches the edge, a form's end
63
+ // leaves it), the viewport, its own show transition and a drawer's opening
64
+ // animation: one measure a frame.
65
+ function scheduleClearance(): void {
66
+ if (frame) return;
67
+ frame = requestAnimationFrame(applyClearance);
68
+ }
69
+
70
+ const LISTENED_EVENTS = [
71
+ "scroll",
72
+ "resize",
73
+ "transitionend",
74
+ "animationend",
75
+ ] as const;
76
+
77
+ function listen(isListening: boolean): void {
78
+ const method = isListening ? "addEventListener" : "removeEventListener";
79
+ for (const event of LISTENED_EVENTS)
80
+ window[method](event, scheduleClearance, { capture: true, passive: true });
81
+ }
82
+
83
+ function setBarShown(id: symbol, element: BarElement, isShown: boolean): void {
84
+ const wasEmpty = shownBars.size === 0;
85
+ if (isShown) shownBars.set(id, element);
86
+ else shownBars.delete(id);
87
+ if (wasEmpty !== (shownBars.size === 0)) listen(shownBars.size > 0);
88
+ scheduleClearance();
89
+ }
90
+
91
+ /**
92
+ * Keeps the toaster clear of the calling bar while `isShown` holds and the
93
+ * bar sits on the bottom edge of the viewport: the toast stack rises above
94
+ * it instead of covering its buttons, and drops back once it hides.
95
+ */
96
+ export function trackToasterClearance(
97
+ element: BarElement,
98
+ isShown: () => boolean,
99
+ ): void {
100
+ const id = Symbol("toaster-clearance");
101
+ let isMounted = false;
102
+ const stop = watch(
103
+ isShown,
104
+ (shown) => {
105
+ if (isMounted) setBarShown(id, element, shown);
106
+ },
107
+ { flush: "post" },
108
+ );
109
+ onMounted(() => {
110
+ isMounted = true;
111
+ setBarShown(id, element, isShown());
112
+ });
113
+ onBeforeUnmount(() => {
114
+ stop();
115
+ isMounted = false;
116
+ setBarShown(id, element, false);
117
+ });
118
+ }
@@ -54,6 +54,11 @@ export interface InstantFormOptions {
54
54
  export interface InstantForm extends InstantSave<FormValues> {
55
55
  /** The form loaded or was reset: its values are the confirmed ones. */
56
56
  start: () => void;
57
+ /**
58
+ * A field the server stored on its own (a rotated secret): its value is the
59
+ * confirmed one, not a change to save again.
60
+ */
61
+ confirmField: (id: string, value: unknown) => void;
57
62
  }
58
63
 
59
64
  const clone = (value: unknown): unknown =>
@@ -92,6 +97,12 @@ export function useInstantForm(options: InstantFormOptions): InstantForm {
92
97
  instant.confirm(values);
93
98
  }
94
99
 
100
+ function confirmField(id: string, value: unknown): void {
101
+ const confirmed = clone(value);
102
+ shown.set(id, confirmed);
103
+ instant.confirm({ [id]: confirmed });
104
+ }
105
+
95
106
  async function saveField(field: FormField, value: unknown): Promise<void> {
96
107
  const check = (checks.get(field.id) ?? 0) + 1;
97
108
  checks.set(field.id, check);
@@ -121,5 +132,5 @@ export function useInstantForm(options: InstantFormOptions): InstantForm {
121
132
  { deep: true },
122
133
  );
123
134
 
124
- return { ...instant, start };
135
+ return { ...instant, start, confirmField };
125
136
  }
@@ -1,14 +1,19 @@
1
1
  import {
2
2
  computed,
3
+ getCurrentInstance,
3
4
  getCurrentScope,
5
+ inject,
4
6
  onScopeDispose,
7
+ provide,
5
8
  shallowRef,
6
9
  type ComputedRef,
10
+ type InjectionKey,
7
11
  } from "vue";
8
12
  import type {
9
13
  LiveSubscription,
10
14
  PageLiveController,
11
15
  } from "./pageLiveController";
16
+ import { markPageRead } from "./pageReadClock";
12
17
 
13
18
  // Client-only state: the page renderer sets it in the browser, where one app
14
19
  // shows one page at a time. The server renders no event and no refetch, so it
@@ -36,6 +41,34 @@ export function setCurrentPageLive(controller: PageLiveController | null) {
36
41
  currentController.value = controller;
37
42
  }
38
43
 
44
+ // Server only: whether the page being rendered declares `live`. The browser
45
+ // installs the page's controller before its blocks set up, so they hydrate
46
+ // as on a live page; the server has no controller, and tells them instead.
47
+ const SERVER_PAGE_LIVE_KEY: InjectionKey<() => boolean> = Symbol(
48
+ "dms-server-page-live",
49
+ );
50
+
51
+ /**
52
+ * Tells the blocks of the page the server renders whether it declares `live`.
53
+ * The page renderer calls it.
54
+ *
55
+ * @internal
56
+ */
57
+ export function provideServerPageLive(isLive: () => boolean): void {
58
+ provide(SERVER_PAGE_LIVE_KEY, isLive);
59
+ }
60
+
61
+ /**
62
+ * While server-rendering, whether the page declares `live`; `false` in the
63
+ * browser, where `currentPageLive()` answers. Call it in a component setup.
64
+ *
65
+ * @internal
66
+ */
67
+ export function isServerPageLive(): boolean {
68
+ if (typeof window !== "undefined" || !getCurrentInstance()) return false;
69
+ return inject(SERVER_PAGE_LIVE_KEY, null)?.() ?? false;
70
+ }
71
+
39
72
  /** @internal */
40
73
  export interface LiveGate {
41
74
  /** An event behind the block arrived: refetch, now or later, or count it. */
@@ -62,7 +95,7 @@ interface BoundSubscription {
62
95
 
63
96
  /**
64
97
  * The gate a block's refetch goes through. On a page without `live` it calls
65
- * `refresh` at once, as before. Bound lazily, on the first event, to the
98
+ * `refresh` at once, as before, and records the read (`pageReadAt`). Bound lazily, on the first event, to the
66
99
  * controller of the page on screen: a block of the page layout sets up before
67
100
  * the page installs its controller.
68
101
  *
@@ -89,6 +122,11 @@ export function useLiveGate(refresh: () => void): LiveGate {
89
122
 
90
123
  if (getCurrentScope()) onScopeDispose(unbind);
91
124
 
125
+ const refreshUngated = () => {
126
+ markPageRead();
127
+ refresh();
128
+ };
129
+
92
130
  const current = () =>
93
131
  bound.value?.controller === currentController.value
94
132
  ? (bound.value?.subscription ?? null)
@@ -98,14 +136,14 @@ export function useLiveGate(refresh: () => void): LiveGate {
98
136
  trigger: (event) => {
99
137
  const subscription = resolve();
100
138
  if (subscription) subscription.trigger(event);
101
- else refresh();
139
+ else refreshUngated();
102
140
  },
103
141
  missed: computed(() => current()?.missed.value ?? 0),
104
142
  lastReadAt: computed(() => current()?.lastReadAt.value ?? 0),
105
143
  catchUp: () => {
106
144
  const subscription = resolve();
107
145
  if (subscription) subscription.catchUp();
108
- else refresh();
146
+ else refreshUngated();
109
147
  },
110
148
  acknowledge: () => resolve()?.acknowledge(),
111
149
  setHeld: (isHeld) => resolve()?.setHeld(isHeld),
@@ -9,9 +9,15 @@ import { readonly, ref, type Ref } from "vue";
9
9
  */
10
10
  export class LivePoller {
11
11
  private readonly active = ref(false);
12
+ private readonly hasPolled = ref(false);
12
13
  private timer: ReturnType<typeof setTimeout> | null = null;
13
- /** The poller is running: the stream is down and the page reads by itself. */
14
- readonly isPolling: Readonly<Ref<boolean>> = readonly(this.active);
14
+ /**
15
+ * The page reads by itself: the stream stayed down for a whole interval and
16
+ * the first poll ran. Not while a page's stream is still connecting (every
17
+ * full load starts "down" until it opens), which would flash the polling
18
+ * hint for a frame.
19
+ */
20
+ readonly isPolling: Readonly<Ref<boolean>> = readonly(this.hasPolled);
15
21
 
16
22
  constructor(
17
23
  private readonly intervalMs: () => number,
@@ -31,10 +37,12 @@ export class LivePoller {
31
37
  if (this.timer) clearTimeout(this.timer);
32
38
  this.timer = null;
33
39
  this.active.value = false;
40
+ this.hasPolled.value = false;
34
41
  }
35
42
 
36
43
  private schedule(): void {
37
44
  this.timer = setTimeout(() => {
45
+ this.hasPolled.value = true;
38
46
  this.poll();
39
47
  if (this.active.value) this.schedule();
40
48
  }, this.intervalMs());
@@ -0,0 +1,28 @@
1
+ import { type Ref, readonly, shallowRef } from "vue";
2
+
3
+ // Client-only, like the live controller: the browser shows one page at a
4
+ // time, and a module-level value on the server would be shared between
5
+ // requests.
6
+ const readAt = shallowRef<number | null>(null);
7
+
8
+ /**
9
+ * When the page on screen last read its data (ms since the epoch), on a page
10
+ * with or without `live`: set when the page is shown, each time its blocks
11
+ * read again outside a live gate, and once a `refreshPageBlocks()` is done.
12
+ * `null` before the first page (and on the server). A live page's own clock
13
+ * is its controller's `lastUpdatedAt`.
14
+ *
15
+ * @internal
16
+ */
17
+ export const pageReadAt: Readonly<Ref<number | null>> = readonly(readAt);
18
+
19
+ /**
20
+ * Records that the page on screen read its data now. Does nothing on the
21
+ * server.
22
+ *
23
+ * @internal
24
+ */
25
+ export function markPageRead(): void {
26
+ if (typeof window === "undefined") return;
27
+ readAt.value = Date.now();
28
+ }
@@ -1,4 +1,4 @@
1
- /** Switches the page between Direct and Pause, while a LiveToggle is on it. */
1
+ /** Switches the page between Live and Pause, while a LiveToggle is on it. */
2
2
  export const LIVE_TOGGLE_L_METADATA: ShortcutMetadata = {
3
3
  key: ["l"],
4
4
  descriptionKey: "$dms.shortcuts.livetoggle.l.description",
@@ -1,10 +1,11 @@
1
1
  import { onMounted, onScopeDispose, watch } from "vue";
2
2
  import type { PageLiveOptions } from "../../../types/live";
3
- import { setCurrentPageLive } from "./currentPageLive";
3
+ import { provideServerPageLive, setCurrentPageLive } from "./currentPageLive";
4
4
  import {
5
5
  createPageLiveController,
6
6
  type PageLiveController,
7
7
  } from "./pageLiveController";
8
+ import { markPageRead } from "./pageReadClock";
8
9
 
9
10
  /** The URL query key opening a live page paused: `?live=0`. */
10
11
  const LIVE_URL_KEY = "live";
@@ -69,7 +70,10 @@ export function bindPageLiveEnvironment(
69
70
 
70
71
  /**
71
72
  * Gives the page on screen its live controller while it declares `live`, and
72
- * a new one when the renderer moves to another page. Browser only.
73
+ * a new one when the renderer moves to another page; on every page, starts
74
+ * its read clock (`pageReadAt`) as it is shown. A server render has no
75
+ * controller: it only tells the page's blocks whether the page is live, so
76
+ * they draw what the hydrating browser draws.
73
77
  *
74
78
  * @internal
75
79
  */
@@ -77,7 +81,13 @@ export function usePageLiveHost(
77
81
  source: () => PageLiveSource,
78
82
  environment: PageLiveEnvironment = {},
79
83
  ): void {
80
- if (typeof window === "undefined") return;
84
+ if (typeof window === "undefined") {
85
+ provideServerPageLive(() => {
86
+ const { pageId, live } = source();
87
+ return Boolean(pageId && live);
88
+ });
89
+ return;
90
+ }
81
91
  let controller: PageLiveController | null = null;
82
92
  let unbind: (() => void) | null = null;
83
93
  // The server renders every page live: the remembered pause applies once
@@ -116,5 +126,9 @@ export function usePageLiveHost(
116
126
  { immediate: true },
117
127
  );
118
128
 
129
+ // The page's blocks read as it is shown: the server's render, or the
130
+ // browser's on a client navigation.
131
+ watch(() => source().pageId, markPageRead, { immediate: true });
132
+
119
133
  onScopeDispose(() => install(null));
120
134
  }
@@ -0,0 +1,38 @@
1
+ import {
2
+ type ComputedRef,
3
+ computed,
4
+ onBeforeUnmount,
5
+ onMounted,
6
+ ref,
7
+ } from "vue";
8
+ import { formatElapsed } from "./formatElapsed";
9
+
10
+ const TICK_MS = 1000;
11
+
12
+ /**
13
+ * "Updated 12 s ago" from the instant `at` gives (ms since the epoch), counted
14
+ * every second once mounted; empty while `at` is `null`. The clock is the
15
+ * browser's: render the label once mounted only.
16
+ *
17
+ * @internal
18
+ */
19
+ export function useUpdatedLabel(
20
+ at: () => number | null,
21
+ shouldTick = true,
22
+ ): ComputedRef<string> {
23
+ const { t } = useI18n();
24
+ const now = ref(Date.now());
25
+ let ticker: ReturnType<typeof setInterval> | undefined;
26
+ onMounted(() => {
27
+ now.value = Date.now();
28
+ if (shouldTick)
29
+ ticker = setInterval(() => (now.value = Date.now()), TICK_MS);
30
+ });
31
+ onBeforeUnmount(() => clearInterval(ticker));
32
+ return computed(() => {
33
+ const instant = at();
34
+ if (instant === null) return "";
35
+ const { unit, count } = formatElapsed(now.value - instant);
36
+ return t(`dms.live.updated_${unit}`, { count }, count);
37
+ });
38
+ }
@@ -0,0 +1,75 @@
1
+ import {
2
+ computed,
3
+ type ComputedRef,
4
+ type InjectionKey,
5
+ inject,
6
+ onBeforeUnmount,
7
+ onMounted,
8
+ provide,
9
+ ref,
10
+ } from "vue";
11
+ import type { PageToolbarSize } from "../../../types/page-toolbar";
12
+
13
+ export type { PageToolbarSize };
14
+
15
+ /**
16
+ * What a `PageToolbar` gives the blocks it holds.
17
+ *
18
+ * @internal
19
+ */
20
+ export interface PageToolbarContext {
21
+ /** The size its `LiveToggle` and `PeriodSelector` take unless they set one. */
22
+ size: ComputedRef<PageToolbarSize>;
23
+ /** A `LiveToggle` is mounted in it: the toolbar leaves the updated label to it. */
24
+ hasLiveToggle: ComputedRef<boolean>;
25
+ /** Counts a mounted `LiveToggle`; returns the uncount. */
26
+ registerLiveToggle(): () => void;
27
+ }
28
+
29
+ const PAGE_TOOLBAR_KEY: InjectionKey<PageToolbarContext> =
30
+ Symbol("dms-page-toolbar");
31
+
32
+ /**
33
+ * Provides the context of a `PageToolbar` to the blocks it holds.
34
+ *
35
+ * @internal
36
+ */
37
+ export function providePageToolbar(
38
+ size: ComputedRef<PageToolbarSize>,
39
+ ): PageToolbarContext {
40
+ const liveToggles = ref(0);
41
+ const context: PageToolbarContext = {
42
+ size,
43
+ hasLiveToggle: computed(() => liveToggles.value > 0),
44
+ registerLiveToggle() {
45
+ liveToggles.value += 1;
46
+ return () => {
47
+ liveToggles.value -= 1;
48
+ };
49
+ },
50
+ };
51
+ provide(PAGE_TOOLBAR_KEY, context);
52
+ return context;
53
+ }
54
+
55
+ /**
56
+ * The `PageToolbar` holding the calling block, `null` outside one.
57
+ *
58
+ * @internal
59
+ */
60
+ export function usePageToolbar(): PageToolbarContext | null {
61
+ return inject(PAGE_TOOLBAR_KEY, null);
62
+ }
63
+
64
+ /**
65
+ * Tells the toolbar holding the calling `LiveToggle`, if any, that it is
66
+ * mounted there, for as long as it is.
67
+ *
68
+ * @internal
69
+ */
70
+ export function useToolbarLiveToggle(toolbar: PageToolbarContext | null): void {
71
+ if (!toolbar) return;
72
+ let unregister: (() => void) | undefined;
73
+ onMounted(() => (unregister = toolbar.registerLiveToggle()));
74
+ onBeforeUnmount(() => unregister?.());
75
+ }
@@ -0,0 +1,29 @@
1
+ import type { PageToolbarSize } from "./pageToolbarContext";
2
+
3
+ /**
4
+ * The one height every control of a toolbar takes at each size -- the live
5
+ * pill, the segmented presets, the comparison trigger, the refresh button,
6
+ * the labels and a leading chip -- so a bar is as tall with live as without.
7
+ * The segmented track's and Nuxt UI's buttons' own steps (24/28/32 px),
8
+ * written out so a theme's padding cannot change them.
9
+ *
10
+ * @internal
11
+ */
12
+ export const PAGE_TOOLBAR_CONTROL_HEIGHT_CLASSES: Record<
13
+ PageToolbarSize,
14
+ string
15
+ > = {
16
+ xs: "h-6",
17
+ sm: "h-7",
18
+ md: "h-8",
19
+ };
20
+
21
+ /**
22
+ * The height class of a control drawn at `size`, empty for a size the
23
+ * toolbar has no step for (a period selector's `lg`, `xl`).
24
+ *
25
+ * @internal
26
+ */
27
+ export function toolbarControlHeightClass(size: string): string {
28
+ return PAGE_TOOLBAR_CONTROL_HEIGHT_CLASSES[size as PageToolbarSize] ?? "";
29
+ }
@@ -0,0 +1,52 @@
1
+ import { computed, type ComputedRef } from "vue";
2
+ import { usePeriodScopeComparison } from "#dms-core/app/composables/period/usePeriodScope";
3
+ import type { PeriodComparison } from "#dms-core/app/composables/period/types";
4
+
5
+ const COMPARE_LABEL_KEY_PREFIX = "dms.period.compare_labels";
6
+ const COMPARE_LEGEND_KEY_PREFIX = "dms.period.compare_options";
7
+ // A custom window has no name of its own: its legend keeps the generic one.
8
+ const NAMED_LEGEND_COMPARISONS: PeriodComparison[] = [
9
+ "previous-period",
10
+ "previous-year",
11
+ ];
12
+
13
+ export interface PeriodComparisonDisplay {
14
+ /**
15
+ * The scope compares against nothing: the user picked "no comparison", or
16
+ * the period (every period, `all`) has no window to compare against. A
17
+ * card hides its change, its comparison value and its label. `false` while
18
+ * the card has no scope, or its scope is not registered yet: the card then
19
+ * shows what its data carries.
20
+ */
21
+ isOff: ComputedRef<boolean>;
22
+ /** "vs previous year", following the scope; `null` without one. */
23
+ label: ComputedRef<string | null>;
24
+ /** "Previous year", a chart legend's name for the comparison series. */
25
+ legendLabel: ComputedRef<string | null>;
26
+ }
27
+
28
+ /**
29
+ * What a period-bound card shows of its comparison, following the
30
+ * comparison selected on the scope's `PeriodSelector`.
31
+ */
32
+ export function usePeriodComparisonDisplay(
33
+ periodScope: string | undefined,
34
+ ): PeriodComparisonDisplay {
35
+ const { t } = useI18n();
36
+ const comparison = usePeriodScopeComparison(periodScope);
37
+ const activeComparison = computed(() =>
38
+ comparison.value && comparison.value !== "none" ? comparison.value : null,
39
+ );
40
+ return {
41
+ isOff: computed(() => comparison.value === "none"),
42
+ label: computed(() => {
43
+ const active = activeComparison.value;
44
+ return active ? t(`${COMPARE_LABEL_KEY_PREFIX}.${active}`, active) : null;
45
+ }),
46
+ legendLabel: computed(() => {
47
+ const active = activeComparison.value;
48
+ if (!active || !NAMED_LEGEND_COMPARISONS.includes(active)) return null;
49
+ return t(`${COMPARE_LEGEND_KEY_PREFIX}.${active}`, active);
50
+ }),
51
+ };
52
+ }
@@ -182,6 +182,27 @@ export function formatPeriodRange(
182
182
  return formatRangeWith(range, locale, options);
183
183
  }
184
184
 
185
+ /**
186
+ * Day-only range label, "16 – 17 Oct": a comparison menu's trigger names the
187
+ * window it compares with, where the hours would only widen it. A window
188
+ * shorter than a day keeps its times, its day alone telling nothing.
189
+ * `withYear: false` drops the year, as in `formatPeriodRange`.
190
+ */
191
+ export function formatPeriodDays(
192
+ range: PeriodRange,
193
+ locale: string,
194
+ withYear = true,
195
+ ): string {
196
+ const span = range.to.getTime() - range.from.getTime();
197
+ if (span < MS_PER_DAY - MS_PER_HOUR) {
198
+ return formatRangeWith(range, locale, { ...DAY_FORMAT, ...TIME_FORMAT });
199
+ }
200
+ const options: FormatOptions = withYear
201
+ ? { ...DAY_FORMAT, year: "numeric" }
202
+ : DAY_FORMAT;
203
+ return formatRangeWith(range, locale, options);
204
+ }
205
+
185
206
  export interface PeriodDuration {
186
207
  unit: "days" | "hours";
187
208
  count: number;
@@ -0,0 +1,28 @@
1
+ import type { PeriodPreset } from "#dms-core/app/composables/period/types";
2
+
3
+ /**
4
+ * Short mono codes, as trails in the preset list and segmented labels: every
5
+ * preset has one, so a row of segments stays as narrow with `all` as without
6
+ * it, where the full label ("All time") widened it. Custom has none: it shows
7
+ * its range. Each is translated under `dms.period.short_presets.<preset>`
8
+ * ("24 h", "7 j" in French); these English codes are the fallback for a
9
+ * locale that lacks one, and the list of presets that have a code.
10
+ *
11
+ * @internal
12
+ */
13
+ export const PRESET_SHORT_LABELS: Partial<Record<PeriodPreset, string>> = {
14
+ "last-hour": "1H",
15
+ "last-24h": "24H",
16
+ today: "1D",
17
+ yesterday: "-1D",
18
+ "last-7-days": "7D",
19
+ "last-30-days": "30D",
20
+ "last-90-days": "90D",
21
+ "this-month": "MTD",
22
+ "last-month": "-1M",
23
+ "this-quarter": "QTD",
24
+ "last-quarter": "-1Q",
25
+ ytd: "YTD",
26
+ "last-year": "-1Y",
27
+ all: "ALL",
28
+ };
@@ -84,6 +84,7 @@ export function usePeriodDraft(period: UsePeriodReturn): UsePeriodDraftReturn {
84
84
  comparison.value,
85
85
  range.value,
86
86
  period.customCompareRange.value,
87
+ { preset: preset.value, now: new Date() },
87
88
  ),
88
89
  );
89
90