@antelopejs/dms 0.9.8 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (420) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/README.md +1 -1
  3. package/data/module-store/catalog.json +2 -2
  4. package/dist/attachments/table-view.js +2 -2
  5. package/dist/attachments/table-view.js.map +1 -1
  6. package/dist/automation/actions.d.ts +2 -0
  7. package/dist/automation/actions.js +116 -71
  8. package/dist/automation/actions.js.map +1 -1
  9. package/dist/automation/i18n.d.ts +4 -0
  10. package/dist/automation/i18n.js +19 -0
  11. package/dist/automation/i18n.js.map +1 -0
  12. package/dist/automation/send-notification.d.ts +21 -0
  13. package/dist/automation/send-notification.js +163 -0
  14. package/dist/automation/send-notification.js.map +1 -0
  15. package/dist/automation/triggers.js +19 -25
  16. package/dist/automation/triggers.js.map +1 -1
  17. package/dist/client-origin-cors.d.ts +1 -0
  18. package/dist/client-origin-cors.js +19 -0
  19. package/dist/client-origin-cors.js.map +1 -0
  20. package/dist/config.d.ts +3 -0
  21. package/dist/config.js +5 -3
  22. package/dist/config.js.map +1 -1
  23. package/dist/frontend-module.d.ts +4 -0
  24. package/dist/frontend-module.js +37 -0
  25. package/dist/frontend-module.js.map +1 -0
  26. package/dist/implementations/dms/client-ip.js +1 -1
  27. package/dist/implementations/dms/client-ip.js.map +1 -1
  28. package/dist/implementations/dms/dev-reload.d.ts +5 -0
  29. package/dist/implementations/dms/dev-reload.js +23 -3
  30. package/dist/implementations/dms/dev-reload.js.map +1 -1
  31. package/dist/implementations/dms/frontend-modules.d.ts +2 -4
  32. package/dist/implementations/dms/frontend-modules.js +22 -89
  33. package/dist/implementations/dms/frontend-modules.js.map +1 -1
  34. package/dist/implementations/dms/frontend-ready.d.ts +12 -2
  35. package/dist/implementations/dms/frontend-ready.js +34 -4
  36. package/dist/implementations/dms/frontend-ready.js.map +1 -1
  37. package/dist/implementations/dms/layout-banners.d.ts +2 -1
  38. package/dist/implementations/dms/layout-banners.js +9 -0
  39. package/dist/implementations/dms/layout-banners.js.map +1 -1
  40. package/dist/implementations/dms/manifest.d.ts +1 -2
  41. package/dist/implementations/dms/manifest.js +2 -0
  42. package/dist/implementations/dms/manifest.js.map +1 -1
  43. package/dist/implementations/dms/page.d.ts +4 -3
  44. package/dist/implementations/dms/page.js +49 -18
  45. package/dist/implementations/dms/page.js.map +1 -1
  46. package/dist/implementations/dms/permissions-resolver.d.ts +2 -2
  47. package/dist/implementations/dms/permissions-resolver.js +2 -2
  48. package/dist/implementations/dms/permissions-resolver.js.map +1 -1
  49. package/dist/implementations/dms/realtime.js +1 -0
  50. package/dist/implementations/dms/realtime.js.map +1 -1
  51. package/dist/implementations/dms-base/bulk-selection.d.ts +1 -1
  52. package/dist/implementations/dms-base/bulk-selection.js +23 -1
  53. package/dist/implementations/dms-base/bulk-selection.js.map +1 -1
  54. package/dist/implementations/dms-base/filter-tokens.js +5 -3
  55. package/dist/implementations/dms-base/filter-tokens.js.map +1 -1
  56. package/dist/implementations/dms-base/search-route.d.ts +1 -0
  57. package/dist/implementations/dms-base/search-route.js +35 -13
  58. package/dist/implementations/dms-base/search-route.js.map +1 -1
  59. package/dist/implementations/dms-base/table-view.js +11 -6
  60. package/dist/implementations/dms-base/table-view.js.map +1 -1
  61. package/dist/implementations/dms-notifications/index.d.ts +2 -2
  62. package/dist/implementations/dms-notifications/index.js +5 -5
  63. package/dist/implementations/dms-notifications/index.js.map +1 -1
  64. package/dist/index.js +28 -48
  65. package/dist/index.js.map +1 -1
  66. package/dist/pages/modules/index.d.ts +1 -0
  67. package/dist/pages/modules/index.js +1 -0
  68. package/dist/pages/modules/index.js.map +1 -1
  69. package/dist/pages/modules/modules-store.d.ts +9 -0
  70. package/dist/pages/modules/modules-store.js +30 -0
  71. package/dist/pages/modules/modules-store.js.map +1 -0
  72. package/dist/pages/settings/users/account-credentials.js +2 -2
  73. package/dist/pages/settings/users/account-credentials.js.map +1 -1
  74. package/dist/pages/settings/users/account-data-store.js +25 -8
  75. package/dist/pages/settings/users/account-data-store.js.map +1 -1
  76. package/dist/pages/settings/users/invite-extension-routes.js +9 -3
  77. package/dist/pages/settings/users/invite-extension-routes.js.map +1 -1
  78. package/dist/pages/settings/users/member-management.js +13 -14
  79. package/dist/pages/settings/users/member-management.js.map +1 -1
  80. package/dist/pages/settings/users/profile.d.ts +1 -4
  81. package/dist/pages/settings/users/profile.js +0 -35
  82. package/dist/pages/settings/users/profile.js.map +1 -1
  83. package/dist/pages/settings/users/role-editor-store.js +26 -18
  84. package/dist/pages/settings/users/role-editor-store.js.map +1 -1
  85. package/dist/pages/settings/users/role-editor.d.ts +0 -1
  86. package/dist/pages/settings/users/role-editor.js +1 -2
  87. package/dist/pages/settings/users/role-editor.js.map +1 -1
  88. package/dist/pages/settings/users/role-preview.js +1 -2
  89. package/dist/pages/settings/users/role-preview.js.map +1 -1
  90. package/dist/pages/settings/users/roles.js +2 -2
  91. package/dist/pages/settings/users/roles.js.map +1 -1
  92. package/dist/pages/settings/users/two-factor-operations.js.map +1 -1
  93. package/dist/realtime/broker.d.ts +6 -0
  94. package/dist/realtime/broker.js +8 -1
  95. package/dist/realtime/broker.js.map +1 -1
  96. package/dist/realtime/presence.d.ts +8 -3
  97. package/dist/realtime/presence.js +8 -4
  98. package/dist/realtime/presence.js.map +1 -1
  99. package/dist/realtime/sessions.d.ts +11 -1
  100. package/dist/realtime/sessions.js +21 -3
  101. package/dist/realtime/sessions.js.map +1 -1
  102. package/dist/realtime/table-view.d.ts +2 -2
  103. package/dist/realtime/table-view.js +4 -3
  104. package/dist/realtime/table-view.js.map +1 -1
  105. package/dist/routes/auth/client-ip.d.ts +1 -0
  106. package/dist/routes/auth/client-ip.js +18 -0
  107. package/dist/routes/auth/client-ip.js.map +1 -1
  108. package/dist/routes/auth/oauth/config.d.ts +2 -1
  109. package/dist/routes/auth/oauth/config.js +5 -1
  110. package/dist/routes/auth/oauth/config.js.map +1 -1
  111. package/dist/routes/auth/session-handoff.d.ts +0 -1
  112. package/dist/routes/auth/session-handoff.js +0 -2
  113. package/dist/routes/auth/session-handoff.js.map +1 -1
  114. package/dist/routes/auth.js +3 -2
  115. package/dist/routes/auth.js.map +1 -1
  116. package/dist/routes/index.d.ts +0 -1
  117. package/dist/routes/index.js +0 -1
  118. package/dist/routes/index.js.map +1 -1
  119. package/dist/routes/realtime.d.ts +13 -1
  120. package/dist/routes/realtime.js +114 -29
  121. package/dist/routes/realtime.js.map +1 -1
  122. package/dist/startup-config.d.ts +2 -0
  123. package/dist/startup-config.js +62 -0
  124. package/dist/startup-config.js.map +1 -0
  125. package/dist/store/interfaces.d.ts +3 -2
  126. package/dist/store/interfaces.js +16 -6
  127. package/dist/store/interfaces.js.map +1 -1
  128. package/dist/store/listing.js +24 -13
  129. package/dist/store/listing.js.map +1 -1
  130. package/dist/store/operations.js +4 -0
  131. package/dist/store/operations.js.map +1 -1
  132. package/dist/store/requirements.js +3 -2
  133. package/dist/store/requirements.js.map +1 -1
  134. package/dist/store/runtime.d.ts +10 -1
  135. package/dist/store/runtime.js +33 -6
  136. package/dist/store/runtime.js.map +1 -1
  137. package/dist/store/types.d.ts +10 -0
  138. package/dist/validation/auth.schema.d.ts +2 -2
  139. package/dist/validation/member-invite.schema.d.ts +2 -10
  140. package/dist/validation/member-invite.schema.js +2 -15
  141. package/dist/validation/member-invite.schema.js.map +1 -1
  142. package/frontend-vue/dms.frontend.ts +24 -5
  143. package/frontend-vue/dms.server.ts +17 -0
  144. package/frontend-vue/layers/dms-auth/server/accounts.ts +3 -4
  145. package/frontend-vue/layers/dms-core/app/build/composables/renderNow.ts +34 -0
  146. package/frontend-vue/layers/dms-core/app/build/composables/user/tablePreferences.ts +5 -1
  147. package/frontend-vue/layers/dms-core/app/build/utils/crossTab.ts +199 -0
  148. package/frontend-vue/layers/dms-core/app/build/utils/sharedRealtimeConnection.ts +410 -0
  149. package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +77 -12
  150. package/frontend-vue/layers/dms-core/app/composables/data-types/useDataType.ts +26 -0
  151. package/frontend-vue/layers/dms-core/app/composables/hydration/isHydratingComponent.ts +29 -0
  152. package/frontend-vue/layers/dms-core/app/composables/live/livePauseSource.ts +29 -0
  153. package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +150 -9
  154. package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +6 -4
  155. package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +27 -1
  156. package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +256 -119
  157. package/frontend-vue/layers/dms-core/app/composables/translation/useComposedText.ts +21 -8
  158. package/frontend-vue/layers/dms-core/app/composables/translation/useTranslation.ts +22 -5
  159. package/frontend-vue/layers/dms-core/app/plugins/dev-error-log.ts +16 -0
  160. package/frontend-vue/layers/dms-core/app/types/user.ts +6 -0
  161. package/frontend-vue/layers/dms-core/app/utils/composedText.ts +7 -2
  162. package/frontend-vue/layers/dms-core/app/utils/formatter.ts +166 -35
  163. package/frontend-vue/layers/dms-core/app/utils/url-interpolation.ts +18 -1
  164. package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +134 -2
  165. package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +134 -2
  166. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +3 -2
  167. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +41 -21
  168. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardPageBody.vue +9 -0
  169. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +4 -19
  170. package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +11 -3
  171. package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +18 -7
  172. package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionBar.vue +8 -0
  173. package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +14 -0
  174. package/frontend-vue/layers/dms-layout/app/build/components/layout/RowSidePanel.vue +43 -82
  175. package/frontend-vue/layers/dms-layout/app/build/components/layout/SidebarUserMenu.vue +44 -3
  176. package/frontend-vue/layers/dms-layout/app/build/components/layout/UserIdentity.vue +11 -3
  177. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleCatalogTile.vue +132 -35
  178. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleManageMenu.vue +59 -0
  179. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleSourceLabel.vue +25 -0
  180. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreBanners.vue +73 -0
  181. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreOperation.vue +116 -0
  182. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreRequirements.vue +8 -3
  183. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue +85 -207
  184. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTile.vue +10 -3
  185. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTileSkeleton.vue +68 -0
  186. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +24 -8
  187. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxItem.vue +8 -5
  188. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/region/RegionTimeZoneInput.vue +1 -0
  189. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-permissions.ts +19 -0
  190. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/useRoleEditor.ts +2 -1
  191. package/frontend-vue/layers/dms-layout/app/build/composables/layout/useSidePanelWidth.ts +31 -70
  192. package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen.ts +11 -1
  193. package/frontend-vue/layers/dms-layout/app/build/utils/dev-reload-stream.ts +81 -0
  194. package/frontend-vue/layers/dms-layout/app/build/utils/module-store-catalog.ts +232 -36
  195. package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +122 -14
  196. package/frontend-vue/layers/dms-layout/app/composables/layout/useLayoutBanners.ts +32 -3
  197. package/frontend-vue/layers/dms-layout/app/composables/layout/usePageRecord.ts +50 -0
  198. package/frontend-vue/layers/dms-layout/app/composables/layout/useServedHeaderActions.ts +25 -7
  199. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleHistory.ts +3 -2
  200. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +51 -4
  201. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStoreActions.ts +194 -0
  202. package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +23 -3
  203. package/frontend-vue/layers/dms-layout/app/composables/page/usePageLayout.ts +4 -2
  204. package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +6 -1
  205. package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +9 -0
  206. package/frontend-vue/layers/dms-layout/app/custom-layouts/SettingsLayout.vue +9 -0
  207. package/frontend-vue/layers/dms-layout/app/custom-pages/modules-store.vue +291 -0
  208. package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +87 -474
  209. package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +3 -0
  210. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +6 -22
  211. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-navigation.client.ts +2 -1
  212. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-personal-pages.client.ts +14 -44
  213. package/frontend-vue/layers/dms-layout/app/plugins/dms-dev-reload.client.ts +14 -9
  214. package/frontend-vue/layers/dms-layout/app/theme/form-controls.ts +9 -4
  215. package/frontend-vue/layers/dms-layout/app/theme/surfaces.ts +22 -6
  216. package/frontend-vue/layers/dms-layout/app/types/module-store.ts +9 -0
  217. package/frontend-vue/layers/dms-layout/app/types/page.ts +15 -1
  218. package/frontend-vue/layers/dms-layout/app/utils/account-menu.ts +11 -0
  219. package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +57 -0
  220. package/frontend-vue/layers/dms-layout/app/utils/pageQuery.ts +26 -0
  221. package/frontend-vue/layers/dms-layout/app/utils/pageRecord.ts +71 -0
  222. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +39 -14
  223. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +38 -13
  224. package/frontend-vue/layers/dms-ui/app/build/components/containers/TargetComponentTree.vue +31 -0
  225. package/frontend-vue/layers/dms-ui/app/build/components/containers/constants.ts +25 -0
  226. package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +244 -15
  227. package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +42 -25
  228. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowHeader.vue +153 -0
  229. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowNotFound.vue +44 -0
  230. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/drawerRowHeader.ts +35 -0
  231. package/frontend-vue/layers/dms-ui/app/build/components/form/FormEntries.vue +44 -9
  232. package/frontend-vue/layers/dms-ui/app/build/components/form/FormErrorSummary.vue +11 -4
  233. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldControl.vue +38 -5
  234. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldNotes.vue +29 -5
  235. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSectionNav.vue +3 -1
  236. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSections.vue +106 -27
  237. package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +12 -4
  238. package/frontend-vue/layers/dms-ui/app/build/components/table/Empty.vue +4 -2
  239. package/frontend-vue/layers/dms-ui/app/build/components/table/FiltersRow.vue +38 -1
  240. package/frontend-vue/layers/dms-ui/app/build/components/table/Pagination.vue +55 -54
  241. package/frontend-vue/layers/dms-ui/app/build/components/table/RowSelection.vue +2 -1
  242. package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +396 -288
  243. package/frontend-vue/layers/dms-ui/app/build/components/table/Tabs.vue +2 -1
  244. package/frontend-vue/layers/dms-ui/app/build/components/table/Views.vue +2 -1
  245. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardAvatar.vue +59 -0
  246. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardGridSkeleton.vue +19 -2
  247. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardRowActions.vue +38 -0
  248. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardsDisplay.vue +148 -104
  249. package/frontend-vue/layers/dms-ui/app/build/components/table-view/LiveHoldBanner.vue +17 -2
  250. package/frontend-vue/layers/dms-ui/app/build/composables/actions/bulkSelection.ts +43 -9
  251. package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowComponentOptions.ts +76 -0
  252. package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowNavigation.ts +172 -27
  253. package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +270 -46
  254. package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +2 -0
  255. package/frontend-vue/layers/dms-ui/app/build/composables/containers/closingLayers.ts +186 -0
  256. package/frontend-vue/layers/dms-ui/app/build/composables/containers/context.ts +14 -0
  257. package/frontend-vue/layers/dms-ui/app/build/composables/containers/drawerSize.ts +229 -0
  258. package/frontend-vue/layers/dms-ui/app/build/composables/containers/useResizableSize.ts +175 -0
  259. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/metricCells.ts +18 -35
  260. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/numberSuffix.ts +47 -0
  261. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +99 -24
  262. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerFieldTypes.ts +2 -1
  263. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationFilter.ts +75 -0
  264. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationRows.ts +71 -0
  265. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationValue.ts +39 -0
  266. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/twoLineCell.ts +3 -3
  267. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/useColumnValueRenderer.ts +6 -2
  268. package/frontend-vue/layers/dms-ui/app/build/composables/form/createFormFields.ts +15 -0
  269. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldSaved.ts +40 -0
  270. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldValueRule.ts +62 -0
  271. package/frontend-vue/layers/dms-ui/app/build/composables/form/formEntryContext.ts +5 -0
  272. package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +42 -7
  273. package/frontend-vue/layers/dms-ui/app/build/composables/form/formLayout.ts +106 -4
  274. package/frontend-vue/layers/dms-ui/app/build/composables/form/formSections.ts +32 -0
  275. package/frontend-vue/layers/dms-ui/app/build/composables/form/readonlyValue.ts +19 -0
  276. package/frontend-vue/layers/dms-ui/app/build/composables/form/registerFormFunctions.ts +27 -0
  277. package/frontend-vue/layers/dms-ui/app/build/composables/form/repeaterTable.ts +65 -0
  278. package/frontend-vue/layers/dms-ui/app/build/composables/form/serverFieldErrors.ts +49 -0
  279. package/frontend-vue/layers/dms-ui/app/build/composables/form/toasterClearance.ts +118 -0
  280. package/frontend-vue/layers/dms-ui/app/build/composables/form/useInstantForm.ts +12 -1
  281. package/frontend-vue/layers/dms-ui/app/build/composables/live/currentPageLive.ts +41 -3
  282. package/frontend-vue/layers/dms-ui/app/build/composables/live/livePoller.ts +10 -2
  283. package/frontend-vue/layers/dms-ui/app/build/composables/live/pageReadClock.ts +28 -0
  284. package/frontend-vue/layers/dms-ui/app/build/composables/live/shortcuts.ts +1 -1
  285. package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageLiveHost.ts +17 -3
  286. package/frontend-vue/layers/dms-ui/app/build/composables/live/useUpdatedLabel.ts +38 -0
  287. package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/pageToolbarContext.ts +75 -0
  288. package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/toolbarControlHeight.ts +29 -0
  289. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodComparison.ts +52 -0
  290. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +21 -0
  291. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodShortLabels.ts +28 -0
  292. package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodDraft.ts +1 -0
  293. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRelationFilterLabels.ts +73 -0
  294. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRowScrollAnchor.ts +106 -0
  295. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +9 -0
  296. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTableColumns.ts +1 -2
  297. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/archivedCount.ts +31 -0
  298. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/filterEditorValue.ts +38 -0
  299. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/formatFilterValue.ts +33 -2
  300. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/scrollEdges.ts +31 -0
  301. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useCardFields.ts +33 -0
  302. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useMobileDisplay.ts +48 -0
  303. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useScrolledPastTop.ts +41 -18
  304. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +3 -2
  305. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLinkedVisit.ts +78 -0
  306. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLiveHold.ts +135 -22
  307. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +56 -25
  308. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +77 -32
  309. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +86 -10
  310. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/bulkActions.ts +14 -4
  311. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/card.ts +127 -2
  312. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/compareValues.ts +13 -0
  313. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/expandedRows.ts +28 -0
  314. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/groupedRows.ts +114 -1
  315. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/linkedArrival.ts +308 -0
  316. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/locateRow.ts +106 -0
  317. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pageArrival.ts +34 -0
  318. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pinnedRows.ts +17 -0
  319. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/quickFilters.ts +41 -33
  320. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/recordLink.ts +24 -8
  321. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableCounts.ts +113 -0
  322. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableQuery.ts +27 -4
  323. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/views.ts +22 -6
  324. package/frontend-vue/layers/dms-ui/app/build/utils/addressSuggestions.ts +49 -0
  325. package/frontend-vue/layers/dms-ui/app/build/utils/eyebrow.ts +12 -0
  326. package/frontend-vue/layers/dms-ui/app/build/utils/fieldOptionsI18n.ts +51 -11
  327. package/frontend-vue/layers/dms-ui/app/build/utils/inputWidth.ts +53 -0
  328. package/frontend-vue/layers/dms-ui/app/build/utils/numberAdjustment.ts +6 -3
  329. package/frontend-vue/layers/dms-ui/app/build/utils/relativeDate.ts +19 -5
  330. package/frontend-vue/layers/dms-ui/app/build/utils/scrollWithin.ts +99 -0
  331. package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +36 -14
  332. package/frontend-vue/layers/dms-ui/app/components/PasswordStrength.vue +1 -1
  333. package/frontend-vue/layers/dms-ui/app/components/RecursiveComponent.vue +9 -1
  334. package/frontend-vue/layers/dms-ui/app/components/blocks/LiveToggleBlock.vue +80 -32
  335. package/frontend-vue/layers/dms-ui/app/components/blocks/PageToolbarBlock.vue +137 -0
  336. package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +5 -0
  337. package/frontend-vue/layers/dms-ui/app/components/chart/ApexChartHost.vue +16 -2
  338. package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +24 -5
  339. package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +41 -23
  340. package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +1 -1
  341. package/frontend-vue/layers/dms-ui/app/components/container-footer/ContainerFooter.vue +34 -0
  342. package/frontend-vue/layers/dms-ui/app/components/field-row/FieldRow.vue +25 -2
  343. package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +2 -2
  344. package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +118 -36
  345. package/frontend-vue/layers/dms-ui/app/components/form/components/DatePicker.vue +20 -1
  346. package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +13 -1
  347. package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +8 -3
  348. package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +53 -5
  349. package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +29 -5
  350. package/frontend-vue/layers/dms-ui/app/components/form/components/InputNumber.vue +45 -8
  351. package/frontend-vue/layers/dms-ui/app/components/form/components/InputPercentage.vue +28 -5
  352. package/frontend-vue/layers/dms-ui/app/components/form/components/InputSecret.vue +5 -0
  353. package/frontend-vue/layers/dms-ui/app/components/form/components/InputText.vue +19 -2
  354. package/frontend-vue/layers/dms-ui/app/components/form/components/InputTime.vue +2 -1
  355. package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +205 -65
  356. package/frontend-vue/layers/dms-ui/app/components/form/components/Repeater.vue +80 -30
  357. package/frontend-vue/layers/dms-ui/app/components/form/components/RepeaterTable.vue +203 -0
  358. package/frontend-vue/layers/dms-ui/app/components/form/components/Select.vue +4 -2
  359. package/frontend-vue/layers/dms-ui/app/components/grid/GridRow.vue +11 -2
  360. package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +79 -70
  361. package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +117 -21
  362. package/frontend-vue/layers/dms-ui/app/components/meter/Meter.vue +2 -1
  363. package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +113 -34
  364. package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +14 -2
  365. package/frontend-vue/layers/dms-ui/app/components/section-header/Eyebrow.vue +6 -1
  366. package/frontend-vue/layers/dms-ui/app/components/segmented/Segmented.vue +13 -0
  367. package/frontend-vue/layers/dms-ui/app/components/stat-group/StatGroup.vue +52 -3
  368. package/frontend-vue/layers/dms-ui/app/components/tab/Tab.vue +2 -1
  369. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +45 -35
  370. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanDisplay.vue +4 -1
  371. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +410 -96
  372. package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +33 -5
  373. package/frontend-vue/layers/dms-ui/app/composables/chart/apexCategoryLabels.ts +68 -0
  374. package/frontend-vue/layers/dms-ui/app/composables/chart/apexDatetimeBuckets.ts +306 -0
  375. package/frontend-vue/layers/dms-ui/app/composables/chart/formatValue.ts +4 -3
  376. package/frontend-vue/layers/dms-ui/app/composables/chart/heatmapColors.ts +93 -0
  377. package/frontend-vue/layers/dms-ui/app/composables/chart/localizeSeries.ts +62 -0
  378. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.ts +116 -8
  379. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.types.ts +7 -0
  380. package/frontend-vue/layers/dms-ui/app/composables/containers/types.ts +84 -1
  381. package/frontend-vue/layers/dms-ui/app/composables/containers/useDrawer.ts +69 -8
  382. package/frontend-vue/layers/dms-ui/app/composables/form/types/field-saved.ts +11 -0
  383. package/frontend-vue/layers/dms-ui/app/composables/form/types/field.ts +4 -0
  384. package/frontend-vue/layers/dms-ui/app/composables/form/types/functions.ts +1 -0
  385. package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +142 -21
  386. package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +11 -4
  387. package/frontend-vue/layers/dms-ui/app/composables/page-toolbar/usePageToolbarSize.ts +30 -0
  388. package/frontend-vue/layers/dms-ui/app/composables/table-view/kanban.ts +4 -2
  389. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +22 -3
  390. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +4 -0
  391. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/display.ts +24 -1
  392. package/frontend-vue/layers/dms-ui/app/plugins/closing-layers.client.ts +10 -0
  393. package/frontend-vue/layers/dms-ui/app/plugins/live-pause-source.ts +15 -0
  394. package/frontend-vue/layers/dms-ui/app/plugins/register.ts +2 -0
  395. package/frontend-vue/layers/dms-ui/app/types/input-number.ts +3 -2
  396. package/frontend-vue/layers/dms-ui/app/types/page-toolbar.ts +7 -0
  397. package/frontend-vue/layers/dms-ui/app/types/row-action.ts +0 -2
  398. package/frontend-vue/layers/dms-ui/app/types/row-navigation.ts +17 -0
  399. package/frontend-vue/layers/dms-ui/app/types/table/query.ts +2 -1
  400. package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +3 -1
  401. package/frontend-vue/layers/dms-ui/app/utils/componentTree.ts +24 -0
  402. package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +16 -4
  403. package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +16 -4
  404. package/frontend-vue/package.json +2 -2
  405. package/package.json +7 -6
  406. package/skills/dms/SKILL.md +1 -1
  407. package/skills/dms-auth/SKILL.md +5 -3
  408. package/skills/dms-best-practices/REFERENCE.md +2 -0
  409. package/skills/dms-best-practices/SKILL.md +4 -0
  410. package/skills/dms-module/SKILL.md +3 -2
  411. package/skills/dms-pages/REFERENCE.md +125 -16
  412. package/skills/dms-pages/SKILL.md +1 -1
  413. package/skills/dms-project/DEV-SERVERS.md +23 -1
  414. package/skills/dms-project/REFERENCE.md +1 -1
  415. package/skills/dms-project/SKILL.md +4 -1
  416. package/skills/dms-project/scripts/dms-dev.sh +205 -17
  417. package/dist/routes/startup-gate.d.ts +0 -7
  418. package/dist/routes/startup-gate.js +0 -39
  419. package/dist/routes/startup-gate.js.map +0 -1
  420. package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +0 -42
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { readableNumberFormat } from "#dms-core/app/utils/formatter";
2
3
  import { MONO_CHIP_CLASS } from "../../utils/monoChip";
