@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
@@ -182,6 +182,27 @@ export function formatPeriodRange(
182
182
  return formatRangeWith(range, locale, options);
183
183
  }
184
184
 
185
+ /**
186
+ * Day-only range label, "16 – 17 Oct": a comparison menu's trigger names the
187
+ * window it compares with, where the hours would only widen it. A window
188
+ * shorter than a day keeps its times, its day alone telling nothing.
189
+ * `withYear: false` drops the year, as in `formatPeriodRange`.
190
+ */
191
+ export function formatPeriodDays(
192
+ range: PeriodRange,
193
+ locale: string,
194
+ withYear = true,
195
+ ): string {
196
+ const span = range.to.getTime() - range.from.getTime();
197
+ if (span < MS_PER_DAY - MS_PER_HOUR) {
198
+ return formatRangeWith(range, locale, { ...DAY_FORMAT, ...TIME_FORMAT });
199
+ }
200
+ const options: FormatOptions = withYear
201
+ ? { ...DAY_FORMAT, year: "numeric" }
202
+ : DAY_FORMAT;
203
+ return formatRangeWith(range, locale, options);
204
+ }
205
+
185
206
  export interface PeriodDuration {
186
207
  unit: "days" | "hours";
187
208
  count: number;
@@ -0,0 +1,28 @@
1
+ import type { PeriodPreset } from "#dms-core/app/composables/period/types";
2
+
3
+ /**
4
+ * Short mono codes, as trails in the preset list and segmented labels: every
5
+ * preset has one, so a row of segments stays as narrow with `all` as without
6
+ * it, where the full label ("All time") widened it. Custom has none: it shows
7
+ * its range. Each is translated under `dms.period.short_presets.<preset>`
8
+ * ("24 h", "7 j" in French); these English codes are the fallback for a
9
+ * locale that lacks one, and the list of presets that have a code.
10
+ *
11
+ * @internal
12
+ */
13
+ export const PRESET_SHORT_LABELS: Partial<Record<PeriodPreset, string>> = {
14
+ "last-hour": "1H",
15
+ "last-24h": "24H",
16
+ today: "1D",
17
+ yesterday: "-1D",
18
+ "last-7-days": "7D",
19
+ "last-30-days": "30D",
20
+ "last-90-days": "90D",
21
+ "this-month": "MTD",
22
+ "last-month": "-1M",
23
+ "this-quarter": "QTD",
24
+ "last-quarter": "-1Q",
25
+ ytd: "YTD",
26
+ "last-year": "-1Y",
27
+ all: "ALL",
28
+ };
@@ -84,6 +84,7 @@ export function usePeriodDraft(period: UsePeriodReturn): UsePeriodDraftReturn {
84
84
  comparison.value,
85
85
  range.value,
86
86
  period.customCompareRange.value,
87
+ { preset: preset.value, now: new Date() },
87
88
  ),
88
89
  );
89
90
 
