@antelopejs/dms 0.9.8 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (420) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/README.md +1 -1
  3. package/data/module-store/catalog.json +2 -2
  4. package/dist/attachments/table-view.js +2 -2
  5. package/dist/attachments/table-view.js.map +1 -1
  6. package/dist/automation/actions.d.ts +2 -0
  7. package/dist/automation/actions.js +116 -71
  8. package/dist/automation/actions.js.map +1 -1
  9. package/dist/automation/i18n.d.ts +4 -0
  10. package/dist/automation/i18n.js +19 -0
  11. package/dist/automation/i18n.js.map +1 -0
  12. package/dist/automation/send-notification.d.ts +21 -0
  13. package/dist/automation/send-notification.js +163 -0
  14. package/dist/automation/send-notification.js.map +1 -0
  15. package/dist/automation/triggers.js +19 -25
  16. package/dist/automation/triggers.js.map +1 -1
  17. package/dist/client-origin-cors.d.ts +1 -0
  18. package/dist/client-origin-cors.js +19 -0
  19. package/dist/client-origin-cors.js.map +1 -0
  20. package/dist/config.d.ts +3 -0
  21. package/dist/config.js +5 -3
  22. package/dist/config.js.map +1 -1
  23. package/dist/frontend-module.d.ts +4 -0
  24. package/dist/frontend-module.js +37 -0
  25. package/dist/frontend-module.js.map +1 -0
  26. package/dist/implementations/dms/client-ip.js +1 -1
  27. package/dist/implementations/dms/client-ip.js.map +1 -1
  28. package/dist/implementations/dms/dev-reload.d.ts +5 -0
  29. package/dist/implementations/dms/dev-reload.js +23 -3
  30. package/dist/implementations/dms/dev-reload.js.map +1 -1
  31. package/dist/implementations/dms/frontend-modules.d.ts +2 -4
  32. package/dist/implementations/dms/frontend-modules.js +22 -89
  33. package/dist/implementations/dms/frontend-modules.js.map +1 -1
  34. package/dist/implementations/dms/frontend-ready.d.ts +12 -2
  35. package/dist/implementations/dms/frontend-ready.js +34 -4
  36. package/dist/implementations/dms/frontend-ready.js.map +1 -1
  37. package/dist/implementations/dms/layout-banners.d.ts +2 -1
  38. package/dist/implementations/dms/layout-banners.js +9 -0
  39. package/dist/implementations/dms/layout-banners.js.map +1 -1
  40. package/dist/implementations/dms/manifest.d.ts +1 -2
  41. package/dist/implementations/dms/manifest.js +2 -0
  42. package/dist/implementations/dms/manifest.js.map +1 -1
  43. package/dist/implementations/dms/page.d.ts +4 -3
  44. package/dist/implementations/dms/page.js +49 -18
  45. package/dist/implementations/dms/page.js.map +1 -1
  46. package/dist/implementations/dms/permissions-resolver.d.ts +2 -2
  47. package/dist/implementations/dms/permissions-resolver.js +2 -2
  48. package/dist/implementations/dms/permissions-resolver.js.map +1 -1
  49. package/dist/implementations/dms/realtime.js +1 -0
  50. package/dist/implementations/dms/realtime.js.map +1 -1
  51. package/dist/implementations/dms-base/bulk-selection.d.ts +1 -1
  52. package/dist/implementations/dms-base/bulk-selection.js +23 -1
  53. package/dist/implementations/dms-base/bulk-selection.js.map +1 -1
  54. package/dist/implementations/dms-base/filter-tokens.js +5 -3
  55. package/dist/implementations/dms-base/filter-tokens.js.map +1 -1
  56. package/dist/implementations/dms-base/search-route.d.ts +1 -0
  57. package/dist/implementations/dms-base/search-route.js +35 -13
  58. package/dist/implementations/dms-base/search-route.js.map +1 -1
  59. package/dist/implementations/dms-base/table-view.js +11 -6
  60. package/dist/implementations/dms-base/table-view.js.map +1 -1
  61. package/dist/implementations/dms-notifications/index.d.ts +2 -2
  62. package/dist/implementations/dms-notifications/index.js +5 -5
  63. package/dist/implementations/dms-notifications/index.js.map +1 -1
  64. package/dist/index.js +28 -48
  65. package/dist/index.js.map +1 -1
  66. package/dist/pages/modules/index.d.ts +1 -0
  67. package/dist/pages/modules/index.js +1 -0
  68. package/dist/pages/modules/index.js.map +1 -1
  69. package/dist/pages/modules/modules-store.d.ts +9 -0
  70. package/dist/pages/modules/modules-store.js +30 -0
  71. package/dist/pages/modules/modules-store.js.map +1 -0
  72. package/dist/pages/settings/users/account-credentials.js +2 -2
  73. package/dist/pages/settings/users/account-credentials.js.map +1 -1
  74. package/dist/pages/settings/users/account-data-store.js +25 -8
  75. package/dist/pages/settings/users/account-data-store.js.map +1 -1
  76. package/dist/pages/settings/users/invite-extension-routes.js +9 -3
  77. package/dist/pages/settings/users/invite-extension-routes.js.map +1 -1
  78. package/dist/pages/settings/users/member-management.js +13 -14
  79. package/dist/pages/settings/users/member-management.js.map +1 -1
  80. package/dist/pages/settings/users/profile.d.ts +1 -4
  81. package/dist/pages/settings/users/profile.js +0 -35
  82. package/dist/pages/settings/users/profile.js.map +1 -1
  83. package/dist/pages/settings/users/role-editor-store.js +26 -18
  84. package/dist/pages/settings/users/role-editor-store.js.map +1 -1
  85. package/dist/pages/settings/users/role-editor.d.ts +0 -1
  86. package/dist/pages/settings/users/role-editor.js +1 -2
  87. package/dist/pages/settings/users/role-editor.js.map +1 -1
  88. package/dist/pages/settings/users/role-preview.js +1 -2
  89. package/dist/pages/settings/users/role-preview.js.map +1 -1
  90. package/dist/pages/settings/users/roles.js +2 -2
  91. package/dist/pages/settings/users/roles.js.map +1 -1
  92. package/dist/pages/settings/users/two-factor-operations.js.map +1 -1
  93. package/dist/realtime/broker.d.ts +6 -0
  94. package/dist/realtime/broker.js +8 -1
  95. package/dist/realtime/broker.js.map +1 -1
  96. package/dist/realtime/presence.d.ts +8 -3
  97. package/dist/realtime/presence.js +8 -4
  98. package/dist/realtime/presence.js.map +1 -1
  99. package/dist/realtime/sessions.d.ts +11 -1
  100. package/dist/realtime/sessions.js +21 -3
  101. package/dist/realtime/sessions.js.map +1 -1
  102. package/dist/realtime/table-view.d.ts +2 -2
  103. package/dist/realtime/table-view.js +4 -3
  104. package/dist/realtime/table-view.js.map +1 -1
  105. package/dist/routes/auth/client-ip.d.ts +1 -0
  106. package/dist/routes/auth/client-ip.js +18 -0
  107. package/dist/routes/auth/client-ip.js.map +1 -1
  108. package/dist/routes/auth/oauth/config.d.ts +2 -1
  109. package/dist/routes/auth/oauth/config.js +5 -1
  110. package/dist/routes/auth/oauth/config.js.map +1 -1
  111. package/dist/routes/auth/session-handoff.d.ts +0 -1
  112. package/dist/routes/auth/session-handoff.js +0 -2
  113. package/dist/routes/auth/session-handoff.js.map +1 -1
  114. package/dist/routes/auth.js +3 -2
  115. package/dist/routes/auth.js.map +1 -1
  116. package/dist/routes/index.d.ts +0 -1
  117. package/dist/routes/index.js +0 -1
  118. package/dist/routes/index.js.map +1 -1
  119. package/dist/routes/realtime.d.ts +13 -1
  120. package/dist/routes/realtime.js +114 -29
  121. package/dist/routes/realtime.js.map +1 -1
  122. package/dist/startup-config.d.ts +2 -0
  123. package/dist/startup-config.js +62 -0
  124. package/dist/startup-config.js.map +1 -0
  125. package/dist/store/interfaces.d.ts +3 -2
  126. package/dist/store/interfaces.js +16 -6
  127. package/dist/store/interfaces.js.map +1 -1
  128. package/dist/store/listing.js +24 -13
  129. package/dist/store/listing.js.map +1 -1
  130. package/dist/store/operations.js +4 -0
  131. package/dist/store/operations.js.map +1 -1
  132. package/dist/store/requirements.js +3 -2
  133. package/dist/store/requirements.js.map +1 -1
  134. package/dist/store/runtime.d.ts +10 -1
  135. package/dist/store/runtime.js +33 -6
  136. package/dist/store/runtime.js.map +1 -1
  137. package/dist/store/types.d.ts +10 -0
  138. package/dist/validation/auth.schema.d.ts +2 -2
  139. package/dist/validation/member-invite.schema.d.ts +2 -10
  140. package/dist/validation/member-invite.schema.js +2 -15
  141. package/dist/validation/member-invite.schema.js.map +1 -1
  142. package/frontend-vue/dms.frontend.ts +24 -5
  143. package/frontend-vue/dms.server.ts +17 -0
  144. package/frontend-vue/layers/dms-auth/server/accounts.ts +3 -4
  145. package/frontend-vue/layers/dms-core/app/build/composables/renderNow.ts +34 -0
  146. package/frontend-vue/layers/dms-core/app/build/composables/user/tablePreferences.ts +5 -1
  147. package/frontend-vue/layers/dms-core/app/build/utils/crossTab.ts +199 -0
  148. package/frontend-vue/layers/dms-core/app/build/utils/sharedRealtimeConnection.ts +410 -0
  149. package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +77 -12
  150. package/frontend-vue/layers/dms-core/app/composables/data-types/useDataType.ts +26 -0
  151. package/frontend-vue/layers/dms-core/app/composables/hydration/isHydratingComponent.ts +29 -0
  152. package/frontend-vue/layers/dms-core/app/composables/live/livePauseSource.ts +29 -0
  153. package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +150 -9
  154. package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +6 -4
  155. package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +27 -1
  156. package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +256 -119
  157. package/frontend-vue/layers/dms-core/app/composables/translation/useComposedText.ts +21 -8
  158. package/frontend-vue/layers/dms-core/app/composables/translation/useTranslation.ts +22 -5
  159. package/frontend-vue/layers/dms-core/app/plugins/dev-error-log.ts +16 -0
  160. package/frontend-vue/layers/dms-core/app/types/user.ts +6 -0
  161. package/frontend-vue/layers/dms-core/app/utils/composedText.ts +7 -2
  162. package/frontend-vue/layers/dms-core/app/utils/formatter.ts +166 -35
  163. package/frontend-vue/layers/dms-core/app/utils/url-interpolation.ts +18 -1
  164. package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +134 -2
  165. package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +134 -2
  166. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +3 -2
  167. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +41 -21
  168. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardPageBody.vue +9 -0
  169. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +4 -19
  170. package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +11 -3
  171. package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +18 -7
  172. package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionBar.vue +8 -0
  173. package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +14 -0
  174. package/frontend-vue/layers/dms-layout/app/build/components/layout/RowSidePanel.vue +43 -82
  175. package/frontend-vue/layers/dms-layout/app/build/components/layout/SidebarUserMenu.vue +44 -3
  176. package/frontend-vue/layers/dms-layout/app/build/components/layout/UserIdentity.vue +11 -3
  177. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleCatalogTile.vue +132 -35
  178. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleManageMenu.vue +59 -0
  179. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleSourceLabel.vue +25 -0
  180. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreBanners.vue +73 -0
  181. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreOperation.vue +116 -0
  182. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreRequirements.vue +8 -3
  183. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue +85 -207
  184. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTile.vue +10 -3
  185. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTileSkeleton.vue +68 -0
  186. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +24 -8
  187. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxItem.vue +8 -5
  188. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/region/RegionTimeZoneInput.vue +1 -0
  189. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-permissions.ts +19 -0
  190. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/useRoleEditor.ts +2 -1
  191. package/frontend-vue/layers/dms-layout/app/build/composables/layout/useSidePanelWidth.ts +31 -70
  192. package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen.ts +11 -1
  193. package/frontend-vue/layers/dms-layout/app/build/utils/dev-reload-stream.ts +81 -0
  194. package/frontend-vue/layers/dms-layout/app/build/utils/module-store-catalog.ts +232 -36
  195. package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +122 -14
  196. package/frontend-vue/layers/dms-layout/app/composables/layout/useLayoutBanners.ts +32 -3
  197. package/frontend-vue/layers/dms-layout/app/composables/layout/usePageRecord.ts +50 -0
  198. package/frontend-vue/layers/dms-layout/app/composables/layout/useServedHeaderActions.ts +25 -7
  199. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleHistory.ts +3 -2
  200. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +51 -4
  201. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStoreActions.ts +194 -0
  202. package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +23 -3
  203. package/frontend-vue/layers/dms-layout/app/composables/page/usePageLayout.ts +4 -2
  204. package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +6 -1
  205. package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +9 -0
  206. package/frontend-vue/layers/dms-layout/app/custom-layouts/SettingsLayout.vue +9 -0
  207. package/frontend-vue/layers/dms-layout/app/custom-pages/modules-store.vue +291 -0
  208. package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +87 -474
  209. package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +3 -0
  210. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +6 -22
  211. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-navigation.client.ts +2 -1
  212. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-personal-pages.client.ts +14 -44
  213. package/frontend-vue/layers/dms-layout/app/plugins/dms-dev-reload.client.ts +14 -9
  214. package/frontend-vue/layers/dms-layout/app/theme/form-controls.ts +9 -4
  215. package/frontend-vue/layers/dms-layout/app/theme/surfaces.ts +22 -6
  216. package/frontend-vue/layers/dms-layout/app/types/module-store.ts +9 -0
  217. package/frontend-vue/layers/dms-layout/app/types/page.ts +15 -1
  218. package/frontend-vue/layers/dms-layout/app/utils/account-menu.ts +11 -0
  219. package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +57 -0
  220. package/frontend-vue/layers/dms-layout/app/utils/pageQuery.ts +26 -0
  221. package/frontend-vue/layers/dms-layout/app/utils/pageRecord.ts +71 -0
  222. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +39 -14
  223. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +38 -13
  224. package/frontend-vue/layers/dms-ui/app/build/components/containers/TargetComponentTree.vue +31 -0
  225. package/frontend-vue/layers/dms-ui/app/build/components/containers/constants.ts +25 -0
  226. package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +244 -15
  227. package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +42 -25
  228. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowHeader.vue +153 -0
  229. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowNotFound.vue +44 -0
  230. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/drawerRowHeader.ts +35 -0
  231. package/frontend-vue/layers/dms-ui/app/build/components/form/FormEntries.vue +44 -9
  232. package/frontend-vue/layers/dms-ui/app/build/components/form/FormErrorSummary.vue +11 -4
  233. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldControl.vue +38 -5
  234. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldNotes.vue +29 -5
  235. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSectionNav.vue +3 -1
  236. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSections.vue +106 -27
  237. package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +12 -4
  238. package/frontend-vue/layers/dms-ui/app/build/components/table/Empty.vue +4 -2
  239. package/frontend-vue/layers/dms-ui/app/build/components/table/FiltersRow.vue +38 -1
  240. package/frontend-vue/layers/dms-ui/app/build/components/table/Pagination.vue +55 -54
  241. package/frontend-vue/layers/dms-ui/app/build/components/table/RowSelection.vue +2 -1
  242. package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +396 -288
  243. package/frontend-vue/layers/dms-ui/app/build/components/table/Tabs.vue +2 -1
  244. package/frontend-vue/layers/dms-ui/app/build/components/table/Views.vue +2 -1
  245. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardAvatar.vue +59 -0
  246. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardGridSkeleton.vue +19 -2
  247. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardRowActions.vue +38 -0
  248. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardsDisplay.vue +148 -104
  249. package/frontend-vue/layers/dms-ui/app/build/components/table-view/LiveHoldBanner.vue +17 -2
  250. package/frontend-vue/layers/dms-ui/app/build/composables/actions/bulkSelection.ts +43 -9
  251. package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowComponentOptions.ts +76 -0
  252. package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowNavigation.ts +172 -27
  253. package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +270 -46
  254. package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +2 -0
  255. package/frontend-vue/layers/dms-ui/app/build/composables/containers/closingLayers.ts +186 -0
  256. package/frontend-vue/layers/dms-ui/app/build/composables/containers/context.ts +14 -0
  257. package/frontend-vue/layers/dms-ui/app/build/composables/containers/drawerSize.ts +229 -0
  258. package/frontend-vue/layers/dms-ui/app/build/composables/containers/useResizableSize.ts +175 -0
  259. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/metricCells.ts +18 -35
  260. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/numberSuffix.ts +47 -0
  261. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +99 -24
  262. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerFieldTypes.ts +2 -1
  263. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationFilter.ts +75 -0
  264. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationRows.ts +71 -0
  265. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationValue.ts +39 -0
  266. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/twoLineCell.ts +3 -3
  267. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/useColumnValueRenderer.ts +6 -2
  268. package/frontend-vue/layers/dms-ui/app/build/composables/form/createFormFields.ts +15 -0
  269. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldSaved.ts +40 -0
  270. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldValueRule.ts +62 -0
  271. package/frontend-vue/layers/dms-ui/app/build/composables/form/formEntryContext.ts +5 -0
  272. package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +42 -7
  273. package/frontend-vue/layers/dms-ui/app/build/composables/form/formLayout.ts +106 -4
  274. package/frontend-vue/layers/dms-ui/app/build/composables/form/formSections.ts +32 -0
  275. package/frontend-vue/layers/dms-ui/app/build/composables/form/readonlyValue.ts +19 -0
  276. package/frontend-vue/layers/dms-ui/app/build/composables/form/registerFormFunctions.ts +27 -0
  277. package/frontend-vue/layers/dms-ui/app/build/composables/form/repeaterTable.ts +65 -0
  278. package/frontend-vue/layers/dms-ui/app/build/composables/form/serverFieldErrors.ts +49 -0
  279. package/frontend-vue/layers/dms-ui/app/build/composables/form/toasterClearance.ts +118 -0
  280. package/frontend-vue/layers/dms-ui/app/build/composables/form/useInstantForm.ts +12 -1
  281. package/frontend-vue/layers/dms-ui/app/build/composables/live/currentPageLive.ts +41 -3
  282. package/frontend-vue/layers/dms-ui/app/build/composables/live/livePoller.ts +10 -2
  283. package/frontend-vue/layers/dms-ui/app/build/composables/live/pageReadClock.ts +28 -0
  284. package/frontend-vue/layers/dms-ui/app/build/composables/live/shortcuts.ts +1 -1
  285. package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageLiveHost.ts +17 -3
  286. package/frontend-vue/layers/dms-ui/app/build/composables/live/useUpdatedLabel.ts +38 -0
  287. package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/pageToolbarContext.ts +75 -0
  288. package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/toolbarControlHeight.ts +29 -0
  289. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodComparison.ts +52 -0
  290. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +21 -0
  291. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodShortLabels.ts +28 -0
  292. package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodDraft.ts +1 -0
  293. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRelationFilterLabels.ts +73 -0
  294. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRowScrollAnchor.ts +106 -0
  295. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +9 -0
  296. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTableColumns.ts +1 -2
  297. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/archivedCount.ts +31 -0
  298. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/filterEditorValue.ts +38 -0
  299. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/formatFilterValue.ts +33 -2
  300. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/scrollEdges.ts +31 -0
  301. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useCardFields.ts +33 -0
  302. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useMobileDisplay.ts +48 -0
  303. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useScrolledPastTop.ts +41 -18
  304. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +3 -2
  305. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLinkedVisit.ts +78 -0
  306. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLiveHold.ts +135 -22
  307. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +56 -25
  308. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +77 -32
  309. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +86 -10
  310. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/bulkActions.ts +14 -4
  311. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/card.ts +127 -2
  312. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/compareValues.ts +13 -0
  313. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/expandedRows.ts +28 -0
  314. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/groupedRows.ts +114 -1
  315. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/linkedArrival.ts +308 -0
  316. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/locateRow.ts +106 -0
  317. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pageArrival.ts +34 -0
  318. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pinnedRows.ts +17 -0
  319. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/quickFilters.ts +41 -33
  320. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/recordLink.ts +24 -8
  321. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableCounts.ts +113 -0
  322. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableQuery.ts +27 -4
  323. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/views.ts +22 -6
  324. package/frontend-vue/layers/dms-ui/app/build/utils/addressSuggestions.ts +49 -0
  325. package/frontend-vue/layers/dms-ui/app/build/utils/eyebrow.ts +12 -0
  326. package/frontend-vue/layers/dms-ui/app/build/utils/fieldOptionsI18n.ts +51 -11
  327. package/frontend-vue/layers/dms-ui/app/build/utils/inputWidth.ts +53 -0
  328. package/frontend-vue/layers/dms-ui/app/build/utils/numberAdjustment.ts +6 -3
  329. package/frontend-vue/layers/dms-ui/app/build/utils/relativeDate.ts +19 -5
  330. package/frontend-vue/layers/dms-ui/app/build/utils/scrollWithin.ts +99 -0
  331. package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +36 -14
  332. package/frontend-vue/layers/dms-ui/app/components/PasswordStrength.vue +1 -1
  333. package/frontend-vue/layers/dms-ui/app/components/RecursiveComponent.vue +9 -1
  334. package/frontend-vue/layers/dms-ui/app/components/blocks/LiveToggleBlock.vue +80 -32
  335. package/frontend-vue/layers/dms-ui/app/components/blocks/PageToolbarBlock.vue +137 -0
  336. package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +5 -0
  337. package/frontend-vue/layers/dms-ui/app/components/chart/ApexChartHost.vue +16 -2
  338. package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +24 -5
  339. package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +41 -23
  340. package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +1 -1
  341. package/frontend-vue/layers/dms-ui/app/components/container-footer/ContainerFooter.vue +34 -0
  342. package/frontend-vue/layers/dms-ui/app/components/field-row/FieldRow.vue +25 -2
  343. package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +2 -2
  344. package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +118 -36
  345. package/frontend-vue/layers/dms-ui/app/components/form/components/DatePicker.vue +20 -1
  346. package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +13 -1
  347. package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +8 -3
  348. package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +53 -5
  349. package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +29 -5
  350. package/frontend-vue/layers/dms-ui/app/components/form/components/InputNumber.vue +45 -8
  351. package/frontend-vue/layers/dms-ui/app/components/form/components/InputPercentage.vue +28 -5
  352. package/frontend-vue/layers/dms-ui/app/components/form/components/InputSecret.vue +5 -0
  353. package/frontend-vue/layers/dms-ui/app/components/form/components/InputText.vue +19 -2
  354. package/frontend-vue/layers/dms-ui/app/components/form/components/InputTime.vue +2 -1
  355. package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +205 -65
  356. package/frontend-vue/layers/dms-ui/app/components/form/components/Repeater.vue +80 -30
  357. package/frontend-vue/layers/dms-ui/app/components/form/components/RepeaterTable.vue +203 -0
  358. package/frontend-vue/layers/dms-ui/app/components/form/components/Select.vue +4 -2
  359. package/frontend-vue/layers/dms-ui/app/components/grid/GridRow.vue +11 -2
  360. package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +79 -70
  361. package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +117 -21
  362. package/frontend-vue/layers/dms-ui/app/components/meter/Meter.vue +2 -1
  363. package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +113 -34
  364. package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +14 -2
  365. package/frontend-vue/layers/dms-ui/app/components/section-header/Eyebrow.vue +6 -1
  366. package/frontend-vue/layers/dms-ui/app/components/segmented/Segmented.vue +13 -0
  367. package/frontend-vue/layers/dms-ui/app/components/stat-group/StatGroup.vue +52 -3
  368. package/frontend-vue/layers/dms-ui/app/components/tab/Tab.vue +2 -1
  369. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +45 -35
  370. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanDisplay.vue +4 -1
  371. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +410 -96
  372. package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +33 -5
  373. package/frontend-vue/layers/dms-ui/app/composables/chart/apexCategoryLabels.ts +68 -0
  374. package/frontend-vue/layers/dms-ui/app/composables/chart/apexDatetimeBuckets.ts +306 -0
  375. package/frontend-vue/layers/dms-ui/app/composables/chart/formatValue.ts +4 -3
  376. package/frontend-vue/layers/dms-ui/app/composables/chart/heatmapColors.ts +93 -0
  377. package/frontend-vue/layers/dms-ui/app/composables/chart/localizeSeries.ts +62 -0
  378. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.ts +116 -8
  379. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.types.ts +7 -0
  380. package/frontend-vue/layers/dms-ui/app/composables/containers/types.ts +84 -1
  381. package/frontend-vue/layers/dms-ui/app/composables/containers/useDrawer.ts +69 -8
  382. package/frontend-vue/layers/dms-ui/app/composables/form/types/field-saved.ts +11 -0
  383. package/frontend-vue/layers/dms-ui/app/composables/form/types/field.ts +4 -0
  384. package/frontend-vue/layers/dms-ui/app/composables/form/types/functions.ts +1 -0
  385. package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +142 -21
  386. package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +11 -4
  387. package/frontend-vue/layers/dms-ui/app/composables/page-toolbar/usePageToolbarSize.ts +30 -0
  388. package/frontend-vue/layers/dms-ui/app/composables/table-view/kanban.ts +4 -2
  389. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +22 -3
  390. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +4 -0
  391. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/display.ts +24 -1
  392. package/frontend-vue/layers/dms-ui/app/plugins/closing-layers.client.ts +10 -0
  393. package/frontend-vue/layers/dms-ui/app/plugins/live-pause-source.ts +15 -0
  394. package/frontend-vue/layers/dms-ui/app/plugins/register.ts +2 -0
  395. package/frontend-vue/layers/dms-ui/app/types/input-number.ts +3 -2
  396. package/frontend-vue/layers/dms-ui/app/types/page-toolbar.ts +7 -0
  397. package/frontend-vue/layers/dms-ui/app/types/row-action.ts +0 -2
  398. package/frontend-vue/layers/dms-ui/app/types/row-navigation.ts +17 -0
  399. package/frontend-vue/layers/dms-ui/app/types/table/query.ts +2 -1
  400. package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +3 -1
  401. package/frontend-vue/layers/dms-ui/app/utils/componentTree.ts +24 -0
  402. package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +16 -4
  403. package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +16 -4
  404. package/frontend-vue/package.json +2 -2
  405. package/package.json +7 -6
  406. package/skills/dms/SKILL.md +1 -1
  407. package/skills/dms-auth/SKILL.md +5 -3
  408. package/skills/dms-best-practices/REFERENCE.md +2 -0
  409. package/skills/dms-best-practices/SKILL.md +4 -0
  410. package/skills/dms-module/SKILL.md +3 -2
  411. package/skills/dms-pages/REFERENCE.md +125 -16
  412. package/skills/dms-pages/SKILL.md +1 -1
  413. package/skills/dms-project/DEV-SERVERS.md +23 -1
  414. package/skills/dms-project/REFERENCE.md +1 -1
  415. package/skills/dms-project/SKILL.md +4 -1
  416. package/skills/dms-project/scripts/dms-dev.sh +205 -17
  417. package/dist/routes/startup-gate.d.ts +0 -7
  418. package/dist/routes/startup-gate.js +0 -39
  419. package/dist/routes/startup-gate.js.map +0 -1
  420. package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +0 -42
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts" generic="T extends Data">
2
+ import { get } from "@nuxt/ui/runtime/utils/index.js";
2
3
  import type {
3
4
  ColumnSizingState,
4
5
  ExpandedState,
@@ -58,9 +59,8 @@ import {
58
59
  } from "../../build/composables/table-view/utils/tableQuery";
59
60
  import { buildTableViewShortcuts } from "../../composables/table-view/shortcuts";
60
61
  import {
61
- defaultExpandedRows,
62
+ expandedRowsOnListing,
62
63
  type ExpandedRowMap,
63
- keepListedRows,
64
64
  nextExpandedRows,
65
65
  } from "../../build/composables/table-view/utils/expandedRows";
66
66
  import { resolveTableChrome } from "../../build/composables/table-view/utils/chrome";
@@ -128,16 +128,32 @@ import {
128
128
  type WatchSource,
129
129
  } from "vue";
130
130
  import { useTableViewConfig } from "../../build/composables/table-view/useTableViewConfig";
131
- import { useTableViews } from "../../build/composables/table-view/useTableViews";
131
+ import {
132
+ keptViewId,
133
+ useTableViews,
134
+ } from "../../build/composables/table-view/useTableViews";
132
135
  import { useGroupedRows } from "../../build/composables/table-view/useGroupedRows";
133
136
  import { useTableFooter } from "../../build/composables/table-view/useTableFooter";
134
137
  import { useAccumulatedPages } from "../../build/composables/table-view/useAccumulatedPages";
138
+ import type { RowStep } from "../../build/composables/actions/rowNavigation";
139
+ import { rowLocator } from "../../build/composables/table-view/utils/locateRow";
135
140
  import { usePersistedPagination } from "../../build/composables/table-view/usePersistedPagination";
136
141
  import { useTablePreferences } from "../../build/composables/table-view/useTablePreferences";
142
+ import {
143
+ isLinkedArrival,
144
+ linkedArrivalPreferences,
145
+ linkFollowingPreferences,
146
+ linkKeyScope,
147
+ urlFilterScope,
148
+ } from "../../build/composables/table-view/utils/linkedArrival";
149
+ import { pageArrivalKind } from "../../build/composables/table-view/utils/pageArrival";
150
+ import { useTableLinkedVisit } from "../../build/composables/table-view/useTableLinkedVisit";
151
+ import { useMobileDisplay } from "../../build/composables/table-view/useMobileDisplay";
137
152
  import type { Ref } from "vue";
138
153
  import type { TableField } from "#dms-core/app/build/composables/user/tablePreferences";
139
154
  import { useTableRows } from "../../build/composables/table-view/useTableRows";
140
155
  import { useRefreshTriggers } from "../../build/composables/blocks/useRefreshTriggers";
156
+ import { refreshPageBlocks } from "../../utils/blockRefresh";
141
157
  import { useTableLiveHold } from "../../build/composables/table-view/useTableLiveHold";
142
158
  import type { TableViewLiveHoldOptions } from "../../types/live";
143
159
  import { usePageLive } from "../../composables/live/usePageLive";
@@ -145,13 +161,24 @@ import LiveHoldBanner from "../../build/components/table-view/LiveHoldBanner.vue
145
161
  import { useTableReorder } from "../../build/composables/table-view/useTableReorder";
146
162
  import {
147
163
  groupedOptionsFor,
148
- groupedSorting,
164
+ listTableSorting,
165
+ orderGroupedRows,
166
+ pickedTableSorting,
167
+ type TableSortingInput,
149
168
  isGroupableColumn,
150
169
  } from "../../build/composables/table-view/utils/groupedRows";
151
170
  import {
152
171
  readTableUrlTab,
153
172
  useTableUrlTab,
154
173
  } from "../../build/composables/table-view/useTableUrlTab";
174
+ import {
175
+ buildTableCountQueries,
176
+ isCountNarrowed,
177
+ navBadgeCount,
178
+ VIEW_COUNT_ID_PREFIX,
179
+ type TableCountState,
180
+ } from "../../build/composables/table-view/utils/tableCounts";
181
+ import { viewFilters } from "../../build/composables/table-view/utils/views";
155
182
  import { useTableRecordLink } from "../../build/composables/table-view/useTableRecordLink";
156
183
  import TableViews, {
157
184
  type TableViewItem,
@@ -187,6 +214,7 @@ interface RealtimePresenceActor {
187
214
  type RealtimePresenceMap = Record<string, RealtimePresenceActor[]>;
188
215
 
189
216
  /** A kept field of the table, its state, and its default. */
217
+ // The default may move once mounted (a phone's display): read when written.
190
218
  type TableStatePreference = [TableField, Ref<unknown>, unknown];
191
219
 
192
220
  // The backend options of a table view, declared here as well so the
@@ -194,6 +222,8 @@ type TableStatePreference = [TableField, Ref<unknown>, unknown];
194
222
  interface TableViewProps<T extends Data> extends TableViewConfig<T> {
195
223
  /** Expandable rows: caret column + detail band (backend `expandable`). */
196
224
  expandable?: TableViewExpandableConfig;
225
+ /** Caption count while the archived rows show (backend `archivedCountLabel`). */
226
+ archivedCountLabel?: string;
197
227
  /** Placeholder of the search field. */
198
228
  searchPlaceholder?: string;
199
229
  /** Key opening the search (`defineShortcuts` notation); `shift_f` else. */
@@ -254,7 +284,51 @@ const {
254
284
  defaultDisplay,
255
285
  } = useTableViewConfig<T>(props);
256
286
 
257
- const tablePreferences = useTablePreferences(componentId, pageId);
287
+ const route = useDmsRoute();
288
+ const urlScope = {
289
+ tableId: props.tableId,
290
+ isSoleTableView: props.isSoleTableView,
291
+ };
292
+ const ALL_TAB_ID = "all";
293
+
294
+ const keptPreferences = useTablePreferences(componentId, pageId);
295
+ // A link naming a view, a tab, a record or a URL filter opens on the table's
296
+ // defaults plus what it names, never over the state kept from the last visit
297
+ // (which stays kept for the next plain visit), until the user makes the
298
+ // state shown their own: a view, a tab, a filter or a sort they pick. Only a
299
+ // reload (or Back) of the URL the table wrote for that state restores it.
300
+ const isLinked = isLinkedArrival({
301
+ query: route.query,
302
+ urlScope,
303
+ queryParamFilters,
304
+ isVisit: pageArrivalKind() === "visit",
305
+ rememberedTab: keptPreferences.get<string>("activeTab", ALL_TAB_ID),
306
+ keptLinkScope: keptPreferences.get<string | undefined>(
307
+ "linkScope",
308
+ undefined,
309
+ ),
310
+ shownLink: keptPreferences.get<string | undefined>("shownLink", undefined),
311
+ });
312
+ const tablePreferences = linkFollowingPreferences(
313
+ isLinked ? linkedArrivalPreferences(keptPreferences) : keptPreferences,
314
+ {
315
+ isLinkedArrival: isLinked,
316
+ urlScope,
317
+ linkScope: () => urlFilterScope(route.query, queryParamFilters),
318
+ linkKeys: () => linkKeyScope(route.query, urlScope, queryParamFilters),
319
+ // Read once the table is mounted, past the declarations below. A page
320
+ // kept without its scope is restored as it is (`usePersistedPagination`).
321
+ listingState: () => [
322
+ ["activeView", keptViewId(activeViewId.value), undefined],
323
+ ["activeTab", activeTabId.value, ALL_TAB_ID],
324
+ ["columnFilters", columnFilters.value, []],
325
+ ["sorting", sorting.value, defaultSortState],
326
+ ["pagination", paginationState.value, DEFAULT_PAGINATION],
327
+ ["paginationScope", undefined, undefined],
328
+ ],
329
+ },
330
+ );
331
+ onMounted(() => void tablePreferences.settleArrival());
258
332
  const { sendComponentEvent } = useComponentEvent(componentId);
259
333
  const { processI18n } = useTranslation();
260
334
 
@@ -394,8 +468,6 @@ const resolvedChrome = resolveTableChrome(props.layout, {
394
468
  });
395
469
  const GLOBAL_FILTER_DEBOUNCE_MS = 400;
396
470
 
397
- const route = useDmsRoute();
398
-
399
471
  const DEFAULT_FILTER_MODE = "is";
400
472
 
401
473
  const queryParamHiddenFilters = computed<TableFilter[]>(() => {
@@ -511,12 +583,16 @@ const defaultSortKind = tableDefaultSort
511
583
  ?.type?.id,
512
584
  )
513
585
  : undefined;
586
+ // A hand-ordered table has no sort to pick: a sort saved before it was
587
+ // ordered by hand is not read back.
514
588
  const sorting = ref<SortingState>(
515
- sortingOrDefault(
516
- tablePreferences.get<SortingState>("sorting", defaultSortState),
517
- declaredSortableIds,
518
- defaultSortState,
519
- ),
589
+ reorderConfig
590
+ ? defaultSortState
591
+ : sortingOrDefault(
592
+ tablePreferences.get<SortingState>("sorting", defaultSortState),
593
+ declaredSortableIds,
594
+ defaultSortState,
595
+ ),
520
596
  );
521
597
 
522
598
  const savedFilters = tablePreferences.get<TableFilter[]>("columnFilters", []);
@@ -538,6 +614,16 @@ const manualFilters = savedFilters
538
614
  .filter((f) => !pinnedKeys.has(f.accessorKey))
539
615
  .map((f) => ({ ...f, pinned: false }));
540
616
  const initialFilters = [...buildPinnedFilters(), ...manualFilters];
617
+ // The pinned filters at their declared value: what a view leaves out of them.
618
+ const pinnedDefaultFilters: TableFilter[] = (defaultFilters || []).map(
619
+ (filter) => ({
620
+ accessorKey: filter.accessorKey,
621
+ mode: filter.mode,
622
+ value: filter.value,
623
+ pinned: true,
624
+ initialValue: filter.value,
625
+ }),
626
+ );
541
627
 
542
628
  const columnFilters = ref<TableFilter[]>(initialFilters);
543
629
  const columnVisibility = ref<VisibilityState>(
@@ -559,18 +645,21 @@ const globalFilterDebounced = refDebounced(
559
645
  );
560
646
  const showArchived = ref(false);
561
647
 
562
- const urlScope = {
563
- tableId: props.tableId,
564
- isSoleTableView: props.isSoleTableView,
565
- };
648
+ // The display the table counts as its default: the phone's `mobileDisplay`
649
+ // once applied (see `useMobileDisplay` below).
650
+ const displayDefault = ref(
651
+ offeredDisplayIds.has(defaultDisplay) ? defaultDisplay : fallbackDisplayId,
652
+ );
566
653
 
567
654
  // Views: named states of the whole table, the module's and the user's own.
568
655
  const {
569
656
  items: tableViewItems,
570
657
  activeViewId,
658
+ activeView,
571
659
  isModified: isViewModified,
572
660
  canSaveViews,
573
- openView,
661
+ selectView,
662
+ openLinkedView,
574
663
  resetView,
575
664
  saveView,
576
665
  saveAsNewView,
@@ -578,21 +667,16 @@ const {
578
667
  } = useTableViews({
579
668
  views: props.views,
580
669
  defaults: {
581
- pinnedFilters: (defaultFilters || []).map((filter) => ({
582
- accessorKey: filter.accessorKey,
583
- mode: filter.mode,
584
- value: filter.value,
585
- pinned: true,
586
- initialValue: filter.value,
587
- })),
670
+ pinnedFilters: pinnedDefaultFilters,
588
671
  sorting: defaultSortState,
589
672
  visibility: initialVisibility,
590
673
  columnOrder: listableColumns.map(
591
674
  (column) => column.id ?? column.accessorKey,
592
675
  ),
593
- display: offeredDisplayIds.has(defaultDisplay)
594
- ? defaultDisplay
595
- : fallbackDisplayId,
676
+ // Read when a view opens: a phone's `mobileDisplay` once applied.
677
+ get display() {
678
+ return displayDefault.value;
679
+ },
596
680
  density: props.density ?? DEFAULT_DENSITY,
597
681
  displays: offeredDisplayIds,
598
682
  },
@@ -610,9 +694,22 @@ const {
610
694
  urlScope,
611
695
  });
612
696
 
613
- const ALL_TAB_ID = "all";
697
+ // A phone opens on the table's `mobileDisplay` (cards rather than a grid cut
698
+ // after its first columns) unless a display was chosen: saved, fixed by the
699
+ // open view, or the only way in.
700
+ useMobileDisplay({
701
+ mobileDisplay: props.mobileDisplay,
702
+ defaultDisplay: displayDefault,
703
+ offered: offeredDisplayIds,
704
+ display: activeDisplayId,
705
+ activeView,
706
+ isDisplayChosen: () =>
707
+ isDefaultDisplayStandalone ||
708
+ tablePreferences.get<string | undefined>("viewMode", undefined) !==
709
+ undefined,
710
+ });
614
711
 
615
- const { t, te } = useI18n();
712
+ const { t, te, locale } = useI18n();
616
713
 
617
714
  // A tab named by the URL wins over the one kept from the last visit.
618
715
  const activeTabId = ref<string>(
@@ -668,12 +765,33 @@ const activeTabFilters = computed<TableFilter[]>(() => {
668
765
  return tab?.filters || [];
669
766
  });
670
767
 
768
+ const isOpenableTab = (tabId: string) =>
769
+ resolvedTabs.value.some((tab) => !tab.to && tab.id === tabId);
770
+
671
771
  useTableUrlTab({
672
772
  route,
673
773
  urlScope,
674
774
  activeTabId,
675
- isOpenable: (tabId) =>
676
- resolvedTabs.value.some((tab) => !tab.to && tab.id === tabId),
775
+ isOpenable: isOpenableTab,
776
+ });
777
+
778
+ // A link to this page while it is shown opens like a linked arrival: on the
779
+ // table's defaults plus what it names, leaving the kept state as it was.
780
+ useTableLinkedVisit({
781
+ route,
782
+ urlScope,
783
+ queryParamFilters,
784
+ activeView: () => activeViewId.value,
785
+ activeTab: () => activeTabId.value,
786
+ canOpenView: (id) => tableViewItems.value.some((view) => view.id === id),
787
+ canOpenTab: isOpenableTab,
788
+ follow: (query) => {
789
+ void tablePreferences.followLink(() => {
790
+ openLinkedView(query);
791
+ const tab = readTableUrlTab(query, urlScope);
792
+ activeTabId.value = tab && isOpenableTab(tab) ? tab : ALL_TAB_ID;
793
+ });
794
+ },
677
795
  });
678
796
 
679
797
  watch(resolvedTabs, (next) => {
@@ -700,6 +818,7 @@ const tableRows = useTableRows<T>({
700
818
  // Quick filters: dropdowns of the toolbar over a column's values (a select's
701
819
  // items, a boolean, the rows a relation points to), writing that column's
702
820
  // filter like the filters row does.
821
+ const { getDataType } = useDataTypes();
703
822
  const relationQuickFilterValues = ref<Record<string, QuickFilterItem[]>>({});
704
823
  // Relation values load after mount: until then their buttons hold their place.
705
824
  const relationQuickFiltersLoaded = ref(false);
@@ -712,7 +831,7 @@ const quickFilterDefinitions = (props.quickFilters ?? []).map((filter) => {
712
831
  column,
713
832
  mode: quickFilterMode(column, filter.mode),
714
833
  items: staticQuickFilterItems(column, processI18n),
715
- source: relationQuickFilterSource(column),
834
+ source: relationQuickFilterSource(column, getDataType),
716
835
  };
717
836
  });
718
837
 
@@ -726,7 +845,9 @@ const loadRelationQuickFilters = async () => {
726
845
  try {
727
846
  const response = await $authFetch<
728
847
  TableViewListResponse<Record<string, unknown>>
729
- >(source!.url, { query: { limit: RELATION_QUICK_FILTER_LIMIT } });
848
+ >(source!.searchUrl, {
849
+ query: { limit: RELATION_QUICK_FILTER_LIMIT },
850
+ });
730
851
  relationQuickFilterValues.value = {
731
852
  ...relationQuickFilterValues.value,
732
853
  [filter.field]: relationQuickFilterItems(response.results, source!),
@@ -800,17 +921,22 @@ const effectiveGlobalFilter = computed(() =>
800
921
  const isGroupedDisplay = computed(
801
922
  () => activeDisplayId.value === GROUPED_DISPLAY_ID && !!groupedOptions,
802
923
  );
924
+ const sortingInput = computed<TableSortingInput>(() => ({
925
+ reorderField: reorderConfig?.field,
926
+ grouped: isGroupedDisplay.value ? activeGroupedOptions.value : undefined,
927
+ canSort: activeCapabilities.value.sorting,
928
+ sorting: sorting.value,
929
+ defaultSorting: defaultSortState,
930
+ }));
931
+ // The order the rows are wanted in; the grouped display lists them on the
932
+ // grouped column and orders each group by it (`shownResults`).
933
+ const pickedSorting = computed(() => pickedTableSorting(sortingInput.value));
803
934
  const effectiveSorting = computed<SortingState>(() => {
804
- if (reorderConfig) return [{ id: reorderConfig.field, desc: false }];
805
- const picked = activeCapabilities.value.sorting
806
- ? sorting.value
807
- : defaultSortState;
808
- return sanitizeSorting(
809
- isGroupedDisplay.value && activeGroupedOptions.value
810
- ? groupedSorting(picked, activeGroupedOptions.value)
811
- : picked,
812
- sortableIds.value,
813
- );
935
+ const listed = listTableSorting(sortingInput.value);
936
+ // The hand order is kept whole: its field need not be a sortable column.
937
+ return reorderConfig && !sortingInput.value.grouped
938
+ ? listed
939
+ : sanitizeSorting(listed, sortableIds.value);
814
940
  });
815
941
 
816
942
  // A feed grows by pages (`loadMore`, `infinite`) rather than paging.
@@ -852,6 +978,7 @@ const openRowId = computed(() =>
852
978
 
853
979
  // The gate is set up with the other refresh triggers below; the hold only
854
980
  // reads it once the table is on screen.
981
+ const NEW_ROWS_COUNT_ID = "liveHold:new";
855
982
  const tableRoot = useTemplateRef<ComponentPublicInstance>("tableRoot");
856
983
  const liveHold = useTableLiveHold({
857
984
  liveHold: props.liveHold,
@@ -863,6 +990,32 @@ const liveHold = useTableLiveHold({
863
990
  isSorted: () => !isSameSorting(effectiveSorting.value, defaultSortState),
864
991
  tableElement: () => tableRoot.value?.$el ?? null,
865
992
  },
993
+ // The pill counts the rows the table would list, on its own count route:
994
+ // the rows its tab, search and filters match now, less those it holds.
995
+ newRows: {
996
+ heldList: () => data.value,
997
+ countNow: async (signal) => {
998
+ const counts = await tableRows.countBatch(
999
+ [
1000
+ {
1001
+ id: NEW_ROWS_COUNT_ID,
1002
+ query: {
1003
+ ...buildTableQuery({
1004
+ pagination: { pageIndex: 0, pageSize: 0 },
1005
+ sorting: [],
1006
+ columnFilters: effectiveColumnFilters.value,
1007
+ hiddenFilters: hiddenFilters.value,
1008
+ globalFilter: effectiveGlobalFilter.value,
1009
+ }),
1010
+ ...archiveQuery.value,
1011
+ },
1012
+ },
1013
+ ],
1014
+ signal,
1015
+ );
1016
+ return counts[NEW_ROWS_COUNT_ID];
1017
+ },
1018
+ },
866
1019
  });
867
1020
 
868
1021
  // The list, its counters and its footer figures load in parallel: a server
@@ -919,7 +1072,13 @@ const listLoadError = computed(() => {
919
1072
  const shownData = computed(() =>
920
1073
  listLoadError.value ? undefined : data.value,
921
1074
  );
922
- const shownResults = computed(() => shownData.value?.results);
1075
+ const shownResults = computed(() => {
1076
+ const results = shownData.value?.results;
1077
+ const grouped = activeGroupedOptions.value;
1078
+ return results && isGroupedDisplay.value && grouped
1079
+ ? orderGroupedRows(results, grouped, pickedSorting.value, locale.value)
1080
+ : results;
1081
+ });
923
1082
 
924
1083
  // Rows selected before the failure can no longer be seen or verified, yet the
925
1084
  // selection toolbar would keep offering delete/archive/export on them.
@@ -1003,28 +1162,46 @@ watch(
1003
1162
  },
1004
1163
  );
1005
1164
 
1006
- // A view's counter shares the tab counters' request, under its own id.
1007
- const VIEW_COUNT_ID_PREFIX = "view:";
1008
- const countQuery = (filters: TableFilter[], search?: string) =>
1009
- buildTableQuery({
1010
- pagination: { pageIndex: 0, pageSize: 0 },
1011
- sorting: [],
1012
- columnFilters: filters,
1013
- hiddenFilters: queryParamHiddenFilters.value,
1014
- globalFilter: search,
1015
- });
1016
-
1017
- const tabCountsQuery = computed(() => [
1018
- ...resolvedTabs.value
1019
- .filter((tab) => !tab.to)
1020
- .map((tab) => ({ id: tab.id, query: countQuery(tab.filters) })),
1021
- ...tableViewItems.value
1022
- .filter((view) => view.count)
1023
- .map((view) => ({
1024
- id: `${VIEW_COUNT_ID_PREFIX}${view.id}`,
1025
- query: countQuery(view.filters ?? [], view.search),
1026
- })),
1165
+ const scopeFilters = computed<TableFilter[]>(() => [
1166
+ ...queryParamHiddenFilters.value,
1167
+ ...routeParamHiddenFilters.value,
1027
1168
  ]);
1169
+ const countState = computed<TableCountState>(() => ({
1170
+ scopeFilters: scopeFilters.value,
1171
+ tabFilters: activeTabFilters.value,
1172
+ columnFilters: effectiveColumnFilters.value,
1173
+ search: effectiveGlobalFilter.value,
1174
+ }));
1175
+ const isCountStateNarrowed = computed(() => isCountNarrowed(countState.value));
1176
+
1177
+ // Each counter counts what a click on it would list, so the counters follow
1178
+ // the filters, the search, the view and the tab: picks made in a row (quick
1179
+ // filters toggled one after the other) are counted once, when they settle.
1180
+ const COUNTS_DEBOUNCE_MS = 200;
1181
+ const debouncedCountsQuery = refDebounced(
1182
+ computed(() =>
1183
+ buildTableCountQueries({
1184
+ ...countState.value,
1185
+ tabs: resolvedTabs.value
1186
+ .filter((tab) => !tab.to)
1187
+ .map((tab) => ({
1188
+ id: tab.id,
1189
+ filters: tab.filters,
1190
+ hasNavBadge: !!tab.navBadge,
1191
+ })),
1192
+ views: tableViewItems.value
1193
+ .filter((view) => view.count)
1194
+ .map((view) => ({
1195
+ id: view.id,
1196
+ filters: viewFilters(view, pinnedDefaultFilters),
1197
+ search: view.search,
1198
+ })),
1199
+ activeViewId: activeViewId.value,
1200
+ }),
1201
+ ),
1202
+ COUNTS_DEBOUNCE_MS,
1203
+ );
1204
+ const tabCountsQuery = computed(() => debouncedCountsQuery.value);
1028
1205
 
1029
1206
  // No default: `null` until the counts arrive, so the tabs draw placeholders
1030
1207
  // instead of a count of nothing.
@@ -1095,6 +1272,83 @@ const hasMoreRows = computed(
1095
1272
  const loadMoreRows = () => {
1096
1273
  if (hasMoreRows.value && !isListLoading.value) loadNextPage();
1097
1274
  };
1275
+ // A row's drawer or side panel steps through every row the table lists: past
1276
+ // the rows loaded, it turns the table's page (or grows its feed) and steps
1277
+ // onto the rows that arrive. A display reading its own rows keeps to them.
1278
+ const ADJACENT_ROWS_TIMEOUT_MS = 15_000;
1279
+ const listsServerPages = computed(() => !isActiveDisplaySelfManaged.value);
1280
+ // What a display reading its own rows shows (`setShownRows`), in its order:
1281
+ // the rows its row's container steps through. Another display, another set.
1282
+ const selfManagedRows = shallowRef<Data[]>([]);
1283
+ const setSelfManagedRows = (rows: T[]) => {
1284
+ selfManagedRows.value = rows as Data[];
1285
+ };
1286
+ watch(activeDisplayId, () => {
1287
+ selfManagedRows.value = [];
1288
+ });
1289
+ const navigableRows = (): Data[] =>
1290
+ listsServerPages.value
1291
+ ? ((shownResults.value ?? []) as Data[])
1292
+ : selfManagedRows.value;
1293
+ const loadedRowsOffset = () => {
1294
+ if (isAccumulating || !listsServerPages.value) return 0;
1295
+ const { pageIndex, pageSize } = paginationState.value;
1296
+ return pageSize > 0 ? pageIndex * pageSize : 0;
1297
+ };
1298
+ const turnPage = (step: RowStep): boolean => {
1299
+ if (isAccumulating) {
1300
+ if (step < 0 || !hasMoreRows.value) return false;
1301
+ loadNextPage();
1302
+ return true;
1303
+ }
1304
+ const { pageIndex, pageSize } = paginationState.value;
1305
+ const next = pageIndex + step;
1306
+ const total = data.value?.total ?? 0;
1307
+ if (pageSize <= 0 || next < 0 || next * pageSize >= total) return false;
1308
+ paginationState.value = { ...paginationState.value, pageIndex: next };
1309
+ return true;
1310
+ };
1311
+ let adjacentRowsLoad: Promise<boolean> | undefined;
1312
+ const loadAdjacentRows = (step: RowStep): Promise<boolean> => {
1313
+ // A second J while the page loads waits for the same page.
1314
+ if (adjacentRowsLoad) return adjacentRowsLoad;
1315
+ if (!listsServerPages.value || isListLoading.value || !turnPage(step)) {
1316
+ return Promise.resolve(false);
1317
+ }
1318
+ const before = shownResults.value;
1319
+ adjacentRowsLoad = until(
1320
+ () => shownResults.value !== before && !isListLoading.value,
1321
+ )
1322
+ .toBe(true, { timeout: ADJACENT_ROWS_TIMEOUT_MS })
1323
+ .then(() => !listLoadError.value && shownResults.value !== before)
1324
+ .finally(() => {
1325
+ adjacentRowsLoad = undefined;
1326
+ });
1327
+ return adjacentRowsLoad;
1328
+ };
1329
+ // A row past the rows loaded (a deep link, a row live rows pushed out) is
1330
+ // found in the table's whole result, under the query the list reads: its
1331
+ // position, and its neighbours for the steps. Its rows read in the order
1332
+ // the table shows them: pinned rows lead each page it reads, not the result.
1333
+ const locateRow = rowLocator({
1334
+ list: async (offset, limit) => {
1335
+ if (!listsServerPages.value) return null;
1336
+ const page = await tableRows.listAsShown(
1337
+ {
1338
+ ...queryRequest.value,
1339
+ ...archiveQuery.value,
1340
+ ...liveHold.heldQuery(),
1341
+ offset,
1342
+ limit,
1343
+ },
1344
+ requestedPagination.value.pageSize,
1345
+ );
1346
+ return page && { results: page.results as Data[], total: page.total };
1347
+ },
1348
+ rowIdKey: props.rowIdKey ?? ROW_ID_DEFAULT_KEY,
1349
+ isWholeRead: () => !!props.source && !props.source.capabilities.paginate,
1350
+ });
1351
+
1098
1352
  const accumulation = computed<TableAccumulation | undefined>(() =>
1099
1353
  isAccumulating
1100
1354
  ? {
@@ -1160,6 +1414,7 @@ provide(KANBAN_DISPLAY_BRIDGE_KEY, {
1160
1414
  groupByField: kanbanGroupBy,
1161
1415
  editAction: tableProps.value.rowActions?.edit,
1162
1416
  deleteAction: tableProps.value.rowActions?.delete,
1417
+ onCardMoved: () => void refreshCounts(),
1163
1418
  });
1164
1419
 
1165
1420
  // A link tab counts the rows of the list it opens; a list the caller may not
@@ -1198,14 +1453,19 @@ const refreshLinkTabCounts = async () => {
1198
1453
  // event) or a refresh: what summarises them elsewhere (a navigation count)
1199
1454
  // is told to read its figure again.
1200
1455
  const { notifyTableDataChanged } = useTableDataChanges();
1201
- const refreshAll = async () => {
1456
+ const refreshCounts = async () => {
1202
1457
  notifyTableDataChanged(location);
1203
1458
  await Promise.all([
1204
- refresh(),
1205
1459
  refreshTabCounts(),
1206
1460
  refreshLinkTabCounts(),
1207
1461
  refreshGroupCounts(),
1208
1462
  refreshSummaries(),
1463
+ ]);
1464
+ };
1465
+ const refreshAll = async () => {
1466
+ await Promise.all([
1467
+ refresh(),
1468
+ refreshCounts(),
1209
1469
  activeDisplayRef.value?.refresh?.(),
1210
1470
  ]);
1211
1471
  };
@@ -1222,6 +1482,11 @@ const refreshByUser = () => {
1222
1482
  liveGate.acknowledge();
1223
1483
  return refreshAll();
1224
1484
  };
1485
+ // A write of the user's (a row or bulk action, a form saved) can move what
1486
+ // the rest of the page shows of the same records — a KPI, a tab's badge,
1487
+ // another list: every block of the page reads its data again, this table
1488
+ // among them, at once even on a paused live page.
1489
+ const refreshAfterWrite = () => refreshPageBlocks();
1225
1490
 
1226
1491
  // "Preview as role": a link tab to a page the previewed role could not open
1227
1492
  // is drawn locked, one to a page it opens partially is drawn partially
@@ -1259,19 +1524,27 @@ const tabsWithCount = computed(() =>
1259
1524
 
1260
1525
  // A tab declared with `navBadge` keeps the navigation badge the server
1261
1526
  // counted for the page it stands for (the linked page, or this one) up to
1262
- // date with its own counter; an empty badge stands for zero.
1527
+ // date: with the tab's own counter while nothing narrows it, else with a
1528
+ // count of the tab alone, since the badge stands for the whole page. An
1529
+ // empty badge stands for zero.
1263
1530
  const { setNavBadge } = useNavBadges();
1531
+ const tabNavBadgeCount = (tab: ResolvedTab, count: number | undefined) =>
1532
+ tab.to
1533
+ ? count
1534
+ : navBadgeCount(tabCountsData.value, tab.id, isCountStateNarrowed.value);
1264
1535
  watch(
1265
1536
  tabsWithCount,
1266
1537
  (next) => {
1267
1538
  for (const tab of next) {
1268
1539
  const source = resolvedTabs.value.find((entry) => entry.id === tab.id);
1269
- if (!source?.navBadge || tab.count === undefined) continue;
1540
+ if (!source?.navBadge) continue;
1541
+ const count = tabNavBadgeCount(source, tab.count);
1542
+ if (count === undefined) continue;
1270
1543
  const fullId = source.to ? source.toPage : pageId;
1271
1544
  const tone =
1272
1545
  typeof source.navBadge === "object" ? source.navBadge.tone : undefined;
1273
1546
  if (fullId) {
1274
- setNavBadge(fullId, tab.count > 0 ? String(tab.count) : "", tone);
1547
+ setNavBadge(fullId, count > 0 ? String(count) : "", tone);
1275
1548
  }
1276
1549
  }
1277
1550
  },
@@ -1299,7 +1572,7 @@ const {
1299
1572
  caption,
1300
1573
  labelKey,
1301
1574
  rowIdKey: props.rowIdKey,
1302
- refreshCallback: refreshByUser,
1575
+ refreshCallback: refreshAfterWrite,
1303
1576
  formComponents,
1304
1577
  formContainer,
1305
1578
  formPages,
@@ -1308,9 +1581,16 @@ const {
1308
1581
  pageId: pageId!,
1309
1582
  queryParamFilters,
1310
1583
  rowNavigation: {
1311
- rows: () => (shownResults.value ?? []) as Data[],
1584
+ rows: navigableRows,
1312
1585
  rowIdKey: props.rowIdKey ?? ROW_ID_DEFAULT_KEY,
1313
1586
  getRow: (id) => tableRows.getRow(id) as Promise<Data | undefined>,
1587
+ total: () =>
1588
+ listsServerPages.value
1589
+ ? (shownData.value?.total ?? undefined)
1590
+ : undefined,
1591
+ offset: loadedRowsOffset,
1592
+ loadAdjacent: loadAdjacentRows,
1593
+ locate: locateRow,
1314
1594
  },
1315
1595
  recordScope: urlScope,
1316
1596
  onRecordShown: (id) => recordLink?.shown(id),
@@ -1382,12 +1662,14 @@ const deselectRows = (ids: string[]) => {
1382
1662
  };
1383
1663
 
1384
1664
  // From the archive, a delete is a permanent one: its confirmation says so.
1385
- const deleteRows = async (ids: string[]) => {
1665
+ // A display holding the rows (a kanban card) hands them for its wording.
1666
+ const deleteRows = async (ids: string[], rows?: T[]) => {
1386
1667
  const done = await deleteRowsAction(
1387
1668
  ids,
1388
1669
  tableProps.value.rowActions?.delete,
1389
1670
  {
1390
1671
  permanently: !!archiveMode && showArchived.value,
1672
+ rows,
1391
1673
  },
1392
1674
  );
1393
1675
  if (done) deselectRows(ids);
@@ -1414,12 +1696,18 @@ const rowActionConfig = (key: "details" | "duplicate" | "add" | "edit") =>
1414
1696
  tableProps.value.rowActions?.[key];
1415
1697
  const handleRowClick = (item: T) =>
1416
1698
  runConfirmedBuiltIn(rowActionConfig("details"), item, () => openRow(item));
1417
- const handleRowDuplicate = (itemId: string) =>
1418
- runConfirmedBuiltIn(
1699
+ // Its dialog words the row shown (`{name}`), else only its id.
1700
+ const handleRowDuplicate = (itemId: string) => {
1701
+ const idKey = props.rowIdKey ?? ROW_ID_DEFAULT_KEY;
1702
+ const shown = shownResults.value?.find(
1703
+ (row) => String(get(row, idKey)) === itemId,
1704
+ );
1705
+ return runConfirmedBuiltIn(
1419
1706
  rowActionConfig("duplicate"),
1420
- { [props.rowIdKey ?? ROW_ID_DEFAULT_KEY]: itemId },
1707
+ shown ?? { [idKey]: itemId },
1421
1708
  () => duplicateRow(itemId),
1422
1709
  );
1710
+ };
1423
1711
  const handleRowAdd = () =>
1424
1712
  runConfirmedBuiltIn(rowActionConfig("add"), undefined, () =>
1425
1713
  newRow(queryParamDefaults.value),
@@ -1484,12 +1772,14 @@ const realtimeEventHandlers: Record<
1484
1772
  return refreshAll();
1485
1773
  },
1486
1774
  [REALTIME_EVENT_TYPE.CREATED]: () => refreshAll(),
1775
+ // An edited row can change tab, group or column: the counts are read again
1776
+ // with it.
1487
1777
  [REALTIME_EVENT_TYPE.UPDATED]: async (ids) => {
1488
1778
  if (isActiveDisplaySelfManaged.value) {
1489
- await activeDisplayRef.value?.refresh?.();
1779
+ await Promise.all([activeDisplayRef.value?.refresh?.(), refreshCounts()]);
1490
1780
  return;
1491
1781
  }
1492
- await Promise.all(ids.map((id) => patchRowLocal(id)));
1782
+ await Promise.all([...ids.map((id) => patchRowLocal(id)), refreshCounts()]);
1493
1783
  },
1494
1784
  };
1495
1785
 
@@ -1636,22 +1926,20 @@ const expandedModel = computed<ExpandedState>({
1636
1926
  },
1637
1927
  });
1638
1928
 
1639
- let expandedQueryKey: string | undefined;
1929
+ let expandedListedKey: string | undefined;
1640
1930
  if (expandableConfig) {
1641
1931
  watch(
1642
1932
  shownResults,
1643
1933
  (rows) => {
1644
- const queryKey = JSON.stringify([queryRequest.value, archiveQuery.value]);
1645
- if (queryKey === expandedQueryKey) {
1646
- expanded.value = keepListedRows(expanded.value, rowIdsOf(rows));
1647
- return;
1648
- }
1649
- expandedQueryKey = queryKey;
1650
- expanded.value = defaultExpandedRows(
1651
- rowIdsOf(rows),
1934
+ const next = expandedRowsOnListing(
1935
+ { expanded: expanded.value, listedKey: expandedListedKey },
1936
+ rows ? rowIdsOf(rows) : undefined,
1937
+ JSON.stringify([queryRequest.value, archiveQuery.value]),
1652
1938
  expandableConfig.defaultExpanded,
1653
1939
  expandableConfig.single,
1654
1940
  );
1941
+ expandedListedKey = next.listedKey;
1942
+ expanded.value = next.expanded;
1655
1943
  },
1656
1944
  { immediate: true },
1657
1945
  );
@@ -1896,6 +2184,7 @@ const displayContext = computed(
1896
2184
  pageId,
1897
2185
  options: optionsForDisplay(activeDisplayId.value),
1898
2186
  refresh: refreshByUser,
2187
+ setShownRows: setSelfManagedRows,
1899
2188
  reorder: reorderState.value,
1900
2189
  table: displayTable.value,
1901
2190
  }),
@@ -1993,13 +2282,16 @@ defineShortcuts(
1993
2282
  );
1994
2283
 
1995
2284
  // Each kept field with its default: a value back to it is forgotten.
2285
+ // A hand-ordered table keeps no sort.
1996
2286
  const tableStatePreferences: TableStatePreference[] = [
1997
- ["sorting", sorting, defaultSortState],
2287
+ ...(reorderConfig
2288
+ ? []
2289
+ : [["sorting", sorting, defaultSortState] as TableStatePreference]),
1998
2290
  ["columnFilters", columnFilters, []],
1999
2291
  ["columnVisibility", columnVisibility, initialVisibility],
2000
2292
  ["filtersOpen", filtersRowOpen, hasEmptyDefaultFilter],
2001
2293
  ["activeTab", activeTabId, ALL_TAB_ID],
2002
- ["viewMode", activeDisplayId, defaultDisplay],
2294
+ ["viewMode", activeDisplayId, displayDefault],
2003
2295
  ["columnOrder", columnOrder, []],
2004
2296
  ["kanbanGroupBy", kanbanGroupBy, defaultKanbanGroupBy],
2005
2297
  ["groupedGroupBy", groupedGroupBy, defaultGroupedGroupBy],
@@ -2010,7 +2302,7 @@ tableStatePreferences.forEach(([field, state, defaultValue]) => {
2010
2302
  watch(
2011
2303
  state,
2012
2304
  (newValue) => {
2013
- tablePreferences.set(field, newValue, defaultValue);
2305
+ tablePreferences.set(field, newValue, unref(defaultValue));
2014
2306
  },
2015
2307
  { deep: true },
2016
2308
  );
@@ -2132,6 +2424,7 @@ onMounted(() => {
2132
2424
  :url-filters="shownUrlFilters"
2133
2425
  :default-page-size="props.pageSize"
2134
2426
  :archive-toggle="canToggleArchived"
2427
+ :archived-count-label="props.archivedCountLabel"
2135
2428
  :displays="availableDisplays"
2136
2429
  :active-capabilities="activeCapabilities"
2137
2430
  :kanban-group-by-options="kanbanGroupByOptions"
@@ -2143,7 +2436,10 @@ onMounted(() => {
2143
2436
  :load-error="listLoadError"
2144
2437
  :data="shownResults || []"
2145
2438
  :pagination-options="{ manualPagination: true, rowCount: data?.total }"
2146
- :sorting-options="{ manualSorting: true }"
2439
+ :sorting-options="{
2440
+ manualSorting: true,
2441
+ enableSorting: activeCapabilities.sorting,
2442
+ }"
2147
2443
  :default-sort="tableDefaultSort"
2148
2444
  :default-sort-label="defaultSortLabel"
2149
2445
  :default-sort-kind="defaultSortKind"
@@ -2171,10 +2467,7 @@ onMounted(() => {
2171
2467
  @remove-url-filter="removeUrlFilter"
2172
2468
  @edit="handleRowEdit"
2173
2469
  >
2174
- <template
2175
- v-if="liveHold.newCount.value > 0 || liveHold.reason.value"
2176
- #notice
2177
- >
2470
+ <template v-if="liveHold.reason.value" #notice>
2178
2471
  <LiveHoldBanner
2179
2472
  :count="liveHold.newCount.value"
2180
2473
  :reason="liveHold.reason.value"
@@ -2183,6 +2476,27 @@ onMounted(() => {
2183
2476
  @show="liveHold.showNew"
2184
2477
  />
2185
2478
  </template>
2479
+ <!-- What came in while the rows were read or the page paused: a pill
2480
+ over the first rows, under the column header band (the table sets
2481
+ its height; a sticky one stays put above a scrolled area, so the pill
2482
+ stays under it), taking no room, so the rows stay where they are.
2483
+ Above the pinned columns and the presence rail it overlaps. -->
2484
+ <template v-else-if="liveHold.newCount.value > 0" #notice>
2485
+ <div class="relative z-30 h-0">
2486
+ <div
2487
+ class="pointer-events-none absolute inset-x-0 top-[calc(var(--dms-table-head-height,0px)+0.5rem)] flex justify-center px-4"
2488
+ >
2489
+ <LiveHoldBanner
2490
+ floating
2491
+ class="pointer-events-auto"
2492
+ :count="liveHold.newCount.value"
2493
+ :missed-label="liveHold.missedLabel"
2494
+ :detail="liveHold.missedDetail.value"
2495
+ @show="liveHold.showNew"
2496
+ />
2497
+ </div>
2498
+ </div>
2499
+ </template>
2186
2500
  <template v-if="hasViews" #views>
2187
2501
  <TableViews
2188
2502
  :items="viewItems"
@@ -2191,7 +2505,7 @@ onMounted(() => {
2191
2505
  :modified="isViewModified"
2192
2506
  :can-save-as="canSaveViews"
2193
2507
  :divided="tabsWithCount.length === 0"
2194
- @open="openView"
2508
+ @open="selectView"
2195
2509
  @reset="resetView"
2196
2510
  @save="saveView"
2197
2511
  @save-as="saveAsNewView"