@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
@@ -6,7 +6,8 @@ import type {
6
6
  UseDmsPagedDataOptions,
7
7
  UseFetchOptions,
8
8
  } from "#dms/frontend-module";
9
- import type { MaybeRefOrGetter } from "vue";
9
+ import { getCurrentInstance, toValue, watch, type MaybeRefOrGetter } from "vue";
10
+ import { useLivePauseSource } from "../live/livePauseSource";
10
11
 
11
12
  /** The authenticated fetch `useAuthFetch` returns. */
12
13
  type AuthFetch = ReturnType<typeof useAuthFetch>["$authFetch"];
@@ -61,7 +62,9 @@ export type ApiDataResult<T> = DmsAsyncDataResult<T>;
61
62
  * - `refreshInterval: ms` reads again on an interval while the caller shows
62
63
  * the data and the page is visible: polling. A ref or a getter changes it,
63
64
  * and `0` or `null` stops it; a call from a plugin polls for as long as
64
- * the application runs.
65
+ * the application runs. On a page declared with `live`, a component's
66
+ * polling stops while the page is paused, and reads once on resume if a
67
+ * poll fell due meanwhile.
65
68
  * - The page's refreshes (`refreshPageBlocks()`) and the events of its
66
69
  * realtime topics read the data again; `refreshOnPageRefresh: false` opts
67
70
  * out, for data with refresh triggers of its own.