@@ -0,0 +1,97 @@
1
+ import { onMounted, shallowRef, watch } from "vue";
2
+ import type { DataTypeSelectSource } from "#dms-core/app/composables/data-types/useDataType";
3
+ import { unlabeledRelationIds } from "../data-types/relationValue";
4
+ import {
5
+ notFoundRelationIds,
6
+ relationRowLabel,
7
+ relationRowValue,
8
+ useRelationRows,
9
+ } from "../data-types/relationRows";
10
+
11
+ /** A relation filter of a table: where its rows are read, and its value. */
12
+ export interface RelationFilterSource extends DataTypeSelectSource {
13
+ value: unknown;
14
+ }
15
+
16
+ const labelKey = (searchUrl: string, id: string) => `${searchUrl}\n${id}`;
17
+
18
+ // The plain ids a filter value holds, each once.
19
+ const NO_LABELS: ReadonlySet<string> = new Set();
20
+ const idsOf = (value: unknown) => unlabeledRelationIds(value, NO_LABELS);
21
+
22
+ /** How a chip names one id of a relation filter. */
23
+ export interface RelationFilterLabel {
24
+ label: string;
25
+ /** The id of a row the route did not answer, shown on hover. */
26
+ notFoundId?: string;
27
+ }
28
+
29
+ /**
30
+ * The labels of the ids a table's relation filters hold: a chip names the
31
+ * related row ("Acme"), not the raw id it filters on ("cust-060"). Each id is
32
+ * read once from the relation's `select` route, as its picker does; an id the
33
+ * route does not answer (deleted, out of reach) is named "Not found".
34
+ */
35
+ export function useRelationFilterLabels(sources: () => RelationFilterSource[]) {
36
+ const { readRelationRows } = useRelationRows();
37
+ const { t } = useI18n();
38
+ const labels = shallowRef<Record<string, string>>({});
39
+ const notFound = shallowRef<ReadonlySet<string>>(new Set());
40
+ const requested = new Set<string>();
41
+
42
+ async function loadLabels(source: RelationFilterSource, ids: string[]) {
43
+ for (const id of ids) requested.add(labelKey(source.searchUrl, id));
44
+ const rows = await readRelationRows(source, ids);
45
+ const found: Record<string, string> = {};
46
+ for (const row of rows) {
47
+ const id = relationRowValue(source, row);
48
+ const label = relationRowLabel(source, row);
49
+ if (id === undefined || label === undefined) continue;
50
+ found[labelKey(source.searchUrl, String(id))] = String(label);
51
+ }
52
+ if (Object.keys(found).length) {
53
+ labels.value = { ...labels.value, ...found };
54
+ }
55
+ const missing = notFoundRelationIds(source, ids, rows);
56
+ if (missing.length) {
57
+ notFound.value = new Set([
58
+ ...notFound.value,
59
+ ...missing.map((id) => labelKey(source.searchUrl, id)),
60
+ ]);
61
+ }
62
+ }
63
+
64
+ const pending = () =>
65
+ sources().flatMap((source) => {
66
+ const ids = idsOf(source.value).filter(
67
+ (id) => !requested.has(labelKey(source.searchUrl, id)),
68
+ );
69
+ return ids.length ? [{ source, ids }] : [];
70
+ });
71
+
72
+ // In the browser only: a server render draws the chips with their ids.
73
+ onMounted(() => {
74
+ watch(
75
+ pending,
76
+ (batches) => {
77
+ for (const { source, ids } of batches) void loadLabels(source, ids);
78
+ },
79
+ { immediate: true },
80
+ );
81
+ });
82
+
83
+ /**
84
+ * The labels of a relation filter's ids, in order: an id not read yet as
85
+ * is, one the route did not answer as "Not found".
86
+ */
87
+ const labelsOf = (source: RelationFilterSource): RelationFilterLabel[] =>
88
+ idsOf(source.value).map((id) => {
89
+ const key = labelKey(source.searchUrl, id);
90
+ if (notFound.value.has(key)) {
91
+ return { label: t("dms.form.relation.not_found"), notFoundId: id };
92
+ }
93
+ return { label: labels.value[key] ?? id };
94
+ });
95
+
96
+ return { labelsOf };
97
+ }
@@ -0,0 +1,106 @@
1
+ import { watch, type Ref } from "vue";
2
+
3
+ /** The attribute naming a body row's id, which the anchor finds it by. */
4
+ export const ROW_ID_ATTRIBUTE = "data-row-id";
5
+
6
+ const SCROLLABLE_OVERFLOW = /(auto|scroll)/;
7
+
8
+ /** The row the reader has at the top of the view, before an update. */
9
+ interface RowAnchor {
10
+ id: string;
11
+ row: Element;
12
+ scroller: HTMLElement;
13
+ /** Where the row stood, in viewport pixels. */
14
+ top: number;
15
+ /** Its position among the rows. */
16
+ index: number;
17
+ previousIds: ReadonlySet<string>;
18
+ }
19
+
20
+ /** The element the table scrolls in: itself, or its nearest scrolled ancestor. */
21
+ function findScroller(start: HTMLElement): HTMLElement | null {
22
+ for (
23
+ let element: HTMLElement | null = start;
24
+ element;
25
+ element = element.parentElement
26
+ ) {
27
+ if (
28
+ SCROLLABLE_OVERFLOW.test(getComputedStyle(element).overflowY) &&
29
+ element.scrollHeight > element.clientHeight
30
+ )
31
+ return element;
32
+ }
33
+ return null;
34
+ }
35
+
36
+ function captureAnchor(
37
+ root: HTMLElement | null,
38
+ ids: readonly string[],
39
+ ): RowAnchor | null {
40
+ if (!root) return null;
41
+ const scroller = findScroller(root);
42
+ // At the top of the list, new rows land in view: what a live list is for.
43
+ if (!scroller || scroller.scrollTop <= 0) return null;
44
+ const viewTop = scroller.getBoundingClientRect().top;
45
+ for (const row of root.querySelectorAll(`tr[${ROW_ID_ATTRIBUTE}]`)) {
46
+ const top = row.getBoundingClientRect().top;
47
+ if (top < viewTop) continue;
48
+ const id = row.getAttribute(ROW_ID_ATTRIBUTE)!;
49
+ return {
50
+ id,
51
+ row,
52
+ scroller,
53
+ top,
54
+ index: ids.indexOf(id),
55
+ previousIds: new Set(ids),
56
+ };
57
+ }
58
+ return null;
59
+ }
60
+
61
+ // Rows were only added above the anchor (a live list's new rows): a sort, a
62
+ // filter or another page moves the reader on, and is left alone.
63
+ function isPrepend(anchor: RowAnchor, ids: readonly string[]): boolean {
64
+ const index = ids.indexOf(anchor.id);
65
+ if (anchor.index < 0 || index <= anchor.index) return false;
66
+ const added = ids
67
+ .slice(0, index)
68
+ .filter((id) => !anchor.previousIds.has(id)).length;
69
+ return added === index - anchor.index;
70
+ }
71
+
72
+ /**
73
+ * Keeps the rows the reader is looking at where they are when rows are added
74
+ * above them (a live list's new rows), by scrolling as far as the rows push
75
+ * them, in the frame they land. The browser's own scroll anchoring does not
76
+ * hold a table's rows. At the top of the list nothing is held: the new rows
77
+ * show, as a live list means them to.
78
+ *
79
+ * @param root The table, whose body rows carry `ROW_ID_ATTRIBUTE`
80
+ * @param rowIds The ids of the body rows, in order
81
+ */
82
+ export function useRowScrollAnchor(
83
+ root: Readonly<Ref<HTMLElement | null>>,
84
+ rowIds: () => readonly string[],
85
+ ): void {
86
+ if (typeof window === "undefined") return;
87
+ let anchor: RowAnchor | null = null;
88
+ watch(
89
+ rowIds,
90
+ (_next, previous) => {
91
+ anchor = captureAnchor(root.value, previous ?? []);
92
+ },
93
+ { flush: "pre" },
94
+ );
95
+ watch(
96
+ rowIds,
97
+ (next) => {
98
+ const held = anchor;
99
+ anchor = null;
100
+ if (!held || !held.row.isConnected || !isPrepend(held, next)) return;
101
+ const moved = held.row.getBoundingClientRect().top - held.top;
102
+ if (moved !== 0) held.scroller.scrollTop += moved;
103
+ },
104
+ { flush: "post" },
105
+ );
106
+ }
@@ -185,6 +185,15 @@ export const useTable = <T extends Data>(props: UseTableProps<T>) => {
185
185
  },
