@antelopejs/dms 0.9.8 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (451) hide show
  1. package/CHANGELOG.md +171 -0
  2. package/README.md +1 -1
  3. package/data/module-store/catalog.json +2 -2
  4. package/dist/attachments/table-view.js +2 -2
  5. package/dist/attachments/table-view.js.map +1 -1
  6. package/dist/automation/actions.d.ts +2 -0
  7. package/dist/automation/actions.js +116 -71
  8. package/dist/automation/actions.js.map +1 -1
  9. package/dist/automation/i18n.d.ts +4 -0
  10. package/dist/automation/i18n.js +19 -0
  11. package/dist/automation/i18n.js.map +1 -0
  12. package/dist/automation/send-notification.d.ts +21 -0
  13. package/dist/automation/send-notification.js +163 -0
  14. package/dist/automation/send-notification.js.map +1 -0
  15. package/dist/automation/triggers.js +19 -25
  16. package/dist/automation/triggers.js.map +1 -1
  17. package/dist/client-origin-cors.d.ts +1 -0
  18. package/dist/client-origin-cors.js +19 -0
  19. package/dist/client-origin-cors.js.map +1 -0
  20. package/dist/config.d.ts +3 -2
  21. package/dist/config.js +5 -7
  22. package/dist/config.js.map +1 -1
  23. package/dist/frontend-module.d.ts +4 -0
  24. package/dist/frontend-module.js +37 -0
  25. package/dist/frontend-module.js.map +1 -0
  26. package/dist/implementations/dms/client-ip.js +1 -1
  27. package/dist/implementations/dms/client-ip.js.map +1 -1
  28. package/dist/implementations/dms/dev-reload.d.ts +5 -0
  29. package/dist/implementations/dms/dev-reload.js +23 -3
  30. package/dist/implementations/dms/dev-reload.js.map +1 -1
  31. package/dist/implementations/dms/frontend-modules.d.ts +2 -4
  32. package/dist/implementations/dms/frontend-modules.js +38 -94
  33. package/dist/implementations/dms/frontend-modules.js.map +1 -1
  34. package/dist/implementations/dms/frontend-ready.d.ts +12 -2
  35. package/dist/implementations/dms/frontend-ready.js +34 -4
  36. package/dist/implementations/dms/frontend-ready.js.map +1 -1
  37. package/dist/implementations/dms/layout-banners.d.ts +2 -1
  38. package/dist/implementations/dms/layout-banners.js +9 -0
  39. package/dist/implementations/dms/layout-banners.js.map +1 -1
  40. package/dist/implementations/dms/manifest.d.ts +7 -2
  41. package/dist/implementations/dms/manifest.js +12 -7
  42. package/dist/implementations/dms/manifest.js.map +1 -1
  43. package/dist/implementations/dms/page.d.ts +4 -3
  44. package/dist/implementations/dms/page.js +49 -18
  45. package/dist/implementations/dms/page.js.map +1 -1
  46. package/dist/implementations/dms/permissions-resolver.d.ts +2 -2
  47. package/dist/implementations/dms/permissions-resolver.js +2 -2
  48. package/dist/implementations/dms/permissions-resolver.js.map +1 -1
  49. package/dist/implementations/dms/realtime.js +1 -0
  50. package/dist/implementations/dms/realtime.js.map +1 -1
  51. package/dist/implementations/dms-base/bulk-selection.d.ts +1 -1
  52. package/dist/implementations/dms-base/bulk-selection.js +23 -1
  53. package/dist/implementations/dms-base/bulk-selection.js.map +1 -1
  54. package/dist/implementations/dms-base/filter-tokens.js +5 -3
  55. package/dist/implementations/dms-base/filter-tokens.js.map +1 -1
  56. package/dist/implementations/dms-base/search-route.d.ts +1 -0
  57. package/dist/implementations/dms-base/search-route.js +35 -13
  58. package/dist/implementations/dms-base/search-route.js.map +1 -1
  59. package/dist/implementations/dms-base/table-view.js +11 -6
  60. package/dist/implementations/dms-base/table-view.js.map +1 -1
  61. package/dist/implementations/dms-notifications/index.d.ts +2 -2
  62. package/dist/implementations/dms-notifications/index.js +5 -5
  63. package/dist/implementations/dms-notifications/index.js.map +1 -1
  64. package/dist/index.js +32 -55
  65. package/dist/index.js.map +1 -1
  66. package/dist/pages/modules/index.d.ts +1 -0
  67. package/dist/pages/modules/index.js +1 -0
  68. package/dist/pages/modules/index.js.map +1 -1
  69. package/dist/pages/modules/modules-store.d.ts +9 -0
  70. package/dist/pages/modules/modules-store.js +30 -0
  71. package/dist/pages/modules/modules-store.js.map +1 -0
  72. package/dist/pages/settings/users/account-credentials.js +2 -2
  73. package/dist/pages/settings/users/account-credentials.js.map +1 -1
  74. package/dist/pages/settings/users/account-data-store.js +25 -8
  75. package/dist/pages/settings/users/account-data-store.js.map +1 -1
  76. package/dist/pages/settings/users/invite-extension-routes.js +9 -3
  77. package/dist/pages/settings/users/invite-extension-routes.js.map +1 -1
  78. package/dist/pages/settings/users/member-management.js +13 -14
  79. package/dist/pages/settings/users/member-management.js.map +1 -1
  80. package/dist/pages/settings/users/profile.d.ts +1 -4
  81. package/dist/pages/settings/users/profile.js +0 -35
  82. package/dist/pages/settings/users/profile.js.map +1 -1
  83. package/dist/pages/settings/users/role-editor-store.js +26 -18
  84. package/dist/pages/settings/users/role-editor-store.js.map +1 -1
  85. package/dist/pages/settings/users/role-editor.d.ts +0 -1
  86. package/dist/pages/settings/users/role-editor.js +1 -2
  87. package/dist/pages/settings/users/role-editor.js.map +1 -1
  88. package/dist/pages/settings/users/role-preview.js +1 -2
  89. package/dist/pages/settings/users/role-preview.js.map +1 -1
  90. package/dist/pages/settings/users/roles.js +2 -2
  91. package/dist/pages/settings/users/roles.js.map +1 -1
  92. package/dist/pages/settings/users/two-factor-operations.js.map +1 -1
  93. package/dist/realtime/broker.d.ts +6 -0
  94. package/dist/realtime/broker.js +8 -1
  95. package/dist/realtime/broker.js.map +1 -1
  96. package/dist/realtime/presence.d.ts +8 -3
  97. package/dist/realtime/presence.js +8 -4
  98. package/dist/realtime/presence.js.map +1 -1
  99. package/dist/realtime/sessions.d.ts +11 -1
  100. package/dist/realtime/sessions.js +21 -3
  101. package/dist/realtime/sessions.js.map +1 -1
  102. package/dist/realtime/table-view.d.ts +2 -2
  103. package/dist/realtime/table-view.js +4 -3
  104. package/dist/realtime/table-view.js.map +1 -1
  105. package/dist/routes/auth/client-ip.d.ts +1 -0
  106. package/dist/routes/auth/client-ip.js +18 -0
  107. package/dist/routes/auth/client-ip.js.map +1 -1
  108. package/dist/routes/auth/oauth/config.d.ts +2 -1
  109. package/dist/routes/auth/oauth/config.js +5 -1
  110. package/dist/routes/auth/oauth/config.js.map +1 -1
  111. package/dist/routes/auth/session-handoff.d.ts +0 -1
  112. package/dist/routes/auth/session-handoff.js +0 -2
  113. package/dist/routes/auth/session-handoff.js.map +1 -1
  114. package/dist/routes/auth.js +3 -2
  115. package/dist/routes/auth.js.map +1 -1
  116. package/dist/routes/index.d.ts +0 -1
  117. package/dist/routes/index.js +0 -1
  118. package/dist/routes/index.js.map +1 -1
  119. package/dist/routes/realtime.d.ts +13 -1
  120. package/dist/routes/realtime.js +114 -29
  121. package/dist/routes/realtime.js.map +1 -1
  122. package/dist/startup-config.d.ts +2 -0
  123. package/dist/startup-config.js +62 -0
  124. package/dist/startup-config.js.map +1 -0
  125. package/dist/store/interfaces.d.ts +3 -2
  126. package/dist/store/interfaces.js +16 -6
  127. package/dist/store/interfaces.js.map +1 -1
  128. package/dist/store/listing.js +25 -15
  129. package/dist/store/listing.js.map +1 -1
  130. package/dist/store/operations.js +5 -2
  131. package/dist/store/operations.js.map +1 -1
  132. package/dist/store/requirements.js +3 -2
  133. package/dist/store/requirements.js.map +1 -1
  134. package/dist/store/runtime.d.ts +10 -1
  135. package/dist/store/runtime.js +33 -6
  136. package/dist/store/runtime.js.map +1 -1
  137. package/dist/store/types.d.ts +10 -0
  138. package/dist/utils/ensure-default-tenant.d.ts +2 -1
  139. package/dist/utils/ensure-default-tenant.js +13 -1
  140. package/dist/utils/ensure-default-tenant.js.map +1 -1
  141. package/dist/utils/export-jobs.js +2 -1
  142. package/dist/utils/export-jobs.js.map +1 -1
  143. package/dist/validation/auth.schema.d.ts +2 -2
  144. package/dist/validation/member-invite.schema.d.ts +2 -10
  145. package/dist/validation/member-invite.schema.js +2 -15
  146. package/dist/validation/member-invite.schema.js.map +1 -1
  147. package/frontend-vue/dms.frontend.ts +24 -5
  148. package/frontend-vue/dms.server.ts +17 -0
  149. package/frontend-vue/layers/dms-auth/server/accounts.ts +3 -4
  150. package/frontend-vue/layers/dms-core/app/build/composables/renderNow.ts +33 -0
  151. package/frontend-vue/layers/dms-core/app/build/composables/user/tablePreferences.ts +5 -1
  152. package/frontend-vue/layers/dms-core/app/build/utils/crossTab.ts +199 -0
  153. package/frontend-vue/layers/dms-core/app/build/utils/sharedRealtimeConnection.ts +410 -0
  154. package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +77 -12
  155. package/frontend-vue/layers/dms-core/app/composables/data-types/useDataType.ts +26 -0
  156. package/frontend-vue/layers/dms-core/app/composables/live/livePauseSource.ts +29 -0
  157. package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +150 -9
  158. package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +6 -4
  159. package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +27 -1
  160. package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +256 -119
  161. package/frontend-vue/layers/dms-core/app/composables/translation/useComposedText.ts +21 -8
  162. package/frontend-vue/layers/dms-core/app/composables/translation/useTranslation.ts +25 -5
  163. package/frontend-vue/layers/dms-core/app/composables/useApiError.ts +3 -5
  164. package/frontend-vue/layers/dms-core/app/composables/useFieldErrors.ts +61 -26
  165. package/frontend-vue/layers/dms-core/app/plugins/dev-error-log.ts +16 -0
  166. package/frontend-vue/layers/dms-core/app/types/user.ts +6 -0
  167. package/frontend-vue/layers/dms-core/app/utils/composedText.ts +7 -2
  168. package/frontend-vue/layers/dms-core/app/utils/formatter.ts +134 -35
  169. package/frontend-vue/layers/dms-core/app/utils/url-interpolation.ts +18 -1
  170. package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +136 -2
  171. package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +136 -2
  172. package/frontend-vue/layers/dms-core/shared/types/app-config.ts +0 -2
  173. package/frontend-vue/layers/dms-layout/app/app.config.ts +0 -1
  174. package/frontend-vue/layers/dms-layout/app/assets/css/main.css +11 -1
  175. package/frontend-vue/layers/dms-layout/app/assets/fonts/Geist-Italic-Variable.woff2 +0 -0
  176. package/frontend-vue/layers/dms-layout/app/assets/fonts/Geist-Variable.woff2 +0 -0
  177. package/frontend-vue/layers/dms-layout/app/assets/fonts/GeistMono-Italic-Variable.woff2 +0 -0
  178. package/frontend-vue/layers/dms-layout/app/assets/fonts/GeistMono-Variable.woff2 +0 -0
  179. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +3 -2
  180. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +41 -21
  181. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardPageBody.vue +6 -2
  182. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +4 -19
  183. package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +11 -107
  184. package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +18 -7
  185. package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +4 -0
  186. package/frontend-vue/layers/dms-layout/app/build/components/layout/RowSidePanel.vue +43 -82
  187. package/frontend-vue/layers/dms-layout/app/build/components/layout/SidebarUserMenu.vue +44 -3
  188. package/frontend-vue/layers/dms-layout/app/build/components/layout/UserIdentity.vue +11 -3
  189. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleCatalogTile.vue +132 -35
  190. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleManageMenu.vue +59 -0
  191. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleSourceLabel.vue +25 -0
  192. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreBanners.vue +73 -0
  193. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreOperation.vue +116 -0
  194. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreRequirements.vue +8 -3
  195. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue +85 -207
  196. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTile.vue +10 -3
  197. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTileSkeleton.vue +68 -0
  198. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +24 -8
  199. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxItem.vue +8 -5
  200. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/region/RegionTimeZoneInput.vue +1 -0
  201. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-permissions.ts +19 -0
  202. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/useRoleEditor.ts +2 -1
  203. package/frontend-vue/layers/dms-layout/app/build/composables/layout/useSidePanelWidth.ts +31 -70
  204. package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen.ts +11 -1
  205. package/frontend-vue/layers/dms-layout/app/build/utils/dev-reload-stream.ts +81 -0
  206. package/frontend-vue/layers/dms-layout/app/build/utils/github-repository.ts +2 -2
  207. package/frontend-vue/layers/dms-layout/app/build/utils/module-store-catalog.ts +232 -36
  208. package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +122 -14
  209. package/frontend-vue/layers/dms-layout/app/composables/layout/useLayoutBanners.ts +32 -3
  210. package/frontend-vue/layers/dms-layout/app/composables/layout/useServedHeaderActions.ts +49 -11
  211. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleHistory.ts +3 -2
  212. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +59 -4
  213. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStoreActions.ts +194 -0
  214. package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +23 -3
  215. package/frontend-vue/layers/dms-layout/app/composables/page/usePageLayout.ts +4 -2
  216. package/frontend-vue/layers/dms-layout/app/composables/page/usePageRecordDeletion.ts +162 -0
  217. package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +6 -1
  218. package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +6 -2
  219. package/frontend-vue/layers/dms-layout/app/custom-layouts/SettingsLayout.vue +6 -2
  220. package/frontend-vue/layers/dms-layout/app/custom-pages/modules-store.vue +291 -0
  221. package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +87 -474
  222. package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +3 -0
  223. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +13 -22
  224. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-navigation.client.ts +11 -10
  225. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-personal-pages.client.ts +22 -52
  226. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-quick-actions.client.ts +7 -7
  227. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-session.client.ts +11 -10
  228. package/frontend-vue/layers/dms-layout/app/plugins/dms-dev-reload.client.ts +14 -9
  229. package/frontend-vue/layers/dms-layout/app/theme/actions.ts +5 -4
  230. package/frontend-vue/layers/dms-layout/app/theme/form-controls.ts +9 -4
  231. package/frontend-vue/layers/dms-layout/app/theme/surfaces.ts +22 -6
  232. package/frontend-vue/layers/dms-layout/app/types/module-store.ts +9 -0
  233. package/frontend-vue/layers/dms-layout/app/types/page.ts +15 -1
  234. package/frontend-vue/layers/dms-layout/app/utils/account-menu.ts +11 -0
  235. package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +57 -0
  236. package/frontend-vue/layers/dms-layout/app/utils/pageQuery.ts +26 -0
  237. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +45 -18
  238. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +44 -17
  239. package/frontend-vue/layers/dms-ui/app/build/components/containers/TargetComponentTree.vue +31 -0
  240. package/frontend-vue/layers/dms-ui/app/build/components/containers/constants.ts +25 -0
  241. package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +244 -15
  242. package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +42 -25
  243. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowHeader.vue +153 -0
  244. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowNotFound.vue +44 -0
  245. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/drawerRowHeader.ts +35 -0
  246. package/frontend-vue/layers/dms-ui/app/build/components/form/FormEntries.vue +44 -9
  247. package/frontend-vue/layers/dms-ui/app/build/components/form/FormErrorSummary.vue +11 -4
  248. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldControl.vue +38 -5
  249. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldNotes.vue +29 -5
  250. package/frontend-vue/layers/dms-ui/app/build/components/form/FormLoadError.vue +5 -1
  251. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSectionNav.vue +3 -1
  252. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSections.vue +106 -27
  253. package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +12 -4
  254. package/frontend-vue/layers/dms-ui/app/build/components/table/Empty.vue +4 -2
  255. package/frontend-vue/layers/dms-ui/app/build/components/table/FiltersRow.vue +48 -1
  256. package/frontend-vue/layers/dms-ui/app/build/components/table/Pagination.vue +55 -54
  257. package/frontend-vue/layers/dms-ui/app/build/components/table/RowSelection.vue +2 -1
  258. package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +396 -288
  259. package/frontend-vue/layers/dms-ui/app/build/components/table/Tabs.vue +2 -1
  260. package/frontend-vue/layers/dms-ui/app/build/components/table/Views.vue +2 -1
  261. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardAvatar.vue +59 -0
  262. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardGridSkeleton.vue +19 -2
  263. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardRowActions.vue +38 -0
  264. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardsDisplay.vue +148 -104
  265. package/frontend-vue/layers/dms-ui/app/build/components/table-view/LiveHoldBanner.vue +17 -2
  266. package/frontend-vue/layers/dms-ui/app/build/composables/actions/bulkSelection.ts +43 -9
  267. package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowComponentOptions.ts +76 -0
  268. package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowNavigation.ts +172 -27
  269. package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +270 -46
  270. package/frontend-vue/layers/dms-ui/app/build/composables/confirm/actionError.ts +6 -1
  271. package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +2 -0
  272. package/frontend-vue/layers/dms-ui/app/build/composables/containers/closingLayers.ts +186 -0
  273. package/frontend-vue/layers/dms-ui/app/build/composables/containers/context.ts +14 -0
  274. package/frontend-vue/layers/dms-ui/app/build/composables/containers/drawerSize.ts +229 -0
  275. package/frontend-vue/layers/dms-ui/app/build/composables/containers/useResizableSize.ts +175 -0
  276. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/metricCells.ts +18 -35
  277. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/numberSuffix.ts +44 -0
  278. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +123 -27
  279. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerFieldTypes.ts +2 -1
  280. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationFilter.ts +75 -0
  281. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationRows.ts +92 -0
  282. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationValue.ts +39 -0
  283. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/twoLineCell.ts +3 -3
  284. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/useColumnValueRenderer.ts +6 -2
  285. package/frontend-vue/layers/dms-ui/app/build/composables/form/createFormFields.ts +15 -0
  286. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldSaved.ts +40 -0
  287. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldValueRule.ts +62 -0
  288. package/frontend-vue/layers/dms-ui/app/build/composables/form/formEntryContext.ts +5 -0
  289. package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +42 -7
  290. package/frontend-vue/layers/dms-ui/app/build/composables/form/formLayout.ts +106 -4
  291. package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +11 -5
  292. package/frontend-vue/layers/dms-ui/app/build/composables/form/formSections.ts +32 -0
  293. package/frontend-vue/layers/dms-ui/app/build/composables/form/formValidation.ts +36 -4
  294. package/frontend-vue/layers/dms-ui/app/build/composables/form/readonlyValue.ts +19 -0
  295. package/frontend-vue/layers/dms-ui/app/build/composables/form/registerFormFunctions.ts +27 -0
  296. package/frontend-vue/layers/dms-ui/app/build/composables/form/repeaterTable.ts +78 -0
  297. package/frontend-vue/layers/dms-ui/app/build/composables/form/serverFieldErrors.ts +49 -0
  298. package/frontend-vue/layers/dms-ui/app/build/composables/form/toasterClearance.ts +118 -0
  299. package/frontend-vue/layers/dms-ui/app/build/composables/form/useInstantForm.ts +12 -1
  300. package/frontend-vue/layers/dms-ui/app/build/composables/live/currentPageLive.ts +41 -3
  301. package/frontend-vue/layers/dms-ui/app/build/composables/live/livePoller.ts +10 -2
  302. package/frontend-vue/layers/dms-ui/app/build/composables/live/pageReadClock.ts +28 -0
  303. package/frontend-vue/layers/dms-ui/app/build/composables/live/shortcuts.ts +1 -1
  304. package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageLiveHost.ts +17 -3
  305. package/frontend-vue/layers/dms-ui/app/build/composables/live/useUpdatedLabel.ts +38 -0
  306. package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/pageToolbarContext.ts +75 -0
  307. package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/toolbarControlHeight.ts +29 -0
  308. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodComparison.ts +52 -0
  309. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +21 -0
  310. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodShortLabels.ts +28 -0
  311. package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodDraft.ts +1 -0
  312. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRelationFilterLabels.ts +97 -0
  313. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRowScrollAnchor.ts +106 -0
  314. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +9 -0
  315. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTableColumns.ts +1 -2
  316. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/archivedCount.ts +31 -0
  317. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/filterEditorValue.ts +38 -0
  318. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/formatFilterValue.ts +33 -2
  319. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/scrollEdges.ts +31 -0
  320. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useCardFields.ts +33 -0
  321. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useMobileDisplay.ts +48 -0
  322. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useScrolledPastTop.ts +41 -18
  323. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +3 -2
  324. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLinkedVisit.ts +78 -0
  325. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLiveHold.ts +135 -22
  326. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +56 -25
  327. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +90 -35
  328. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +86 -10
  329. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/bulkActions.ts +14 -4
  330. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/card.ts +127 -2
  331. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/compareValues.ts +13 -0
  332. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/expandedRows.ts +28 -0
  333. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/groupedRows.ts +114 -1
  334. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/linkedArrival.ts +308 -0
  335. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/locateRow.ts +106 -0
  336. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pageArrival.ts +34 -0
  337. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pinnedRows.ts +17 -0
  338. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/quickFilters.ts +41 -33
  339. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/recordLink.ts +24 -8
  340. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableCounts.ts +113 -0
  341. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableQuery.ts +27 -4
  342. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/views.ts +22 -6
  343. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/writeError.ts +21 -0
  344. package/frontend-vue/layers/dms-ui/app/build/utils/addressSuggestions.ts +49 -0
  345. package/frontend-vue/layers/dms-ui/app/build/utils/codeHighlight.ts +67 -29
  346. package/frontend-vue/layers/dms-ui/app/build/utils/eyebrow.ts +12 -0
  347. package/frontend-vue/layers/dms-ui/app/build/utils/fieldOptionsI18n.ts +51 -11
  348. package/frontend-vue/layers/dms-ui/app/build/utils/htmlGrammar.ts +79 -0
  349. package/frontend-vue/layers/dms-ui/app/build/utils/inputWidth.ts +53 -0
  350. package/frontend-vue/layers/dms-ui/app/build/utils/numberAdjustment.ts +6 -3
  351. package/frontend-vue/layers/dms-ui/app/build/utils/relativeDate.ts +19 -5
  352. package/frontend-vue/layers/dms-ui/app/build/utils/scrollWithin.ts +99 -0
  353. package/frontend-vue/layers/dms-ui/app/build/utils/shellParser.ts +1 -1
  354. package/frontend-vue/layers/dms-ui/app/build/utils/streamHighlight.ts +90 -0
  355. package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +36 -14
  356. package/frontend-vue/layers/dms-ui/app/components/PasswordStrength.vue +1 -1
  357. package/frontend-vue/layers/dms-ui/app/components/RecursiveComponent.vue +9 -1
  358. package/frontend-vue/layers/dms-ui/app/components/blocks/LiveToggleBlock.vue +80 -32
  359. package/frontend-vue/layers/dms-ui/app/components/blocks/PageToolbarBlock.vue +137 -0
  360. package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +5 -0
  361. package/frontend-vue/layers/dms-ui/app/components/chart/ApexChartHost.vue +16 -2
  362. package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +24 -5
  363. package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +41 -23
  364. package/frontend-vue/layers/dms-ui/app/components/code-snippet/CodeSnippet.vue +23 -12
  365. package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +1 -1
  366. package/frontend-vue/layers/dms-ui/app/components/container-footer/ContainerFooter.vue +34 -0
  367. package/frontend-vue/layers/dms-ui/app/components/field-row/FieldRow.vue +25 -2
  368. package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +2 -2
  369. package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +119 -36
  370. package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +53 -22
  371. package/frontend-vue/layers/dms-ui/app/components/form/components/DatePicker.vue +20 -1
  372. package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +13 -1
  373. package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +8 -3
  374. package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +53 -5
  375. package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +29 -5
  376. package/frontend-vue/layers/dms-ui/app/components/form/components/InputNumber.vue +45 -8
  377. package/frontend-vue/layers/dms-ui/app/components/form/components/InputPercentage.vue +28 -5
  378. package/frontend-vue/layers/dms-ui/app/components/form/components/InputSecret.vue +5 -0
  379. package/frontend-vue/layers/dms-ui/app/components/form/components/InputText.vue +19 -2
  380. package/frontend-vue/layers/dms-ui/app/components/form/components/InputTime.vue +2 -1
  381. package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +226 -65
  382. package/frontend-vue/layers/dms-ui/app/components/form/components/Repeater.vue +80 -30
  383. package/frontend-vue/layers/dms-ui/app/components/form/components/RepeaterTable.vue +210 -0
  384. package/frontend-vue/layers/dms-ui/app/components/form/components/Select.vue +4 -2
  385. package/frontend-vue/layers/dms-ui/app/components/grid/GridRow.vue +11 -2
  386. package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +79 -70
  387. package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +117 -21
  388. package/frontend-vue/layers/dms-ui/app/components/meter/Meter.vue +2 -1
  389. package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +113 -34
  390. package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +14 -2
  391. package/frontend-vue/layers/dms-ui/app/components/section-header/Eyebrow.vue +6 -1
  392. package/frontend-vue/layers/dms-ui/app/components/segmented/Segmented.vue +13 -0
  393. package/frontend-vue/layers/dms-ui/app/components/stat-group/StatGroup.vue +52 -3
  394. package/frontend-vue/layers/dms-ui/app/components/tab/Tab.vue +2 -1
  395. package/frontend-vue/layers/dms-ui/app/components/table-view/CascaderPath.vue +74 -25
  396. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +48 -44
  397. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanDisplay.vue +4 -1
  398. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +426 -97
  399. package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +33 -5
  400. package/frontend-vue/layers/dms-ui/app/composables/chart/apexCategoryLabels.ts +68 -0
  401. package/frontend-vue/layers/dms-ui/app/composables/chart/apexDatetimeBuckets.ts +306 -0
  402. package/frontend-vue/layers/dms-ui/app/composables/chart/formatValue.ts +4 -3
  403. package/frontend-vue/layers/dms-ui/app/composables/chart/heatmapColors.ts +93 -0
  404. package/frontend-vue/layers/dms-ui/app/composables/chart/localizeSeries.ts +62 -0
  405. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.ts +116 -8
  406. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.types.ts +7 -0
  407. package/frontend-vue/layers/dms-ui/app/composables/containers/types.ts +84 -1
  408. package/frontend-vue/layers/dms-ui/app/composables/containers/useDrawer.ts +69 -8
  409. package/frontend-vue/layers/dms-ui/app/composables/form/types/field-saved.ts +11 -0
  410. package/frontend-vue/layers/dms-ui/app/composables/form/types/field.ts +16 -0
  411. package/frontend-vue/layers/dms-ui/app/composables/form/types/functions.ts +1 -0
  412. package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +189 -28
  413. package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +29 -12
  414. package/frontend-vue/layers/dms-ui/app/composables/page-toolbar/usePageToolbarSize.ts +30 -0
  415. package/frontend-vue/layers/dms-ui/app/composables/table-view/kanban.ts +4 -2
  416. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +22 -3
  417. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +4 -0
  418. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/display.ts +24 -1
  419. package/frontend-vue/layers/dms-ui/app/plugins/closing-layers.client.ts +10 -0
  420. package/frontend-vue/layers/dms-ui/app/plugins/live-pause-source.ts +15 -0
  421. package/frontend-vue/layers/dms-ui/app/plugins/register.ts +2 -0
  422. package/frontend-vue/layers/dms-ui/app/types/input-number.ts +3 -2
  423. package/frontend-vue/layers/dms-ui/app/types/page-toolbar.ts +7 -0
  424. package/frontend-vue/layers/dms-ui/app/types/row-action.ts +0 -2
  425. package/frontend-vue/layers/dms-ui/app/types/row-navigation.ts +17 -0
  426. package/frontend-vue/layers/dms-ui/app/types/table/query.ts +2 -1
  427. package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +3 -1
  428. package/frontend-vue/layers/dms-ui/app/utils/cascader.ts +94 -3
  429. package/frontend-vue/layers/dms-ui/app/utils/componentTree.ts +24 -0
  430. package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +20 -5
  431. package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +20 -5
  432. package/frontend-vue/package.json +6 -2
  433. package/package.json +11 -10
  434. package/skills/dms/SKILL.md +1 -1
  435. package/skills/dms-auth/SKILL.md +5 -3
  436. package/skills/dms-best-practices/REFERENCE.md +2 -0
  437. package/skills/dms-best-practices/SKILL.md +4 -0
  438. package/skills/dms-module/SKILL.md +3 -2
  439. package/skills/dms-pages/REFERENCE.md +129 -16
  440. package/skills/dms-pages/SKILL.md +1 -1
  441. package/skills/dms-project/DEV-SERVERS.md +23 -1
  442. package/skills/dms-project/REFERENCE.md +1 -1
  443. package/skills/dms-project/SKILL.md +4 -1
  444. package/skills/dms-project/scripts/dms-dev.sh +205 -17
  445. package/dist/hooks/tenant-instances.d.ts +0 -1
  446. package/dist/hooks/tenant-instances.js +0 -13
  447. package/dist/hooks/tenant-instances.js.map +0 -1
  448. package/dist/routes/startup-gate.d.ts +0 -7
  449. package/dist/routes/startup-gate.js +0 -39
  450. package/dist/routes/startup-gate.js.map +0 -1
  451. package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +0 -42
