@antelopejs/dms 0.9.7 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (414) hide show
  1. package/CHANGELOG.md +114 -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 -45
  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/realtime.d.ts +13 -1
  117. package/dist/routes/realtime.js +114 -29
  118. package/dist/routes/realtime.js.map +1 -1
  119. package/dist/startup-config.d.ts +2 -0
  120. package/dist/startup-config.js +62 -0
  121. package/dist/startup-config.js.map +1 -0
  122. package/dist/store/interfaces.d.ts +3 -2
  123. package/dist/store/interfaces.js +16 -6
  124. package/dist/store/interfaces.js.map +1 -1
  125. package/dist/store/listing.js +24 -13
  126. package/dist/store/listing.js.map +1 -1
  127. package/dist/store/operations.js +4 -0
  128. package/dist/store/operations.js.map +1 -1
  129. package/dist/store/requirements.js +3 -2
  130. package/dist/store/requirements.js.map +1 -1
  131. package/dist/store/runtime.d.ts +10 -1
  132. package/dist/store/runtime.js +33 -6
  133. package/dist/store/runtime.js.map +1 -1
  134. package/dist/store/types.d.ts +10 -0
  135. package/dist/validation/auth.schema.d.ts +2 -2
  136. package/dist/validation/member-invite.schema.d.ts +2 -10
  137. package/dist/validation/member-invite.schema.js +2 -15
  138. package/dist/validation/member-invite.schema.js.map +1 -1
  139. package/frontend-vue/dms.frontend.ts +24 -5
  140. package/frontend-vue/dms.server.ts +17 -0
  141. package/frontend-vue/layers/dms-auth/server/accounts.ts +3 -4
  142. package/frontend-vue/layers/dms-core/app/build/composables/renderNow.ts +34 -0
  143. package/frontend-vue/layers/dms-core/app/build/composables/user/tablePreferences.ts +5 -1
  144. package/frontend-vue/layers/dms-core/app/build/utils/crossTab.ts +199 -0
  145. package/frontend-vue/layers/dms-core/app/build/utils/sharedRealtimeConnection.ts +410 -0
  146. package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +77 -12
  147. package/frontend-vue/layers/dms-core/app/composables/data-types/useDataType.ts +26 -0
  148. package/frontend-vue/layers/dms-core/app/composables/hydration/isHydratingComponent.ts +29 -0
  149. package/frontend-vue/layers/dms-core/app/composables/live/livePauseSource.ts +29 -0
  150. package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +150 -9
  151. package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +6 -4
  152. package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +27 -1
  153. package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +256 -119
  154. package/frontend-vue/layers/dms-core/app/composables/translation/useComposedText.ts +21 -8
  155. package/frontend-vue/layers/dms-core/app/composables/translation/useTranslation.ts +22 -5
  156. package/frontend-vue/layers/dms-core/app/plugins/dev-error-log.ts +16 -0
  157. package/frontend-vue/layers/dms-core/app/types/user.ts +6 -0
  158. package/frontend-vue/layers/dms-core/app/utils/composedText.ts +7 -2
  159. package/frontend-vue/layers/dms-core/app/utils/formatter.ts +166 -35
  160. package/frontend-vue/layers/dms-core/app/utils/url-interpolation.ts +18 -1
  161. package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +134 -2
  162. package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +134 -2
  163. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +3 -2
  164. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +41 -21
  165. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardPageBody.vue +9 -0
  166. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +4 -19
  167. package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +11 -3
  168. package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +18 -7
  169. package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionBar.vue +8 -0
  170. package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +14 -0
  171. package/frontend-vue/layers/dms-layout/app/build/components/layout/RowSidePanel.vue +43 -82
  172. package/frontend-vue/layers/dms-layout/app/build/components/layout/SidebarUserMenu.vue +44 -3
  173. package/frontend-vue/layers/dms-layout/app/build/components/layout/UserIdentity.vue +11 -3
  174. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleCatalogTile.vue +132 -35
  175. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleManageMenu.vue +59 -0
  176. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleSourceLabel.vue +25 -0
  177. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreBanners.vue +73 -0
  178. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreOperation.vue +116 -0
  179. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreRequirements.vue +8 -3
  180. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue +85 -207
  181. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTile.vue +10 -3
  182. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTileSkeleton.vue +68 -0
  183. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +24 -8
  184. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxItem.vue +8 -5
  185. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/region/RegionTimeZoneInput.vue +1 -0
  186. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-permissions.ts +19 -0
  187. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/useRoleEditor.ts +2 -1
  188. package/frontend-vue/layers/dms-layout/app/build/composables/layout/useSidePanelWidth.ts +31 -70
  189. package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen.ts +11 -1
  190. package/frontend-vue/layers/dms-layout/app/build/utils/dev-reload-stream.ts +81 -0
  191. package/frontend-vue/layers/dms-layout/app/build/utils/module-store-catalog.ts +232 -36
  192. package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +122 -14
  193. package/frontend-vue/layers/dms-layout/app/composables/layout/useLayoutBanners.ts +32 -3
  194. package/frontend-vue/layers/dms-layout/app/composables/layout/usePageRecord.ts +50 -0
  195. package/frontend-vue/layers/dms-layout/app/composables/layout/useServedHeaderActions.ts +25 -7
  196. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleHistory.ts +3 -2
  197. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +51 -4
  198. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStoreActions.ts +194 -0
  199. package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +23 -3
  200. package/frontend-vue/layers/dms-layout/app/composables/page/usePageLayout.ts +4 -2
  201. package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +6 -1
  202. package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +9 -0
  203. package/frontend-vue/layers/dms-layout/app/custom-layouts/SettingsLayout.vue +9 -0
  204. package/frontend-vue/layers/dms-layout/app/custom-pages/modules-store.vue +291 -0
  205. package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +87 -474
  206. package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +3 -0
  207. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +6 -22
  208. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-navigation.client.ts +2 -1
  209. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-personal-pages.client.ts +14 -44
  210. package/frontend-vue/layers/dms-layout/app/plugins/dms-dev-reload.client.ts +14 -9
  211. package/frontend-vue/layers/dms-layout/app/theme/form-controls.ts +9 -4
  212. package/frontend-vue/layers/dms-layout/app/theme/surfaces.ts +22 -6
  213. package/frontend-vue/layers/dms-layout/app/types/module-store.ts +9 -0
  214. package/frontend-vue/layers/dms-layout/app/types/page.ts +15 -1
  215. package/frontend-vue/layers/dms-layout/app/utils/account-menu.ts +11 -0
  216. package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +57 -0
  217. package/frontend-vue/layers/dms-layout/app/utils/pageQuery.ts +26 -0
  218. package/frontend-vue/layers/dms-layout/app/utils/pageRecord.ts +71 -0
  219. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +39 -14
  220. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +38 -13
  221. package/frontend-vue/layers/dms-ui/app/build/components/containers/TargetComponentTree.vue +31 -0
  222. package/frontend-vue/layers/dms-ui/app/build/components/containers/constants.ts +25 -0
  223. package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +244 -15
  224. package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +42 -25
  225. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowHeader.vue +153 -0
  226. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowNotFound.vue +44 -0
  227. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/drawerRowHeader.ts +35 -0
  228. package/frontend-vue/layers/dms-ui/app/build/components/form/FormEntries.vue +44 -9
  229. package/frontend-vue/layers/dms-ui/app/build/components/form/FormErrorSummary.vue +11 -4
  230. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldControl.vue +38 -5
  231. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldNotes.vue +29 -5
  232. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSectionNav.vue +3 -1
  233. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSections.vue +106 -27
  234. package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +12 -4
  235. package/frontend-vue/layers/dms-ui/app/build/components/table/Empty.vue +4 -2
  236. package/frontend-vue/layers/dms-ui/app/build/components/table/FiltersRow.vue +38 -1
  237. package/frontend-vue/layers/dms-ui/app/build/components/table/Pagination.vue +55 -54
  238. package/frontend-vue/layers/dms-ui/app/build/components/table/RowSelection.vue +2 -1
  239. package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +396 -288
  240. package/frontend-vue/layers/dms-ui/app/build/components/table/Tabs.vue +2 -1
  241. package/frontend-vue/layers/dms-ui/app/build/components/table/Views.vue +2 -1
  242. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardAvatar.vue +59 -0
  243. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardGridSkeleton.vue +19 -2
  244. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardRowActions.vue +38 -0
  245. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardsDisplay.vue +148 -104
  246. package/frontend-vue/layers/dms-ui/app/build/components/table-view/LiveHoldBanner.vue +17 -2
  247. package/frontend-vue/layers/dms-ui/app/build/composables/actions/bulkSelection.ts +43 -9
  248. package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowComponentOptions.ts +76 -0
  249. package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowNavigation.ts +172 -27
  250. package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +270 -46
  251. package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +2 -0
  252. package/frontend-vue/layers/dms-ui/app/build/composables/containers/closingLayers.ts +186 -0
  253. package/frontend-vue/layers/dms-ui/app/build/composables/containers/context.ts +14 -0
  254. package/frontend-vue/layers/dms-ui/app/build/composables/containers/drawerSize.ts +229 -0
  255. package/frontend-vue/layers/dms-ui/app/build/composables/containers/useResizableSize.ts +175 -0
  256. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/metricCells.ts +18 -35
  257. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/numberSuffix.ts +47 -0
  258. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +99 -24
  259. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerFieldTypes.ts +2 -1
  260. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationFilter.ts +75 -0
  261. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationRows.ts +71 -0
  262. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationValue.ts +39 -0
  263. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/twoLineCell.ts +3 -3
  264. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/useColumnValueRenderer.ts +6 -2
  265. package/frontend-vue/layers/dms-ui/app/build/composables/form/createFormFields.ts +15 -0
  266. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldSaved.ts +40 -0
  267. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldValueRule.ts +62 -0
  268. package/frontend-vue/layers/dms-ui/app/build/composables/form/formEntryContext.ts +5 -0
  269. package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +42 -7
  270. package/frontend-vue/layers/dms-ui/app/build/composables/form/formLayout.ts +106 -4
  271. package/frontend-vue/layers/dms-ui/app/build/composables/form/formSections.ts +32 -0
  272. package/frontend-vue/layers/dms-ui/app/build/composables/form/readonlyValue.ts +19 -0
  273. package/frontend-vue/layers/dms-ui/app/build/composables/form/registerFormFunctions.ts +27 -0
  274. package/frontend-vue/layers/dms-ui/app/build/composables/form/repeaterTable.ts +65 -0
  275. package/frontend-vue/layers/dms-ui/app/build/composables/form/serverFieldErrors.ts +49 -0
  276. package/frontend-vue/layers/dms-ui/app/build/composables/form/toasterClearance.ts +118 -0
  277. package/frontend-vue/layers/dms-ui/app/build/composables/form/useInstantForm.ts +12 -1
  278. package/frontend-vue/layers/dms-ui/app/build/composables/live/currentPageLive.ts +41 -3
  279. package/frontend-vue/layers/dms-ui/app/build/composables/live/livePoller.ts +10 -2
  280. package/frontend-vue/layers/dms-ui/app/build/composables/live/pageReadClock.ts +28 -0
  281. package/frontend-vue/layers/dms-ui/app/build/composables/live/shortcuts.ts +1 -1
  282. package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageLiveHost.ts +17 -3
  283. package/frontend-vue/layers/dms-ui/app/build/composables/live/useUpdatedLabel.ts +38 -0
  284. package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/pageToolbarContext.ts +75 -0
  285. package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/toolbarControlHeight.ts +29 -0
  286. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodComparison.ts +52 -0
  287. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +21 -0
  288. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodShortLabels.ts +28 -0
  289. package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodDraft.ts +1 -0
  290. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRelationFilterLabels.ts +73 -0
  291. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRowScrollAnchor.ts +106 -0
  292. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +9 -0
  293. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTableColumns.ts +1 -2
  294. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/archivedCount.ts +31 -0
  295. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/filterEditorValue.ts +38 -0
  296. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/formatFilterValue.ts +33 -2
  297. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/scrollEdges.ts +31 -0
  298. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useCardFields.ts +33 -0
  299. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useMobileDisplay.ts +48 -0
  300. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useScrolledPastTop.ts +41 -18
  301. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +3 -2
  302. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLinkedVisit.ts +78 -0
  303. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLiveHold.ts +135 -22
  304. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +56 -25
  305. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +77 -32
  306. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +86 -10
  307. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/bulkActions.ts +14 -4
  308. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/card.ts +127 -2
  309. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/compareValues.ts +13 -0
  310. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/expandedRows.ts +28 -0
  311. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/groupedRows.ts +114 -1
  312. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/linkedArrival.ts +308 -0
  313. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/locateRow.ts +106 -0
  314. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pageArrival.ts +34 -0
  315. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pinnedRows.ts +17 -0
  316. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/quickFilters.ts +41 -33
  317. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/recordLink.ts +24 -8
  318. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableCounts.ts +113 -0
  319. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableQuery.ts +27 -4
  320. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/views.ts +22 -6
  321. package/frontend-vue/layers/dms-ui/app/build/utils/addressSuggestions.ts +49 -0
  322. package/frontend-vue/layers/dms-ui/app/build/utils/eyebrow.ts +12 -0
  323. package/frontend-vue/layers/dms-ui/app/build/utils/fieldOptionsI18n.ts +51 -11
  324. package/frontend-vue/layers/dms-ui/app/build/utils/inputWidth.ts +53 -0
  325. package/frontend-vue/layers/dms-ui/app/build/utils/numberAdjustment.ts +6 -3
  326. package/frontend-vue/layers/dms-ui/app/build/utils/relativeDate.ts +19 -5
  327. package/frontend-vue/layers/dms-ui/app/build/utils/scrollWithin.ts +99 -0
  328. package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +36 -14
  329. package/frontend-vue/layers/dms-ui/app/components/PasswordStrength.vue +1 -1
  330. package/frontend-vue/layers/dms-ui/app/components/RecursiveComponent.vue +9 -1
  331. package/frontend-vue/layers/dms-ui/app/components/blocks/LiveToggleBlock.vue +80 -32
  332. package/frontend-vue/layers/dms-ui/app/components/blocks/PageToolbarBlock.vue +137 -0
  333. package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +5 -0
  334. package/frontend-vue/layers/dms-ui/app/components/chart/ApexChartHost.vue +16 -2
  335. package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +24 -5
  336. package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +41 -23
  337. package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +1 -1
  338. package/frontend-vue/layers/dms-ui/app/components/container-footer/ContainerFooter.vue +34 -0
  339. package/frontend-vue/layers/dms-ui/app/components/field-row/FieldRow.vue +25 -2
  340. package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +2 -2
  341. package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +118 -36
  342. package/frontend-vue/layers/dms-ui/app/components/form/components/DatePicker.vue +20 -1
  343. package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +13 -1
  344. package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +8 -3
  345. package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +53 -5
  346. package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +29 -5
  347. package/frontend-vue/layers/dms-ui/app/components/form/components/InputNumber.vue +45 -8
  348. package/frontend-vue/layers/dms-ui/app/components/form/components/InputPercentage.vue +28 -5
  349. package/frontend-vue/layers/dms-ui/app/components/form/components/InputSecret.vue +5 -0
  350. package/frontend-vue/layers/dms-ui/app/components/form/components/InputText.vue +19 -2
  351. package/frontend-vue/layers/dms-ui/app/components/form/components/InputTime.vue +2 -1
  352. package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +205 -65
  353. package/frontend-vue/layers/dms-ui/app/components/form/components/Repeater.vue +80 -30
  354. package/frontend-vue/layers/dms-ui/app/components/form/components/RepeaterTable.vue +203 -0
  355. package/frontend-vue/layers/dms-ui/app/components/form/components/Select.vue +4 -2
  356. package/frontend-vue/layers/dms-ui/app/components/grid/GridRow.vue +11 -2
  357. package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +79 -70
  358. package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +117 -21
  359. package/frontend-vue/layers/dms-ui/app/components/meter/Meter.vue +2 -1
  360. package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +113 -34
  361. package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +14 -2
  362. package/frontend-vue/layers/dms-ui/app/components/section-header/Eyebrow.vue +6 -1
  363. package/frontend-vue/layers/dms-ui/app/components/segmented/Segmented.vue +13 -0
  364. package/frontend-vue/layers/dms-ui/app/components/stat-group/StatGroup.vue +52 -3
  365. package/frontend-vue/layers/dms-ui/app/components/tab/Tab.vue +2 -1
  366. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +45 -35
  367. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanDisplay.vue +4 -1
  368. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +410 -96
  369. package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +33 -5
  370. package/frontend-vue/layers/dms-ui/app/composables/chart/apexCategoryLabels.ts +68 -0
  371. package/frontend-vue/layers/dms-ui/app/composables/chart/apexDatetimeBuckets.ts +306 -0
  372. package/frontend-vue/layers/dms-ui/app/composables/chart/formatValue.ts +4 -3
  373. package/frontend-vue/layers/dms-ui/app/composables/chart/heatmapColors.ts +93 -0
  374. package/frontend-vue/layers/dms-ui/app/composables/chart/localizeSeries.ts +62 -0
  375. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.ts +116 -8
  376. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.types.ts +7 -0
  377. package/frontend-vue/layers/dms-ui/app/composables/containers/types.ts +84 -1
  378. package/frontend-vue/layers/dms-ui/app/composables/containers/useDrawer.ts +69 -8
  379. package/frontend-vue/layers/dms-ui/app/composables/form/types/field-saved.ts +11 -0
  380. package/frontend-vue/layers/dms-ui/app/composables/form/types/field.ts +4 -0
  381. package/frontend-vue/layers/dms-ui/app/composables/form/types/functions.ts +1 -0
  382. package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +142 -21
  383. package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +11 -4
  384. package/frontend-vue/layers/dms-ui/app/composables/page-toolbar/usePageToolbarSize.ts +30 -0
  385. package/frontend-vue/layers/dms-ui/app/composables/table-view/kanban.ts +4 -2
  386. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +22 -3
  387. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +4 -0
  388. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/display.ts +24 -1
  389. package/frontend-vue/layers/dms-ui/app/plugins/closing-layers.client.ts +10 -0
  390. package/frontend-vue/layers/dms-ui/app/plugins/live-pause-source.ts +15 -0
  391. package/frontend-vue/layers/dms-ui/app/plugins/register.ts +2 -0
  392. package/frontend-vue/layers/dms-ui/app/types/input-number.ts +3 -2
  393. package/frontend-vue/layers/dms-ui/app/types/page-toolbar.ts +7 -0
  394. package/frontend-vue/layers/dms-ui/app/types/row-action.ts +0 -2
  395. package/frontend-vue/layers/dms-ui/app/types/row-navigation.ts +17 -0
  396. package/frontend-vue/layers/dms-ui/app/types/table/query.ts +2 -1
  397. package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +3 -1
  398. package/frontend-vue/layers/dms-ui/app/utils/componentTree.ts +24 -0
  399. package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +16 -4
  400. package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +16 -4
  401. package/frontend-vue/package.json +2 -2
  402. package/package.json +7 -6
  403. package/skills/dms/SKILL.md +1 -1
  404. package/skills/dms-auth/SKILL.md +5 -3
  405. package/skills/dms-best-practices/REFERENCE.md +2 -0
  406. package/skills/dms-best-practices/SKILL.md +4 -0
  407. package/skills/dms-module/SKILL.md +3 -2
  408. package/skills/dms-pages/REFERENCE.md +125 -16
  409. package/skills/dms-pages/SKILL.md +1 -1
  410. package/skills/dms-project/DEV-SERVERS.md +23 -1
  411. package/skills/dms-project/REFERENCE.md +1 -1
  412. package/skills/dms-project/SKILL.md +4 -1
  413. package/skills/dms-project/scripts/dms-dev.sh +205 -17
  414. package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +0 -42