186
186
  );
187
187
 
188
+ // A display that cannot sort (a hand-ordered table) leaves every header
189
+ // without its sort button, arrow and menu items.
190
+ watch(
191
+ () => props.tableProps.sortingOptions?.enableSorting,
192
+ (enableSorting) => {
193
+ table.setOptions((prev) => ({ ...prev, enableSorting }));
194
+ },
195
+ );
196
+
188
197
  const labeledColumns: ComputedRef<LabeledColumn<T>[]> = computed(() => {
189
198
  return table
190
199
  .getAllColumns()
@@ -471,9 +471,8 @@ export const useTableColumns = <T extends Data>(config: ColumnConfig<T>) => {
471
471
 
472
472
  const isShowingArchived = () => config.showArchived?.value === true;
473
473
 
474
- // The backend serializes `isVisible`; `visible` is the former spelling.
475
474
  const isCustomActionInline = (action: CustomRowAction): boolean =>
476
- action.isVisible === true || action.visible === true;
475
+ action.isVisible === true;
477
476
 
478
477
  const { confirmAction } = useActionConfirm();
479
478
 
@@ -0,0 +1,31 @@
1
+ type Translate = (
2
+ key: string,
3
+ named: Record<string, unknown>,
4
+ plural: number,
5
+ ) => string;
6
+
7
+ /**
8
+ * The caption count while a table lists its archived rows. The default
9
+ * ("Archived: 13", "Archives : 13") agrees with no noun, so it reads right
10
+ * over invoices as over members; a table view's `archivedCountLabel` names
11
+ * its rows instead: a `$` key receiving `{ count }` and pluralized on it, or
12
+ * a text whose `{count}` is replaced.
13
+ */
14
+ export function formatArchivedCount(
15
+ translate: Translate,
16
+ count: number,
17
+ formattedCount: string,
18
+ label?: string,
19
+ ): string {
20
+ if (!label) {
21
+ return translate(
22
+ "dms.table.archived_count",
23
+ { count: formattedCount },
24
+ count,
25
+ );
26
+ }
27
+ if (label.startsWith("$")) {
28
+ return translate(label.slice(1), { count: formattedCount }, count);
29
+ }
30
+ return label.replaceAll("{count}", formattedCount);
31
+ }
@@ -0,0 +1,38 @@
1
+ import type {
2
+ DataType,
3
+ DataTypeFilterEditorValue,
4
+ } from "#dms-core/app/composables/data-types/useDataType";
5
+
6
+ // A view or tab declares its filter value as text (`TableViewTabFilter.value`),
7
+ // the shape the query sends. The chip's value editor wants the value its
8
+ // column's data type holds there ({@link DataType.filterEditorValue}). The
9
+ // editor reads the converted value; the filter keeps its text until the user
10
+ // edits it, so the query is unchanged.
11
+
12
+ const LIST_SEPARATOR = ",";
13
+ const BETWEEN_MODE = "is_between";
14
+
15
+ /** A number type's filter: the number a numeric text names, else the text. */
16
+ export const numberFilterEditorValue: DataTypeFilterEditorValue = (text) => {
17
+ const parsed = Number(text);
18
+ return text.trim() !== "" && Number.isFinite(parsed) ? parsed : text;
19
+ };
20
+
21
+ /** A date's filter: the two days of a range, a single day as written. */
22
+ export const dateFilterEditorValue: DataTypeFilterEditorValue = (text, mode) =>
23
+ mode === BETWEEN_MODE
24
+ ? text.split(LIST_SEPARATOR).map((item) => item.trim())
25
+ : text;
26
+
27
+ /**
28
+ * The value a filter's editor reads: a declared text value converted by its
29
+ * column's data type, any other value as it is.
30
+ */
31
+ export function filterEditorValue(
32
+ dataType: Pick<DataType, "filterEditorValue"> | undefined,
33
+ mode: string,
34
+ value: unknown,
35
+ ): unknown {
36
+ if (typeof value !== "string" || !dataType?.filterEditorValue) return value;
37
+ return dataType.filterEditorValue(value, mode);
38
+ }
@@ -6,6 +6,32 @@ interface FilterColumn {
6
6
  type?: DataTypeConfig;
7
7
  }
8
8
 
9
+ const DAY_MS = 24 * 60 * 60 * 1000;
10
+ // `{{now}}`, `{{now-7d}}`, `{{now+14d}}`: the tokens the server resolves in a
11
+ // filter value (`filter-tokens.ts`), read here with the same rule.
12
+ const NOW_TOKEN = /\{\{\s*now(?:\s*([+-])\s*(\d+)d)?\s*\}\}/g;
13
+ const BETWEEN_MODE = "is_between";
14
+ const BETWEEN_SEPARATOR = ",";
15
+
16
+ /** A filter value with its `{{now±Nd}}` tokens replaced by that instant. */
17
+ export function resolveNowTokens(value: string, now = Date.now()): string {
18
+ return value.replace(NOW_TOKEN, (_match, sign?: string, days?: string) => {
19
+ const offset = sign ? Number(`${sign}${days}`) * DAY_MS : 0;
20
+ return new Date(now + offset).toISOString();
21
+ });
22
+ }
23
+
24
+ /**
25
+ * The value as the formatters read it: tokens resolved, and an `is_between`
26
+ * bound pair written as text (`a,b`, the way the query sends it) split.
27
+ */
28
+ const readableValue = (filter: TableFilter): unknown => {
29
+ if (typeof filter.value !== "string") return filter.value;
30
+ const value = resolveNowTokens(filter.value);
31
+ if (filter.mode !== BETWEEN_MODE) return value;
32
+ return value.split(BETWEEN_SEPARATOR).map((bound) => bound.trim());
33
+ };
34
+
9
35
  export function formatFilterValue(
10
36
  filter: TableFilter,
11
37
  columns: FilterColumn[],
@@ -23,9 +49,14 @@ export function formatFilterValue(
23
49
  const formatter =
24
50
  dataType.formatter[filter.mode] || dataType.formatter.default;
25
51
 
52
+ // A filter's value need not have the column's shape (a tag typed against
53
+ // a tags column, whose formatter draws a list): a formatter that writes
54
+ // nothing for it leaves the value as typed.
26
55
  try {
27
- const result = formatter(filter.value, locale);
28
- return typeof result === "string" ? result : String(filter.value);
56
+ const result = formatter(readableValue(filter), locale);
57
+ return typeof result === "string" && result !== ""
58
+ ? result
59
+ : String(filter.value);
29
60
  } catch {
30
61
  return String(filter.value);
31
62
  }
@@ -0,0 +1,31 @@
1
+ /** Which sides of a scroll area hide content the reader can scroll to. */
2
+ export interface ScrollEdges {
3
+ start: boolean;
4
+ end: boolean;
5
+ }
6
+
7
+ /** What {@link horizontalScrollEdges} reads off a scroll area. */
8
+ export interface HorizontalScroll {
9
+ scrollLeft: number;
10
+ scrollWidth: number;
11
+ clientWidth: number;
12
+ }
13
+
14
+ // Sub-pixel widths leave a fraction of a pixel to scroll at either end.
15
+ const EDGE_TOLERANCE_PX = 1;
16
+
17
+ /**
18
+ * The sides of a table wider than its card that still hide columns: a phone
19
+ * shows the first columns, and nothing else would tell that the rest are a
20
+ * swipe away. The scroll position is read as the reader sees it, so a
21
+ * right-to-left page (negative `scrollLeft`) reads the same.
22
+ */
23
+ export function horizontalScrollEdges(area: HorizontalScroll): ScrollEdges {
24
+ const hidden = area.scrollWidth - area.clientWidth;
25
+ if (hidden <= EDGE_TOLERANCE_PX) return { start: false, end: false };
26
+ const scrolled = Math.abs(area.scrollLeft);
27
+ return {
28
+ start: scrolled > EDGE_TOLERANCE_PX,
29
+ end: scrolled < hidden - EDGE_TOLERANCE_PX,
30
+ };
31
+ }
@@ -0,0 +1,33 @@
1
+ import { get } from "@nuxt/ui/runtime/utils/index.js";
2
+ import { h, type VNodeChild } from "vue";
3
+ import type { TableViewColumn } from "../../../composables/table-view/types/column";
4
+ import { useColumnValueRenderer } from "../data-types/useColumnValueRenderer";
5
+
6
+ type Row = Record<string, unknown>;
7
+
8
+ /**
9
+ * Draws a row's fields on a card (the cards display, the kanban board) the
10
+ * way the grid draws its cells: through the column's `display`, else its data
11
+ * type, handed the row for what a display reads beside the value (a progress
12
+ * bar's total, a tone, an avatar).
13
+ */
14
+ export function useCardFields() {
15
+ const { renderColumnValue } = useColumnValueRenderer();
16
+
17
+ const renderField = (column: TableViewColumn, item: Row): VNodeChild => {
18
+ const rendered = renderColumnValue(column, item, "cell");
19
+ if (typeof rendered !== "string" && typeof rendered !== "number") {
20
+ return rendered;
21
+ }
22
+ const value = get(item, column.accessorKey);
23
+ // The fallback of a missing value reads dimmed, as an empty cell does.
24
+ const isMissing = value === null || value === undefined;
25
+ return h(
26
+ "span",
27
+ { class: isMissing ? "text-dimmed truncate" : "truncate" },
28
+ String(rendered),
29
+ );
30
+ };
31
+
32
+ return { renderField };
33
+ }
@@ -0,0 +1,48 @@
1
+ import { onMounted, type Ref } from "vue";
2
+ import type { TableViewViewStateConfig } from "../../../composables/table-view/types";
3
+
4
+ /** The viewports a table view opens on its `mobileDisplay`: a phone's. */
5
+ export const PHONE_VIEWPORT_QUERY = "(max-width: 767px)";
6
+
7
+ export interface MobileDisplayOptions {
8
+ /** The table view's `mobileDisplay`, if any. */
9
+ mobileDisplay: string | undefined;
10
+ /**
11
+ * The display the table counts as its default, starting at its resolved
12
+ * `defaultDisplay`: the phone's once applied, so it is not saved as the
13
+ * user's choice, and a view that sets no display opens on it.
14
+ */
15
+ defaultDisplay: Ref<string>;
16
+ /** The ids of the displays the table view offers. */
17
+ offered: ReadonlySet<string>;
18
+ /** The display shown. */
19
+ display: Ref<string>;
20
+ /**
21
+ * The view open, if any. One that fixes a display keeps it; one that
22
+ * leaves the display out (a `defaultView` showing every row) does not.
23
+ */
24
+ activeView: Ref<TableViewViewStateConfig | undefined>;
25
+ /**
26
+ * The display shown is someone's choice — the user saved one, the default
27
+ * display is the only way in: a phone keeps it.
28
+ */
29
+ isDisplayChosen: () => boolean;
30
+ }
31
+
32
+ /**
33
+ * Opens a phone on the table view's `mobileDisplay` once mounted, the
34
+ * server render having drawn the default display, when nobody chose one.
35
+ * Called after the table's views, so a view resolved once mounted is open.
36
+ */
37
+ export function useMobileDisplay(options: MobileDisplayOptions): void {
38
+ onMounted(() => {
39
+ const { mobileDisplay, offered } = options;
40
+ if (!mobileDisplay || !offered.has(mobileDisplay)) return;
41
+ if (options.isDisplayChosen()) return;
42
+ const viewDisplay = options.activeView.value?.display;
43
+ if (viewDisplay && offered.has(viewDisplay)) return;
44
+ if (!window.matchMedia?.(PHONE_VIEWPORT_QUERY).matches) return;
45
+ options.defaultDisplay.value = mobileDisplay;
46
+ options.display.value = mobileDisplay;
47
+ });
48
+ }
@@ -1,20 +1,7 @@
1
1
  import { onBeforeUnmount, onMounted, ref, type Ref } from "vue";
2
+ import { scrollParent } from "../../utils/scrollWithin";
2
3
 
3
4
  const FIRST_ROW_SELECTOR = "tbody tr";
4
- const SCROLLABLE_OVERFLOW = /(auto|scroll)/;
5
-
6
- function scrollParent(element: HTMLElement): HTMLElement | null {
7
- for (let node = element.parentElement; node; node = node.parentElement) {
8
- const { overflowY } = getComputedStyle(node);
9
- if (
10
- SCROLLABLE_OVERFLOW.test(overflowY) &&
11
- node.scrollHeight > node.clientHeight
12
- ) {
13
- return node;
14
- }
15
- }
16
- return null;
17
- }
18
5
 
19
6
  /**
20
7
  * Whether the first row of the table in `element` is out of view above the
@@ -29,6 +16,21 @@ export function isFirstRowScrolledPast(element: HTMLElement): boolean {
29
16
  return row.getBoundingClientRect().bottom <= viewTop;
30
17
  }
31
18
 
19
+ /**
20
+ * Whether the table in `element` is on screen or above it: its top stands
21
+ * above the bottom of the area it scrolls in (its scrolling parent, else the
22
+ * window). Rows added to such a table move rows the user can see, or did.
23
+ *
24
+ * @internal
25
+ */
26
+ export function isTableReached(element: HTMLElement): boolean {
27
+ const scroller = scrollParent(element);
28
+ const viewBottom = scroller
29
+ ? scroller.getBoundingClientRect().bottom
30
+ : window.innerHeight;
31
+ return element.getBoundingClientRect().top < viewBottom;
32
+ }
33
+
32
34
  /**
33
35
  * Follows whether the user scrolled the table's first row out of view, on
34
36
  * any scroll of the page (read once per frame). Inert when `isEnabled` is
@@ -40,13 +42,34 @@ export function useScrolledPastTop(
40
42
  element: () => HTMLElement | null,
41
43
  isEnabled: boolean,
42
44
  ): Ref<boolean> {
43
- const isScrolled = ref(false);
44
- if (!isEnabled || typeof window === "undefined") return isScrolled;
45
+ return useScrollMeasure(element, isEnabled, isFirstRowScrolledPast);
46
+ }
47
+
48
+ /**
49
+ * Follows whether the table is on screen or scrolled past (`isTableReached`),
50
+ * like `useScrolledPastTop`.
51
+ *
52
+ * @internal
53
+ */
54
+ export function useTableReached(
55
+ element: () => HTMLElement | null,
56
+ isEnabled: boolean,
57
+ ): Ref<boolean> {
58
+ return useScrollMeasure(element, isEnabled, isTableReached);
59
+ }
60
+
61
+ function useScrollMeasure(
62
+ element: () => HTMLElement | null,
63
+ isEnabled: boolean,
64
+ test: (element: HTMLElement) => boolean,
65
+ ): Ref<boolean> {
66
+ const isMatching = ref(false);
67
+ if (!isEnabled || typeof window === "undefined") return isMatching;
45
68
  let frame: number | null = null;
46
69
  const measure = () => {
47
70
  frame = null;
48
71
  const target = element();
49
- isScrolled.value = !!target && isFirstRowScrolledPast(target);
72
+ isMatching.value = !!target && test(target);
50
73
  };
51
74
  const onScroll = () => {
52
75
  frame ??= requestAnimationFrame(measure);
@@ -62,5 +85,5 @@ export function useScrolledPastTop(
62
85
  window.removeEventListener("scroll", onScroll, { capture: true });
63
86
  if (frame !== null) cancelAnimationFrame(frame);
64
87
  });
65
- return isScrolled;
88
+ return isMatching;
66
89
  }
@@ -1,3 +1,4 @@
1
+ import { readableNumberFormat } from "#dms-core/app/utils/formatter";
1
2
  import type { ComputedRef } from "vue";
2
3
  import type {
3
4
  TableViewColumn,
@@ -69,7 +70,7 @@ export function useTableFooter(options: TableFooterOptions) {
69
70
  ) => {
70
71
  if (value === undefined) return undefined;
71
72
  if (summary.format) {
72
- return new Intl.NumberFormat(locale.value, summary.format).format(value);
73
+ return readableNumberFormat(locale.value, summary.format).format(value);
73
74
  }
74
75
  const column =
75
76
  summary.op === "sum"
@@ -77,7 +78,7 @@ export function useTableFooter(options: TableFooterOptions) {
77
78
  : undefined;
78
79
  return column
79
80
  ? renderValue(column, value, undefined)
80
- : new Intl.NumberFormat(locale.value).format(value);
81
+ : readableNumberFormat(locale.value).format(value);
81
82
  };
82
83
 
83
84
  const legendColumn = columns.find(