@@ -0,0 +1,78 @@
1
+ import { router as inertiaRouter } from "@inertiajs/vue3";
2
+ import { onBeforeUnmount, onMounted } from "vue";
3
+ import type { QueryParamFilters } from "../../../composables/table-view/types/config";
4
+ import { isLinkedVisit } from "./utils/linkedArrival";
5
+ import { queryOfUrl } from "./utils/urlFilters";
6
+ import type { TableUrlScope } from "./utils/views";
7
+
8
+ const URL_BASE = "http://dms.invalid";
9
+
10
+ /** What `useTableLinkedVisit` reads a visit against, and what it calls. */
11
+ export interface TableLinkedVisitOptions {
12
+ route: { path: string; query: Record<string, unknown> };
13
+ urlScope: TableUrlScope;
14
+ queryParamFilters: QueryParamFilters | undefined;
15
+ activeView: () => string | undefined;
16
+ activeTab: () => string;
17
+ canOpenView: (id: string) => boolean;
18
+ canOpenTab: (id: string) => boolean;
19
+ /** Opens the table on what the visit's link names, as an arrival would. */
20
+ follow: (query: Record<string, unknown>) => void;
21
+ }
22
+
23
+ /**
24
+ * Follows a link to the page the table is already shown on (a notification,
25
+ * a menu entry, back and forward): Inertia keeps the page mounted, so the
26
+ * table reads the visit's query as an arrival would (`isLinkedVisit`). A
27
+ * link clicked is announced as a client visit pushed, even to the URL shown,
28
+ * which Inertia turns into a replace without a `navigate` event; a page
29
+ * control rebuilding the query replaces it, and is no link. A visit to
30
+ * another page is left alone: the table is about to unmount.
31
+ */
32
+ export function useTableLinkedVisit(options: TableLinkedVisitOptions): void {
33
+ // The page the table is shown on, as the visit that mounted it reached it.
34
+ const pathname = options.route.path;
35
+ let previousQuery: Record<string, unknown> = { ...options.route.query };
36
+ // A pushed visit announces itself twice when it changes the URL.
37
+ let followedVisitId: string | undefined;
38
+ const stops: Array<() => void> = [];
39
+
40
+ const read = (
41
+ url: string,
42
+ visitId: string | undefined,
43
+ isLinkClick: boolean,
44
+ ) => {
45
+ const query = queryOfUrl(url);
46
+ const lastQuery = previousQuery;
47
+ previousQuery = query;
48
+ if (new URL(url, URL_BASE).pathname !== pathname) return;
49
+ if (visitId !== undefined && visitId === followedVisitId) return;
50
+ const isLink = isLinkedVisit({
51
+ query,
52
+ isLinkClick,
53
+ previousQuery: lastQuery,
54
+ urlScope: options.urlScope,
55
+ queryParamFilters: options.queryParamFilters,
56
+ activeView: options.activeView(),
57
+ activeTab: options.activeTab(),
58
+ canOpenView: options.canOpenView,
59
+ canOpenTab: options.canOpenTab,
60
+ });
61
+ if (!isLink) return;
62
+ followedVisitId = visitId;
63
+ options.follow(query);
64
+ };
65
+
66
+ onMounted(() => {
67
+ stops.push(
68
+ inertiaRouter.on("navigate", (event) =>
69
+ read(event.detail.page.url, event.detail.visitId, false),
70
+ ),
71
+ inertiaRouter.on("clientVisit", (event) => {
72
+ if (event.detail.replace) return;
73
+ read(event.detail.page.url, event.detail.visitId, true);
74
+ }),
75
+ );
76
+ });
77
+ onBeforeUnmount(() => stops.forEach((stop) => stop()));
78
+ }
@@ -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
  };
