@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,11 +1,19 @@
1
- import { computed, ref, watch, type ComputedRef } from "vue";
1
+ import {
2
+ computed,
3
+ getCurrentScope,
4
+ onMounted,
5
+ onScopeDispose,
6
+ ref,
7
+ watch,
8
+ type ComputedRef,
9
+ } from "vue";
2
10
  import { usePageLive } from "../../../composables/live/usePageLive";
3
11
  import type {
4
12
  PageLiveNote,
5
13
  TableViewLiveHoldOptions,
6
14
  } from "../../../types/live";
7
15
  import type { LiveGate } from "../live/currentPageLive";
8
- import { useScrolledPastTop } from "./useScrolledPastTop";
16
+ import { useScrolledPastTop, useTableReached } from "./useScrolledPastTop";
9
17
  import { LIVE_HELD_AT_QUERY_KEY } from "./useTableRows";
10
18
 
11
19
  type Query = Record<string, unknown>;
@@ -18,7 +26,10 @@ export interface TableLiveHoldSignals {
18
26
  isSearching?: () => boolean;
19
27
  /** The rows are sorted otherwise than by the table's default. */
20
28
  isSorted?: () => boolean;
21
- /** The table's element, whose first row `whileScrolled` follows. */
29
+ /**
30
+ * The table's element: whether it is on screen, and where its first row
31
+ * stands for `whileScrolled`.
32
+ */
22
33
  tableElement?: () => unknown;
23
34
  }
24
35
 
@@ -29,6 +40,29 @@ export type TableLiveHoldReason =
29
40
  | "sorted"
30
41
  | "scrolled";
31
42
 
