@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
@@ -5,7 +5,12 @@ import type {
5
5
  } from "../../../composables/form/types/field";
6
6
  import { isFieldGroup } from "../../../composables/form/types/field";
7
7
  import { FORM_ENTRY_CONTEXT_KEY } from "../../composables/form/formEntryContext";
8
- import { GROUP_FIELDS_CLASSES } from "../../composables/form/formLayout";
8
+ import {
9
+ GROUP_FIELDS_CLASSES,
10
+ entryFields,
11
+ entryRowLayout,
12
+ GROUP_MEMBER_CLASSES,
13
+ } from "../../composables/form/formLayout";
9
14
  import { combineSaveStates } from "#dms-layout/app/composables/layout/useInstantSaveHeader";
10
15
  import type { SaveStatusState } from "../../../components/save-bar/SaveStatus.vue";
11
16
  import DmsFormFieldControl from "./FormFieldControl.vue";
@@ -19,9 +24,15 @@ interface FormEntriesProps {
19
24
 
20
25
  const props = defineProps<FormEntriesProps>();
21
26
  const context = inject(FORM_ENTRY_CONTEXT_KEY)!;
27
+ const { processI18n } = useTranslation();
22
28
 
23
29
  const classes = computed(() => context.layoutClasses(props.inSection));
24
30
 
31
+ /** The row's layout: a wide field takes the full width under its label. */
32
+ function rowOf(entry: FormFieldOrGroup) {
33
+ return entryRowLayout(classes.value.row, entryFields(entry));
34
+ }
35
+
25
36
  function groupFieldsClass(group: FieldGroup): string {
26
37
  return GROUP_FIELDS_CLASSES[
27
38
  group.orientation === "vertical" ? "vertical" : "horizontal"
@@ -32,6 +43,12 @@ function isGroupVisible(group: FieldGroup): boolean {
32
43
  return group.fields.some((field) => !context.isFieldHidden(field));
33
44
  }
34
45
 
46
+ // A description tells how to fill the field ("Left empty, the default rate
47
+ // applies"): a form where nothing can be changed shows only the values.
48
+ function descriptionOf(entry: FormFieldOrGroup): string | undefined {
49
+ return context.isReadOnly.value ? undefined : entry.description;
50
+ }
51
+
35
52
  function isGroupRequired(group: FieldGroup): boolean {
36
53
  return group.fields.some(context.isFieldRequired);
37
54
  }
@@ -51,9 +68,9 @@ function saveStateOf(entry: FormFieldOrGroup): SaveStatusState {
51
68
  <template v-if="isFieldGroup(entry)">
52
69
  <DmsFieldRow
53
70
  v-if="isGroupVisible(entry)"
54
- v-bind="classes.row"
71
+ v-bind="rowOf(entry)"
55
72
  :label="entry.label || ''"
56
- :description="entry.description"
73
+ :description="descriptionOf(entry)"
57
74
  :required="isGroupRequired(entry)"
58
75
  >
59
76
  <template v-if="saveStateOf(entry) !== 'idle'" #details>
@@ -63,14 +80,32 @@ function saveStateOf(entry: FormFieldOrGroup): SaveStatusState {
63
80
  />
64
81
  </template>
65
82
  <div :class="groupFieldsClass(entry)">
83
+ <!-- The group's label heads the cluster; each member keeps its
84
+ own above its control, so a group can title a run of
85
+ labelled fields. -->
66
86
  <template v-for="field in entry.fields" :key="field.id">
67
- <DmsFormFieldControl
87
+ <div
68
88
  v-if="
69
89
  field.component.componentName && !context.isFieldHidden(field)
70
90
  "
71
- :field
72
- class="min-w-0 flex-1"
73
- />
91
+ :class="GROUP_MEMBER_CLASSES.root"
92
+ >
93
+ <label
94
+ v-if="field.label"
95
+ :for="field.id"
96
+ :class="GROUP_MEMBER_CLASSES.label"
97
+ >
98
+ {{ processI18n(field.label) }}
99
+ <span
100
+ v-if="context.isFieldRequired(field)"
101
+ class="text-error"
102
+ aria-hidden="true"
103
+ >
104
+ *
105
+ </span>
106
+ </label>
107
+ <DmsFormFieldControl :field class="min-w-0" />
108
+ </div>
74
109
  </template>
75
110
  </div>
76
111
  </DmsFieldRow>
@@ -78,10 +113,10 @@ function saveStateOf(entry: FormFieldOrGroup): SaveStatusState {
78
113
 
79
114
  <DmsFieldRow
80
115
  v-else-if="!context.isFieldHidden(entry)"
81
- v-bind="classes.row"
116
+ v-bind="rowOf(entry)"
82
117
  :label="entry.label || ''"
83
118
  :label-for="entry.id"
84
- :description="entry.description"
119
+ :description="descriptionOf(entry)"
85
120
  :required="context.isFieldRequired(entry)"
86
121
  >
87
122
  <template v-if="saveStateOf(entry) !== 'idle'" #details>
@@ -15,14 +15,18 @@ interface FormErrorSummaryProps {
15
15
  const props = defineProps<FormErrorSummaryProps>();
16
16
  const emit = defineEmits<{ select: [fieldId: string] }>();
17
17
  const { t } = useI18n();
18
+
19
+ // Every field fixed: the summary says so in place, on as many lines, rather
20
+ // than leaving and moving the form up under the pointer.
21
+ const isResolved = computed(() => props.items.length === 0);
18
22
  </script>
19
23
 
20
24
  <template>
21
25
  <UAlert
22
- color="error"
26
+ :color="isResolved ? 'success' : 'error'"
23
27
  variant="subtle"
24
- icon="i-ph-warning-circle"
25
- role="alert"
28
+ :icon="isResolved ? 'i-ph-check-circle' : 'i-ph-warning-circle'"
29
+ :role="isResolved ? 'status' : 'alert'"
26
30
  :title="
27
31
  t(
28
32
  'dms.form.error_summary.title',
@@ -32,7 +36,10 @@ const { t } = useI18n();
32
36
  "
33
37
  >
34
38
  <template #description>
35
- <ul class="mt-1 flex flex-wrap gap-x-4 gap-y-1">
39
+ <p v-if="isResolved" class="mt-1">
40
+ {{ t("dms.form.error_summary.resolved") }}
41
+ </p>
42
+ <ul v-else class="mt-1 flex flex-wrap gap-x-4 gap-y-1">
36
43
  <li v-for="item in props.items" :key="item.id">
37
44
  <a
38
45
  :href="`#${item.id}`"
@@ -11,6 +11,7 @@ import {
11
11
  import { formErrorsInjectionKey } from "@nuxt/ui/composables/useFormField";
12
12
  import DmsFormErrorText from "./FormErrorText.vue";
13
13
  import DmsFormFieldNotes from "./FormFieldNotes.vue";
14
+ import { readonlyFieldValue } from "../../composables/form/readonlyValue";
14
15
 
15
16
  interface FormFieldControlProps {
16
17
  field: FormField;
@@ -22,13 +23,30 @@ const { state, initialValues, loading } = context;
22
23
  const { processI18n } = useTranslation();
23
24
 
24
25
  const isDisabled = computed(() => context.isFieldDisabled(props.field));
26
+ // A list of rows reads best as its rows, its own control disabled: the
27
+ // display of a cell only counts them. An empty one still reads "empty".
28
+ const READS_AS_CONTROL = new Set(["array", "key_value"]);
29
+ const hasRows = (value: unknown): boolean =>
30
+ !!value && typeof value === "object" && Object.keys(value).length > 0;
31
+ const showsDisplay = computed(
32
+ () =>
33
+ isDisabled.value &&
34
+ !!props.field.type &&
35
+ !(
36
+ READS_AS_CONTROL.has(props.field.type) &&
37
+ hasRows(state.value[props.field.id])
38
+ ),
39
+ );
25
40
  const component = computed(() => {
26
41
  const name = props.field.component.componentName;
27
42
  return name ? resolveDmsComponent(name) || name : null;
28
43
  });
29
- // The hint under the control, which describes it (`aria-describedby`).
44
+ // The hint under the control, which describes it (`aria-describedby`): a
45
+ // read-only field has no control to fill, and no hint telling how.
30
46
  const hint = computed(() =>
31
- props.field.hint ? processI18n(props.field.hint) : undefined,
47
+ props.field.hint && !isDisabled.value
48
+ ? processI18n(props.field.hint)
49
+ : undefined,
32
50
  );
33
51
  // UFormField shows its error slot whenever one is given, in the hint's
34
52
  // place: it gets one only while the field holds an error.
@@ -37,6 +55,17 @@ const hasError = computed(() => {
37
55
  const pattern = fieldErrorPattern(props.field.id);
38
56
  return !!formErrors?.value.some((error) => pattern.test(error.name ?? ""));
39
57
  });
58
+ // A read-only relation shows the row the server joined (its label, its
59
+ // avatar), as a table cell does, rather than the id its state holds.
60
+ const { getDataType } = useDataTypes();
61
+ const readonlyValue = computed(() =>
62
+ readonlyFieldValue(
63
+ state.value[props.field.id],
64
+ initialValues.value?.[props.field.id],
65
+ props.field.type ? getDataType(props.field.type) : undefined,
66
+ props.field.component.options,
67
+ ),
68
+ );
40
69
  // What a read-only field shows beside its value: a pill, a link.
41
70
  const readonly = computed<FormFieldReadonly>(() =>
42
71
  typeof props.field.readonly === "object" ? props.field.readonly : {},
@@ -44,7 +73,9 @@ const readonly = computed<FormFieldReadonly>(() =>
44
73
  </script>
45
74
 
46
75
  <template>
47
- <div class="grid min-w-0 gap-1.5">
76
+ <!-- One explicit column (minmax(0, 1fr)): an implicit one grows to its
77
+ widest content, a long date or select label, past a narrow card. -->
78
+ <div class="grid min-w-0 grid-cols-1 gap-1.5">
48
79
  <DmsLocalizedField
49
80
  v-if="field.localized"
50
81
  v-model="state[field.id] as Record<string, string> | undefined"
@@ -63,13 +94,15 @@ const readonly = computed<FormFieldReadonly>(() =>
63
94
  :help="hint"
64
95
  >
65
96
  <div
66
- v-if="isDisabled && !!field.type"
97
+ v-if="showsDisplay"
67
98
  class="flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1"
68
99
  >
69
100
  <DmsDisplay
70
- :model-value="state[field.id]"
101
+ :model-value="readonlyValue"
71
102
  :type="field.type"
72
103
  :loading
104
+ :label="field.label ? processI18n(field.label) : undefined"
105
+ :row="state"
73
106
  class="min-w-0"
74
107
  v-bind="field.component.options || {}"
75
108
  />
@@ -24,10 +24,12 @@ const defaultValue = computed(() => props.field.defaultValue);
24
24
  const showsChange = computed(
25
25
  () => context.showsChanges && context.isFieldChanged(props.field.id),
26
26
  );
27
- // "Module default · Use default" on a value that is not the default, unless
28
- // the change line already says what it was.
27
+ // "Module default · Use default" on a value that is not the default, for a
28
+ // field that offers it (a setting, not a record's initial value), unless the
29
+ // change line already says what it was.
29
30
  const showsDefault = computed(
30
31
  () =>
32
+ !!props.field.offersDefault &&
31
33
  defaultValue.value !== undefined &&
32
34
  !context.isFieldDisabled(props.field) &&
33
35
  !sameFormValue(value.value, defaultValue.value) &&
@@ -51,7 +53,26 @@ function wordValue(raw: unknown): VNodeChild | undefined {
51
53
  return undefined;
52
54
  }
53
55
 
54
- const wordedBaseline = computed(() => wordValue(baseline.value));
56
+ /**
57
+ * The baseline as the form loaded it. A type keeping less in the form than
58
+ * it reads (a relation keeps the ids of the rows the server joined) words
59
+ * the loaded value, its label, while that is still the baseline.
60
+ */
61
+ const loadedBaseline = computed(() => {
62
+ const loaded = context.initialValues.value[props.field.id];
63
+ const dataType = props.field.type ? getDataType(props.field.type) : undefined;
64
+ if (!dataType?.beforeStateMapper || isEmptyFormValue(loaded)) {
65
+ return baseline.value;
66
+ }
67
+ const options = props.field.component.options;
68
+ const mapped = dataType.beforeStateMapper(loaded, options);
69
+ const isSame = dataType.isSameValue
70
+ ? dataType.isSameValue(mapped, baseline.value, options)
71
+ : sameFormValue(mapped, baseline.value);
72
+ return isSame ? loaded : baseline.value;
73
+ });
74
+
75
+ const wordedBaseline = computed(() => wordValue(loadedBaseline.value));
55
76
 
56
77
  function useDefault(): void {
57
78
  context.state.value[props.field.id] = JSON.parse(
@@ -61,7 +82,10 @@ function useDefault(): void {
61
82
  </script>
62
83
 
63
84
  <template>
64
- <div v-if="showsChange || showsDefault" class="grid gap-0.5 text-xs">
85
+ <div
86
+ v-if="showsChange || showsDefault"
87
+ class="grid min-w-0 gap-0.5 text-xs break-words"
88
+ >
65
89
  <p
66
90
  v-if="showsChange"
67
91
  class="text-muted flex flex-wrap items-center gap-1.5"
@@ -88,7 +112,7 @@ function useDefault(): void {
88
112
  color="primary"
89
113
  variant="link"
90
114
  size="xs"
91
- class="p-0"
115
+ class="p-0 text-start whitespace-normal"
92
116
  @click="useDefault"
93
117
  />
94
118
  </p>
@@ -63,8 +63,10 @@ const ITEM_CLASSES: Record<FormSectionNavVariant, string> = {
63
63
  "border-default text-muted hover:text-default flex h-7 shrink-0 items-center gap-1.5 rounded-full border px-3 text-[12.5px]",
64
64
  };
65
65
 
66
+ // The current section carries a mark of its own, so the entry under the
67
+ // pointer never reads as a second current one.
66
68
  const ACTIVE_CLASSES: Record<FormSectionNavVariant, string> = {
67
- list: "bg-elevated text-highlighted font-[550]",
69
+ list: "bg-elevated text-highlighted font-[550] shadow-[inset_2px_0_0_var(--ui-primary)]",
68
70
  chips: "border-accented bg-elevated text-highlighted",
69
71
  };
70
72
 
@@ -1,11 +1,13 @@
1
1
  <script setup lang="ts">
2
2
  import type { FormSectionNav } from "../../../composables/form/types/field";
3
- import type {
4
- FormSectionState,
5
- FormSectionsLayout,
3
+ import {
4
+ currentSectionId,
5
+ type FormSectionState,
6
+ type FormSectionsLayout,
6
7
  } from "../../composables/form/formSections";
7
8
  import DmsFormEntries from "./FormEntries.vue";
8
9
  import DmsFormSectionNav from "./FormSectionNav.vue";
10
+ import { scrollParent, scrollWithin } from "../../utils/scrollWithin";
9
11
 
10
12
  interface FormSectionsProps {
11
13
  layout: FormSectionsLayout;
@@ -17,9 +19,15 @@ interface FormSectionsProps {
17
19
 
18
20
  const props = defineProps<FormSectionsProps>();
19
21
 
20
- // A section is current once its top passes this band near the top of the
21
- // viewport: the one being read, not the one merely peeking in at the bottom.
22
- const SPY_ROOT_MARGIN = "0px 0px -65% 0px";
22
+ // A section is current once its top passes this line, a third of the way
23
+ // down the visible area: the one being read, not the one merely peeking in
24
+ // at the bottom.
25
+ const READING_LINE_RATIO = 0.35;
26
+ // A scroll this close to the end is at the end (subpixel rounding).
27
+ const END_TOLERANCE_PX = 2;
28
+ // A picked section stays current until the scroll it started has settled:
29
+ // on its way, the smooth scroll passes the sections in between.
30
+ const SCROLL_SETTLE_MS = 150;
23
31
 
24
32
  const activeId = ref(props.layout.sections[0]?.id);
25
33
  const sectionElements = useTemplateRef<HTMLElement[]>("sectionElements");
@@ -28,45 +36,116 @@ function anchorId(sectionId: string): string {
28
36
  return `${props.formId}-section-${sectionId}`;
29
37
  }
30
38
 
39
+ let pickedTimer: ReturnType<typeof setTimeout> | undefined;
40
+ let isPicked = false;
41
+
42
+ function holdPicked(): void {
43
+ clearTimeout(pickedTimer);
44
+ pickedTimer = setTimeout(() => {
45
+ isPicked = false;
46
+ }, SCROLL_SETTLE_MS);
47
+ }
48
+
31
49
  function scrollToSection(sectionId: string): void {
32
50
  activeId.value = sectionId;
33
- document
34
- .getElementById(anchorId(sectionId))
35
- ?.scrollIntoView({ behavior: "smooth", block: "start" });
51
+ isPicked = true;
52
+ holdPicked();
53
+ const section = document.getElementById(anchorId(sectionId));
54
+ if (section) scrollWithin(section, { behavior: "smooth", block: "start" });
36
55
  history.replaceState(history.state, "", `#${anchorId(sectionId)}`);
37
56
  }
38
57
 
39
- let observer: IntersectionObserver | undefined;
40
- const visible = new Set<string>();
58
+ interface SpyViewport {
59
+ top: number;
60
+ height: number;
61
+ isAtEnd: boolean;
62
+ }
41
63
 
42
- function onIntersect(entries: IntersectionObserverEntry[]): void {
43
- for (const entry of entries) {
44
- const id = (entry.target as HTMLElement).dataset.section;
45
- if (!id) continue;
46
- if (entry.isIntersecting) visible.add(id);
47
- else visible.delete(id);
64
+ // Scrolled at all, and to the end: a form that fits never scrolls, and its
65
+ // first section stays current.
66
+ function isScrolledToEnd(
67
+ scrollTop: number,
68
+ height: number,
69
+ scrollHeight: number,
70
+ ): boolean {
71
+ return scrollTop > 0 && scrollTop + height >= scrollHeight - END_TOLERANCE_PX;
72
+ }
73
+
74
+ function spyViewport(scroller: HTMLElement | null): SpyViewport {
75
+ if (scroller) {
76
+ return {
77
+ top: scroller.getBoundingClientRect().top,
78
+ height: scroller.clientHeight,
79
+ isAtEnd: isScrolledToEnd(
80
+ scroller.scrollTop,
81
+ scroller.clientHeight,
82
+ scroller.scrollHeight,
83
+ ),
84
+ };
48
85
  }
49
- const first = props.layout.sections.find((section) =>
50
- visible.has(section.id),
51
- );
52
- if (first) activeId.value = first.id;
86
+ const root = document.documentElement;
87
+ return {
88
+ top: 0,
89
+ height: window.innerHeight,
90
+ isAtEnd: isScrolledToEnd(
91
+ window.scrollY,
92
+ window.innerHeight,
93
+ root.scrollHeight,
94
+ ),
95
+ };
53
96
  }
54
97
 
55
- onMounted(() => {
56
- if (props.nav === "none" || typeof IntersectionObserver === "undefined") {
98
+ function updateActiveSection(): void {
99
+ const elements = sectionElements.value ?? [];
100
+ const first = elements[0];
101
+ if (!first) return;
102
+ const viewport = spyViewport(scrollParent(first));
103
+ const id = currentSectionId({
104
+ sections: elements.map((element) => ({
105
+ id: element.dataset.section ?? "",
106
+ top: element.getBoundingClientRect().top - viewport.top,
107
+ })),
108
+ readingLine: viewport.height * READING_LINE_RATIO,
109
+ isAtEnd: viewport.isAtEnd,
110
+ });
111
+ if (id) activeId.value = id;
112
+ }
113
+
114
+ let frame: number | undefined;
115
+
116
+ // Captured on the document: whichever container scrolls the form (the page
117
+ // body, a drawer, the window) reports here, even one that only became
118
+ // scrollable once the form loaded.
119
+ function onScroll(): void {
120
+ if (isPicked) {
121
+ holdPicked();
57
122
  return;
58
123
  }
59
- observer = new IntersectionObserver(onIntersect, {
60
- rootMargin: SPY_ROOT_MARGIN,
124
+ if (frame !== undefined) return;
125
+ frame = requestAnimationFrame(() => {
126
+ frame = undefined;
127
+ updateActiveSection();
128
+ });
129
+ }
130
+
131
+ onMounted(() => {
132
+ if (props.nav === "none") return;
133
+ document.addEventListener("scroll", onScroll, {
134
+ capture: true,
135
+ passive: true,
61
136
  });
62
- for (const element of sectionElements.value ?? []) observer.observe(element);
63
137
  const linked = props.layout.sections.find(
64
138
  (section) => location.hash === `#${anchorId(section.id)}`,
65
139
  );
66
140
  if (linked) scrollToSection(linked.id);
141
+ else updateActiveSection();
67
142
  });
68
143
 
69
- onBeforeUnmount(() => observer?.disconnect());
144
+ onBeforeUnmount(() => {
145
+ document.removeEventListener("scroll", onScroll, { capture: true });
146
+ clearTimeout(pickedTimer);
147
+ if (frame !== undefined) cancelAnimationFrame(frame);
148
+ });
70
149
  </script>
71
150
 
72
151
  <template>
@@ -106,6 +106,10 @@ const theme = tv({
106
106
 
107
107
  interface TableActionsProps {
108
108
  canAddRow?: boolean;
109
+ /** The add action's configured label (`$`: i18n key); "New entry" without. */
110
+ addLabel?: string;
111
+ /** The add action's configured icon; the theme's plus without. */
112
+ addIcon?: string;
109
113
  /** Shows the archive mode toggle, bound to the `showArchived` model. */
110
114
  archiveToggle?: boolean;
111
115
  customButtons?: CustomButton[];
@@ -162,6 +166,10 @@ const appConfig = useDmsAppConfig() as DmsAppConfig & {
162
166
 
163
167
  const { t } = useI18n();
164
168
  const { processI18n } = useTranslation();
169
+ const addRowLabel = computed(() =>
170
+ props.addLabel ? processI18n(props.addLabel) : t("dms.table.new_row"),
171
+ );
172
+ const addRowIcon = computed(() => props.addIcon ?? appConfig.ui.icons.plus);
165
173
 
166
174
  const searchActive = ref(false);
167
175
  const searchInputRef = ref<HTMLInputElement>();
@@ -625,16 +633,16 @@ const uiTableActions = computed(() => uiTableActionsVariant());
625
633
 
626
634
  <UButton
627
635
  v-if="canAddRow"
628
- :label="t('dms.table.new_row')"
629
- :icon="appConfig.ui.icons.plus"
636
+ :label="addRowLabel"
637
+ :icon="addRowIcon"
630
638
  :class="uiTableActions.add()"
631
639
  size="sm"
632
640
  @click="tableSharedData!.emits('add')"
633
641
  />
634
642
  <UButton
635
643
  v-if="canAddRow"
636
- :aria-label="t('dms.table.new_row')"
637
- :icon="appConfig.ui.icons.plus"
644
+ :aria-label="addRowLabel"
645
+ :icon="addRowIcon"
638
646
  :class="uiTableActions.addMobile()"
639
647
  size="sm"
640
648
  @click="tableSharedData!.emits('add')"
@@ -211,10 +211,12 @@ const builtInActions = computed(() => {
211
211
  ];
212
212
  }
213
213
  if (props.canAddRow) {
214
+ const addLabel = tableSharedData.value?.addLabel;
215
+ const addIcon = tableSharedData.value?.addIcon;
214
216
  return [
215
217
  {
216
- label: t("dms.table.new_row"),
217
- icon: "i-ph-plus",
218
+ label: addLabel ? processI18n(addLabel) : t("dms.table.new_row"),
219
+ icon: addIcon ?? "i-ph-plus",
218
220
  color: "primary" as const,
219
221
  size: "md" as const,
220
222
  onClick: () => tableSharedData.value?.emits("add"),
@@ -13,7 +13,12 @@ import type {
13
13
  } from "./Table.vue";
14
14
  import TableMenuFilter from "./MenuFilter.vue";
15
15
  import { formatFilterValue } from "../../composables/table/utils/formatFilterValue";
16
+ import { filterEditorValue } from "../../composables/table/utils/filterEditorValue";
16
17
  import { clearTableFiltersLabelKey } from "../../composables/table/utils/clearTableFilters";
18
+ import {
19
+ type RelationFilterSource,
20
+ useRelationFilterLabels,
21
+ } from "../../composables/table/useRelationFilterLabels";
17
22
 
18
23
  // v2 filters band: a muted strip of segmented chips (field │ mode │ value │ ×);
19
24
  // a chip opens its value editor in a popover.
@@ -155,6 +160,16 @@ const addFilterOpen = ref(false);
155
160
  const { locale } = useI18n();
156
161
  const { getDataType } = useDataTypes();
157
162
 
163
+ // What a chip's editor holds: a view's text value read as its column's type.
164
+ const editorValue = (filter: TableFilter): unknown => {
165
+ const typeId = getColumn(filter.accessorKey)?.type?.id;
166
+ return filterEditorValue(
167
+ typeId ? getDataType(typeId) : undefined,
168
+ filter.mode,
169
+ filter.value,
170
+ );
171
+ };
172
+
158
173
  // Select-like filters show the option labels, not the raw stored values
159
174
  // (their formatter renders a pill, which a chip can't hold).
160
175
  const itemLabels = (filter: TableFilter): string | undefined => {
@@ -170,7 +185,29 @@ const itemLabels = (filter: TableFilter): string | undefined => {
170
185
  return labels.length ? labels.join(", ") : undefined;
171
186
  };
172
187
 
188
+ // A relation filter holds the related rows' ids: its chip names the rows,
189
+ // read from where the column's data type says they live (its `select` route).
190
+ const relationSource = (
191
+ filter: TableFilter,
192
+ ): RelationFilterSource | undefined => {
193
+ const column = getColumn(filter.accessorKey);
194
+ const component = resolveFilterComponent(filter);
195
+ if (!column?.type || !component) return undefined;
196
+ const source = getDataType(column.type.id)?.selectSource?.(component.options);
197
+ return source ? { ...source, value: filter.value } : undefined;
198
+ };
199
+
200
+ const { labelsOf: relationLabels } = useRelationFilterLabels(() =>
201
+ [...urlFilters.value, ...filters.value].flatMap((filter) => {
202
+ const source = relationSource(filter);
203
+ return source ? [source] : [];
204
+ }),
205
+ );
206
+
173
207
  const displayValue = (filter: TableFilter): string => {
208
+ const source = relationSource(filter);
209
+ const names = source ? relationLabels(source) : [];
210
+ if (names.length) return names.join(", ");
174
211
  const labels = itemLabels(filter);
175
212
  if (labels) return labels;
176
213
  const formatted = formatFilterValue(
@@ -251,7 +288,7 @@ const chipTitle = (filter: TableFilter, label?: string): string =>
251
288
  )
252
289
  "
253
290
  v-if="resolveFilterComponent(filter)"
254
- :model-value="filter.value"
291
+ :model-value="editorValue(filter)"
255
292
  :class="ui.inputWrapper()"
256
293
  v-bind="
257
294
  JSON.parse(