@antelopejs/dms 0.9.7 → 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 (414) hide show
  1. package/CHANGELOG.md +114 -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 -45
  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/realtime.d.ts +13 -1
  117. package/dist/routes/realtime.js +114 -29
  118. package/dist/routes/realtime.js.map +1 -1
  119. package/dist/startup-config.d.ts +2 -0
  120. package/dist/startup-config.js +62 -0
  121. package/dist/startup-config.js.map +1 -0
  122. package/dist/store/interfaces.d.ts +3 -2
  123. package/dist/store/interfaces.js +16 -6
  124. package/dist/store/interfaces.js.map +1 -1
  125. package/dist/store/listing.js +24 -13
  126. package/dist/store/listing.js.map +1 -1
  127. package/dist/store/operations.js +4 -0
  128. package/dist/store/operations.js.map +1 -1
  129. package/dist/store/requirements.js +3 -2
  130. package/dist/store/requirements.js.map +1 -1
  131. package/dist/store/runtime.d.ts +10 -1
  132. package/dist/store/runtime.js +33 -6
  133. package/dist/store/runtime.js.map +1 -1
  134. package/dist/store/types.d.ts +10 -0
  135. package/dist/validation/auth.schema.d.ts +2 -2
  136. package/dist/validation/member-invite.schema.d.ts +2 -10
  137. package/dist/validation/member-invite.schema.js +2 -15
  138. package/dist/validation/member-invite.schema.js.map +1 -1
  139. package/frontend-vue/dms.frontend.ts +24 -5
  140. package/frontend-vue/dms.server.ts +17 -0
  141. package/frontend-vue/layers/dms-auth/server/accounts.ts +3 -4
  142. package/frontend-vue/layers/dms-core/app/build/composables/renderNow.ts +34 -0
  143. package/frontend-vue/layers/dms-core/app/build/composables/user/tablePreferences.ts +5 -1
  144. package/frontend-vue/layers/dms-core/app/build/utils/crossTab.ts +199 -0
  145. package/frontend-vue/layers/dms-core/app/build/utils/sharedRealtimeConnection.ts +410 -0
  146. package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +77 -12
  147. package/frontend-vue/layers/dms-core/app/composables/data-types/useDataType.ts +26 -0
  148. package/frontend-vue/layers/dms-core/app/composables/hydration/isHydratingComponent.ts +29 -0
  149. package/frontend-vue/layers/dms-core/app/composables/live/livePauseSource.ts +29 -0
  150. package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +150 -9
  151. package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +6 -4
  152. package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +27 -1
  153. package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +256 -119
  154. package/frontend-vue/layers/dms-core/app/composables/translation/useComposedText.ts +21 -8
  155. package/frontend-vue/layers/dms-core/app/composables/translation/useTranslation.ts +22 -5
  156. package/frontend-vue/layers/dms-core/app/plugins/dev-error-log.ts +16 -0
  157. package/frontend-vue/layers/dms-core/app/types/user.ts +6 -0
  158. package/frontend-vue/layers/dms-core/app/utils/composedText.ts +7 -2
  159. package/frontend-vue/layers/dms-core/app/utils/formatter.ts +166 -35
  160. package/frontend-vue/layers/dms-core/app/utils/url-interpolation.ts +18 -1
  161. package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +134 -2
  162. package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +134 -2
  163. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +3 -2
  164. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +41 -21
  165. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardPageBody.vue +9 -0
  166. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +4 -19
  167. package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +11 -3
  168. package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +18 -7
  169. package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionBar.vue +8 -0
  170. package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +14 -0
  171. package/frontend-vue/layers/dms-layout/app/build/components/layout/RowSidePanel.vue +43 -82
  172. package/frontend-vue/layers/dms-layout/app/build/components/layout/SidebarUserMenu.vue +44 -3
  173. package/frontend-vue/layers/dms-layout/app/build/components/layout/UserIdentity.vue +11 -3
  174. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleCatalogTile.vue +132 -35
  175. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleManageMenu.vue +59 -0
  176. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleSourceLabel.vue +25 -0
  177. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreBanners.vue +73 -0
  178. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreOperation.vue +116 -0
  179. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreRequirements.vue +8 -3
  180. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue +85 -207
  181. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTile.vue +10 -3
  182. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTileSkeleton.vue +68 -0
  183. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +24 -8
  184. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxItem.vue +8 -5
  185. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/region/RegionTimeZoneInput.vue +1 -0
  186. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-permissions.ts +19 -0
  187. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/useRoleEditor.ts +2 -1
  188. package/frontend-vue/layers/dms-layout/app/build/composables/layout/useSidePanelWidth.ts +31 -70
  189. package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen.ts +11 -1
  190. package/frontend-vue/layers/dms-layout/app/build/utils/dev-reload-stream.ts +81 -0
  191. package/frontend-vue/layers/dms-layout/app/build/utils/module-store-catalog.ts +232 -36
  192. package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +122 -14
  193. package/frontend-vue/layers/dms-layout/app/composables/layout/useLayoutBanners.ts +32 -3
  194. package/frontend-vue/layers/dms-layout/app/composables/layout/usePageRecord.ts +50 -0
  195. package/frontend-vue/layers/dms-layout/app/composables/layout/useServedHeaderActions.ts +25 -7
  196. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleHistory.ts +3 -2
  197. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +51 -4
  198. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStoreActions.ts +194 -0
  199. package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +23 -3
  200. package/frontend-vue/layers/dms-layout/app/composables/page/usePageLayout.ts +4 -2
  201. package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +6 -1
  202. package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +9 -0
  203. package/frontend-vue/layers/dms-layout/app/custom-layouts/SettingsLayout.vue +9 -0
  204. package/frontend-vue/layers/dms-layout/app/custom-pages/modules-store.vue +291 -0
  205. package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +87 -474
  206. package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +3 -0
  207. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +6 -22
  208. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-navigation.client.ts +2 -1
  209. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-personal-pages.client.ts +14 -44
  210. package/frontend-vue/layers/dms-layout/app/plugins/dms-dev-reload.client.ts +14 -9
  211. package/frontend-vue/layers/dms-layout/app/theme/form-controls.ts +9 -4
  212. package/frontend-vue/layers/dms-layout/app/theme/surfaces.ts +22 -6
  213. package/frontend-vue/layers/dms-layout/app/types/module-store.ts +9 -0
  214. package/frontend-vue/layers/dms-layout/app/types/page.ts +15 -1
  215. package/frontend-vue/layers/dms-layout/app/utils/account-menu.ts +11 -0
  216. package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +57 -0
  217. package/frontend-vue/layers/dms-layout/app/utils/pageQuery.ts +26 -0
  218. package/frontend-vue/layers/dms-layout/app/utils/pageRecord.ts +71 -0
  219. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +39 -14
  220. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +38 -13
  221. package/frontend-vue/layers/dms-ui/app/build/components/containers/TargetComponentTree.vue +31 -0
  222. package/frontend-vue/layers/dms-ui/app/build/components/containers/constants.ts +25 -0
  223. package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +244 -15
  224. package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +42 -25
  225. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowHeader.vue +153 -0
  226. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowNotFound.vue +44 -0
  227. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/drawerRowHeader.ts +35 -0
  228. package/frontend-vue/layers/dms-ui/app/build/components/form/FormEntries.vue +44 -9
  229. package/frontend-vue/layers/dms-ui/app/build/components/form/FormErrorSummary.vue +11 -4
  230. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldControl.vue +38 -5
  231. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldNotes.vue +29 -5
  232. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSectionNav.vue +3 -1
  233. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSections.vue +106 -27
  234. package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +12 -4
  235. package/frontend-vue/layers/dms-ui/app/build/components/table/Empty.vue +4 -2
  236. package/frontend-vue/layers/dms-ui/app/build/components/table/FiltersRow.vue +38 -1
  237. package/frontend-vue/layers/dms-ui/app/build/components/table/Pagination.vue +55 -54
  238. package/frontend-vue/layers/dms-ui/app/build/components/table/RowSelection.vue +2 -1
  239. package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +396 -288
  240. package/frontend-vue/layers/dms-ui/app/build/components/table/Tabs.vue +2 -1
  241. package/frontend-vue/layers/dms-ui/app/build/components/table/Views.vue +2 -1
  242. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardAvatar.vue +59 -0
  243. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardGridSkeleton.vue +19 -2
  244. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardRowActions.vue +38 -0
  245. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardsDisplay.vue +148 -104
  246. package/frontend-vue/layers/dms-ui/app/build/components/table-view/LiveHoldBanner.vue +17 -2
  247. package/frontend-vue/layers/dms-ui/app/build/composables/actions/bulkSelection.ts +43 -9
  248. package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowComponentOptions.ts +76 -0
  249. package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowNavigation.ts +172 -27
  250. package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +270 -46
  251. package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +2 -0
  252. package/frontend-vue/layers/dms-ui/app/build/composables/containers/closingLayers.ts +186 -0
  253. package/frontend-vue/layers/dms-ui/app/build/composables/containers/context.ts +14 -0
  254. package/frontend-vue/layers/dms-ui/app/build/composables/containers/drawerSize.ts +229 -0
  255. package/frontend-vue/layers/dms-ui/app/build/composables/containers/useResizableSize.ts +175 -0
  256. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/metricCells.ts +18 -35
  257. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/numberSuffix.ts +47 -0
  258. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +99 -24
  259. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerFieldTypes.ts +2 -1
  260. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationFilter.ts +75 -0
  261. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationRows.ts +71 -0
  262. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationValue.ts +39 -0
  263. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/twoLineCell.ts +3 -3
  264. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/useColumnValueRenderer.ts +6 -2
  265. package/frontend-vue/layers/dms-ui/app/build/composables/form/createFormFields.ts +15 -0
  266. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldSaved.ts +40 -0
  267. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldValueRule.ts +62 -0
  268. package/frontend-vue/layers/dms-ui/app/build/composables/form/formEntryContext.ts +5 -0
  269. package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +42 -7
  270. package/frontend-vue/layers/dms-ui/app/build/composables/form/formLayout.ts +106 -4
  271. package/frontend-vue/layers/dms-ui/app/build/composables/form/formSections.ts +32 -0
  272. package/frontend-vue/layers/dms-ui/app/build/composables/form/readonlyValue.ts +19 -0
  273. package/frontend-vue/layers/dms-ui/app/build/composables/form/registerFormFunctions.ts +27 -0
  274. package/frontend-vue/layers/dms-ui/app/build/composables/form/repeaterTable.ts +65 -0
  275. package/frontend-vue/layers/dms-ui/app/build/composables/form/serverFieldErrors.ts +49 -0
  276. package/frontend-vue/layers/dms-ui/app/build/composables/form/toasterClearance.ts +118 -0
  277. package/frontend-vue/layers/dms-ui/app/build/composables/form/useInstantForm.ts +12 -1
  278. package/frontend-vue/layers/dms-ui/app/build/composables/live/currentPageLive.ts +41 -3
  279. package/frontend-vue/layers/dms-ui/app/build/composables/live/livePoller.ts +10 -2
  280. package/frontend-vue/layers/dms-ui/app/build/composables/live/pageReadClock.ts +28 -0
  281. package/frontend-vue/layers/dms-ui/app/build/composables/live/shortcuts.ts +1 -1
  282. package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageLiveHost.ts +17 -3
  283. package/frontend-vue/layers/dms-ui/app/build/composables/live/useUpdatedLabel.ts +38 -0
  284. package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/pageToolbarContext.ts +75 -0
  285. package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/toolbarControlHeight.ts +29 -0
  286. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodComparison.ts +52 -0
  287. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +21 -0
  288. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodShortLabels.ts +28 -0
  289. package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodDraft.ts +1 -0
  290. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRelationFilterLabels.ts +73 -0
  291. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRowScrollAnchor.ts +106 -0
  292. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +9 -0
  293. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTableColumns.ts +1 -2
  294. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/archivedCount.ts +31 -0
  295. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/filterEditorValue.ts +38 -0
  296. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/formatFilterValue.ts +33 -2
  297. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/scrollEdges.ts +31 -0
  298. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useCardFields.ts +33 -0
  299. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useMobileDisplay.ts +48 -0
  300. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useScrolledPastTop.ts +41 -18
  301. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +3 -2
  302. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLinkedVisit.ts +78 -0
  303. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLiveHold.ts +135 -22
  304. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +56 -25
  305. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +77 -32
  306. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +86 -10
  307. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/bulkActions.ts +14 -4
  308. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/card.ts +127 -2
  309. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/compareValues.ts +13 -0
  310. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/expandedRows.ts +28 -0
  311. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/groupedRows.ts +114 -1
  312. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/linkedArrival.ts +308 -0
  313. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/locateRow.ts +106 -0
  314. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pageArrival.ts +34 -0
  315. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pinnedRows.ts +17 -0
  316. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/quickFilters.ts +41 -33
  317. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/recordLink.ts +24 -8
  318. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableCounts.ts +113 -0
  319. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableQuery.ts +27 -4
  320. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/views.ts +22 -6
  321. package/frontend-vue/layers/dms-ui/app/build/utils/addressSuggestions.ts +49 -0
  322. package/frontend-vue/layers/dms-ui/app/build/utils/eyebrow.ts +12 -0
  323. package/frontend-vue/layers/dms-ui/app/build/utils/fieldOptionsI18n.ts +51 -11
  324. package/frontend-vue/layers/dms-ui/app/build/utils/inputWidth.ts +53 -0
  325. package/frontend-vue/layers/dms-ui/app/build/utils/numberAdjustment.ts +6 -3
  326. package/frontend-vue/layers/dms-ui/app/build/utils/relativeDate.ts +19 -5
  327. package/frontend-vue/layers/dms-ui/app/build/utils/scrollWithin.ts +99 -0
  328. package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +36 -14
  329. package/frontend-vue/layers/dms-ui/app/components/PasswordStrength.vue +1 -1
  330. package/frontend-vue/layers/dms-ui/app/components/RecursiveComponent.vue +9 -1
  331. package/frontend-vue/layers/dms-ui/app/components/blocks/LiveToggleBlock.vue +80 -32
  332. package/frontend-vue/layers/dms-ui/app/components/blocks/PageToolbarBlock.vue +137 -0
  333. package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +5 -0
  334. package/frontend-vue/layers/dms-ui/app/components/chart/ApexChartHost.vue +16 -2
  335. package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +24 -5
  336. package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +41 -23
  337. package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +1 -1
  338. package/frontend-vue/layers/dms-ui/app/components/container-footer/ContainerFooter.vue +34 -0
  339. package/frontend-vue/layers/dms-ui/app/components/field-row/FieldRow.vue +25 -2
  340. package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +2 -2
  341. package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +118 -36
  342. package/frontend-vue/layers/dms-ui/app/components/form/components/DatePicker.vue +20 -1
  343. package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +13 -1
  344. package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +8 -3
  345. package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +53 -5
  346. package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +29 -5
  347. package/frontend-vue/layers/dms-ui/app/components/form/components/InputNumber.vue +45 -8
  348. package/frontend-vue/layers/dms-ui/app/components/form/components/InputPercentage.vue +28 -5
  349. package/frontend-vue/layers/dms-ui/app/components/form/components/InputSecret.vue +5 -0
  350. package/frontend-vue/layers/dms-ui/app/components/form/components/InputText.vue +19 -2
  351. package/frontend-vue/layers/dms-ui/app/components/form/components/InputTime.vue +2 -1
  352. package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +205 -65
  353. package/frontend-vue/layers/dms-ui/app/components/form/components/Repeater.vue +80 -30
  354. package/frontend-vue/layers/dms-ui/app/components/form/components/RepeaterTable.vue +203 -0
  355. package/frontend-vue/layers/dms-ui/app/components/form/components/Select.vue +4 -2
  356. package/frontend-vue/layers/dms-ui/app/components/grid/GridRow.vue +11 -2
  357. package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +79 -70
  358. package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +117 -21
  359. package/frontend-vue/layers/dms-ui/app/components/meter/Meter.vue +2 -1
  360. package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +113 -34
  361. package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +14 -2
  362. package/frontend-vue/layers/dms-ui/app/components/section-header/Eyebrow.vue +6 -1
  363. package/frontend-vue/layers/dms-ui/app/components/segmented/Segmented.vue +13 -0
  364. package/frontend-vue/layers/dms-ui/app/components/stat-group/StatGroup.vue +52 -3
  365. package/frontend-vue/layers/dms-ui/app/components/tab/Tab.vue +2 -1
  366. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +45 -35
  367. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanDisplay.vue +4 -1
  368. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +410 -96
  369. package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +33 -5
  370. package/frontend-vue/layers/dms-ui/app/composables/chart/apexCategoryLabels.ts +68 -0
  371. package/frontend-vue/layers/dms-ui/app/composables/chart/apexDatetimeBuckets.ts +306 -0
  372. package/frontend-vue/layers/dms-ui/app/composables/chart/formatValue.ts +4 -3
  373. package/frontend-vue/layers/dms-ui/app/composables/chart/heatmapColors.ts +93 -0
  374. package/frontend-vue/layers/dms-ui/app/composables/chart/localizeSeries.ts +62 -0
  375. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.ts +116 -8
  376. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.types.ts +7 -0
  377. package/frontend-vue/layers/dms-ui/app/composables/containers/types.ts +84 -1
  378. package/frontend-vue/layers/dms-ui/app/composables/containers/useDrawer.ts +69 -8
  379. package/frontend-vue/layers/dms-ui/app/composables/form/types/field-saved.ts +11 -0
  380. package/frontend-vue/layers/dms-ui/app/composables/form/types/field.ts +4 -0
  381. package/frontend-vue/layers/dms-ui/app/composables/form/types/functions.ts +1 -0
  382. package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +142 -21
  383. package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +11 -4
  384. package/frontend-vue/layers/dms-ui/app/composables/page-toolbar/usePageToolbarSize.ts +30 -0
  385. package/frontend-vue/layers/dms-ui/app/composables/table-view/kanban.ts +4 -2
  386. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +22 -3
  387. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +4 -0
  388. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/display.ts +24 -1
  389. package/frontend-vue/layers/dms-ui/app/plugins/closing-layers.client.ts +10 -0
  390. package/frontend-vue/layers/dms-ui/app/plugins/live-pause-source.ts +15 -0
  391. package/frontend-vue/layers/dms-ui/app/plugins/register.ts +2 -0
  392. package/frontend-vue/layers/dms-ui/app/types/input-number.ts +3 -2
  393. package/frontend-vue/layers/dms-ui/app/types/page-toolbar.ts +7 -0
  394. package/frontend-vue/layers/dms-ui/app/types/row-action.ts +0 -2
  395. package/frontend-vue/layers/dms-ui/app/types/row-navigation.ts +17 -0
  396. package/frontend-vue/layers/dms-ui/app/types/table/query.ts +2 -1
  397. package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +3 -1
  398. package/frontend-vue/layers/dms-ui/app/utils/componentTree.ts +24 -0
  399. package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +16 -4
  400. package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +16 -4
  401. package/frontend-vue/package.json +2 -2
  402. package/package.json +7 -6
  403. package/skills/dms/SKILL.md +1 -1
  404. package/skills/dms-auth/SKILL.md +5 -3
  405. package/skills/dms-best-practices/REFERENCE.md +2 -0
  406. package/skills/dms-best-practices/SKILL.md +4 -0
  407. package/skills/dms-module/SKILL.md +3 -2
  408. package/skills/dms-pages/REFERENCE.md +125 -16
  409. package/skills/dms-pages/SKILL.md +1 -1
  410. package/skills/dms-project/DEV-SERVERS.md +23 -1
  411. package/skills/dms-project/REFERENCE.md +1 -1
  412. package/skills/dms-project/SKILL.md +4 -1
  413. package/skills/dms-project/scripts/dms-dev.sh +205 -17
  414. package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +0 -42