43
+ /**
44
+ * A table holds by itself for a reason it names, or because the user has its
45
+ * rows on screen (`reading`): new rows would push them down. The latter goes
46
+ * unsaid; the new rows wait behind the "N new · Show" pill.
47
+ */
48
+ type LocalHoldReason = TableLiveHoldReason | "reading";
49
+
50
+ /**
51
+ * How a held table counts the new rows it would show: the rows its current
52
+ * query (tab, search, filters) matches now, less the rows it holds. Without
53
+ * it the pill counts every event of the table's topic, whatever row each
54
+ * one is about.
55
+ */
56
+ export interface TableNewRowsCounter {
57
+ /**
58
+ * The list on screen, `null` before it arrives; read again whenever it
59
+ * changes (a new read, another query), which counts again.
60
+ */
61
+ heldList: () => { total: number } | null | undefined;
62
+ /** How many rows the table's current query matches now, unbounded. */
63
+ countNow: (signal: AbortSignal) => Promise<number | undefined>;
64
+ }
65
+
32
66
  export interface TableLiveHoldOptions {
33
67
  /** The table's `liveHold`: `true` or an object enables it. */
34
68
  liveHold: boolean | TableViewLiveHoldOptions | undefined;
@@ -40,12 +74,16 @@ export interface TableLiveHoldOptions {
40
74
  */
41
75
  gate: () => LiveGate;
42
76
  signals?: TableLiveHoldSignals;
77
+ newRows?: TableNewRowsCounter;
43
78
  }
44
79
 
45
80
  export interface TableLiveHold {
46
81
  /** The rows are held: the page is paused, or the table holds by itself. */
47
82
  isHolding: ComputedRef<boolean>;
48
- /** Events the table received since its rows were held or last shown. */
83
+ /**
84
+ * The rows the table would add since its rows were held or last shown
85
+ * (`newRows`), or else the events it received meanwhile.
86
+ */
49
87
  newCount: ComputedRef<number>;
50
88
  /**
51
89
  * Why the table holds by itself while its page is live, `null` otherwise:
@@ -77,22 +115,26 @@ function holdOptionsOf(
77
115
  }
78
116
 
79
117
  // The table alone holds while the page is live: the user reads a row in the
80
- // side panel, narrows or reorders the rows, or reads further down than the
81
- // rows that would move. The first reason in this order is the one shown.
118
+ // side panel, narrows or reorders the rows, reads further down than the rows
119
+ // that would move, or has them on screen at all — a new row at the top would
120
+ // push them down. The first reason in this order is the one shown.
82
121
  function useLocalHold(
83
122
  holdOptions: TableViewLiveHoldOptions | null,
84
123
  signals: TableLiveHoldSignals,
85
- ): ComputedRef<TableLiveHoldReason | null> {
124
+ ): ComputedRef<LocalHoldReason | null> {
86
125
  const tableElement = signals.tableElement ?? NO_ELEMENT;
87
- const isScrolled = useScrolledPastTop(() => {
88
- const element = tableElement();
89
- return element instanceof HTMLElement ? element : null;
90
- }, !!holdOptions?.whileScrolled);
91
- const reasons: Array<[TableLiveHoldReason, boolean, () => boolean]> = [
126
+ const element = () => {
127
+ const target = tableElement();
128
+ return target instanceof HTMLElement ? target : null;
129
+ };
130
+ const isScrolled = useScrolledPastTop(element, !!holdOptions?.whileScrolled);
131
+ const isReached = useTableReached(element, !!holdOptions);
132
+ const reasons: Array<[LocalHoldReason, boolean, () => boolean]> = [
92
133
  ["rowOpen", !!holdOptions?.whileRowOpen, signals.isRowOpen ?? NEVER],
93
134
  ["searching", !!holdOptions?.whileSearching, signals.isSearching ?? NEVER],
94
135
  ["sorted", !!holdOptions?.whileSorted, signals.isSorted ?? NEVER],
95
136
  ["scrolled", !!holdOptions?.whileScrolled, () => isScrolled.value],
137
+ ["reading", !!holdOptions, () => isReached.value],
96
138
  ];
97
139
  return computed(
98
140
  () =>
@@ -102,10 +144,11 @@ function useLocalHold(
102
144
  }
103
145
 
104
146
  /**
105
- * The `liveHold` of a table view: while its live page is paused — or, with
106
- * `whileRowOpen` / `whileSearching` / `whileSorted` / `whileScrolled`, while
107
- * the user reads a row, searches, sorts or scrolled down — the table keeps
108
- * its rows: the gate requests nothing on its events and counts them, and a
147
+ * The `liveHold` of a table view: while its live page is paused, while its
148
+ * rows are on screen or scrolled past (rows landing at the top would move
149
+ * them) — or, with `whileRowOpen` / `whileSearching` / `whileSorted` /
150
+ * `whileScrolled`, saying so, while the user reads a row, searches, sorts or
151
+ * scrolled down — the table keeps its rows: the gate requests nothing on its events and counts them, and a
109
152
  * source route is told the moment they were frozen, so the user's own
110
153
  * search, sort and pagination list the rows as they stood.
111
154
  * Show reads them once more without leaving the hold; the end of a hold of
@@ -132,16 +175,19 @@ export function useTableLiveHold(options: TableLiveHoldOptions): TableLiveHold {
132
175
  },
133
176
  { flush: "sync" },
134
177
  );
135
- const newCount = computed(() =>
136
- isHolding.value ? options.gate().missed.value : 0,
137
- );
178
+ const missed = () => (isHolding.value ? options.gate().missed.value : 0);
179
+ const newCount = options.newRows
180
+ ? useCountedNewRows(missed, options.newRows)
181
+ : computed(missed);
138
182
 
139
183
  return {
140
184
  isHolding,
141
185
  newCount,
142
- reason: computed(() =>
143
- isHolding.value && !isPaused.value ? localReason.value : null,
144
- ),
186
+ reason: computed(() => {
187
+ if (!isHolding.value || isPaused.value) return null;
188
+ const reason = localReason.value;
189
+ return reason === "reading" ? null : reason;
190
+ }),
145
191
  missedLabel: holdOptions?.missedLabel,
146
192
  missedDetail: computed(() =>
147
193
  isPaused.value && newCount.value > 0 ? missedDetail.value : null,
@@ -151,6 +197,73 @@ export function useTableLiveHold(options: TableLiveHoldOptions): TableLiveHold {
151
197
  };
152
198
  }
153
199
 
200
+ /** Lets a burst of events through before counting the new rows once. */
201
+ const NEW_ROWS_COUNT_DELAY_MS = 500;
202
+
203
+ // The new rows the table would show, counted on its source while it holds
204
+ // some events: a topic publishes on every row, so a "4xx" tab, a search or a
205
+ // filter would otherwise announce rows it never lists. Counted again on each
206
+ // new event or held list, a burst at a time. Rows removed meanwhile lower
207
+ // the count: the table answers what it lists, not what came in. A count that
208
+ // fails falls back to the events. Only in the browser, once mounted: the
209
+ // table sets up its gate and its list after its hold.
210
+ function useCountedNewRows(
211
+ missed: () => number,
212
+ counter: TableNewRowsCounter,
213
+ ): ComputedRef<number> {
214
+ const counted = ref(0);
215
+ const hasFailed = ref(false);
216
+ let timer: ReturnType<typeof setTimeout> | undefined;
217
+ let inFlight: AbortController | undefined;
218
+
219
+ const cancel = () => {
220
+ clearTimeout(timer);
221
+ timer = undefined;
222
+ inFlight?.abort();
223
+ inFlight = undefined;
224
+ };
225
+ const count = async () => {
226
+ timer = undefined;
227
+ const held = counter.heldList()?.total;
228
+ if (held === undefined) return;
229
+ inFlight?.abort();
230
+ const controller = new AbortController();
231
+ inFlight = controller;
232
+ try {
233
+ const now = await counter.countNow(controller.signal);
234
+ if (controller.signal.aborted) return;
235
+ hasFailed.value = now === undefined;
236
+ counted.value = Math.max(0, (now ?? held) - held);
237
+ } catch {
238
+ if (!controller.signal.aborted) hasFailed.value = true;
239
+ }
240
+ };
241
+
242
+ const scope = getCurrentScope();
243
+ const follow = () =>
244
+ watch(
245
+ [missed, counter.heldList],
246
+ ([events]) => {
247
+ if (events === 0) {
248
+ cancel();
249
+ counted.value = 0;
250
+ hasFailed.value = false;
251
+ return;
252
+ }
253
+ timer ??= setTimeout(count, NEW_ROWS_COUNT_DELAY_MS);
254
+ },
255
+ { immediate: true },
256
+ );
257
+ onMounted(() => scope?.run(follow));
258
+ onScopeDispose(cancel);
259
+
260
+ return computed(() => {
261
+ const events = missed();
262
+ if (events === 0) return 0;
263
+ return hasFailed.value ? events : counted.value;
264
+ });
265
+ }
266
+
154
267
  // The bound a source route receives while the rows are held: they are as
155
268
  // fresh as the hold, or as the last read the user asked for since (Show, an
156
269
  // action of theirs).
@@ -11,7 +11,8 @@ import {
11
11
  resolveUrlVariables,
12
12
  } from "../../utils/urlVariables";
13
13
  import type { RowActionRule } from "#dms-core/app/types/row-action";
14
- import { pinRows } from "./utils/pinnedRows";
14
+ import { compareValues, textOf } from "./utils/compareValues";
15
+ import { pinRows, pinRowsBySpan } from "./utils/pinnedRows";
15
16
  import {
16
17
  appendPeriodToUrl,
17
18
  usePeriodScope,
@@ -86,18 +87,6 @@ export function sourceRouteQuery(
86
87
  );
87
88
  }
88
89
 
89
- const textOf = (value: unknown): string =>
90
- value === null || value === undefined
91
- ? ""
92
- : typeof value === "object"
93
- ? JSON.stringify(value)
94
- : String(value);
95
-
96
- const compareValues = (a: unknown, b: unknown): number => {
97
- if (typeof a === "number" && typeof b === "number") return a - b;
98
- return textOf(a).localeCompare(textOf(b), undefined, { numeric: true });
99
- };
100
-
101
90
  /**
102
91
  * The rows the browser narrows, sorts and pages itself, for what the source
103
92
  * route left to it: a search over the listed columns' text, the sort on one
@@ -155,7 +144,10 @@ interface SharedRead<T> {
155
144
  * order its keys were built in.
156
145
  */
157
146
  const sourceReadKey = (url: string, query: Query): string =>
158
- JSON.stringify([url, Object.entries(query).sort(([a], [b]) => a.localeCompare(b))]);
147
+ JSON.stringify([
148
+ url,
149
+ Object.entries(query).sort(([a], [b]) => a.localeCompare(b)),
150
+ ]);
159
151
 
160
152
  /**
161
153
  * Settles with `answer`, or rejects as soon as `signal` aborts, calling
@@ -173,9 +165,9 @@ function awaitUnlessAborted<R>(
173
165
  };
174
166
  if (signal.aborted) return abort();
175
167
  signal.addEventListener("abort", abort, { once: true });
176
- answer.then(resolve, reject).finally(() =>
177
- signal.removeEventListener("abort", abort),
178
- );
168
+ answer
169
+ .then(resolve, reject)
170
+ .finally(() => signal.removeEventListener("abort", abort));
179
171
  });
180
172
  }
181
173
 
@@ -294,19 +286,57 @@ export function useTableRows<T extends Record<string, unknown>>(
294
286
  ): TableViewListResponse<T> | null =>
295
287
  page && { ...page, results: pinRows(page.results, options.pinnedRows?.()) };
296
288
 
289
+ // The list the route answers, before the pinned rows lead it.
290
+ const listAnswered = (
291
+ query: Query,
292
+ signal?: AbortSignal,
293
+ ): Promise<TableViewListResponse<T> | null> =>
294
+ source
295
+ ? listFromSource(source, query, signal)
296
+ : api<TableViewListResponse<T>>(`${location}/list`, { query, signal });
297
+
297
298
  // `signal` aborts the request once its answer is no longer wanted.
298
299
  const list = async (
299
300
  query: Query,
300
301
  signal?: AbortSignal,
301
302
  ): Promise<TableViewListResponse<T> | null> =>
302
- withPinnedRows(
303
- source
304
- ? await listFromSource(source, query, signal)
305
- : await api<TableViewListResponse<T>>(`${location}/list`, {
306
- query,
307
- signal,
308
- }),
309
- );
303
+ withPinnedRows(await listAnswered(query, signal));
304
+
305
+ /**
306
+ * The rows `query.offset` and `query.limit` name, in the order a table
307
+ * reading `span` rows at a time from the first shows them: the pinned
308
+ * rows lead each of its reads (a page), not the rows read here. The reads
309
+ * they fall in are read whole, then each pinned on its own. A source
310
+ * answering every row pins them all before its pages are cut: as `list`.
311
+ */
312
+ const listAsShown = async (
313
+ query: Query,
314
+ span: number,
315
+ ): Promise<TableViewListResponse<T> | null> => {
316
+ const pinnedRows = options.pinnedRows?.();
317
+ const offset = Number(query.offset) || 0;
318
+ const limit = Number(query.limit) || 0;
319
+ const isWholeSource = !!source && !source.capabilities.paginate;
320
+ if (!pinnedRows || span <= 0 || limit <= 0 || isWholeSource) {
321
+ return list(query);
322
+ }
323
+ const start = Math.floor(offset / span) * span;
324
+ const end = Math.ceil((offset + limit) / span) * span;
325
+ const page = await listAnswered({
326
+ ...query,
327
+ offset: start,
328
+ limit: end - start,
329
+ });
330
+ if (!page) return null;
331
+ const shown = pinRowsBySpan(page.results, span, pinnedRows);
332
+ const from = offset - start;
333
+ return {
334
+ ...page,
335
+ results: shown.slice(from, from + limit),
336
+ offset,
337
+ limit,
338
+ };
339
+ };
310
340
 
311
341
  // A source answers its totals one list at a time.
312
342
  const countBatch = async (
@@ -358,6 +388,7 @@ export function useTableRows<T extends Record<string, unknown>>(
358
388
  sourceUrl,
359
389
  isPeriodPending,
360
390
  list,
391
+ listAsShown,
361
392
  countBatch,
362
393
  getRow,
363
394
  };
@@ -44,6 +44,7 @@ import {
44
44
  resolveFormContainerTexts,
45
45
  type TableViewFormKind,
46
46
  } from "./utils/formTexts";
47
+ import { dmsOwnDrawer } from "../containers/context";
47
48
 
48
49
  const DEFAULT_ROW_ID_KEY = "_id";
49
50
  const DEFAULT_MODAL_SIZE = "xl";
@@ -95,6 +96,12 @@ type ConfirmedRun = NonNullable<ConfirmOptions["onConfirm"]>;
95
96
  /** Options of a delete: from the archive, it is a permanent one. */
96
97
  interface DeleteRowsOptions {
97
98
  permanently?: boolean;
99
+ /**
100
+ * The rows deleted, when the caller holds them: a dialog naming the row
101
+ * (`{name}`) reads its fields here first. A kanban card is not among the
102
+ * rows the table shows.
103
+ */
104
+ rows?: Data[];
98
105
  }
99
106
 
100
107
  export const useTableRowActions = <T extends Data>(
@@ -249,24 +256,26 @@ export const useTableRowActions = <T extends Data>(
249
256
  fetchUrl?: string,
250
257
  submitDefaults?: Record<string, unknown>,
251
258
  ) => {
252
- const drawer = openDrawer({
253
- title,
254
- description,
255
- containerId,
256
- component: DmsForm,
257
- componentOptions: {
258
- ...processedOptions,
259
- ...(fetchUrl && { fetchUrl }),
260
- ...(submitDefaults && { submitDefaults }),
261
- componentId: `${config.componentId}-${componentIdSuffix}`,
262
- pageId: pageIdSuffix,
259
+ const drawer = openDrawer(
260
+ dmsOwnDrawer({
261
+ title,
262
+ description,
263
263
  containerId,
264
- onSuccessCallback: () => {
265
- drawer.close();
266
- config.refreshCallback?.();
264
+ component: DmsForm,
265
+ componentOptions: {
266
+ ...processedOptions,
267
+ ...(fetchUrl && { fetchUrl }),
268
+ ...(submitDefaults && { submitDefaults }),
269
+ componentId: `${config.componentId}-${componentIdSuffix}`,
270
+ pageId: pageIdSuffix,
271
+ containerId,
272
+ onSuccessCallback: () => {
273
+ drawer.close();
274
+ config.refreshCallback?.();
275
+ },
267
276
  },
268
- },
269
- });
277
+ }),
278
+ );
270
279
  return drawer;
271
280
  };
272
281
 
@@ -654,38 +663,65 @@ export const useTableRowActions = <T extends Data>(
654
663
  };
655
664
  };
656
665
 
657
- const findShownRow = (id: string | undefined): Data | undefined =>
658
- config.rowNavigation
659
- ?.rows()
660
- .find(
661
- (row) =>
662
- String(getItemId(row, config.rowIdKey ?? DEFAULT_ROW_ID_KEY)) === id,
663
- );
666
+ const rowIdOf = (row: Data): string =>
667
+ String(getItemId(row, config.rowIdKey ?? DEFAULT_ROW_ID_KEY));
668
+
669
+ const findRowIn = (rows: Data[] | undefined, id: string) =>
670
+ rows?.find((row) => rowIdOf(row) === id);
671
+
672
+ // A row the table does not show (a kanban card, a row deleted from a
673
+ // custom card) is read by id: a `from` dialog asks the server instead.
674
+ const readRow = async (
675
+ id: string,
676
+ declared: ActionConfirm,
677
+ ): Promise<Data | undefined> => {
678
+ const getRow = config.rowNavigation?.getRow;
679
+ if (!getRow || isConfirmFrom(declared)) return undefined;
680
+ return getRow(id).catch(() => undefined);
681
+ };
682
+
683
+ /** The one row a dialog words: the caller's, the table's, else read. */
684
+ const findConfirmRow = async (
685
+ id: string,
686
+ declared: ActionConfirm,
687
+ rows?: Data[],
688
+ ): Promise<Data | undefined> =>
689
+ findRowIn(rows, id) ??
690
+ findRowIn(config.rowNavigation?.rows(), id) ??
691
+ (await readRow(id, declared));
664
692
 
665
693
  /**
666
- * What a fixed dialog words: the rows' count, and for one row shown in the
667
- * table its own fields too (`{name}`). The count wins a clash, it is what
668
- * every bulk dialog may name.
694
+ * What a fixed dialog words: the rows' count, and for one row its own
695
+ * fields too (`{name}`). The count wins a clash, it is what every bulk
696
+ * dialog may name.
669
697
  */
670
- const confirmRowFields = (ids: string[]): Data => {
698
+ const confirmRowFields = async (
699
+ ids: string[],
700
+ declared: ActionConfirm,
701
+ rows?: Data[],
702
+ ): Promise<Data> => {
671
703
  const count = { count: ids.length };
672
- const row = ids.length === 1 ? findShownRow(ids[0]) : undefined;
704
+ const row =
705
+ ids.length === 1
706
+ ? await findConfirmRow(ids[0]!, declared, rows)
707
+ : undefined;
673
708
  return row ? { ...row, ...count } : count;
674
709
  };
675
710
 
676
711
  /** Asks the declared confirmation of a bulk action, running it inside. */
677
- const askDeclaredBulkConfirm = (
712
+ const askDeclaredBulkConfirm = async (
678
713
  declared: ActionConfirm,
679
714
  ids: string[],
680
715
  fallback: Partial<ConfirmOptions>,
681
716
  run: ConfirmedRun,
717
+ rows?: Data[],
682
718
  ): Promise<boolean> =>
683
719
  confirmAction(declared, {
684
720
  urlParams: {
685
721
  id: ids[0],
686
722
  [config.rowIdKey ?? DEFAULT_ROW_ID_KEY]: ids[0],
687
723
  },
688
- row: confirmRowFields(ids),
724
+ row: await confirmRowFields(ids, declared, rows),
689
725
  fallback,
690
726
  run,
691
727
  });
@@ -703,11 +739,14 @@ export const useTableRowActions = <T extends Data>(
703
739
  actionConfig: boolean | RowActionConfig | undefined,
704
740
  action: ConfirmedBulkAction,
705
741
  bulkConfig: BulkActionConfig,
742
+ rows?: Data[],
706
743
  ): Promise<boolean> => {
707
744
  const fallback = bulkActionConfirm(action, ids.length, t);
708
745
  const run = bulkActionRun(ids, actionConfig, bulkConfig);
709
746
  const declared = declaredBulkConfirm(actionConfig, ids);
710
- if (declared) return askDeclaredBulkConfirm(declared, ids, fallback, run);
747
+ if (declared) {
748
+ return askDeclaredBulkConfirm(declared, ids, fallback, run, rows);
749
+ }
711
750
  return confirm({ ...fallback, onConfirm: run });
712
751
  };
713
752
 
@@ -746,7 +785,13 @@ export const useTableRowActions = <T extends Data>(
746
785
  ): Promise<boolean> => {
747
786
  if (!normalizeActionConfig(deleteConfig).isEnabled) return false;
748
787
  const action = options.permanently ? "deletePermanently" : "delete";
749
- return confirmBulkAction(ids, deleteConfig, action, DELETE_CONFIG);
788
+ return confirmBulkAction(
789
+ ids,
790
+ deleteConfig,
791
+ action,
792
+ DELETE_CONFIG,
793
+ options.rows,
794
+ );
750
795
  };
751
796
 
752
797
  const archiveRows = async (
@@ -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,