@@ -5,7 +5,10 @@ import {
5
5
  type FormPageUrls,
6
6
  } from "./useTableViewConfig";
7
7
  import type { FormProps } from "../../../composables/form/types";
8
- import type { QueryParamFilters } from "../../../composables/table-view/types";
8
+ import type {
9
+ QueryParamFilters,
10
+ TableViewColumn,
11
+ } from "../../../composables/table-view/types";
9
12
  import type { LocationQuery, LocationQueryValue } from "#dms/frontend-module";
10
13
  import {
11
14
  TableRowAction,
@@ -33,7 +36,7 @@ import {
33
36
  type BulkActionOutcome,
34
37
  type ConfirmedBulkAction,
35
38
  } from "./utils/bulkActions";
36
- import { resolveActionError } from "../confirm/actionError";
39
+ import { describeTableWriteError } from "./utils/writeError";
37
40
  import {
38
41
  ConfirmActionError,
39
42
  type ConfirmOptions,
@@ -44,6 +47,7 @@ import {
44
47
  resolveFormContainerTexts,
45
48
  type TableViewFormKind,
46
49
  } from "./utils/formTexts";
50
+ import { dmsOwnDrawer } from "../containers/context";
47
51
 
48
52
  const DEFAULT_ROW_ID_KEY = "_id";
49
53
  const DEFAULT_MODAL_SIZE = "xl";
@@ -58,6 +62,8 @@ const getItemId = (
58
62
  interface TableRowActionsConfig {
59
63
  api: ReturnType<typeof $fetch.create>;
60
64
  location: string;
65
+ /** The columns, whose labels name the fields a refused write lacks. */
66
+ columns: ReadonlyArray<Pick<TableViewColumn, "accessorKey" | "header">>;
61
67
  caption?: string;
62
68
  labelKey?: string;
63
69
  rowIdKey?: string;
@@ -95,6 +101,12 @@ type ConfirmedRun = NonNullable<ConfirmOptions["onConfirm"]>;
95
101
  /** Options of a delete: from the archive, it is a permanent one. */
96
102
  interface DeleteRowsOptions {
97
103
  permanently?: boolean;
104
+ /**
105
+ * The rows deleted, when the caller holds them: a dialog naming the row
106
+ * (`{name}`) reads its fields here first. A kanban card is not among the
107
+ * rows the table shows.
108
+ */
109
+ rows?: Data[];
98
110
  }
99
111
 
100
112
  export const useTableRowActions = <T extends Data>(
@@ -139,7 +151,12 @@ export const useTableRowActions = <T extends Data>(
139
151
  color: Color.error,
140
152
  icon: "i-ph-warning-circle",
141
153
  title: title || t("dms.form.error_title"),
142
- description: resolveActionError(error, t),
154
+ description: describeTableWriteError(
155
+ error,
156
+ t,
157
+ config.columns,
158
+ processI18n,
159
+ ),
143
160
  });
144
161
  };
145
162
 
@@ -249,24 +266,26 @@ export const useTableRowActions = <T extends Data>(
249
266
  fetchUrl?: string,
250
267
  submitDefaults?: Record<string, unknown>,
251
268
  ) => {
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,
269
+ const drawer = openDrawer(
270
+ dmsOwnDrawer({
271
+ title,
272
+ description,
263
273
  containerId,
264
- onSuccessCallback: () => {
265
- drawer.close();
266
- config.refreshCallback?.();
274
+ component: DmsForm,
275
+ componentOptions: {
276
+ ...processedOptions,
277
+ ...(fetchUrl && { fetchUrl }),
278
+ ...(submitDefaults && { submitDefaults }),
279
+ componentId: `${config.componentId}-${componentIdSuffix}`,
280
+ pageId: pageIdSuffix,
281
+ containerId,
282
+ onSuccessCallback: () => {
283
+ drawer.close();
284
+ config.refreshCallback?.();
285
+ },
267
286
  },
268
- },
269
- });
287
+ }),
288
+ );
270
289
  return drawer;
271
290
  };
272
291
 
@@ -654,38 +673,65 @@ export const useTableRowActions = <T extends Data>(
654
673
  };
655
674
  };
