@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
@@ -54,6 +54,12 @@ export interface ResolvedTableView extends TableViewViewConfig {
54
54
 
55
55
  export const VIEW_URL_KEY = "view";
56
56
  const NO_VIEW = "";
57
+
58
+ /**
59
+ * The open view as the preferences keep it: no view open is kept too, so the
60
+ * next visit does not fall back to the default view.
61
+ */
62
+ export const keptViewId = (id: string | undefined): string => id ?? NO_VIEW;
57
63
  const VIEW_NAME_FIELD = "label";
58
64
 
59
65
  /**
@@ -128,11 +134,58 @@ export function useTableViews(options: TableViewsOptions) {
128
134
  applySnapshot(viewSnapshot(view, defaults));
129
135
  };
130
136
 
137
+ /**
138
+ * Opens a view, or, for the one already open, closes it: back to the
139
+ * table's own default state without a view.
140
+ */
141
+ const selectView = (id: string) =>
142
+ openView(id === activeViewId.value ? undefined : id);
143
+
144
+ /**
145
+ * Back on the view kept from the last visit. The table's state is kept
146
+ * apart and restored as it was left, modified or not, but the search is
147
+ * not kept: the view's own comes back with it.
148
+ */
149
+ const restoreKeptView = (id: string) => {
150
+ const view = findView(id);
151
+ activeViewId.value = view?.id;
152
+ if (view) state.globalFilter.value = view.search || undefined;
153
+ };
154
+
155
+ /**
156
+ * Opens a link followed on the table already shown as a linked arrival
157
+ * reads it: the view it names, else the default view, else the table's
158
+ * default listing state under the reader's own presentation.
159
+ */
160
+ const openLinkedView = (query: Record<string, unknown>) => {
161
+ const named = readTableUrlKey(query, urlScope, VIEW_URL_KEY);
162
+ const view = named.isCleared
163
+ ? undefined
164
+ : (findView(named.value) ?? findView(views?.defaultView));
165
+ if (view) return openView(view.id);
166
+ openDefaultListing();
167
+ };
168
+
169
+ /**
170
+ * No view open, the table's default listing state (filters, search, sort)
171
+ * under the reader's own presentation: what a link to no view opens.
172
+ */
173
+ const openDefaultListing = () => {
174
+ const snapshot = viewSnapshot(undefined, defaults);
175
+ activeViewId.value = undefined;
176
+ state.columnFilters.value = snapshot.filters.map((filter) => ({
177
+ ...filter,
178
+ }));
179
+ state.globalFilter.value = undefined;
180
+ state.sorting.value = snapshot.sorting;
181
+ state.pagination.value = { ...state.pagination.value, pageIndex: 0 };
182
+ };
183
+
131
184
  // Read once, on arrival: a view named by the URL wins over the state kept
132
185
  // from the last visit; a view the caller may not open falls back to the
133
186
  // default one, with a word (on the client, once mounted). The user's views
134
187
  // are kept in the browser, so a view that is not the module's is resolved
135
- // once mounted.
188
+ // once mounted. An empty one (`?view=`) names no view.
136
189
  const urlView = readTableUrlKey(route.query, urlScope, VIEW_URL_KEY);
137
190
  const persistedViewId = preferences.get<string | undefined>(
138
191
  "activeView",
@@ -141,7 +194,9 @@ export function useTableViews(options: TableViewsOptions) {
141
194
  let isUrlViewUnavailable = false;
142
195
  let isUrlViewPending = false;
143
196
  let isOpenedOnArrival = false;
144
- if (urlView.value && findView(urlView.value)) {
197
+ if (urlView.isCleared) {
198
+ openDefaultListing();
199
+ } else if (urlView.value && findView(urlView.value)) {
145
200
  openView(urlView.value);
146
201
  isOpenedOnArrival = true;
147
202
  } else if (urlView.value) {
@@ -150,7 +205,7 @@ export function useTableViews(options: TableViewsOptions) {
150
205
  if (views?.defaultView) openView(views.defaultView);
151
206
  isOpenedOnArrival = !!views?.defaultView;
152
207
  } else {
153
- activeViewId.value = findView(persistedViewId)?.id;
208
+ restoreKeptView(persistedViewId);
154
209
  }
155
210
 
156
211
  // The browser-only state (order, density) is restored over the view opened
@@ -168,7 +223,7 @@ export function useTableViews(options: TableViewsOptions) {
168
223
  } else if (isOpenedOnArrival) {
169
224
  reapplyBrowserState();
170
225
  } else if (persistedViewId && !activeViewId.value) {
171
- activeViewId.value = findView(persistedViewId)?.id;
226
+ restoreKeptView(persistedViewId);
172
227
  }
173
228
  };
174
229
 
@@ -189,7 +244,11 @@ export function useTableViews(options: TableViewsOptions) {
189
244
  });
190
245
 
191
246
  watch(activeViewId, (id) => {
192
- preferences.set<string | undefined>("activeView", id ?? NO_VIEW, undefined);
247
+ preferences.set<string | undefined>(
248
+ "activeView",
249
+ keptViewId(id),
250
+ undefined,
251
+ );
193
252
  });
194
253
 
195
254
  const isModified = computed(() => {
@@ -265,11 +324,26 @@ export function useTableViews(options: TableViewsOptions) {
265
324
  activeViewId.value = view.id;
266
325
  };
267
326
 
268
- /** Deletes one of the user's views; the table keeps its state. */
269
- const deleteView = (id: string) => {
270
- if (!findView(id)?.isUserView) return;
271
- writeUserViews(storedUserViews.value.filter((view) => view.id !== id));
272
- if (activeViewId.value === id) activeViewId.value = undefined;
327
+ /**
328
+ * Deletes one of the user's views, once confirmed. Deleting the open one
329
+ * goes back to the table's own default state: what it set (its search
330
+ * above all) would otherwise stay applied with no view to show it.
331
+ */
332
+ const deleteView = async (id: string) => {
333
+ const view = findView(id);
334
+ if (!view?.isUserView) return;
335
+ const isConfirmed = await confirm({
336
+ title: t("dms.table.views.delete_title"),
337
+ description: t("dms.table.views.delete_description", {
338
+ view: view.label,
339
+ }),
340
+ icon: "i-ph-trash",
341
+ color: "error",
342
+ confirmLabel: t("dms.table.views.delete"),
343
+ });
344
+ if (!isConfirmed) return;
345
+ writeUserViews(storedUserViews.value.filter((stored) => stored.id !== id));
346
+ if (activeViewId.value === id) openView(undefined);
273
347
  };
274
348
 
275
349
  return {
@@ -279,6 +353,8 @@ export function useTableViews(options: TableViewsOptions) {
279
353
  isModified,
280
354
  canSaveViews: !!views?.userViews,
281
355
  openView,
356
+ selectView,
357
+ openLinkedView,
282
358
  resetView,
283
359
  saveView,
284
360
  saveAsNewView,
@@ -90,10 +90,20 @@ export interface BulkActionOutcome {
90
90
 
91
91
  // The count each bulk route answers with: delete sends the number of deleted
92
92
  // rows (or `{ deleted }` when a row rule filtered out every id), archive and
93
- // restore send `{ archivedCount }` / `{ restoredCount }`.
94
- const COUNT_KEYS = ["deleted", "archivedCount", "restoredCount"] as const;
93
+ // restore send `{ archivedCount }` / `{ restoredCount }`, and a custom bulk
94
+ // route `{ count }`.
95
+ const COUNT_KEYS = [
96
+ "count",
97
+ "deleted",
98
+ "archivedCount",
99
+ "restoredCount",
100
+ ] as const;
95
101
 
96
- const readCount = (response: unknown): number | undefined => {
102
+ /**
103
+ * The number of rows a bulk route says it changed: a bare number or one of
104
+ * its count keys, `undefined` when it does not say.
105
+ */
106
+ export const readBulkCount = (response: unknown): number | undefined => {
97
107
  if (typeof response === "number") return response;
98
108
  if (!response || typeof response !== "object") return undefined;
99
109
  for (const key of COUNT_KEYS) {
@@ -112,7 +122,7 @@ export function bulkActionOutcome(
112
122
  response: unknown,
113
123
  requested: number,
114
124
  ): BulkActionOutcome {
115
- const count = readCount(response);
125
+ const count = readBulkCount(response);
116
126
  const processed =
117
127
  count === undefined ? requested : Math.min(Math.max(count, 0), requested);
118
128
  return { processed, skipped: requested - processed };
@@ -1,3 +1,4 @@
1
+ import type { CSSProperties } from "vue";
1
2
  import { get } from "@nuxt/ui/runtime/utils/index.js";
2
3
  import type {
3
4
  TableViewCardProps,
@@ -68,8 +69,132 @@ export function cardFieldColumns(
68
69
  ]);
69
70
  return columns
70
71
  .filter(
71
- (column) =>
72
- column.listable !== false && !skipped.has(column.accessorKey),
72
+ (column) => column.listable !== false && !skipped.has(column.accessorKey),
73
73
  )
74
74
  .slice(0, FALLBACK_CARD_FIELD_COUNT);
75
75
  }
76
+
77
+ /** The id of the data type drawing a name with its avatar and subtitle. */
78
+ const IDENTITY_DISPLAY_TYPE = "identity";
79
+
80
+ /** The database id: a technical key, never worth a line on a card. */
81
+ const DATABASE_ID_KEY = "_id";
82
+
83
+ /**
84
+ * What a built-in card reads off its row besides its title: the subtitle and
85
+ * the avatar the title column's `identity` display declares, so a card shows
86
+ * the row as its grid cell does.
87
+ */
88
+ export interface CardIdentity {
89
+ /** Row field drawn under the title. */
90
+ subtitleField?: string;
91
+ /** Row field holding the avatar: an image value or a URL. */
92
+ avatarField?: string;
93
+ /** File storage the avatar is read from. */
94
+ storage?: string;
95
+ }
96
+
97
+ const optionString = (
98
+ options: Record<string, unknown> | undefined,
99
+ key: string,
100
+ ): string | undefined =>
101
+ typeof options?.[key] === "string" ? options[key] : undefined;
102
+
103
+ /** The identity the title column (`labelKey`) declares, if it is one. */
104
+ export function cardIdentity(
105
+ columns: TableViewColumn[],
106
+ labelKey: string | undefined,
107
+ ): CardIdentity {
108
+ if (!labelKey) return {};
109
+ const display = columns.find(
110
+ (column) => column.accessorKey === labelKey,
111
+ )?.display;
112
+ if (display?.type !== IDENTITY_DISPLAY_TYPE) return {};
113
+ return {
114
+ subtitleField: optionString(display.options, "subtitleField"),
115
+ avatarField: optionString(display.options, "avatarField"),
116
+ storage: optionString(display.options, "storage"),
117
+ };
118
+ }
119
+
120
+ const hasValue = (value: unknown): boolean =>
121
+ value !== undefined && value !== null && value !== "";
122
+
123
+ /**
124
+ * The line a card draws under its title, as the row holds it: the identity's
125
+ * subtitle, else the row's id when the table names it with a key of its own
126
+ * (`rowIdKey: "code"`) and the title is not it already. The database `_id`
127
+ * is never shown: a UUID or `proj-074` tells the reader nothing.
128
+ */
129
+ export function cardSubtitle<T>(
130
+ item: T,
131
+ identity: CardIdentity,
132
+ shown: CardShownKeys,
133
+ ): unknown {
134
+ const row = item as Record<string, unknown>;
135
+ if (identity.subtitleField) {
136
+ const subtitle: unknown = get(row, identity.subtitleField);
137
+ if (hasValue(subtitle)) return subtitle;
138
+ }
139
+ if (shown.rowIdKey === DATABASE_ID_KEY) return undefined;
140
+ if (!shown.labelKey || !hasValue(get(row, shown.labelKey))) return undefined;
141
+ const id = cardRowId(item, shown.rowIdKey);
142
+ return id || undefined;
143
+ }
144
+
145
+ /** The avatar a card draws in place of the title's initials, if any. */
146
+ export function cardAvatar<T>(
147
+ item: T,
148
+ identity: CardIdentity,
149
+ ): { key: string } | string | undefined {
150
+ if (!identity.avatarField) return undefined;
151
+ const avatar: unknown = get(
152
+ item as Record<string, unknown>,
153
+ identity.avatarField,
154
+ );
155
+ if (typeof avatar === "string") return avatar || undefined;
156
+ if (avatar && typeof avatar === "object" && "key" in avatar) {
157
+ const { key } = avatar as { key: unknown };
158
+ return typeof key === "string" && key ? { key } : undefined;
159
+ }
160
+ return undefined;
161
+ }
162
+
163
+ /** The narrowest a card of the `cards` display gets (backend `card.minWidth`). */
164
+ export const DEFAULT_CARD_MIN_WIDTH = "230px";
165
+
166
+ // The grid's gap and side padding, as `CARD_GRID_CLASS` sets them: the width
167
+ // cap below adds them to the cards' own.
168
+ const CARD_GRID_GAP = "0.75rem";
169
+ const CARD_GRID_PADDING = "36px";
170
+
171
+ // How much wider than `card.minWidth` a card may stretch when the row has room
172
+ // for more cards than the list holds. A full row hardly reaches it: n cards
173
+ // fit, but not n + 1, so each one stays under twice the minimum, give or take
174
+ // a gap.
175
+ const CARD_MAX_WIDTH_FACTOR = 2;
176
+
177
+ /**
178
+ * The cards grid: auto-fit tracks of at least `--dms-card-min-width` (the
179
+ * whole row on a narrower screen), so a short list's empty tracks collapse
180
+ * and its cards stretch across the row instead of leaving a hole at its end.
181
+ */
182
+ export const CARD_GRID_CLASS =
183
+ "grid grid-cols-[repeat(auto-fit,minmax(min(var(--dms-card-min-width),100%),1fr))] gap-3 px-[18px] pt-4 pb-[18px]";
184
+
185
+ /**
186
+ * The style of a cards grid holding `count` cards of at least `minWidth` (a
187
+ * CSS length): the width its tracks start from, and a cap keeping one or two
188
+ * cards from stretching across a whole wide screen.
189
+ */
190
+ export function cardGridStyle(
191
+ minWidth: string | undefined,
192
+ count: number,
193
+ ): CSSProperties {
194
+ const width = minWidth || DEFAULT_CARD_MIN_WIDTH;
195
+ const cards = Math.max(1, count);
196
+ return {
197
+ "--dms-card-min-width": width,
198
+ maxWidth: `calc(${cards} * ${CARD_MAX_WIDTH_FACTOR} * ${width} + ${cards - 1} * ${CARD_GRID_GAP} + ${CARD_GRID_PADDING})`,
199
+ };
200
+ }
@@ -0,0 +1,13 @@
1
+ /** The text of a cell value, as the browser searches and sorts it. */
2
+ export const textOf = (value: unknown): string =>
3
+ value === null || value === undefined
4
+ ? ""
5
+ : typeof value === "object"
6
+ ? JSON.stringify(value)
7
+ : String(value);
8
+
9
+ /** Orders two cell values the browser sorts: numbers by value, else as text. */
10
+ export const compareValues = (a: unknown, b: unknown): number => {
11
+ if (typeof a === "number" && typeof b === "number") return a - b;
12
+ return textOf(a).localeCompare(textOf(b), undefined, { numeric: true });
13
+ };
@@ -49,3 +49,31 @@ export function keepListedRows(
49
49
  Object.entries(expanded).filter(([id]) => listed.has(id)),
50
50
  );
51
51
  }
52
+
53
+ /** A table's open rows, and the listed set (its query) they belong to. */
54
+ export interface ExpandedRowsState {
55
+ expanded: ExpandedRowMap;
56
+ listedKey?: string;
57
+ }
58
+
59
+ /**
60
+ * The open rows once the listed rows change: a newly listed set (another
61
+ * `listedKey`: page, filter, search, sort, tab, archive view) opens with
62
+ * `defaultExpanded`; a refresh of the same set keeps what the user opened,
63
+ * minus rows no longer listed. Rows that have not arrived yet (`undefined`,
64
+ * the list still loading, as on a first load) list no set: the default waits
65
+ * for them instead of being spent on an empty table.
66
+ */
67
+ export function expandedRowsOnListing(
68
+ state: ExpandedRowsState,
69
+ rowIds: string[] | undefined,
70
+ listedKey: string,
71
+ mode?: ExpandedDefault,
72
+ single?: boolean,
73
+ ): ExpandedRowsState {
74
+ if (!rowIds) return state;
75
+ if (listedKey === state.listedKey) {
76
+ return { listedKey, expanded: keepListedRows(state.expanded, rowIds) };
77
+ }
78
+ return { listedKey, expanded: defaultExpandedRows(rowIds, mode, single) };
79
+ }
@@ -1,4 +1,5 @@
1
- import type { SortingState } from "@tanstack/vue-table";
1
+ import { get } from "@nuxt/ui/runtime/utils/index.js";
2
+ import type { ColumnSort, SortingState } from "@tanstack/vue-table";
2
3
  import {
3
4
  regionalDateTimeFormat,
4
5
  regionalDayKey,
@@ -13,6 +14,7 @@ import type {
13
14
  TableViewGroupedConfig,
14
15
  } from "../../../../composables/table-view/types";
15
16
  import type { TableFilter } from "../../../components/table/Table.vue";
17
+ import { compareValues } from "./compareValues";
16
18
  import { sortValueKind } from "./sortableColumns";
17
19
 
18
20
  /** Key of the group of rows without a value. */
@@ -146,6 +148,91 @@ export function groupedSorting(
146
148
  return [{ id: groupByField, desc: own ? !!own.desc : by !== "value" }];
147
149
  }
148
150
 
151
+ /**
152
+ * The sort a group's rows follow on the grouped display. The list route sorts
153
+ * on one field, the grouped column, so the sort picked on another column —
154
+ * by the user or a view, or the hand order of a `reorder` table — orders each
155
+ * group's rows in the browser instead, over the page listed.
156
+ */
157
+ export function withinGroupSort(
158
+ picked: SortingState,
159
+ grouped: TableViewGroupedConfig,
160
+ ): ColumnSort | undefined {
161
+ return picked.find((entry) => entry.id !== grouped.groupByField);
162
+ }
163
+
164
+ /** What decides the order a table view lists its rows in. */
165
+ export interface TableSortingInput {
166
+ /** The order field of a hand-ordered table (`reorder`). */
167
+ reorderField?: string;
168
+ /** The grouped options, while the grouped display shows. */
169
+ grouped?: TableViewGroupedConfig;
170
+ /** The display shown offers the sort picker. */
171
+ canSort: boolean;
172
+ /** The sort the user picked or a view opened with. */
173
+ sorting: SortingState;
174
+ /** The table's default sort. */
175
+ defaultSorting: SortingState;
176
+ }
177
+
178
+ /**
179
+ * The order the rows are wanted in: the hand order of a `reorder` table,
180
+ * else the picked sort. The grouped display hides the sort picker but keeps
181
+ * the sort a view opened it with: it orders the rows of each group.
182
+ */
183
+ export function pickedTableSorting(input: TableSortingInput): SortingState {
184
+ if (input.reorderField) return [{ id: input.reorderField, desc: false }];
185
+ return input.canSort || input.grouped ? input.sorting : input.defaultSorting;
186
+ }
187
+
188
+ /**
189
+ * The sort the list route is asked for (the route sorts on one field): on the
190
+ * grouped display, the grouped column, so that a group's rows follow each
191
+ * other — the picked order then sorts each group, see
192
+ * {@link orderGroupedRows}; else the picked order.
193
+ */
194
+ export function listTableSorting(input: TableSortingInput): SortingState {
195
+ const picked = pickedTableSorting(input);
196
+ if (input.grouped) return groupedSorting(picked, input.grouped);
197
+ return picked;
198
+ }
199
+
200
+ /**
201
+ * The rows of a page listed on the grouped column, each group's rows in the
202
+ * order `picked` gives them (see {@link withinGroupSort}). The groups keep
203
+ * the order the list gave them; rows the sort cannot tell apart keep theirs.
204
+ */
205
+ export function orderGroupedRows<T>(
206
+ rows: T[],
207
+ grouped: TableViewGroupedConfig,
208
+ picked: SortingState,
209
+ locale: string,
210
+ ): T[] {
211
+ const sort = withinGroupSort(picked, grouped);
212
+ if (!sort) return rows;
213
+ const by = grouped.by ?? "value";
214
+ const groupOf = new Map<string, number>();
215
+ const entries = rows.map((row, index) => {
216
+ const key = rowGroupKey(
217
+ get(row as Record<string, unknown>, grouped.groupByField),
218
+ by,
219
+ locale,
220
+ );
221
+ if (!groupOf.has(key)) groupOf.set(key, groupOf.size);
222
+ return { row, index, group: groupOf.get(key)! };
223
+ });
224
+ const direction = sort.desc ? -1 : 1;
225
+ const valueOf = (row: T) => get(row as Record<string, unknown>, sort.id);
226
+ return entries
227
+ .sort(
228
+ (a, b) =>
229
+ a.group - b.group ||
230
+ direction * compareValues(valueOf(a.row), valueOf(b.row)) ||
231
+ a.index - b.index,
232
+ )
233
+ .map((entry) => entry.row);
234
+ }
235
+
149
236
  /** How a date column picked in the options menu is cut, unless configured. */
150
237
  const DEFAULT_DATE_GROUP_BY: TableViewGroupBy = "day";
151
238
 
@@ -178,3 +265,29 @@ export function groupedOptionsFor(
178
265
  grouped.by && grouped.by !== "value" ? grouped.by : DEFAULT_DATE_GROUP_BY;
179
266
  return { ...grouped, groupByField: field, by };
180
267
  }
268
+
269
+ /** The rows of one group, under its key. */
270
+ export interface RowGroup<T> {
271
+ key: string;
272
+ rows: T[];
273
+ }
274
+
275
+ /**
276
+ * The rows filed under their group, each group once, in the order its first
277
+ * row comes. The list is sorted on the grouped column, but rows listed in
278
+ * another order — the previous sort, still shown while the grouped one loads
279
+ * — must not open a group twice: two headers under one key.
280
+ */
281
+ export function groupRows<T>(
282
+ rows: readonly T[],
283
+ keyOf: (row: T) => string,
284
+ ): RowGroup<T>[] {
285
+ const groups = new Map<string, T[]>();
286
+ for (const row of rows) {
287
+ const key = keyOf(row);
288
+ const group = groups.get(key);
289
+ if (group) group.push(row);
290
+ else groups.set(key, [row]);
291
+ }
292
+ return [...groups].map(([key, grouped]) => ({ key, rows: grouped }));
293
+ }