3
4
  import { onMounted, ref, resolveComponent, useTemplateRef } from "vue";
4
5
  import { useResizeObserver } from "@vueuse/core";
@@ -143,7 +144,7 @@ const fadeStyle = computed(() => {
143
144
  });
144
145
 
145
146
  const { locale, t } = useI18n();
146
- const countFormat = computed(() => new Intl.NumberFormat(locale.value));
147
+ const countFormat = computed(() => readableNumberFormat(locale.value));
147
148
  </script>
148
149
 
149
150
  <template>
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { readableNumberFormat } from "#dms-core/app/utils/formatter";
2
3
  import { EYEBROW_CLASS } from "../../utils/eyebrow";
3
4
  import { tv } from "tailwind-variants";
4
5
  import type { DmsAppConfig } from "#dms-core/shared/types/app-config";
@@ -78,7 +79,7 @@ const uiVariant = tv({
78
79
  const ui = computed(() => uiVariant({ divided: props.divided }));
79
80
 
80
81
  const { t, locale } = useI18n();
81
- const countFormat = computed(() => new Intl.NumberFormat(locale.value));
82
+ const countFormat = computed(() => readableNumberFormat(locale.value));
82
83
 
83
84
  const activeView = computed(() =>
84
85
  props.items.find((view) => view.id === props.activeId),
@@ -0,0 +1,59 @@
1
+ <script setup lang="ts">
2
+ import { computed, watch } from "vue";
3
+ import { useFileReadUrls } from "../../../composables/useFileReadUrls";
4
+
5
+ /**
6
+ * The leading tile of a built-in card: the row's avatar when its title column
7
+ * declares one (an image value, read through the file API, or a URL), else
8
+ * the initials of its title.
9
+ */
10
+ interface CardAvatarProps {
11
+ avatar?: { key: string } | string;
12
+ /** File storage the avatar's key lives in. */
13
+ storage?: string;
14
+ /** The card's title, whose initials stand in for a missing avatar. */
15
+ label: string;
16
+ }
17
+
18
+ const props = defineProps<CardAvatarProps>();
19
+
20
+ const INITIALS_LENGTH = 2;
21
+
22
+ const { getUrl, resolve } = useFileReadUrls(props.storage);
23
+
24
+ const avatarKey = computed(() =>
25
+ props.avatar && typeof props.avatar === "object" ? props.avatar.key : null,
26
+ );
27
+ watch(avatarKey, (key) => key && resolve(key), { immediate: true });
28
+
29
+ const src = computed(() => {
30
+ if (typeof props.avatar === "string") return props.avatar;
31
+ return avatarKey.value ? getUrl(avatarKey.value) : undefined;
32
+ });
33
+
34
+ const initials = computed(() =>
35
+ props.label
36
+ .split(/\s+/)
37
+ .filter(Boolean)
38
+ .slice(0, INITIALS_LENGTH)
39
+ .map((word) => word.charAt(0).toUpperCase())
40
+ .join(""),
41
+ );
42
+ </script>
43
+
44
+ <template>
45
+ <UAvatar
46
+ v-if="src"
47
+ :src="src"
48
+ :alt="props.label"
49
+ :text="initials"
50
+ size="sm"
51
+ class="size-7 shrink-0 rounded-[7px]"
52
+ />
53
+ <span
54
+ v-else
55
+ class="bg-accented text-default grid size-7 shrink-0 place-items-center rounded-[7px] font-mono text-[10.5px] font-bold"
56
+ >
57
+ {{ initials }}
58
+ </span>
59
+ </template>
@@ -1,4 +1,10 @@
1
1
  <script setup lang="ts">
2
+ import { MAX_PAGE_SIZE } from "../../composables/table/constants";
3
+ import {
4
+ CARD_GRID_CLASS,
5
+ cardGridStyle,
6
+ } from "../../composables/table-view/utils/card";
7
+
2
8
  /**
3
9
  * Placeholder of a table view card grid while its first page loads: cards
4
10
  * built like the cards display's (same frame, same line boxes), so nothing
@@ -11,13 +17,22 @@ interface CardGridSkeletonProps {
11
17
  fields: number;
12
18
  /** Size classes of a field label, to match the real card's line box. */
13
19
  labelClass?: string;
20
+ /** The narrowest card, a CSS length: the table's `card.minWidth`. */
21
+ minWidth?: string;
22
+ /**
23
+ * The height of a card, a CSS length: the table's `card.skeletonHeight`,
24
+ * for a custom card taller than the built-in one.
25
+ */
26
+ cardHeight?: string;
14
27
  }
15
28
 
16
29
  const props = withDefaults(defineProps<CardGridSkeletonProps>(), {
17
30
  labelClass: "text-[10.5px] leading-[1.2]",
18
31
  });
19
32
 
20
- const MAX_CARD_COUNT = 12;
33
+ // A full page of cards, as for the rows of a table: the pagination footer
34
+ // below them stands where the loaded page puts it.
35
+ const MAX_CARD_COUNT = MAX_PAGE_SIZE;
21
36
  const cardCount = computed(() =>
22
37
  Math.max(1, Math.min(props.count, MAX_CARD_COUNT)),
23
38
  );
@@ -28,12 +43,14 @@ const { t } = useI18n();
28
43
  <template>
29
44
  <div
30
45
  aria-hidden="true"
31
- class="grid grid-cols-[repeat(auto-fill,minmax(min(230px,100%),1fr))] gap-3 px-[18px] pt-4 pb-[18px]"
46
+ :class="CARD_GRID_CLASS"
47
+ :style="cardGridStyle(props.minWidth, cardCount)"
32
48
  >
33
49
  <div
34
50
  v-for="n in cardCount"
35
51
  :key="n"
36
52
  class="border-default @container rounded-[10px] border bg-(--ui-bg) p-3.5 text-[12.5px]"
53
+ :style="props.cardHeight ? { minHeight: props.cardHeight } : undefined"
37
54
  >
38
55
  <div class="flex items-center gap-2.5">
39
56
  <USkeleton
@@ -0,0 +1,38 @@
1
+ <script setup lang="ts" generic="T extends Record<string, unknown>">
2
+ import { computed } from "vue";
3
+ import { FlexRender, type Table } from "@tanstack/vue-table";
4
+ import { ACTIONS_COLUMN_ID } from "../../composables/table/constants";
5
+
6
+ /**
7
+ * A card's row actions: the cell of the grid's actions column for the card's
8
+ * row, so its buttons and its … menu offer the same actions, under the same
9
+ * rules and confirmations, as the row does in the grid. Nothing is drawn
10
+ * outside a table (no TanStack instance) or for a table with no row action.
11
+ */
12
+ interface CardRowActionsProps {
13
+ table?: Table<T>;
14
+ rowId: string;
15
+ }
16
+
17
+ const props = defineProps<CardRowActionsProps>();
18
+
19
+ // The core model holds every listed row: the paginated one may not, when the
20
+ // table pages on the client.
21
+ const cell = computed(() =>
22
+ props.table
23
+ ?.getCoreRowModel()
24
+ .rowsById[props.rowId]?.getAllCells()
25
+ .find((candidate) => candidate.column.id === ACTIONS_COLUMN_ID),
26
+ );
27
+ </script>
28
+
29
+ <template>
30
+ <!-- Full strength: the grid fades a row's actions until it is hovered,
31
+ which a phone never does. -->
32
+ <div v-if="cell" class="flex shrink-0 [&>div]:opacity-100" @click.stop>
33
+ <FlexRender
34
+ :render="cell.column.columnDef.cell"
35
+ :props="cell.getContext()"
36
+ />
37
+ </div>
38
+ </template>
@@ -1,8 +1,12 @@
1
1
  <script setup lang="ts" generic="T extends Record<string, unknown>">
2
+ import { defineComponent, markRaw, type Component } from "vue";
2
3
  import { get } from "@nuxt/ui/runtime/utils/index.js";
4
+ import type { Table } from "@tanstack/vue-table";
3
5
  import TablePagination from "../table/Pagination.vue";
4
6
  import TableEmpty from "../table/Empty.vue";
5
7
  import CardGridSkeleton from "./CardGridSkeleton.vue";
8
+ import CardAvatar from "./CardAvatar.vue";
9
+ import CardRowActions from "./CardRowActions.vue";
6
10
  import type { TableViewColumn } from "../../../composables/table-view/types/column";
7
11
  import type {
8
12
  TableViewCardConfig,
@@ -11,12 +15,20 @@ import type {
11
15
  } from "../../../composables/table-view/types/display";
12
16
  import {
13
17
  buildCardProps,
18
+ CARD_GRID_CLASS,
19
+ cardAvatar,
14
20
  cardFieldColumns,
21
+ cardGridStyle,
22
+ cardIdentity,
23
+ cardSubtitle,
15
24
  } from "../../composables/table-view/utils/card";
25
+ import { readSubline } from "../../composables/data-types/cellHelpers";
26
+ import { useComposedText } from "#dms-core/app/composables/translation/useComposedText";
16
27
  import {
17
28
  REORDER_HANDLE_CLASS,
18
29
  useReorderHandles,
19
30
  } from "../../composables/table-view/useReorderHandles";
31
+ import { useCardFields } from "../../composables/table-view/useCardFields";
20
32
 
21
33
  interface CardsDisplayProps {
22
34
  context: TableViewDisplayContext<T>;
@@ -24,12 +36,9 @@ interface CardsDisplayProps {
24
36
 
25
37
  const props = defineProps<CardsDisplayProps>();
26
38
 
27
- const INITIALS_LENGTH = 2;
28
- const EMPTY_VALUE = "—";
29
-
30
- const { locale } = useI18n();
31
39
  const { processI18n } = useTranslation();
32
- const { getDataType } = useDataTypes();
40
+ const { processText } = useComposedText();
41
+ const { renderField } = useCardFields();
33
42
 
34
43
  const rowId = (item: T): string =>
35
44
  String(get(item, props.context.rowIdKey) ?? "");
@@ -43,13 +52,20 @@ const titleOf = (item: T): string => {
43
52
  : rowId(item);
44
53
  };
45
54
 
46
- const initialsOf = (item: T): string =>
47
- titleOf(item)
48
- .split(/\s+/)
49
- .filter(Boolean)
50
- .slice(0, INITIALS_LENGTH)
51
- .map((word) => word.charAt(0).toUpperCase())
52
- .join("");
55
+ // The subtitle and avatar the title column's identity display declares.
56
+ const identity = computed(() =>
57
+ cardIdentity(props.context.columns, props.context.labelKey),
58
+ );
59
+
60
+ // An id or an address is data: only a composed text is translated.
61
+ const subtitleOf = (item: T): string | undefined =>
62
+ readSubline(
63
+ cardSubtitle(item, identity.value, {
64
+ labelKey: props.context.labelKey,
65
+ rowIdKey: props.context.rowIdKey,
66
+ }),
67
+ { processText, isStringTranslated: false },
68
+ )?.text;
53
69
 
54
70
  const card = computed(
55
71
  () =>
@@ -69,28 +85,35 @@ const fieldColumns = computed<TableViewColumn[]>(() =>
69
85
  }),
70
86
  );
71
87
 
72
- // A field renders the same way a table cell does: through the formatter of
73
- // the column's data type (status pills, links, dates…).
74
- const FieldValue = (fieldProps: { column: TableViewColumn; item: T }) => {
75
- const value = get(fieldProps.item, fieldProps.column.accessorKey);
76
- if (value === null || value === undefined || value === "") {
77
- return h("span", { class: "text-dimmed" }, EMPTY_VALUE);
78
- }
79
- const options = fieldProps.column.type.inputComponent?.options as
80
- | Record<string, unknown>
81
- | undefined;
82
- const formatter = getDataType(fieldProps.column.type.id)?.formatter;
83
- const rendered = formatter
84
- ? formatter.default(value, locale.value, options)
85
- : value;
86
- if (typeof rendered === "string" || typeof rendered === "number") {
87
- return h("span", { class: "truncate" }, String(rendered));
88
- }
89
- return rendered as ReturnType<typeof h>;
90
- };
88
+ // A field renders the same way a table cell does: through its column's
89
+ // display, else its data type (status pills, links, dates…).
90
+ const FieldValue = (fieldProps: { column: TableViewColumn; item: T }) =>
91
+ renderField(fieldProps.column, fieldProps.item);
91
92
 
92
93
  const openItem = (item: T) => props.context.actions.open(item);
93
94
 
95
+ // A custom card draws its row's actions where it likes, as a component: one
96
+ // per row, kept across renders so an open menu is not remounted shut.
97
+ const rowActionsComponents = new Map<string, Component>();
98
+ const rowActionsFor = (id: string): Component => {
99
+ let component = rowActionsComponents.get(id);
100
+ if (!component) {
101
+ component = markRaw(
102
+ defineComponent({
103
+ name: "CardRowActionsOf",
104
+ render: () =>
105
+ h(CardRowActions, {
106
+ // `h` cannot carry the display's row type to a generic component.
107
+ table: props.context.table as Table<Record<string, unknown>>,
108
+ rowId: id,
109
+ }),
110
+ }),
111
+ );
112
+ rowActionsComponents.set(id, component);
113
+ }
114
+ return component;
115
+ };
116
+
94
117
  // A hand-ordered table (backend `reorder`): each card carries a move handle,
95
118
  // and is where another card dragged by its handle drops.
96
119
  const { draggedIndex, handleFor, drop, allowDrop } = useReorderHandles({
@@ -110,12 +133,14 @@ const cardReorder = (index: number): TableViewCardReorder | undefined =>
110
133
 
111
134
  <template>
112
135
  <div>
113
- <!-- v2 cards display: an auto-fill grid inside the table card, sharing
136
+ <!-- v2 cards display: an auto-fit grid inside the table card, sharing
114
137
  its selection and its pagination footer. -->
115
138
  <CardGridSkeleton
116
139
  v-if="props.context.firstPageLoading"
117
140
  :count="props.context.pagination.pageSize"
118
141
  :fields="fieldColumns.length"
142
+ :min-width="card?.minWidth"
143
+ :card-height="card?.skeletonHeight"
119
144
  />
120
145
 
121
146
  <TableEmpty
@@ -123,11 +148,15 @@ const cardReorder = (index: number): TableViewCardReorder | undefined =>
123
148
  :can-add-row="props.context.actions.canAdd"
124
149
  />
125
150
 
126
- <div
127
- v-else
128
- class="grid grid-cols-[repeat(auto-fill,minmax(min(230px,100%),1fr))] gap-3 px-[18px] pt-4 pb-[18px]"
129
- >
130
- <template v-if="cardComponent">
151
+ <!-- A custom card loads lazily, and so may the components it draws (a
152
+ record card, a pill): the skeleton stays until every one of them is
153
+ there, rather than an empty grid lifting the footer for a beat. A
154
+ server render and a hydration draw the cards at once. -->
155
+ <Suspense v-else-if="cardComponent">
156
+ <div
157
+ :class="CARD_GRID_CLASS"
158
+ :style="cardGridStyle(card?.minWidth, props.context.items.length)"
159
+ >
131
160
  <!-- A wrapper out of the grid's layout, catching the drop on the
132
161
  card whatever its root element. -->
133
162
  <div
@@ -143,82 +172,97 @@ const cardReorder = (index: number): TableViewCardReorder | undefined =>
143
172
  ...(card?.component?.options ?? {}),
144
173
  ...buildCardProps(item, props.context),
145
174
  reorder: cardReorder(index),
175
+ rowActions: rowActionsFor(rowId(item)),
146
176
  }"
147
177
  />
148
178
  </div>
179
+ </div>
180
+ <template #fallback>
181
+ <CardGridSkeleton
182
+ :count="props.context.pagination.pageSize"
183
+ :fields="fieldColumns.length"
184
+ :min-width="card?.minWidth"
185
+ :card-height="card?.skeletonHeight"
186
+ />
149
187
  </template>
150
- <template v-else>
151
- <article
152
- v-for="(item, index) in props.context.items"
153
- :key="rowId(item)"
154
- class="group border-default hover:border-primary/35 @container cursor-pointer rounded-[10px] border bg-(--ui-bg) p-3.5 text-[12.5px] transition-colors focus-visible:outline-2 focus-visible:outline-(--dms-accent-line)"
155
- :class="{
156
- 'border-primary ring-primary ring-1':
157
- props.context.selection.isSelected(rowId(item)),
158
- 'opacity-50': draggedIndex === index,
159
- }"
160
- tabindex="0"
161
- @click="openItem(item)"
162
- @keydown.enter.self="openItem(item)"
163
- @dragover="allowDrop"
164
- @drop="drop(index)"
165
- >
166
- <header class="flex items-center gap-2.5">
167
- <button
168
- v-if="props.context.reorder"
169
- v-bind="handleFor(index)"
170
- :class="['-ms-1.5', REORDER_HANDLE_CLASS]"
171
- >
172
- <UIcon name="i-ph-dots-six-vertical" />
173
- </button>
174
- <span
175
- class="bg-accented text-default grid size-7 shrink-0 place-items-center rounded-[7px] font-mono text-[10.5px] font-bold"
188
+ </Suspense>
189
+
190
+ <div
191
+ v-else
192
+ :class="CARD_GRID_CLASS"
193
+ :style="cardGridStyle(card?.minWidth, props.context.items.length)"
194
+ >
195
+ <article
196
+ v-for="(item, index) in props.context.items"
197
+ :key="rowId(item)"
198
+ class="group border-default hover:border-primary/35 @container cursor-pointer rounded-[10px] border bg-(--ui-bg) p-3.5 text-[12.5px] transition-colors focus-visible:outline-2 focus-visible:outline-(--dms-accent-line)"
199
+ :class="{
200
+ 'border-primary ring-primary ring-1':
201
+ props.context.selection.isSelected(rowId(item)),
202
+ 'opacity-50': draggedIndex === index,
203
+ }"
204
+ tabindex="0"
205
+ @click="openItem(item)"
206
+ @keydown.enter.self="openItem(item)"
207
+ @dragover="allowDrop"
208
+ @drop="drop(index)"
209
+ >
210
+ <header class="flex items-center gap-2.5">
211
+ <button
212
+ v-if="props.context.reorder"
213
+ v-bind="handleFor(index)"
214
+ :class="['-ms-1.5', REORDER_HANDLE_CLASS]"
215
+ >
216
+ <UIcon name="i-ph-dots-six-vertical" />
217
+ </button>
218
+ <CardAvatar
219
+ :avatar="cardAvatar(item, identity)"
220
+ :storage="identity.storage"
221
+ :label="titleOf(item)"
222
+ />
223
+ <div class="min-w-0 flex-1">
224
+ <div
225
+ class="text-highlighted truncate text-[13px] font-semibold"
226
+ :title="titleOf(item)"
176
227
  >
177
- {{ initialsOf(item) }}
178
- </span>
179
- <div class="min-w-0 flex-1">
180
- <div
181
- class="text-highlighted truncate text-[13px] font-semibold"
182
- :title="titleOf(item)"
183
- >
184
- {{ titleOf(item) }}
185
- </div>
186
- <div class="text-dimmed truncate font-mono text-[11px]">
187
- {{ rowId(item) }}
188
- </div>
228
+ {{ titleOf(item) }}
189
229
  </div>
190
- <UCheckbox
191
- :model-value="props.context.selection.isSelected(rowId(item))"
192
- :aria-label="titleOf(item)"
193
- @click.stop
194
- @update:model-value="
195
- props.context.selection.toggle(rowId(item), !!$event)
196
- "
197
- />
198
- </header>
199
-
200
- <!-- A phone-narrow card stacks its fields: two columns would clip
201
- every value. -->
202
- <dl
203
- class="mt-3 grid grid-cols-2 gap-x-3 gap-y-2.5 max-sm:@max-[16rem]:grid-cols-1"
204
- >
205
230
  <div
206
- v-for="column in fieldColumns"
207
- :key="column.id"
208
- class="min-w-0"
231
+ v-if="subtitleOf(item)"
232
+ class="text-dimmed truncate font-mono text-[11px]"
233
+ :title="subtitleOf(item)"
209
234
  >
210
- <DmsEyebrow
211
- as="dt"
212
- truncate
213
- :label="processI18n(column.header)"
214
- />
215
- <dd class="text-default mt-0.5 flex min-w-0">
216
- <FieldValue :column="column" :item="item" />
217
- </dd>
235
+ {{ subtitleOf(item) }}
218
236
  </div>
219
- </dl>
220
- </article>
221
- </template>
237
+ </div>
238
+ <UCheckbox
239
+ :model-value="props.context.selection.isSelected(rowId(item))"
240
+ :aria-label="titleOf(item)"
241
+ @click.stop
242
+ @update:model-value="
243
+ props.context.selection.toggle(rowId(item), !!$event)
244
+ "
245
+ />
246
+ <CardRowActions
247
+ :table="props.context.table"
248
+ :row-id="rowId(item)"
249
+ class="-me-1.5"
250
+ />
251
+ </header>
252
+
253
+ <!-- A phone-narrow card stacks its fields: two columns would clip
254
+ every value. -->
255
+ <dl
256
+ class="mt-3 grid grid-cols-2 gap-x-3 gap-y-2.5 max-sm:@max-[16rem]:grid-cols-1"
257
+ >
258
+ <div v-for="column in fieldColumns" :key="column.id" class="min-w-0">
259
+ <DmsEyebrow as="dt" truncate :label="processI18n(column.header)" />
260
+ <dd class="text-default mt-0.5 flex min-w-0">
261
+ <FieldValue :column="column" :item="item" />
262
+ </dd>
263
+ </div>
264
+ </dl>
265
+ </article>
222
266
  </div>
223
267
 
224
268
  <TablePagination />
@@ -11,6 +11,9 @@ import type { PageLiveNote } from "../../../types/live";
11
11
  * live ("Table on hold · search active · 3 new · Show"), and followed, while
12
12
  * the page is paused, by the breakdown a component gave the page ("12 new
13
13
  * requests · 2 in 5xx · Show"). Show reads the rows once; the hold stays.
14
+ * `floating`, it is a pill laid over the first rows, under the column header band instead: what came
15
+ * in while the user had the rows on screen, or the page paused, says so
16
+ * without pushing them down.
14
17
  */
15
18
  const props = withDefaults(
16
19
  defineProps<{
@@ -22,10 +25,20 @@ const props = withDefaults(
22
25
  missedLabel?: string;
23
26
  /** The breakdown of what the paused page missed. */
24
27
  detail?: PageLiveNote | null;
28
+ /** A pill over the rows, taking no room, in place of the strip. */
29
+ floating?: boolean;
25
30
  }>(),
26
- { reason: null, missedLabel: undefined, detail: null },
31
+ { reason: null, missedLabel: undefined, detail: null, floating: false },
27
32
  );
28
33
 
34
+ // Literal class strings (Tailwind extracts them). The pill is opaque and
35
+ // lifted by the theme's pop shadow (deeper in dark), so the rows under it
36
+ // stay apart from it.
37
+ const STRIP_CLASS =
38
+ "border-b border-(--dms-info-line) bg-(--dms-info-tint) py-[7px] ps-[18px] pe-3.5 text-[13px]";
39
+ const PILL_CLASS =
40
+ "rounded-full border border-(--dms-info-line) bg-(--ui-bg) py-1 ps-2.5 pe-3.5 text-[12.5px] shadow-(--dms-shadow-pop)";
41
+
29
42
  const emit = defineEmits<{ show: [] }>();
30
43
 
31
44
  const { t } = useI18n();
@@ -55,7 +68,9 @@ const detailNote = computed(() =>
55
68
  <template>
56
69
  <div
57
70
  role="status"
58
- class="dms-live-hold-banner text-highlighted flex items-center gap-2.5 border-b border-(--dms-info-line) bg-(--dms-info-tint) py-[7px] ps-[18px] pe-3.5 text-[13px]"
71
+ aria-live="polite"
72
+ class="dms-live-hold-banner text-highlighted flex items-center gap-2.5"
73
+ :class="props.floating ? PILL_CLASS : STRIP_CLASS"
59
74
  >
60
75
  <UIcon name="i-ph-pause-circle" class="text-info size-4 shrink-0" />
61
76
  <span v-if="reasonText" class="dms-live-hold-banner__reason min-w-0">
@@ -1,5 +1,7 @@
1
1
  import type { ActionTarget } from "../../../composables/table-view/types/action-target";
2
2
  import type { CustomRowAction } from "../../../types/row-action";
3
+ import { namesRowField } from "#dms-core/app/utils/url-interpolation";
4
+ import { dropRowTokens } from "../../utils/urlVariables";
3
5
 
4
6
  /** Query flag of a bulk request covering every matching row. */
5
7
  export const ALL_MATCHING_QUERY_KEY = "allMatching";
@@ -34,17 +36,27 @@ export function bulkSelectionQuery(
34
36
  return query;
35
37
  }
36
38
 
37
- /** `url` with `query` appended, a list value as a repeated key. */
39
+ const FRAGMENT_SEPARATOR = "#";
40
+
41
+ /**
42
+ * `url` with `query` appended, a list value as a repeated key. The URL's own
43
+ * query is kept as written: its tokens (`{{query.X}}`, `{{row.X}}`) are filled
44
+ * after, which percent-encoded braces would no longer match.
45
+ */
38
46
  export function withQuery(url: string, query: Record<string, QueryValue>) {
39
- const [path, search = ""] = url.split("?");
40
- const params = new URLSearchParams(search);
47
+ const params = new URLSearchParams();
41
48
  for (const [key, value] of Object.entries(query)) {
42
49
  for (const entry of Array.isArray(value) ? value : [value]) {
43
50
  params.append(key, entry);
44
51
  }
45
52
  }
46
- const joined = params.toString();
47
- return joined ? `${path}?${joined}` : (path ?? url);
53
+ const appended = params.toString();
54
+ if (!appended) return url;
55
+ const [base = "", ...fragment] = url.split(FRAGMENT_SEPARATOR);
56
+ const separator = !base.includes("?") ? "?" : /[?&]$/.test(base) ? "" : "&";
57
+ return [`${base}${separator}${appended}`, ...fragment].join(
58
+ FRAGMENT_SEPARATOR,
59
+ );
48
60
  }
49
61
 
50
62
  const URL_TARGET_TYPES = new Set([
@@ -55,14 +67,36 @@ const URL_TARGET_TYPES = new Set([
55
67
  "exportJob",
56
68
  ]);
57
69
 
58
- /** A target sending the selection along: its URL carries the query. */
70
+ type UrlTarget = ActionTarget & { url: string };
71
+
72
+ export const isUrlTarget = (target: ActionTarget): target is UrlTarget =>
73
+ URL_TARGET_TYPES.has(target.type);
74
+
75
+ /**
76
+ * A target sending the selection along: its URL carries the query. Sent
77
+ * without a row, its `{{row.X}}` tokens are taken out (a `?ids={{row._id}}`
78
+ * pair gives way to the selection's `ids`).
79
+ */
59
80
  export function targetWithSelection(
60
81
  target: ActionTarget,
61
82
  query: Record<string, QueryValue>,
62
83
  ): ActionTarget {
63
- if (!URL_TARGET_TYPES.has(target.type)) return target;
64
- const { url } = target as ActionTarget & { url: string };
65
- return { ...target, url: withQuery(url, query) } as ActionTarget;
84
+ if (!isUrlTarget(target)) return target;
85
+ const url = withQuery(dropRowTokens(target.url), query);
86
+ return { ...target, url } as ActionTarget;
87
+ }
88
+
89
+ /**
90
+ * A bulk action's target run on one row: its URL carries that row's id as the
91
+ * selection bar's carries the selection's, unless it names the row itself
92
+ * (`{{row._id}}`), which then fills it.
93
+ */
94
+ export function targetForRow(
95
+ target: ActionTarget,
96
+ query: Record<string, QueryValue>,
97
+ ): ActionTarget {
98
+ if (!isUrlTarget(target) || namesRowField(target.url)) return target;
99
+ return { ...target, url: withQuery(target.url, query) } as ActionTarget;
66
100
  }
67
101
 
68
102
  /** Whether a custom action runs on the selection bar. */