@@ -88,15 +91,74 @@ export function useApiData<T>(
88
91
  ): ApiDataResult<T> {
89
92
  const { $authFetch } = useAuthFetch();
90
93
  if (typeof handlerOrOptions === "function")
91
- return useDmsAsyncData<T>(
92
- source,
93
- ({ signal }) => handlerOrOptions({ $authFetch, signal }),
94
+ return followLivePause(
95
+ useDmsAsyncData<T>(
96
+ source,
97
+ ({ signal }) => handlerOrOptions({ $authFetch, signal }),
98
+ gateLivePolling(options),
99
+ ),
94
100
  options,
95
101
  );
96
- return useDmsFetch<T>(source, {
97
- ...handlerOrOptions,
98
- $fetch: $authFetch as UseFetchOptions<T>["$fetch"],
102
+ return followLivePause(
103
+ useDmsFetch<T>(source, {
104
+ ...gateLivePolling(handlerOrOptions),
105
+ $fetch: $authFetch as UseFetchOptions<T>["$fetch"],
106
+ }),
107
+ handlerOrOptions,
108
+ );
109
+ }
110
+
111
+ /** The options of a read that polls, as far as its live gate goes. */
112
+ interface PolledReadOptions {
113
+ refreshInterval?: MaybeRefOrGetter<number | null | undefined>;
114
+ immediate?: boolean;
115
+ }
116
+
117
+ // A component's polling is one more read nobody asked for: on a live page it
118
+ // stops while the page is paused, as the blocks' refetches do. A plugin's
119
+ // polling (the shell's, outside any page) keeps running. The pause is read
120
+ // through the app's `LIVE_PAUSE_SOURCE`, which the UI layer provides.
121
+ function isGatedPolling(
122
+ options: PolledReadOptions | undefined,
123
+ ): options is PolledReadOptions {
124
+ return options?.refreshInterval !== undefined && !!getCurrentInstance();
125
+ }
126
+
127
+ /** `options` with a `refreshInterval` that is `null` while the page is paused. */
128
+ function gateLivePolling<O extends PolledReadOptions>(
129
+ options: O | undefined,
130
+ ): O | undefined {
131
+ if (!isGatedPolling(options)) return options;
132
+ const interval = options.refreshInterval;
133
+ const isPaused = useLivePauseSource();
134
+ return {
135
+ ...options,
136
+ refreshInterval: () => (isPaused() ? null : toValue(interval)),
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Reads once as the page resumes when a poll fell due while it was paused:
142
+ * the polling restarts with a full interval, which would show the data of
143
+ * before the pause that long.
144
+ */
145
+ function followLivePause<
146
+ R extends Pick<DmsAsyncDataResult<unknown>, "status" | "refresh">,
147
+ >(result: R, options: PolledReadOptions | undefined): R {
148
+ if (typeof window === "undefined" || !isGatedPolling(options)) return result;
149
+ let pausedAt = 0;
150
+ watch(useLivePauseSource(), (isPaused) => {
151
+ if (isPaused) {
152
+ pausedAt = Date.now();
153
+ return;
154
+ }
155
+ const period = toValue(options.refreshInterval);
156
+ if (!period || period <= 0 || Date.now() - pausedAt < period) return;
157
+ // Data a caller waits to `execute()` is not read before it is.
158
+ if (result.status.value === "idle" && options.immediate === false) return;
159
+ void result.refresh({ dedupe: "defer" });
99
160
  });
161
+ return result;
100
162
  }
101
163
 
102
164
  /** What a handler reading one page of API data receives. */
@@ -139,10 +201,13 @@ export function useApiPagedData<TPage, TCursor = unknown>(
139
201
  options: UseDmsPagedDataOptions<TPage, TCursor>,
140
202
  ): ApiPagedDataResult<TPage, TCursor> {
141
203
  const { $authFetch } = useAuthFetch();
142
- return useDmsPagedData<TPage, TCursor>(
143
- key,
144
- ({ cursor, index, signal }) =>
145
- handler({ $authFetch, cursor, index, signal }),
204
+ return followLivePause(
205
+ useDmsPagedData<TPage, TCursor>(
206
+ key,
207
+ ({ cursor, index, signal }) =>
208
+ handler({ $authFetch, cursor, index, signal }),
209
+ gateLivePolling(options) ?? options,
210
+ ),
146
211
  options,
147
212
  );
148
213
  }
@@ -36,6 +36,24 @@ export type DataTypeValueComparer = (
36
36
  options?: unknown,
37
37
  ) => boolean;
38
38
 
39
+ /**
40
+ * The value a filter chip's editor holds for a filter value a view declared
41
+ * as text (`"0"`, `"2026-10-01,2026-10-15"`), under a compare mode: a number
42
+ * input refuses `"0"`, a date range wants its two days. The filter itself
43
+ * keeps its text, so the query is unchanged.
44
+ */
45
+ export type DataTypeFilterEditorValue = (text: string, mode: string) => unknown;
46
+
47
+ /**
48
+ * Where the rows a value names are read: the `select` route of the table a
49
+ * relation points to, and the keys its rows carry their label and value under
50
+ * (`label` and `value` unless mapped).
51
+ */
52
+ export interface DataTypeSelectSource {
53
+ searchUrl: string;
54
+ keyMapping?: { label?: string; value?: string };
55
+ }
56
+
39
57
  export interface DataType {
40
58
  id: string;
41
59
  formatter?: RecordWithDefault<DataTypeFormatter>;
@@ -46,6 +64,14 @@ export interface DataType {
46
64
  * or whose order does not count. Without it, values compare deeply.
47
65
  */
48
66
  isSameValue?: DataTypeValueComparer;
67
+ /** Without it, a filter's editor reads a declared text value as is. */
68
+ filterEditorValue?: DataTypeFilterEditorValue;
69
+ /**
70
+ * For a type whose value holds the ids of rows another table serves (a
71
+ * relation): where those rows are read, from the options of the type's
72
+ * control. A filter chip or a read-only list names the ids through it.
73
+ */
74
+ selectSource?: (options: unknown) => DataTypeSelectSource | undefined;
49
75
  displayComponent?: Component;
50
76
  }
51
77
  const dataTypes: Record<string, DataType> = {};
@@ -0,0 +1,29 @@
1
+ import { inject, type InjectionKey } from "vue";
2
+
3
+ /**
4
+ * Whether the page on screen is paused right now (by the user, a hold or a
5
+ * hidden tab). Reactive: it reads reactive state, so a `watch` or a
6
+ * `computed` over it follows the pause.
7
+ */
8
+ export type LivePauseSource = () => boolean;
9
+
10
+ /**
11
+ * The extension point the core layer reads a page's pause through. The core
12
+ * owns `useApiData` but not the page's live controller: the layer that owns
13
+ * the controller provides a source at the app level
14
+ * (`app.provide(LIVE_PAUSE_SOURCE, source)`), so every component reads the
15
+ * same page, drawers and modals included. Without one, nothing is paused.
16
+ */
17
+ export const LIVE_PAUSE_SOURCE: InjectionKey<LivePauseSource> = Symbol(
18
+ "dms-live-pause-source",
19
+ );
20
+
21
+ const NEVER_PAUSED: LivePauseSource = () => false;
22
+
23
+ /**
24
+ * The pause source of the app the calling component belongs to; never
25
+ * paused without one. Call it in a component setup.
26
+ */
27
+ export function useLivePauseSource(): LivePauseSource {
28
+ return inject(LIVE_PAUSE_SOURCE, NEVER_PAUSED);
29
+ }
@@ -1,5 +1,5 @@
1
1
  import { readerClock } from "./periodClock";
2
- import type { PeriodComparison, PeriodRange } from "./types";
2
+ import type { PeriodComparison, PeriodPreset, PeriodRange } from "./types";
3
3
 
4
4
  const NON_OVERLAP_GAP_MS = 1;
5
5
 
@@ -30,7 +30,132 @@ function isWholeDayRange(range: PeriodRange): boolean {
30
30
  );
31
31
  }
32
32
 
33
- function shiftPreviousPeriod(range: PeriodRange): PeriodRange {
33
+ const MONTHS_PER_YEAR = 12;
34
+ const MONTHS_PER_QUARTER = 3;
35
+
36
+ // The calendar unit a preset counts in, in months: its previous period is
37
+ // the previous month, quarter or year, not the same number of days before.
38
+ const PRESET_MONTH_SPANS: Partial<Record<PeriodPreset, number>> = {
39
+ "this-month": 1,
40
+ "last-month": 1,
41
+ "this-quarter": MONTHS_PER_QUARTER,
42
+ "last-quarter": MONTHS_PER_QUARTER,
43
+ ytd: MONTHS_PER_YEAR,
44
+ "last-year": MONTHS_PER_YEAR,
45
+ };
46
+
47
+ /** What a comparison is resolved against beyond the range itself. */
48
+ export interface ComparisonContext {
49
+ /** The preset the range comes from: a calendar one compares by calendar. */
50
+ preset?: PeriodPreset;
51
+ /**
52
+ * The instant the range was resolved at: a range running past today
53
+ * (`this-month` on the 18th) compares what has elapsed of it.
54
+ */
55
+ now?: Date;
56
+ }
57
+
58
+ /** `date` moved by whole months, its day kept within the month reached. */
59
+ function shiftMonths(date: Date, months: number): Date {
60
+ const clock = readerClock();
61
+ const wall = clock.wall(date);
62
+ const month = wall.month + months;
63
+ // Day 0 of the next month is the last day of this one.
64
+ const lastDay = clock.wall(
65
+ clock.instant({ ...wall, month: month + 1, day: 0 }),
66
+ ).day;
67
+ return clock.instant({ ...wall, month, day: Math.min(wall.day, lastDay) });
68
+ }
69
+
70
+ /** Whether `date` is the midnight opening a calendar month. */
71
+ function isMonthStart(date: Date): boolean {
72
+ return isLocalMidnight(date) && readerClock().wall(date).day === 1;
73
+ }
74
+
75
+ /** Months from one month start to another. */
76
+ function monthsBetween(from: Date, to: Date): number {
77
+ const clock = readerClock();
78
+ const start = clock.wall(from);
79
+ const end = clock.wall(to);
80
+ return (end.year - start.year) * MONTHS_PER_YEAR + end.month - start.month;
81
+ }
82
+
83
+ /** The instant right after a range: the start of what follows it. */
84
+ const afterRange = (range: PeriodRange): Date =>
85
+ new Date(range.to.getTime() + NON_OVERLAP_GAP_MS);
86
+
87
+ /**
88
+ * The calendar months a range's previous period steps back by: the unit of a
89
+ * calendar preset, else the months of a range made of whole months (a custom
90
+ * September compares to August). Any other range has none.
91
+ */
92
+ function calendarMonthSpan(
93
+ range: PeriodRange,
94
+ preset: PeriodPreset | undefined,
95
+ ): number | undefined {
96
+ if (!isMonthStart(range.from)) return undefined;
97
+ const presetSpan = preset ? PRESET_MONTH_SPANS[preset] : undefined;
98
+ if (presetSpan) return presetSpan;
99
+ const end = afterRange(range);
100
+ return isMonthStart(end) ? monthsBetween(range.from, end) : undefined;
101
+ }
102
+
103
+ /** The last millisecond of `date`'s day. */
104
+ function endOfDay(date: Date): Date {
105
+ const clock = readerClock();
106
+ const nextDay = clock.instant({
107
+ ...clock.wall(date),
108
+ day: clock.wall(date).day + 1,
109
+ hours: 0,
110
+ minutes: 0,
111
+ seconds: 0,
112
+ milliseconds: 0,
113
+ });
114
+ return new Date(nextDay.getTime() - NON_OVERLAP_GAP_MS);
115
+ }
116
+
117
+ /**
118
+ * What a comparison measures of a range: all of it, or what has elapsed of
119
+ * it when it runs past today — the rest holds no data yet, and comparing it
120
+ * with a whole period reads as a fall.
121
+ */
122
+ function elapsedEnd(range: PeriodRange, now: Date | undefined): Date {
123
+ if (!now) return range.to;
124
+ const today = endOfDay(now);
125
+ return range.to.getTime() > today.getTime() ? today : range.to;
126
+ }
127
+
128
+ /**
129
+ * The same span of the calendar `months` earlier: the whole month, quarter
130
+ * or year before when the range is complete and ends with a month, else its
131
+ * elapsed days (the 1st to the 18th of the month before).
132
+ */
133
+ function shiftCalendarPeriod(
134
+ range: PeriodRange,
135
+ months: number,
136
+ end: Date,
137
+ ): PeriodRange {
138
+ const from = shiftMonths(range.from, -months);
139
+ const isComplete = end.getTime() === range.to.getTime();
140
+ if (isComplete && isMonthStart(afterRange(range))) {
141
+ // The span before ends where the range starts: August, then September.
142
+ return { from, to: new Date(range.from.getTime() - NON_OVERLAP_GAP_MS) };
143
+ }
144
+ return { from, to: endOfDay(shiftMonths(end, -months)) };
145
+ }
146
+
147
+ function shiftPreviousPeriod(
148
+ range: PeriodRange,
149
+ context: ComparisonContext,
150
+ ): PeriodRange {
151
+ const months = calendarMonthSpan(range, context.preset);
152
+ if (months) {
153
+ return shiftCalendarPeriod(range, months, elapsedEnd(range, context.now));
154
+ }
155
+ return shiftEqualLength(range);
156
+ }
157
+
158
+ function shiftEqualLength(range: PeriodRange): PeriodRange {
34
159
  // Whole-day ranges move by calendar days, so a span crossing a daylight
35
160
  // saving change still starts at midnight (a fixed-ms shift lands an hour off).
36
161
  if (isWholeDayRange(range)) {
@@ -50,7 +175,14 @@ function shiftPreviousPeriod(range: PeriodRange): PeriodRange {
50
175
  };
51
176
  }
52
177
 
53
- function shiftPreviousYear(range: PeriodRange): PeriodRange {
178
+ function shiftPreviousYear(
179
+ range: PeriodRange,
180
+ context: ComparisonContext,
181
+ ): PeriodRange {
182
+ const end = elapsedEnd(range, context.now);
183
+ if (end.getTime() !== range.to.getTime()) {
184
+ return shiftCalendarPeriod(range, MONTHS_PER_YEAR, end);
185
+ }
54
186
  return {
55
187
  from: shiftCalendar(range.from, 0, -1),
56
188
  to: shiftCalendar(range.to, 0, -1),
@@ -59,7 +191,7 @@ function shiftPreviousYear(range: PeriodRange): PeriodRange {
59
191
 
60
192
  const COMPARISON_RESOLVERS: Record<
61
193
  Exclude<PeriodComparison, "none" | "custom">,
62
- (range: PeriodRange) => PeriodRange
194
+ (range: PeriodRange, context: ComparisonContext) => PeriodRange
63
195
  > = {
64
196
  "previous-period": shiftPreviousPeriod,
65
197
  "previous-year": shiftPreviousYear,
@@ -68,18 +200,27 @@ const COMPARISON_RESOLVERS: Record<
68
200
  /**
69
201
  * Resolves the range a period is compared against.
70
202
  *
71
- * `previous-period` returns the equal-length span immediately before the
72
- * range, offset by a single millisecond so the two never overlap. Ranges of
203
+ * `previous-period` follows the calendar for a calendar range: a month, a
204
+ * quarter or a year (`this-month`, `last-quarter`, `ytd`…, or a custom range
205
+ * of whole months) compares to the one before — September to August, Q3 to
206
+ * Q2. Any other range compares to the equal-length span immediately before
207
+ * it, offset by a single millisecond so the two never overlap; ranges of
73
208
  * whole days are measured in calendar days, so daylight saving changes don't
74
- * move their midnight boundaries. A range with no bounds (`all`) has nothing
75
- * to compare against: `null`.
209
+ * move their midnight boundaries.
210
+ *
211
+ * A range running past today (`this-month` on the 18th) compares what has
212
+ * elapsed of it — the 1st to the 18th of the month before, or of the year
213
+ * before with `previous-year` — not a whole period against days that hold no
214
+ * data yet. A range with no bounds (`all`) has nothing to compare against:
215
+ * `null`.
76
216
  */
77
217
  export function resolveComparisonRange(
78
218
  comparison: PeriodComparison,
79
219
  range: PeriodRange | null,
80
220
  customRange: PeriodRange | null,
221
+ context: ComparisonContext = {},
81
222
  ): PeriodRange | null {
82
223
  if (comparison === "none" || !range) return null;
83
224
  if (comparison === "custom") return customRange;
84
- return COMPARISON_RESOLVERS[comparison](range);
225
+ return COMPARISON_RESOLVERS[comparison](range, context);
85
226
  }
@@ -25,7 +25,7 @@ import {
25
25
  resolvePresetRange,
26
26
  } from "./presetResolvers";
27
27
  import { resolveComparisonRange } from "./compareResolvers";
28
- import { periodRenderNow } from "./periodRenderNow";
28
+ import { serverRenderNow } from "../../build/composables/renderNow";
29
29
 
30
30
  export interface UsePeriodOptions {
31
31
  defaultPreset?: PeriodPreset;
@@ -52,7 +52,7 @@ export interface UsePeriodReturn {
52
52
  state: ComputedRef<PeriodState>;
53
53
  /**
54
54
  * The instant `state` was resolved from: what a label relative to now
55
- * reads, so it holds still with the period (see `periodRenderNow`).
55
+ * reads, so it holds still with the period (see `serverRenderNow`).
56
56
  */
57
57
  resolvedAt: ComputedRef<Date>;
58
58
  setPreset: (preset: PeriodPreset) => void;
@@ -150,6 +150,7 @@ function buildState(refs: PeriodRefs, now: Date): PeriodState {
150
150
  refs.comparison.value,
151
151
  range,
152
152
  refs.customCompareRange.value,
153
+ { preset: refs.preset.value, now },
153
154
  );
154
155
  const partial = {
155
156
  preset: refs.preset.value,
@@ -184,7 +185,7 @@ function makeSetters(refs: PeriodRefs) {
184
185
  * comparisons and custom ranges resolved into a publishable `state`.
185
186
  *
186
187
  * A server render and the browser hydrating it resolve from the same instant
187
- * (`periodRenderNow`), so both render the same bounds.
188
+ * (`serverRenderNow`), so both render the same bounds.
188
189
  *
189
190
  * Relative presets (`last-hour`, `last-24h`) re-resolve every minute, and
190
191
  * calendar presets (`today`, `last-7-days`, `this-month`...) at each local
@@ -214,7 +215,8 @@ export function usePeriod(options: UsePeriodOptions = {}): UsePeriodReturn {
214
215
  // The server render's instant, until the clock ticks or the period changes
215
216
  // once on screen: what the setup applies (the URL's period) the server
216
217
  // applied as well.
217
- const renderNow = shallowRef(periodRenderNow());
218
+ const serverNow = serverRenderNow();
219
+ const renderNow = shallowRef(serverNow === null ? null : new Date(serverNow));
218
220
  if (renderNow.value) {
219
221
  onMounted(() => {
220
222
  watch(
@@ -88,7 +88,7 @@ function readPeriodScope(
88
88
  * first render must match the HTML. It also answers `null` for a period the
89
89
  * browser could resolve to other bounds: before the browser's cookie names
90
90
  * its time zone. A relative preset resolves from the instant the server
91
- * hands the browser (`periodRenderNow`), so both read the same bounds.
91
+ * hands the browser (`serverRenderNow`), so both read the same bounds.
92
92
  */
93
93
  export function usePeriodScope(
94
94
  id: string | undefined,
@@ -144,3 +144,29 @@ export function appendPeriodToUrl(
144
144
  const separator = url.includes("?") ? "&" : "?";
145
145
  return `${url}${separator}${params.toString()}`;
146
146
  }
147
+
148
+ /**
149
+ * The comparison a period scope applies, `null` while no selector registers
150
+ * it: `none` too when the selected comparison resolves to no window (every
151
+ * period, `all`, has nothing to compare against), so a consumer hides its
152
+ * comparison exactly when the request carries no `compareFrom` /
153
+ * `compareTo`. A server render reads it once, as the caller sets up, like
154
+ * `usePeriodScopePreset`: the mode is a name, the same on both sides.
155
+ */
156
+ export function usePeriodScopeComparison(
157
+ id: string | undefined,
158
+ ): ComputedRef<PeriodState["comparison"] | null> {
159
+ if (!id) return computed(() => null);
160
+ const registry = usePeriodScopeRegistry();
161
+ if (typeof window !== "undefined")
162
+ return computed(() => effectiveComparison(readPeriodScope(registry, id)));
163
+ const comparison = effectiveComparison(readPeriodScope(registry, id));
164
+ return computed(() => comparison);
165
+ }
166
+
167
+ function effectiveComparison(
168
+ state: PeriodState | null,
169
+ ): PeriodState["comparison"] | null {
170
+ if (!state) return null;
171
+ return state.compareRange ? state.comparison : "none";
172
+ }