@@ -6,6 +6,7 @@ import {
6
6
  useFormField,
7
7
  } from "@nuxt/ui/composables/useFormField";
8
8
  import { resolveDmsComponent } from "../../../composables/resolveDmsComponent";
9
+ import DmsRepeaterTable from "./RepeaterTable.vue";
9
10
 
10
11
  /** A field of the rows: one column. */
11
12
  interface RepeaterColumn {
@@ -72,21 +73,43 @@ const FLUID_COLUMN = "minmax(0,1fr)";
72
73
  const COLUMN_WIDTHS: Record<string, string> = { boolean: "20px" };
73
74
  const HANDLE_COLUMN = "20px";
74
75
  const REMOVE_COLUMN = "28px";
76
+
77
+ /** The columns of a row: the handle, the given cell columns, the remove button. */
78
+ function rowTemplate(cellColumns: string[]): string {
79
+ return [props.sortable ? HANDLE_COLUMN : "", ...cellColumns, REMOVE_COLUMN]
80
+ .filter(Boolean)
81
+ .join(" ");
82
+ }
83
+
84
+ // Below 480px of the field's own width (a drawer, a phone), columns side by
85
+ // side would each be a few characters wide: each row stacks its cells in one
86
+ // column instead, a labelled field under another, as a card, and the column
87
+ // heads hide.
88
+ const STACKED_HIDDEN_CLASS = "@max-[30rem]:hidden";
89
+ const STACKED_ROW_CLASSES = {
90
+ row: "@max-[30rem]:grid-cols-(--dms-repeater-stacked-columns) @max-[30rem]:items-start @max-[30rem]:rounded-md @max-[30rem]:border @max-[30rem]:border-default @max-[30rem]:p-2.5",
91
+ // Side by side, each cell is a column of the row itself.
92
+ cells: "contents @max-[30rem]:grid @max-[30rem]:min-w-0 @max-[30rem]:gap-2.5",
93
+ cell: "grid min-w-0 gap-1",
94
+ label: `${EYEBROW_CLASS} text-dimmed hidden truncate @max-[30rem]:block`,
95
+ };
75
96
  const gridTemplate = computed(() => ({
76
- gridTemplateColumns: [
77
- props.sortable ? HANDLE_COLUMN : "",
78
- ...props.columns.map(
97
+ "--dms-repeater-columns": rowTemplate(
98
+ props.columns.map(
79
99
  (column) => COLUMN_WIDTHS[column.type ?? ""] ?? FLUID_COLUMN,
80
100
  ),
81
- REMOVE_COLUMN,
82
- ]
83
- .filter(Boolean)
84
- .join(" "),
101
+ ),
102
+ "--dms-repeater-stacked-columns": rowTemplate([FLUID_COLUMN]),
85
103
  }));
86
104
 
87
- function emitRows(next: RepeaterRow[]): void {
105
+ /**
106
+ * Sets the rows; `shouldValidate: false` leaves the form's validation alone: a row
107
+ * just added is empty, and is only checked once filled or on submit, never
108
+ * flagged "required" the moment it appears.
109
+ */
110
+ function emitRows(next: RepeaterRow[], shouldValidate = true): void {
88
111
  model.value = next;
89
- emitFormChange();
112
+ if (shouldValidate) emitFormChange();
90
113
  }
91
114
 
92
115
  function setCell(index: number, column: string, value: unknown): void {
@@ -99,7 +122,7 @@ function setCell(index: number, column: string, value: unknown): void {
99
122
 
100
123
  function addRow(): void {
101
124
  rowKeys.value.push(nextKey++);
102
- emitRows([...rows.value, {}]);
125
+ emitRows([...rows.value, {}], false);
103
126
  }
104
127
 
105
128
  function removeRow(index: number): void {
@@ -176,10 +199,21 @@ const hasHeaders = computed(() => props.columns.some((column) => column.label));
176
199
  </script>
177
200
 
178
201
  <template>
179
- <div :id="props.id" class="grid gap-1.5" role="group">
202
+ <!-- Read-only, the rows are values to read, not controls: a table. -->
203
+ <DmsRepeaterTable
204
+ v-if="props.disabled"
205
+ :id="props.id"
206
+ :columns="props.columns"
207
+ :rows="rows"
208
+ />
209
+ <div v-else :id="props.id" class="@container grid gap-1.5" role="group">
180
210
  <div
181
211
  v-if="hasHeaders && rows.length"
182
- :class="[EYEBROW_CLASS, 'text-dimmed grid items-end gap-2']"
212
+ :class="[
213
+ EYEBROW_CLASS,
214
+ 'text-dimmed grid grid-cols-(--dms-repeater-columns) items-end gap-2',
215
+ STACKED_HIDDEN_CLASS,
216
+ ]"
183
217
  :style="gridTemplate"
184
218
  aria-hidden="true"
185
219
  >
@@ -193,8 +227,11 @@ const hasHeaders = computed(() => props.columns.some((column) => column.label));
193
227
  <div
194
228
  v-for="(row, index) in rows"
195
229
  :key="rowKeys[index]"
196
- class="grid items-center gap-2"
197
- :class="draggedIndex === index && 'opacity-50'"
230
+ :class="[
231
+ 'grid grid-cols-(--dms-repeater-columns) items-center gap-2',
232
+ STACKED_ROW_CLASSES.row,
233
+ draggedIndex === index && 'opacity-50',
234
+ ]"
198
235
  :style="gridTemplate"
199
236
  @dragover.prevent
200
237
  @drop.prevent="onDrop(index)"
@@ -212,22 +249,35 @@ const hasHeaders = computed(() => props.columns.some((column) => column.label));
212
249
  >
213
250
  <UIcon name="i-ph-dots-six-vertical" class="size-4" />
214
251
  </button>
215
- <Component
216
- :is="cellComponent(column)"
217
- v-for="column in props.columns"
218
- :id="cellName(index, column.id)"
219
- :key="column.id"
220
- :model-value="row[column.id]"
221
- size="sm"
222
- class="w-full min-w-0"
223
- :disabled="props.disabled"
224
- :aria-label="column.label"
225
- :aria-invalid="isCellInvalid(index, column.id) || undefined"
226
- :color="isCellInvalid(index, column.id) ? 'error' : undefined"
227
- :highlight="isCellInvalid(index, column.id) || undefined"
228
- v-bind="column.component.options || {}"
229
- @update:model-value="setCell(index, column.id, $event)"
230
- />
252
+ <div :class="STACKED_ROW_CLASSES.cells">
253
+ <div
254
+ v-for="column in props.columns"
255
+ :key="column.id"
256
+ :class="STACKED_ROW_CLASSES.cell"
257
+ >
258
+ <span
259
+ v-if="column.label"
260
+ :class="STACKED_ROW_CLASSES.label"
261
+ aria-hidden="true"
262
+ >
263
+ {{ column.label }}
264
+ </span>
265
+ <Component
266
+ :is="cellComponent(column)"
267
+ :id="cellName(index, column.id)"
268
+ :model-value="row[column.id]"
269
+ size="sm"
270
+ class="w-full min-w-0"
271
+ :disabled="props.disabled"
272
+ :aria-label="column.label"
273
+ :aria-invalid="isCellInvalid(index, column.id) || undefined"
274
+ :color="isCellInvalid(index, column.id) ? 'error' : undefined"
275
+ :highlight="isCellInvalid(index, column.id) || undefined"
276
+ v-bind="column.component.options || {}"
277
+ @update:model-value="setCell(index, column.id, $event)"
278
+ />
279
+ </div>
280
+ </div>
231
281
  <UButton
232
282
  icon="i-ph-x"
233
283
  color="neutral"
@@ -0,0 +1,203 @@
1
+ <script setup lang="ts">
2
+ import { useId, type VNodeChild } from "vue";
3
+ import { EYEBROW_CLASS } from "../../../build/utils/eyebrow";
4
+ import {
5
+ relationRowValue,
6
+ useRelationRows,
7
+ } from "../../../build/composables/data-types/relationRows";
8
+ import { unlabeledRelationIds } from "../../../build/composables/data-types/relationValue";
9
+ import {
10
+ NUMERIC_TYPE_IDS,
11
+ useColumnValueRenderer,
12
+ } from "../../../build/composables/data-types/useColumnValueRenderer";
13
+ import {
14
+ type RelationLabelSource,
15
+ type RepeaterTableColumn,
16
+ joinRelationValue,
17
+ relationLabelSource,
18
+ renderableColumn,
19
+ } from "../../../build/composables/form/repeaterTable";
20
+
21
+ /**
22
+ * The rows of a read-only list (an array or key-value field shown, not
23
+ * edited) as a table: each value drawn as a grid cell draws it (amounts
24
+ * right-aligned in the form's tabular figures, a select's label, a
25
+ * relation's name), text wrapping between words. In a narrow container
26
+ * (a phone's drawer) a list of more than two fields reads each row as a
27
+ * block of `label value` lines instead, so no amount is ever cut; a wider
28
+ * table still too narrow for its words scrolls sideways.
29
+ */
30
+ interface RepeaterTableProps {
31
+ columns: RepeaterTableColumn[];
32
+ rows: Record<string, unknown>[];
33
+ }
34
+
35
+ const props = defineProps<RepeaterTableProps>();
36
+ const { renderValue } = useColumnValueRenderer();
37
+ const { getDataType } = useDataTypes();
38
+ const { readRelationRows } = useRelationRows();
39
+ // Each column of each list reads its own ids: a key shared by two columns or
40
+ // two lists naming the same route would hand one of them the other's rows.
41
+ const instanceId = useId();
42
+
43
+ /**
44
+ * The related rows of a relation column, by the value its cells hold: read
45
+ * once for the ids the rows hold, narrowed to them with `?ids=`.
46
+ */
47
+ function useColumnRelationRows(columnId: string, source: RelationLabelSource) {
48
+ const ids = computed(() =>
49
+ unlabeledRelationIds(
50
+ props.rows.flatMap((row) => [row[columnId]].flat()),
51
+ NO_LABELS,
52
+ ),
53
+ );
54
+ const { data } = useDmsAsyncData(
55
+ `repeater-table-relation-${instanceId}-${columnId}`,
56
+ () => readRelationRows(source, ids.value),
57
+ // Names for the cells, not the page's data.
58
+ { lazy: true, refreshOnPageRefresh: false, watch: [ids] },
59
+ );
60
+ return computed(
61
+ () =>
62
+ new Map(
63
+ (data.value ?? []).map((row) => [relationRowValue(source, row), row]),
64
+ ),
65
+ );
66
+ }
67
+
68
+ const NO_LABELS: ReadonlySet<string> = new Set();
69
+
70
+ // The columns are the field's own and do not change while it is shown.
71
+ const relationRows = new Map(
72
+ props.columns.flatMap((column) => {
73
+ const source = relationLabelSource(column, getDataType(column.type ?? ""));
74
+ return source
75
+ ? [
76
+ [
77
+ column.id,
78
+ { source, rows: useColumnRelationRows(column.id, source) },
79
+ ],
80
+ ]
81
+ : [];
82
+ }),
83
+ );
84
+
85
+ // Two fields (a key and its value) fit any width as a table; more stack
86
+ // below the container's `@md` width.
87
+ const TABLE_FITS_COLUMNS = 2;
88
+ const stacksWhenNarrow = computed(
89
+ () => props.columns.length > TABLE_FITS_COLUMNS,
90
+ );
91
+
92
+ const renderables = computed(() => props.columns.map(renderableColumn));
93
+
94
+ function isNumeric(column: RepeaterTableColumn): boolean {
95
+ return NUMERIC_TYPE_IDS.has(column.type ?? "");
96
+ }
97
+
98
+ function cellValue(column: RepeaterTableColumn, row: Record<string, unknown>) {
99
+ const value = row[column.id];
100
+ const relation = relationRows.get(column.id);
101
+ if (!relation) return value;
102
+ return joinRelationValue(
103
+ value,
104
+ relation.rows.value,
105
+ relation.source.multiple,
106
+ );
107
+ }
108
+
109
+ // A cell renders a VNode tree; a functional wrapper keeps it in the
110
+ // template's flow.
111
+ interface CellProps {
112
+ index: number;
113
+ rowIndex: number;
114
+ }
115
+ const Cell = ({ index, rowIndex }: CellProps): VNodeChild => {
116
+ const column = props.columns[index]!;
117
+ const row = props.rows[rowIndex]!;
118
+ return renderValue(
119
+ renderables.value[index]!,
120
+ cellValue(column, row),
121
+ row,
122
+ "detail",
123
+ );
124
+ };
125
+ // Declared, so `row-index` reaches it camelized, as a component's props do.
126
+ Cell.props = ["index", "rowIndex"];
127
+ </script>
128
+
129
+ <template>
130
+ <div class="@container min-w-0">
131
+ <div
132
+ class="min-w-0 overflow-x-auto"
133
+ :class="stacksWhenNarrow && 'hidden @md:block'"
134
+ >
135
+ <table class="w-full text-sm">
136
+ <thead>
137
+ <tr :class="[EYEBROW_CLASS, 'text-dimmed']">
138
+ <th
139
+ v-for="column in props.columns"
140
+ :key="column.id"
141
+ scope="col"
142
+ class="pe-4 pb-1.5 text-start align-bottom font-semibold whitespace-nowrap last:pe-0"
143
+ :class="isNumeric(column) && 'text-end'"
144
+ >
145
+ {{ column.label }}
146
+ </th>
147
+ </tr>
148
+ </thead>
149
+ <tbody>
150
+ <tr
151
+ v-for="(row, rowIndex) in props.rows"
152
+ :key="rowIndex"
153
+ class="border-default border-t"
154
+ >
155
+ <!-- A text cell wraps between words: `break-word`, unlike
156
+ `anywhere`, keeps a column at least as wide as its longest
157
+ word, so a narrow table scrolls instead of splitting one. -->
158
+ <td
159
+ v-for="(column, index) in props.columns"
160
+ :key="column.id"
161
+ class="text-default py-2 pe-4 align-top last:pe-0"
162
+ :class="
163
+ isNumeric(column)
164
+ ? 'text-end whitespace-nowrap'
165
+ : 'min-w-24 [overflow-wrap:break-word]'
166
+ "
167
+ >
168
+ <Cell :index :row-index="rowIndex" />
169
+ </td>
170
+ </tr>
171
+ </tbody>
172
+ </table>
173
+ </div>
174
+ <!-- The narrow reading: a row per block, a field per line, its label
175
+ beside its value. A value has the line's remaining width, an amount
176
+ never wraps. -->
177
+ <ul v-if="stacksWhenNarrow" class="text-sm @md:hidden">
178
+ <li
179
+ v-for="(row, rowIndex) in props.rows"
180
+ :key="rowIndex"
181
+ class="border-default border-t py-2 first:border-t-0 first:pt-0"
182
+ >
183
+ <dl class="grid grid-cols-[auto_minmax(0,1fr)] gap-x-4 gap-y-1">
184
+ <template v-for="(column, index) in props.columns" :key="column.id">
185
+ <dt class="text-dimmed pt-0.5 font-semibold" :class="EYEBROW_CLASS">
186
+ {{ column.label }}
187
+ </dt>
188
+ <dd
189
+ class="text-default min-w-0"
190
+ :class="
191
+ isNumeric(column)
192
+ ? 'text-end whitespace-nowrap'
193
+ : '[overflow-wrap:break-word]'
194
+ "
195
+ >
196
+ <Cell :index :row-index="rowIndex" />
197
+ </dd>
198
+ </template>
199
+ </dl>
200
+ </li>
201
+ </ul>
202
+ </div>
203
+ </template>
@@ -89,14 +89,16 @@ function handleUpdateModelValue(value: unknown) {
89
89
  "
90
90
  #default
91
91
  >
92
- <span class="inline-flex items-center gap-1.5">
92
+ <!-- Top-aligned: on the text's baseline, the icon would add the
93
+ line's descent and make the trigger taller than a text field. -->
94
+ <span class="inline-flex max-w-full items-center gap-1.5 align-top">
93
95
  <UIcon
94
96
  v-if="selectedItem.icon"
95
97
  :name="selectedItem.icon"
96
98
  class="size-4 shrink-0"
97
99
  :style="colorVar(selectedItem.iconColor)"
98
100
  />
99
- <span :style="colorVar(selectedItem.textColor)">
101
+ <span class="truncate" :style="colorVar(selectedItem.textColor)">
100
102
  {{ selectedItem.label }}
101
103
  </span>
102
104
  </span>
@@ -17,7 +17,13 @@ const DEFAULT_GAP = "1rem";
17
17
 
18
18
  const props = defineProps<GridRowProps>();
19
19
 
20
- const gridContext = inject<GridContext>(GRID_CONTEXT);
20
+ const gridContext = inject<GridContext | null>(GRID_CONTEXT, null);
21
+
22
+ // The grid's template is for its own rows only. A row nested in one of this
23
+ // row's cells (inside a Section, a stack...) lays out its own children: were it
24
+ // to read the outer grid, it would take that grid's column count and leave
25
+ // empty tracks beside its cells. A Grid inside a cell provides its own context.
26
+ provide<GridContext | null>(GRID_CONTEXT, null);
21
27
 
22
28
  /** The identity this row counts under, so its width dies with it. */
23
29
  const rowId = Symbol("grid-row");
@@ -28,7 +34,10 @@ watchEffect(() => gridContext?.setRowColumnCount(rowId, columnCount.value));
28
34
  onUnmounted(() => gridContext?.dropRow(rowId));
29
35
 
30
36
  const gap = computed(() => gridContext?.gap.value ?? DEFAULT_GAP);
31
- const maxColumns = computed(() => gridContext?.maxColumns.value || 1);
37
+ // Outside a Grid, the row spreads its own children over the width.
38
+ const maxColumns = computed(() =>
39
+ Math.max(1, gridContext ? gridContext.maxColumns.value : columnCount.value),
40
+ );
32
41
  const minColumnWidth = computed(
33
42
  () => gridContext?.minColumnWidth.value ?? GRID_DEFAULT_MIN_COLUMN_WIDTH,
34
43
  );
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { readableNumberFormat } from "#dms-core/app/utils/formatter";
2
3
  import { pageLink } from "../../build/composables/page/pageUrlValues";
3
4
  import { computed } from "vue";
4
5
  import { regionalDateTimeFormat } from "#dms-core/app/utils/regional";
@@ -48,7 +49,7 @@ interface KeyValueListProps {
48
49
  items?: KeyValueItem[];
49
50
  /** Tighter rows (32px instead of 38px) for side panels. */
50
51
  dense?: boolean;
51
- /** Columns on wide screens (1–3); one on phones. */
52
+ /** Columns once the list is wide enough (1–3); one when narrow. */
52
53
  columns?: number;
53
54
  /** Loading: values turn to skeletons, or `skeletonCount` rows are drawn. */
54
55
  loading?: boolean;
@@ -76,11 +77,13 @@ const EMPTY_VALUE = "—";
76
77
  const MAX_COLUMNS = 3;
77
78
 
78
79
  // v2 .c-dl: label left, value right, rows split by a muted hairline. Each
79
- // column's first row drops its line, at every breakpoint.
80
+ // column's first row drops its line, at every breakpoint. The columns follow
81
+ // the list's own width (a drawer, a card), not the window's: each keeps its
82
+ // 120px label and room for a value.
80
83
  const COLUMN_CLASSES: Record<number, string> = {
81
84
  1: "[&>div:first-child]:border-t-0",
82
- 2: "gap-x-8 sm:grid-cols-2 [&>div:first-child]:border-t-0 sm:[&>div:nth-child(2)]:border-t-0",
83
- 3: "gap-x-8 sm:grid-cols-2 lg:grid-cols-3 [&>div:first-child]:border-t-0 sm:[&>div:nth-child(2)]:border-t-0 lg:[&>div:nth-child(3)]:border-t-0",
85
+ 2: "gap-x-8 @min-[560px]:grid-cols-2 [&>div:first-child]:border-t-0 @min-[560px]:[&>div:nth-child(2)]:border-t-0",
86
+ 3: "gap-x-8 @min-[560px]:grid-cols-2 @min-[840px]:grid-cols-3 [&>div:first-child]:border-t-0 @min-[560px]:[&>div:nth-child(2)]:border-t-0 @min-[840px]:[&>div:nth-child(3)]:border-t-0",
84
87
  };
85
88
 
86
89
  const { locale, t } = useI18n();
@@ -105,13 +108,13 @@ type ValueFormatter = (item: KeyValueItem, value: string | number) => string;
105
108
 
106
109
  function formatNumber(_item: KeyValueItem, value: string | number): string {
107
110
  if (typeof value !== "number") return value;
108
- return new Intl.NumberFormat(locale.value).format(value);
111
+ return readableNumberFormat(locale.value).format(value);
109
112
  }
110
113
 
111
114
  function formatMoney(item: KeyValueItem, value: string | number): string {
112
115
  if (typeof value !== "number") return value;
113
116
  try {
114
- return new Intl.NumberFormat(locale.value, {
117
+ return readableNumberFormat(locale.value, {
115
118
  style: "currency",
116
119
  currency: item.currency ?? props.currency,
117
120
  }).format(value);
@@ -178,70 +181,76 @@ function keyOf(item: KeyValueItem, index: number): string {
178
181
  <template>
179
182
  <!-- grid-cols-1: minmax(0, 1fr) tracks, so a long value truncates instead
180
183
  of widening the list past a narrow screen. -->
181
- <dl
182
- class="grid grid-cols-1"
183
- :class="[columnClass, props.dense ? 'text-[12.5px]' : 'text-[13px]']"
184
- :aria-busy="props.loading || undefined"
185
- >
186
- <template v-if="showPlaceholders">
187
- <div v-for="index in props.skeletonCount" :key="index" :class="rowClass">
188
- <dt class="min-w-[120px] flex-none">
189
- <USkeleton :aria-label="t('dms.a11y.loading')" class="h-3 w-20" />
190
- </dt>
191
- <dd class="ms-auto">
192
- <USkeleton :aria-label="t('dms.a11y.loading')" class="h-3 w-24" />
193
- </dd>
194
- </div>
195
- </template>
196
- <template v-else>
197
- <div
198
- v-for="(item, index) in props.items"
199
- :key="keyOf(item, index)"
200
- :class="rowClass"
201
- >
202
- <dt class="text-muted min-w-[120px] flex-none">{{ item.label }}</dt>
203
- <dd
204
- class="ms-auto flex min-w-0 flex-wrap items-center justify-end gap-x-1.5 gap-y-0.5 text-end"
184
+ <div class="@container">
185
+ <dl
186
+ class="grid grid-cols-1"
187
+ :class="[columnClass, props.dense ? 'text-[12.5px]' : 'text-[13px]']"
188
+ :aria-busy="props.loading || undefined"
189
+ >
190
+ <template v-if="showPlaceholders">
191
+ <div
192
+ v-for="index in props.skeletonCount"
193
+ :key="index"
194
+ :class="rowClass"
205
195
  >
206
- <USkeleton
207
- :aria-label="t('dms.a11y.loading')"
208
- v-if="props.loading || item.loading"
209
- class="h-3 w-24"
210
- />
211
- <slot
212
- v-else-if="slots.value"
213
- name="value"
214
- :item="item"
215
- :formatted="format(item)"
216
- />
217
- <template v-else>
218
- <component
219
- :is="item.to ? DmsAutoLink : 'span'"
220
- :to="pageLink(item.to)"
221
- class="min-w-0"
222
- :class="item.to && item.type !== 'status' && 'hover:underline'"
223
- >
224
- <DmsStatusPill
225
- v-if="item.type === 'status'"
226
- :tone="item.tone ?? 'neutral'"
227
- :label="format(item)"
228
- />
229
- <span v-else class="block" :class="valueClass(item)">
230
- {{ format(item) }}
196
+ <dt class="min-w-[120px] flex-none">
197
+ <USkeleton :aria-label="t('dms.a11y.loading')" class="h-3 w-20" />
198
+ </dt>
199
+ <dd class="ms-auto">
200
+ <USkeleton :aria-label="t('dms.a11y.loading')" class="h-3 w-24" />
201
+ </dd>
202
+ </div>
203
+ </template>
204
+ <template v-else>
205
+ <div
206
+ v-for="(item, index) in props.items"
207
+ :key="keyOf(item, index)"
208
+ :class="rowClass"
209
+ >
210
+ <dt class="text-muted min-w-[120px] flex-none">{{ item.label }}</dt>
211
+ <dd
212
+ class="ms-auto flex min-w-0 flex-wrap items-center justify-end gap-x-1.5 gap-y-0.5 text-end"
213
+ >
214
+ <USkeleton
215
+ :aria-label="t('dms.a11y.loading')"
216
+ v-if="props.loading || item.loading"
217
+ class="h-3 w-24"
218
+ />
219
+ <slot
220
+ v-else-if="slots.value"
221
+ name="value"
222
+ :item="item"
223
+ :formatted="format(item)"
224
+ />
225
+ <template v-else>
226
+ <component
227
+ :is="item.to ? DmsAutoLink : 'span'"
228
+ :to="pageLink(item.to)"
229
+ class="min-w-0"
230
+ :class="item.to && item.type !== 'status' && 'hover:underline'"
231
+ >
232
+ <DmsStatusPill
233
+ v-if="item.type === 'status'"
234
+ :tone="item.tone ?? 'neutral'"
235
+ :label="format(item)"
236
+ />
237
+ <span v-else class="block" :class="valueClass(item)">
238
+ {{ format(item) }}
239
+ </span>
240
+ </component>
241
+ <span v-if="item.detail" class="text-dimmed truncate text-xs">
242
+ {{ item.detail }}
231
243
  </span>
232
- </component>
233
- <span v-if="item.detail" class="text-dimmed truncate text-xs">
234
- {{ item.detail }}
235
- </span>
236
- </template>
237
- <!-- -my-1: the button is taller than a dense row's text line. -->
238
- <DmsCopyButton
239
- v-if="!props.loading && !item.loading && copyTextOf(item)"
240
- :value="copyTextOf(item)!"
241
- class="-my-1"
242
- />
243
- </dd>
244
- </div>
245
- </template>
246
- </dl>
244
+ </template>
245
+ <!-- -my-1: the button is taller than a dense row's text line. -->
246
+ <DmsCopyButton
247
+ v-if="!props.loading && !item.loading && copyTextOf(item)"
248
+ :value="copyTextOf(item)!"
249
+ class="-my-1"
250
+ />
251
+ </dd>
252
+ </div>
253
+ </template>
254
+ </dl>
255
+ </div>
247
256
  </template>