656
675
 
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
- );
676
+ const rowIdOf = (row: Data): string =>
677
+ String(getItemId(row, config.rowIdKey ?? DEFAULT_ROW_ID_KEY));
678
+
679
+ const findRowIn = (rows: Data[] | undefined, id: string) =>
680
+ rows?.find((row) => rowIdOf(row) === id);
681
+
682
+ // A row the table does not show (a kanban card, a row deleted from a
683
+ // custom card) is read by id: a `from` dialog asks the server instead.
684
+ const readRow = async (
685
+ id: string,
686
+ declared: ActionConfirm,
687
+ ): Promise<Data | undefined> => {
688
+ const getRow = config.rowNavigation?.getRow;
689
+ if (!getRow || isConfirmFrom(declared)) return undefined;
690
+ return getRow(id).catch(() => undefined);
691
+ };
692
+
693
+ /** The one row a dialog words: the caller's, the table's, else read. */
694
+ const findConfirmRow = async (
695
+ id: string,
696
+ declared: ActionConfirm,
697
+ rows?: Data[],
698
+ ): Promise<Data | undefined> =>
699
+ findRowIn(rows, id) ??
700
+ findRowIn(config.rowNavigation?.rows(), id) ??
701
+ (await readRow(id, declared));
664
702
 
665
703
  /**
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.
704
+ * What a fixed dialog words: the rows' count, and for one row its own
705
+ * fields too (`{name}`). The count wins a clash, it is what every bulk
706
+ * dialog may name.
669
707
  */