@@ -1,6 +1,11 @@
1
+ /** An input component, with the options its field declares. */
2
+ interface ComponentWithOptions {
3
+ options?: object;
4
+ }
5
+
1
6
  /** A field whose input component carries options. */
2
7
  interface FieldWithOptions {
3
- component: { options?: object };
8
+ component: ComponentWithOptions;
4
9
  }
5
10
 
6
11
  type Translate = (key: string) => string;
@@ -23,6 +28,9 @@ const ENTRY_TEXTS = ["label", "description"] as const;
23
28
 
24
29
  const isText = (value: unknown): value is string => typeof value === "string";
25
30
 
31
+ const isOptions = (value: unknown): value is Options =>
32
+ typeof value === "object" && value !== null && !Array.isArray(value);
33
+
26
34
  function translateTexts(
27
35
  source: Options,
28
36
  keys: readonly string[],
@@ -36,25 +44,57 @@ function translateTexts(
36
44
  }
37
45
 
38
46
  /**
39
- * A field with the texts of its input component's options translated
40
- * (`$`-prefixed i18n keys): placeholder, labels, descriptions, and those of
41
- * its options and columns, the way forms show them.
47
+ * An entry holding a field of its own (a repeater's column, a key-value
48
+ * editor's value) has the options of that field's component translated too,
49
+ * so a select inside a repeater shows its options in the reader's language.
42
50
  */
43
- export function processFieldI18n<F extends FieldWithOptions>(
44
- field: F,
51
+ function translateNestedField(entry: Options, processI18n: Translate): Options {
52
+ if (!isOptions(entry.component)) return entry;
53
+ return {
54
+ ...entry,
55
+ component: translateComponent(entry.component, processI18n),
56
+ };
57
+ }
58
+
59
+ function translateComponent<C extends ComponentWithOptions>(
60
+ component: C,
45
61
  processI18n: Translate,
46
- ): F {
47
- const opts = field.component.options as Options | undefined;
48
- if (!opts) return field;
62
+ ): C {
63
+ const opts = component.options as Options | undefined;
64
+ if (!opts) return component;
49
65
 
50
66
  const options = translateTexts(opts, TEXT_OPTIONS, processI18n);
51
67
  for (const key of LIST_OPTIONS) {
52
68
  const list = opts[key];
53
69
  if (!Array.isArray(list)) continue;
54
70
  options[key] = list.map((entry: Options) =>
55
- translateTexts(entry, ENTRY_TEXTS, processI18n),
71
+ translateNestedField(
72
+ translateTexts(entry, ENTRY_TEXTS, processI18n),
73
+ processI18n,
74
+ ),
56
75
  );
57
76
  }
77
+ if (isOptions(opts.value)) {
78
+ options.value = translateNestedField(opts.value, processI18n);
79
+ }
58
80
 
59
- return { ...field, component: { ...field.component, options } };
81
+ return { ...component, options };
82
+ }
83
+
84
+ /**
85
+ * A field with the texts of its input component's options translated
86
+ * (`$`-prefixed i18n keys): placeholder, labels, descriptions, and those of
87
+ * its options and columns, the way forms show them. The fields nested in it
88
+ * (a repeater's columns, a key-value editor's value) are translated the same
89
+ * way, at any depth.
90
+ */
91
+ export function processFieldI18n<F extends FieldWithOptions>(
92
+ field: F,
93
+ processI18n: Translate,
94
+ ): F {
95
+ if (!field.component.options) return field;
96
+ return {
97
+ ...field,
98
+ component: translateComponent(field.component, processI18n),
99
+ };
60
100
  }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * How wide a single-line input sits in its row: `"compact"` for a short
3
+ * value, `"full"` to fill the row. Left out, the input's bounds decide.
4
+ */
5
+ export type InputWidth = "compact" | "full";
6
+
7
+ /** The widest number, in digits, a number input sizes compact by itself. */
8
+ const COMPACT_NUMBER_DIGITS = 6;
9
+ /** The longest `maxLength` a text input sizes compact by itself. */
10
+ const COMPACT_TEXT_LENGTH = 16;
11
+
12
+ interface NumberWidthOptions {
13
+ width?: InputWidth;
14
+ min?: number;
15
+ max?: number;
16
+ }
17
+
18
+ interface TextWidthOptions {
19
+ width?: InputWidth;
20
+ maxLength?: number;
21
+ }
22
+
23
+ function integerDigits(value: number): number {
24
+ return String(Math.trunc(Math.abs(value))).length;
25
+ }
26
+
27
+ /**
28
+ * Whether a number input takes a compact width: asked for, or left out with
29
+ * a `max` (and `min`) that keep the value within a few digits. Without a
30
+ * `max` nothing bounds the value, so it fills the row.
31
+ */
32
+ export function isCompactNumberInput({
33
+ width,
34
+ min,
35
+ max,
36
+ }: NumberWidthOptions): boolean {
37
+ if (width) return width === "compact";
38
+ if (max === undefined || !Number.isFinite(max)) return false;
39
+ const bounds = min === undefined ? [max] : [min, max];
40
+ return Math.max(...bounds.map(integerDigits)) <= COMPACT_NUMBER_DIGITS;
41
+ }
42
+
43
+ /**
44
+ * Whether a text input takes a compact width: asked for, or left out with a
45
+ * `maxLength` short enough for a code or an abbreviation.
46
+ */
47
+ export function isCompactTextInput({
48
+ width,
49
+ maxLength,
50
+ }: TextWidthOptions): boolean {
51
+ if (width) return width === "compact";
52
+ return maxLength !== undefined && maxLength <= COMPACT_TEXT_LENGTH;
53
+ }
@@ -1,3 +1,4 @@
1
+ import { readableNumberFormat } from "#dms-core/app/utils/formatter";
1
2
  import type {
2
3
  InputNumberAdjustment,
3
4
  InputNumberAdjustmentReason,
@@ -15,7 +16,7 @@ const WHITESPACE = /\s/g;
15
16
  const NOT_NUMERIC = /[^\d.+-]/g;
16
17
 
17
18
  function separatorsOf(locale: string): { group?: string; decimal: string } {
18
- const parts = new Intl.NumberFormat(locale).formatToParts(SEPARATOR_SAMPLE);
19
+ const parts = readableNumberFormat(locale).formatToParts(SEPARATOR_SAMPLE);
19
20
  return {
20
21
  group: parts.find((part) => part.type === "group")?.value,
21
22
  decimal: parts.find((part) => part.type === "decimal")?.value ?? ".",
@@ -23,8 +24,10 @@ function separatorsOf(locale: string): { group?: string; decimal: string } {
23
24
  }
24
25
 
25
26
  /**
26
- * A number as typed in a field written in `locale` ("1,020", "1 020,5"):
27
- * `null` when nothing is typed or it does not read as a number.
27
+ * A number as typed in a field written in `locale` ("1,020", "1 020,5"),
28
+ * its digits grouped by any space (the narrow one the field draws in French,
29
+ * the no-break one the cells write, a typed one): `null` when nothing is
30
+ * typed or it does not read as a number.
28
31
  */
29
32
  export function parseTypedNumber(text: string, locale: string): number | null {
30
33
  const { group, decimal } = separatorsOf(locale);
@@ -19,15 +19,29 @@ export function formatRelativeDate(
19
19
  );
20
20
  }
21
21
 
22
- /** "Sep 27": day and short month. */
22
+ const DAY_MONTH_FORMAT: Intl.DateTimeFormatOptions = {
23
+ month: "short",
24
+ day: "numeric",
25
+ };
26
+ const YEAR_FORMAT: Intl.DateTimeFormatOptions = { year: "numeric" };
27
+
28
+ /**
29
+ * "Sep 27": day and short month, read against `now`; a date of another year
30
+ * ("Apr 2, 2018") names its year as well.
31
+ */
23
32
  export function formatDayMonth(
24
33
  date: string | number | Date,
25
34
  locale: string,
35
+ now: number = Date.now(),
26
36
  ): string {
27
- return regionalDateTimeFormat(locale, {
28
- month: "short",
29
- day: "numeric",
30
- }).format(new Date(date));
37
+ const day = new Date(date);
38
+ // Years read in the reader's time zone, as the date is written.
39
+ const year = regionalDateTimeFormat(locale, YEAR_FORMAT);
40
+ const isOtherYear = year.format(day) !== year.format(now);
41
+ return regionalDateTimeFormat(
42
+ locale,
43
+ isOtherYear ? { ...DAY_MONTH_FORMAT, ...YEAR_FORMAT } : DAY_MONTH_FORMAT,
44
+ ).format(day);
31
45
  }
32
46
 
33
47
  /** First word of a name, for "Sep 27 · by Camille". */
@@ -0,0 +1,99 @@
1
+ // `scrollIntoView` scrolls every ancestor that can scroll, including the
2
+ // layout's `overflow: hidden` root: the header and breadcrumb slide out of
3
+ // view and nothing brings them back. These helpers scroll the one container
4
+ // that actually scrolls the element instead.
5
+
6
+ const SCROLLABLE_OVERFLOW = /(auto|scroll)/;
7
+
8
+ /** Where an element lands in its scroll container. */
9
+ export type ScrollWithinBlock = "start" | "center" | "nearest";
10
+
11
+ /** How `scrollWithin` brings an element into view. */
12
+ export interface ScrollWithinOptions {
13
+ block?: ScrollWithinBlock;
14
+ behavior?: ScrollBehavior;
15
+ }
16
+
17
+ /**
18
+ * The closest ancestor of `element` the user can scroll (`overflow-y: auto`
19
+ * or `scroll`, with content taller than itself), or `null` when only the
20
+ * window scrolls it.
21
+ *
22
+ * @internal
23
+ */
24
+ export function scrollParent(element: HTMLElement): HTMLElement | null {
25
+ for (let node = element.parentElement; node; node = node.parentElement) {
26
+ const { overflowY } = getComputedStyle(node);
27
+ if (
28
+ SCROLLABLE_OVERFLOW.test(overflowY) &&
29
+ node.scrollHeight > node.clientHeight
30
+ ) {
31
+ return node;
32
+ }
33
+ }
34
+ return null;
35
+ }
36
+
37
+ interface ScrollViewport {
38
+ top: number;
39
+ height: number;
40
+ scrollTop: number;
41
+ }
42
+
43
+ function viewportOf(scroller: HTMLElement | null): ScrollViewport {
44
+ if (scroller) {
45
+ return {
46
+ top: scroller.getBoundingClientRect().top,
47
+ height: scroller.clientHeight,
48
+ scrollTop: scroller.scrollTop,
49
+ };
50
+ }
51
+ return { top: 0, height: window.innerHeight, scrollTop: window.scrollY };
52
+ }
53
+
54
+ function scrollMarginTop(element: HTMLElement): number {
55
+ return Number.parseFloat(getComputedStyle(element).scrollMarginTop) || 0;
56
+ }
57
+
58
+ /** The scroll offset that puts `rect` at `block` in `viewport`. */
59
+ function targetOffset(
60
+ rect: DOMRect,
61
+ margin: number,
62
+ viewport: ScrollViewport,
63
+ block: ScrollWithinBlock,
64
+ ): number | null {
65
+ const top = rect.top - viewport.top - margin;
66
+ if (block === "start") return viewport.scrollTop + top;
67
+ if (block === "center") {
68
+ return viewport.scrollTop + top - (viewport.height - rect.height) / 2;
69
+ }
70
+ const bottom = rect.bottom - viewport.top;
71
+ if (top >= 0 && bottom <= viewport.height) return null;
72
+ return top < 0
73
+ ? viewport.scrollTop + top
74
+ : viewport.scrollTop + bottom - viewport.height;
75
+ }
76
+
77
+ /**
78
+ * Brings `element` into view by scrolling its own scroll container only
79
+ * (the window when nothing closer scrolls it), honouring its
80
+ * `scroll-margin-top`. Unlike `scrollIntoView`, no other ancestor moves.
81
+ *
82
+ * @internal
83
+ */
84
+ export function scrollWithin(
85
+ element: HTMLElement,
86
+ { block = "start", behavior = "auto" }: ScrollWithinOptions = {},
87
+ ): void {
88
+ const scroller = scrollParent(element);
89
+ const offset = targetOffset(
90
+ element.getBoundingClientRect(),
91
+ block === "start" ? scrollMarginTop(element) : 0,
92
+ viewportOf(scroller),
93
+ block,
94
+ );
95
+ if (offset === null) return;
96
+ const top = Math.max(0, offset);
97
+ if (scroller) scroller.scrollTo({ top, behavior });
98
+ else window.scrollTo({ top, behavior });
99
+ }
@@ -154,19 +154,32 @@ const FRAGMENT_SEPARATOR = "#";
154
154
  const PAIR_SEPARATOR = "&";
155
155
  const VALUE_SEPARATOR = "=";
156
156
 
157
- /** The query of a URL without the pairs whose whole value is a token left empty. */
158
- function dropUnresolvedPairs(search: string): string {
157
+ /** The query of a URL without the pairs whose whole value matches `value`. */
158
+ function dropPairs(search: string, value: RegExp): string {
159
159
  return search
160
160
  .split(PAIR_SEPARATOR)
161
161
  .filter((pair) => {
162
- const value = pair.slice(pair.indexOf(VALUE_SEPARATOR) + 1);
163
- return (
164
- !pair.includes(VALUE_SEPARATOR) || !UNRESOLVED_ROUTE_VALUE.test(value)
165
- );
162
+ const pairValue = pair.slice(pair.indexOf(VALUE_SEPARATOR) + 1);
163
+ return !pair.includes(VALUE_SEPARATOR) || !value.test(pairValue);
166
164
  })
167
165
  .join(PAIR_SEPARATOR);
168
166
  }
169
167
 
168
+ /**
169
+ * A URL without its `token`s: a query pair whose whole value is one (`value`)
170
+ * is left out, any other is left empty.
171
+ */
172
+ function stripTokens(url: string, token: RegExp, value: RegExp): string {
173
+ const [beforeFragment = "", ...fragment] = url.split(FRAGMENT_SEPARATOR);
174
+ const [path = "", ...search] = beforeFragment.split(QUERY_SEPARATOR);
175
+ const query = dropPairs(search.join(QUERY_SEPARATOR), value);
176
+ const rebuilt = [
177
+ query ? `${path}${QUERY_SEPARATOR}${query}` : path,
178
+ ...fragment,
179
+ ].join(FRAGMENT_SEPARATOR);
180
+ return rebuilt.replace(token, "");
181
+ }
182
+
170
183
  /**
171
184
  * A link's URL with the `{{params.X}}` / `{{query.X}}` tokens of the page it
172
185
  * sits on filled, for a button that always leads somewhere: a query pair
@@ -180,12 +193,21 @@ export function resolveLinkVariables(
180
193
  ): string {
181
194
  const filled = replaceUrlVariables(url, context);
182
195
  if (!hasUrlVariables(filled)) return filled;
183
- const [beforeFragment = "", ...fragment] = filled.split(FRAGMENT_SEPARATOR);
184
- const [path = "", ...search] = beforeFragment.split(QUERY_SEPARATOR);
185
- const query = dropUnresolvedPairs(search.join(QUERY_SEPARATOR));
186
- const rebuilt = [
187
- query ? `${path}${QUERY_SEPARATOR}${query}` : path,
188
- ...fragment,
189
- ].join(FRAGMENT_SEPARATOR);
190
- return rebuilt.replace(UNRESOLVED_ROUTE_TOKEN, "");
196
+ return stripTokens(filled, UNRESOLVED_ROUTE_TOKEN, UNRESOLVED_ROUTE_VALUE);
197
+ }
198
+
199
+ // A row field, `{{row.<path>}}`, as `interpolateUrl` fills it.
200
+ const ROW_KEY = String.raw`row\.[^{}\s]+`;
201
+ const ROW_TOKEN = tokenPattern(ROW_KEY);
202
+ const ROW_VALUE = new RegExp(`^${tokenPattern(ROW_KEY, "").source}$`);
203
+
204
+ /**
205
+ * A URL sent without a row (a bulk action on the selection bar) with its
206
+ * `{{row.X}}` tokens taken out as a link's unresolved page tokens are: a query
207
+ * pair whose whole value is one is left out (`?ids={{row._id}}` gives way to
208
+ * the selection's `ids`), any other is left empty.
209
+ */
210
+ export function dropRowTokens(url: string): string {
211
+ if (!new RegExp(ROW_TOKEN.source).test(url)) return url;
212
+ return stripTokens(url, ROW_TOKEN, ROW_VALUE);
191
213
  }
@@ -16,7 +16,7 @@
16
16
  {{ $t("form.password_strength.contains") }}
17
17
  </p>
18
18
 
19
- <ul class="space-y-1 pl-3" aria-label="Password requirements">
19
+ <ul class="space-y-1 pl-3" aria-labelledby="password-strength">
20
20
  <li
21
21
  v-for="(req, index) in props.strength"
22
22
  :key="index"
@@ -20,6 +20,11 @@ interface Props {
20
20
  * a role preview veil this block; without it the block is never veiled.
21
21
  */
22
22
  layoutPath?: string;
23
+ /**
24
+ * Props handed to this block only, as they are (functions included), over
25
+ * its options: what an action target's container gives its block.
26
+ */
27
+ rootProps?: Record<string, unknown>;
23
28
  }
24
29
 
25
30
  const props = defineProps<Props>();
@@ -108,7 +113,10 @@ provide<GridDeclaredColumns>(GRID_DECLARED_COLUMNS, {
108
113
  <Component
109
114
  :is="component.component"
110
115
  :ref="anchorBlock"
111
- v-bind="JSON.parse(JSON.stringify(component.options || {})) || {}"
116
+ v-bind="{
117
+ ...JSON.parse(JSON.stringify(component.options || {})),
118
+ ...rootProps,
119
+ }"
112
120
  :page-id="pageId"
113
121
  :component-id="componentId"
114
122
  :child-count="columnCount"
@@ -1,8 +1,8 @@
1
1
  <script setup lang="ts">
2
- import { computed, onBeforeUnmount, onMounted, ref } from "vue";
2
+ import { computed, onMounted, ref } from "vue";
3
3
  import DmsSegmented from "../segmented/Segmented.vue";
4
4
  import { usePageLive } from "../../composables/live/usePageLive";
5
- import { formatElapsed } from "../../build/composables/live/formatElapsed";
5
+ import { useUpdatedLabel } from "../../build/composables/live/useUpdatedLabel";
6
6
  import { useLiveToggleShortcut } from "../../build/composables/live/useLiveToggleShortcut";
7
7
  import { HIDDEN_TAB_NOTE } from "../../build/composables/live/pageLiveController";
8
8
  import type { ConfirmTranslate } from "../../build/composables/confirm/confirmDialogTexts";
@@ -10,14 +10,24 @@ import {
10
10
  missedRowsText,
11
11
  showLiveNote,
12
12
  } from "../../build/composables/live/liveNotes";
13
+ import {
14
+ type PageToolbarSize,
15
+ usePageToolbar,
16
+ useToolbarLiveToggle,
17
+ } from "../../build/composables/page-toolbar/pageToolbarContext";
18
+ import { PAGE_TOOLBAR_CONTROL_HEIGHT_CLASSES } from "../../build/composables/page-toolbar/toolbarControlHeight";
13
19
  import type { DefaultComponentProps } from "../../../../dms-core/app/types/component";
14
20
 
15
- interface LiveToggleBlockProps extends DefaultComponentProps {
21
+ // Its ids are optional: it reads the page's live state, not its own, and a
22
+ // component may draw it by hand inside its own `PageToolbar`.
23
+ interface LiveToggleBlockProps extends Partial<DefaultComponentProps> {
16
24
  align?: "left" | "center" | "right";
17
25
  /** Shows "updated N s ago". */
18
26
  showUpdatedAt?: boolean;
19
27
  /** `segmented` (Live / Pause segments) or `pill` (a pulsing Live pill). */
20
28
  variant?: "segmented" | "pill";
29
+ /** How large it is drawn; the holding `PageToolbar`'s size, else `sm`. */
30
+ size?: PageToolbarSize;
21
31
  /** What the paused toggle calls the missed events, given `{count}`. */
22
32
  missedLabel?: string;
23
33
  }
@@ -26,12 +36,13 @@ const props = withDefaults(defineProps<LiveToggleBlockProps>(), {
26
36
  align: "right",
27
37
  showUpdatedAt: true,
28
38
  variant: "segmented",
39
+ size: undefined,
29
40
  missedLabel: undefined,
30
41
  });
31
42
 
32
43
  const DIRECT = "direct";
33
44
  const PAUSE = "pause";
34
- const TICK_MS = 1000;
45
+ const DEFAULT_SIZE: PageToolbarSize = "sm";
35
46
  const ALIGN_CLASSES: Record<
36
47
  NonNullable<LiveToggleBlockProps["align"]>,
37
48
  string
@@ -41,26 +52,53 @@ const ALIGN_CLASSES: Record<
41
52
  right: "justify-end",
42
53
  };
43
54
  const PILL_BUTTON_CLASS =
44
- "focus-visible:outline-primary flex items-center gap-1.5 rounded-[5px] px-2 py-1 text-[12px] font-medium transition-colors focus-visible:outline-2";
55
+ "focus-visible:outline-primary flex h-full items-center rounded-[5px] font-medium transition-colors focus-visible:outline-2";
45
56
  const PILL_IDLE_CLASS = "text-muted hover:text-default hover:bg-elevated";
46
57
 
58
+ /** What each size changes: the texts and the pill's buttons. */
59
+ interface LiveToggleSizeClasses {
60
+ text: string;
61
+ pillButton: string;
62
+ }
63
+
64
+ // The pill takes the toolbar's one height (its buttons fill it), as the
65
+ // period selector's segments beside it and the Resume button do.
66
+ const SIZE_CLASSES: Record<PageToolbarSize, LiveToggleSizeClasses> = {
67
+ xs: {
68
+ text: "text-[11.5px]",
69
+ pillButton: "gap-1 px-1.5 text-[11px]",
70
+ },
71
+ sm: {
72
+ text: "text-[12.5px]",
73
+ pillButton: "gap-1.5 px-2 text-[12px]",
74
+ },
75
+ md: {
76
+ text: "text-[13px]",
77
+ pillButton: "gap-1.5 px-2.5 text-[13px]",
78
+ },
79
+ };
80
+
47
81
  const { t } = useI18n();
48
82
  const translate = t as ConfirmTranslate;
49
83
  const live = usePageLive();
84
+ const toolbar = usePageToolbar();
85
+ useToolbarLiveToggle(toolbar);
86
+ const size = computed(() => props.size ?? toolbar?.size.value ?? DEFAULT_SIZE);
87
+ const sizeClasses = computed(() => SIZE_CLASSES[size.value]);
88
+ const heightClass = computed(
89
+ () => PAGE_TOOLBAR_CONTROL_HEIGHT_CLASSES[size.value],
90
+ );
50
91
 
51
- // The state lives in the browser (its storage, its clock): the server and the
52
- // hydrating render draw nothing, the mounted block draws the real state.
92
+ // The toggle is drawn from the server on, live (the remembered pause applies
93
+ // once mounted), so it takes its place before the page paints. Only the
94
+ // "updated N s ago" text waits for the browser's clock: the server's would
95
+ // not match the hydrating render's.
53
96
  const isMounted = ref(false);
54
- const now = ref(Date.now());
55
- let ticker: ReturnType<typeof setInterval> | undefined;
56
-
57
- onMounted(() => {
58
- isMounted.value = true;
59
- if (props.showUpdatedAt) {
60
- ticker = setInterval(() => (now.value = Date.now()), TICK_MS);
61
- }
62
- });
63
- onBeforeUnmount(() => clearInterval(ticker));
97
+ onMounted(() => (isMounted.value = true));
98
+ const updatedText = useUpdatedLabel(
99
+ () => live.lastUpdatedAt.value,
100
+ props.showUpdatedAt,
101
+ );
64
102
 
65
103
  const mode = computed({
66
104
  get: () => (live.isPaused.value ? PAUSE : DIRECT),
@@ -75,15 +113,9 @@ const items = computed(() => [
75
113
  { value: PAUSE, label: t("dms.live.pause"), icon: "i-ph-pause" },
76
114
  ]);
77
115
 
78
- const updatedLabel = computed(() => {
79
- const at = live.lastUpdatedAt.value;
80
- if (at === null) return "";
81
- const { unit, count } = formatElapsed(now.value - at);
82
- const updated = t(`dms.live.updated_${unit}`, { count }, count);
83
- return live.isPolling.value
84
- ? `${updated} · ${t("dms.live.polling")}`
85
- : updated;
86
- });
116
+ // The browser's clock only: the server's text would not match the hydrating
117
+ // render's.
118
+ const updatedLabel = computed(() => (isMounted.value ? updatedText.value : ""));
87
119
 
88
120
  const missedText = computed(() => {
89
121
  const count = live.missedCount.value;
@@ -122,13 +154,14 @@ useLiveToggleShortcut(() => live.toggle());
122
154
 
123
155
  <template>
124
156
  <div
125
- v-if="isMounted && live.isEnabled.value"
157
+ v-if="live.isEnabled.value"
126
158
  class="dms-live-toggle flex flex-wrap items-center gap-x-2.5 gap-y-1"
127
159
  :class="[ALIGN_CLASSES[align], `dms-live-toggle--${variant}`]"
128
160
  >
129
161
  <span
130
162
  v-if="live.isPaused.value"
131
- class="dms-live-toggle__status text-muted min-w-0 text-[12.5px] tabular-nums"
163
+ class="dms-live-toggle__status text-muted min-w-0 tabular-nums"
164
+ :class="sizeClasses.text"
132
165
  aria-live="polite"
133
166
  >
134
167
  {{ pausedText }}
@@ -136,16 +169,28 @@ useLiveToggleShortcut(() => live.toggle());
136
169
  </span>
137
170
  <span
138
171
  v-else-if="showUpdatedAt"
139
- class="text-muted text-[12.5px] tabular-nums"
172
+ class="text-muted inline-flex items-center gap-1.5 tabular-nums"
173
+ :class="sizeClasses.text"
140
174
  aria-live="off"
141
175
  >
142
176
  {{ updatedLabel }}
177
+ <!-- The polling hint, its box always kept: showing it never widens the
178
+ toggle, which would wrap the toolbar and move the page. -->
179
+ <UIcon
180
+ name="i-ph-wifi-slash"
181
+ class="dms-live-toggle__polling size-3.5 shrink-0"
182
+ :class="!live.isPolling.value && 'invisible'"
183
+ :title="live.isPolling.value ? t('dms.live.polling') : undefined"
184
+ :aria-label="live.isPolling.value ? t('dms.live.polling') : undefined"
185
+ :aria-hidden="!live.isPolling.value || undefined"
186
+ />
143
187
  </span>
144
188
  <div
145
189
  v-if="variant === 'pill'"
146
190
  role="group"
147
191
  :aria-label="t('dms.live.aria_label')"
148
- class="border-default bg-default inline-flex shrink-0 items-center gap-0.5 rounded-md border p-0.5"
192
+ class="border-default bg-default inline-flex shrink-0 items-stretch gap-0.5 rounded-md border p-0.5"
193
+ :class="heightClass"
149
194
  >
150
195
  <button
151
196
  type="button"
@@ -153,6 +198,7 @@ useLiveToggleShortcut(() => live.toggle());
153
198
  :aria-pressed="live.isLive.value"
154
199
  :class="[
155
200
  PILL_BUTTON_CLASS,
201
+ sizeClasses.pillButton,
156
202
  live.isLive.value ? 'bg-success/10 text-success' : PILL_IDLE_CLASS,
157
203
  ]"
158
204
  @click="live.resume()"
@@ -173,6 +219,7 @@ useLiveToggleShortcut(() => live.toggle());
173
219
  :aria-pressed="live.isPaused.value"
174
220
  :class="[
175
221
  PILL_BUTTON_CLASS,
222
+ sizeClasses.pillButton,
176
223
  live.isPaused.value
177
224
  ? 'bg-elevated text-highlighted'
178
225
  : PILL_IDLE_CLASS,
@@ -188,12 +235,13 @@ useLiveToggleShortcut(() => live.toggle());
188
235
  v-model="mode"
189
236
  :items="items"
190
237
  :aria-label="t('dms.live.aria_label')"
191
- size="sm"
238
+ :size="size"
192
239
  />
193
240
  <UButton
194
241
  v-if="variant === 'pill' && canResume"
195
242
  data-action="resume"
196
- size="xs"
243
+ :size="size"
244
+ :class="heightClass"
197
245
  color="neutral"
198
246
  variant="soft"
199
247
  icon="i-ph-play-fill"