670
- const confirmRowFields = (ids: string[]): Data => {
708
+ const confirmRowFields = async (
709
+ ids: string[],
710
+ declared: ActionConfirm,
711
+ rows?: Data[],
712
+ ): Promise<Data> => {
671
713
  const count = { count: ids.length };
672
- const row = ids.length === 1 ? findShownRow(ids[0]) : undefined;
714
+ const row =
715
+ ids.length === 1
716
+ ? await findConfirmRow(ids[0]!, declared, rows)
717
+ : undefined;
673
718
  return row ? { ...row, ...count } : count;
674
719
  };
675
720
 
676
721
  /** Asks the declared confirmation of a bulk action, running it inside. */
677
- const askDeclaredBulkConfirm = (
722
+ const askDeclaredBulkConfirm = async (
678
723
  declared: ActionConfirm,
679
724
  ids: string[],
680
725
  fallback: Partial<ConfirmOptions>,
681
726
  run: ConfirmedRun,
727
+ rows?: Data[],
682
728
  ): Promise<boolean> =>
683
729
  confirmAction(declared, {
684
730
  urlParams: {
685
731
  id: ids[0],
686
732
  [config.rowIdKey ?? DEFAULT_ROW_ID_KEY]: ids[0],
687
733
  },
688
- row: confirmRowFields(ids),
734
+ row: await confirmRowFields(ids, declared, rows),
689
735
  fallback,
690
736
  run,
691
737
  });
@@ -703,11 +749,14 @@ export const useTableRowActions = <T extends Data>(
703
749
  actionConfig: boolean | RowActionConfig | undefined,
704
750
  action: ConfirmedBulkAction,
705
751
  bulkConfig: BulkActionConfig,
752
+ rows?: Data[],
706
753
  ): Promise<boolean> => {
707
754
  const fallback = bulkActionConfirm(action, ids.length, t);
708
755
  const run = bulkActionRun(ids, actionConfig, bulkConfig);
709
756
  const declared = declaredBulkConfirm(actionConfig, ids);
710
- if (declared) return askDeclaredBulkConfirm(declared, ids, fallback, run);
757
+ if (declared) {
758
+ return askDeclaredBulkConfirm(declared, ids, fallback, run, rows);
759
+ }
711
760
  return confirm({ ...fallback, onConfirm: run });
712
761
  };
713
762
 
@@ -746,7 +795,13 @@ export const useTableRowActions = <T extends Data>(
746
795
  ): Promise<boolean> => {
747
796
  if (!normalizeActionConfig(deleteConfig).isEnabled) return false;
748
797
  const action = options.permanently ? "deletePermanently" : "delete";
749
- return confirmBulkAction(ids, deleteConfig, action, DELETE_CONFIG);
798
+ return confirmBulkAction(
799
+ ids,
800
+ deleteConfig,
801
+ action,
802
+ DELETE_CONFIG,
803
+ options.rows,
804
+ );
750
805
  };
751
806
 
752
807
  const archiveRows = async (