@antelopejs/dms 0.9.8 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (451) hide show
  1. package/CHANGELOG.md +171 -0
  2. package/README.md +1 -1
  3. package/data/module-store/catalog.json +2 -2
  4. package/dist/attachments/table-view.js +2 -2
  5. package/dist/attachments/table-view.js.map +1 -1
  6. package/dist/automation/actions.d.ts +2 -0
  7. package/dist/automation/actions.js +116 -71
  8. package/dist/automation/actions.js.map +1 -1
  9. package/dist/automation/i18n.d.ts +4 -0
  10. package/dist/automation/i18n.js +19 -0
  11. package/dist/automation/i18n.js.map +1 -0
  12. package/dist/automation/send-notification.d.ts +21 -0
  13. package/dist/automation/send-notification.js +163 -0
  14. package/dist/automation/send-notification.js.map +1 -0
  15. package/dist/automation/triggers.js +19 -25
  16. package/dist/automation/triggers.js.map +1 -1
  17. package/dist/client-origin-cors.d.ts +1 -0
  18. package/dist/client-origin-cors.js +19 -0
  19. package/dist/client-origin-cors.js.map +1 -0
  20. package/dist/config.d.ts +3 -2
  21. package/dist/config.js +5 -7
  22. package/dist/config.js.map +1 -1
  23. package/dist/frontend-module.d.ts +4 -0
  24. package/dist/frontend-module.js +37 -0
  25. package/dist/frontend-module.js.map +1 -0
  26. package/dist/implementations/dms/client-ip.js +1 -1
  27. package/dist/implementations/dms/client-ip.js.map +1 -1
  28. package/dist/implementations/dms/dev-reload.d.ts +5 -0
  29. package/dist/implementations/dms/dev-reload.js +23 -3
  30. package/dist/implementations/dms/dev-reload.js.map +1 -1
  31. package/dist/implementations/dms/frontend-modules.d.ts +2 -4
  32. package/dist/implementations/dms/frontend-modules.js +38 -94
  33. package/dist/implementations/dms/frontend-modules.js.map +1 -1
  34. package/dist/implementations/dms/frontend-ready.d.ts +12 -2
  35. package/dist/implementations/dms/frontend-ready.js +34 -4
  36. package/dist/implementations/dms/frontend-ready.js.map +1 -1
  37. package/dist/implementations/dms/layout-banners.d.ts +2 -1
  38. package/dist/implementations/dms/layout-banners.js +9 -0
  39. package/dist/implementations/dms/layout-banners.js.map +1 -1
  40. package/dist/implementations/dms/manifest.d.ts +7 -2
  41. package/dist/implementations/dms/manifest.js +12 -7
  42. package/dist/implementations/dms/manifest.js.map +1 -1
  43. package/dist/implementations/dms/page.d.ts +4 -3
  44. package/dist/implementations/dms/page.js +49 -18
  45. package/dist/implementations/dms/page.js.map +1 -1
  46. package/dist/implementations/dms/permissions-resolver.d.ts +2 -2
  47. package/dist/implementations/dms/permissions-resolver.js +2 -2
  48. package/dist/implementations/dms/permissions-resolver.js.map +1 -1
  49. package/dist/implementations/dms/realtime.js +1 -0
  50. package/dist/implementations/dms/realtime.js.map +1 -1
  51. package/dist/implementations/dms-base/bulk-selection.d.ts +1 -1
  52. package/dist/implementations/dms-base/bulk-selection.js +23 -1
  53. package/dist/implementations/dms-base/bulk-selection.js.map +1 -1
  54. package/dist/implementations/dms-base/filter-tokens.js +5 -3
  55. package/dist/implementations/dms-base/filter-tokens.js.map +1 -1
  56. package/dist/implementations/dms-base/search-route.d.ts +1 -0
  57. package/dist/implementations/dms-base/search-route.js +35 -13
  58. package/dist/implementations/dms-base/search-route.js.map +1 -1
  59. package/dist/implementations/dms-base/table-view.js +11 -6
  60. package/dist/implementations/dms-base/table-view.js.map +1 -1
  61. package/dist/implementations/dms-notifications/index.d.ts +2 -2
  62. package/dist/implementations/dms-notifications/index.js +5 -5
  63. package/dist/implementations/dms-notifications/index.js.map +1 -1
  64. package/dist/index.js +32 -55
  65. package/dist/index.js.map +1 -1
  66. package/dist/pages/modules/index.d.ts +1 -0
  67. package/dist/pages/modules/index.js +1 -0
  68. package/dist/pages/modules/index.js.map +1 -1
  69. package/dist/pages/modules/modules-store.d.ts +9 -0
  70. package/dist/pages/modules/modules-store.js +30 -0
  71. package/dist/pages/modules/modules-store.js.map +1 -0
  72. package/dist/pages/settings/users/account-credentials.js +2 -2
  73. package/dist/pages/settings/users/account-credentials.js.map +1 -1
  74. package/dist/pages/settings/users/account-data-store.js +25 -8
  75. package/dist/pages/settings/users/account-data-store.js.map +1 -1
  76. package/dist/pages/settings/users/invite-extension-routes.js +9 -3
  77. package/dist/pages/settings/users/invite-extension-routes.js.map +1 -1
  78. package/dist/pages/settings/users/member-management.js +13 -14
  79. package/dist/pages/settings/users/member-management.js.map +1 -1
  80. package/dist/pages/settings/users/profile.d.ts +1 -4
  81. package/dist/pages/settings/users/profile.js +0 -35
  82. package/dist/pages/settings/users/profile.js.map +1 -1
  83. package/dist/pages/settings/users/role-editor-store.js +26 -18
  84. package/dist/pages/settings/users/role-editor-store.js.map +1 -1
  85. package/dist/pages/settings/users/role-editor.d.ts +0 -1
  86. package/dist/pages/settings/users/role-editor.js +1 -2
  87. package/dist/pages/settings/users/role-editor.js.map +1 -1
  88. package/dist/pages/settings/users/role-preview.js +1 -2
  89. package/dist/pages/settings/users/role-preview.js.map +1 -1
  90. package/dist/pages/settings/users/roles.js +2 -2
  91. package/dist/pages/settings/users/roles.js.map +1 -1
  92. package/dist/pages/settings/users/two-factor-operations.js.map +1 -1
  93. package/dist/realtime/broker.d.ts +6 -0
  94. package/dist/realtime/broker.js +8 -1
  95. package/dist/realtime/broker.js.map +1 -1
  96. package/dist/realtime/presence.d.ts +8 -3
  97. package/dist/realtime/presence.js +8 -4
  98. package/dist/realtime/presence.js.map +1 -1
  99. package/dist/realtime/sessions.d.ts +11 -1
  100. package/dist/realtime/sessions.js +21 -3
  101. package/dist/realtime/sessions.js.map +1 -1
  102. package/dist/realtime/table-view.d.ts +2 -2
  103. package/dist/realtime/table-view.js +4 -3
  104. package/dist/realtime/table-view.js.map +1 -1
  105. package/dist/routes/auth/client-ip.d.ts +1 -0
  106. package/dist/routes/auth/client-ip.js +18 -0
  107. package/dist/routes/auth/client-ip.js.map +1 -1
  108. package/dist/routes/auth/oauth/config.d.ts +2 -1
  109. package/dist/routes/auth/oauth/config.js +5 -1
  110. package/dist/routes/auth/oauth/config.js.map +1 -1
  111. package/dist/routes/auth/session-handoff.d.ts +0 -1
  112. package/dist/routes/auth/session-handoff.js +0 -2
  113. package/dist/routes/auth/session-handoff.js.map +1 -1
  114. package/dist/routes/auth.js +3 -2
  115. package/dist/routes/auth.js.map +1 -1
  116. package/dist/routes/index.d.ts +0 -1
  117. package/dist/routes/index.js +0 -1
  118. package/dist/routes/index.js.map +1 -1
  119. package/dist/routes/realtime.d.ts +13 -1
  120. package/dist/routes/realtime.js +114 -29
  121. package/dist/routes/realtime.js.map +1 -1
  122. package/dist/startup-config.d.ts +2 -0
  123. package/dist/startup-config.js +62 -0
  124. package/dist/startup-config.js.map +1 -0
  125. package/dist/store/interfaces.d.ts +3 -2
  126. package/dist/store/interfaces.js +16 -6
  127. package/dist/store/interfaces.js.map +1 -1
  128. package/dist/store/listing.js +25 -15
  129. package/dist/store/listing.js.map +1 -1
  130. package/dist/store/operations.js +5 -2
  131. package/dist/store/operations.js.map +1 -1
  132. package/dist/store/requirements.js +3 -2
  133. package/dist/store/requirements.js.map +1 -1
  134. package/dist/store/runtime.d.ts +10 -1
  135. package/dist/store/runtime.js +33 -6
  136. package/dist/store/runtime.js.map +1 -1
  137. package/dist/store/types.d.ts +10 -0
  138. package/dist/utils/ensure-default-tenant.d.ts +2 -1
  139. package/dist/utils/ensure-default-tenant.js +13 -1
  140. package/dist/utils/ensure-default-tenant.js.map +1 -1
  141. package/dist/utils/export-jobs.js +2 -1
  142. package/dist/utils/export-jobs.js.map +1 -1
  143. package/dist/validation/auth.schema.d.ts +2 -2
  144. package/dist/validation/member-invite.schema.d.ts +2 -10
  145. package/dist/validation/member-invite.schema.js +2 -15
  146. package/dist/validation/member-invite.schema.js.map +1 -1
  147. package/frontend-vue/dms.frontend.ts +24 -5
  148. package/frontend-vue/dms.server.ts +17 -0
  149. package/frontend-vue/layers/dms-auth/server/accounts.ts +3 -4
  150. package/frontend-vue/layers/dms-core/app/build/composables/renderNow.ts +33 -0
  151. package/frontend-vue/layers/dms-core/app/build/composables/user/tablePreferences.ts +5 -1
  152. package/frontend-vue/layers/dms-core/app/build/utils/crossTab.ts +199 -0
  153. package/frontend-vue/layers/dms-core/app/build/utils/sharedRealtimeConnection.ts +410 -0
  154. package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +77 -12
  155. package/frontend-vue/layers/dms-core/app/composables/data-types/useDataType.ts +26 -0
  156. package/frontend-vue/layers/dms-core/app/composables/live/livePauseSource.ts +29 -0
  157. package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +150 -9
  158. package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +6 -4
  159. package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +27 -1
  160. package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +256 -119
  161. package/frontend-vue/layers/dms-core/app/composables/translation/useComposedText.ts +21 -8
  162. package/frontend-vue/layers/dms-core/app/composables/translation/useTranslation.ts +25 -5
  163. package/frontend-vue/layers/dms-core/app/composables/useApiError.ts +3 -5
  164. package/frontend-vue/layers/dms-core/app/composables/useFieldErrors.ts +61 -26
  165. package/frontend-vue/layers/dms-core/app/plugins/dev-error-log.ts +16 -0
  166. package/frontend-vue/layers/dms-core/app/types/user.ts +6 -0
  167. package/frontend-vue/layers/dms-core/app/utils/composedText.ts +7 -2
  168. package/frontend-vue/layers/dms-core/app/utils/formatter.ts +134 -35
  169. package/frontend-vue/layers/dms-core/app/utils/url-interpolation.ts +18 -1
  170. package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +136 -2
  171. package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +136 -2
  172. package/frontend-vue/layers/dms-core/shared/types/app-config.ts +0 -2
  173. package/frontend-vue/layers/dms-layout/app/app.config.ts +0 -1
  174. package/frontend-vue/layers/dms-layout/app/assets/css/main.css +11 -1
  175. package/frontend-vue/layers/dms-layout/app/assets/fonts/Geist-Italic-Variable.woff2 +0 -0
  176. package/frontend-vue/layers/dms-layout/app/assets/fonts/Geist-Variable.woff2 +0 -0
  177. package/frontend-vue/layers/dms-layout/app/assets/fonts/GeistMono-Italic-Variable.woff2 +0 -0
  178. package/frontend-vue/layers/dms-layout/app/assets/fonts/GeistMono-Variable.woff2 +0 -0
  179. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +3 -2
  180. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +41 -21
  181. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardPageBody.vue +6 -2
  182. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +4 -19
  183. package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +11 -107
  184. package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +18 -7
  185. package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +4 -0
  186. package/frontend-vue/layers/dms-layout/app/build/components/layout/RowSidePanel.vue +43 -82
  187. package/frontend-vue/layers/dms-layout/app/build/components/layout/SidebarUserMenu.vue +44 -3
  188. package/frontend-vue/layers/dms-layout/app/build/components/layout/UserIdentity.vue +11 -3
  189. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleCatalogTile.vue +132 -35
  190. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleManageMenu.vue +59 -0
  191. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleSourceLabel.vue +25 -0
  192. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreBanners.vue +73 -0
  193. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreOperation.vue +116 -0
  194. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreRequirements.vue +8 -3
  195. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue +85 -207
  196. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTile.vue +10 -3
  197. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTileSkeleton.vue +68 -0
  198. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +24 -8
  199. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxItem.vue +8 -5
  200. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/region/RegionTimeZoneInput.vue +1 -0
  201. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-permissions.ts +19 -0
  202. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/useRoleEditor.ts +2 -1
  203. package/frontend-vue/layers/dms-layout/app/build/composables/layout/useSidePanelWidth.ts +31 -70
  204. package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen.ts +11 -1
  205. package/frontend-vue/layers/dms-layout/app/build/utils/dev-reload-stream.ts +81 -0
  206. package/frontend-vue/layers/dms-layout/app/build/utils/github-repository.ts +2 -2
  207. package/frontend-vue/layers/dms-layout/app/build/utils/module-store-catalog.ts +232 -36
  208. package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +122 -14
  209. package/frontend-vue/layers/dms-layout/app/composables/layout/useLayoutBanners.ts +32 -3
  210. package/frontend-vue/layers/dms-layout/app/composables/layout/useServedHeaderActions.ts +49 -11
  211. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleHistory.ts +3 -2
  212. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +59 -4
  213. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStoreActions.ts +194 -0
  214. package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +23 -3
  215. package/frontend-vue/layers/dms-layout/app/composables/page/usePageLayout.ts +4 -2
  216. package/frontend-vue/layers/dms-layout/app/composables/page/usePageRecordDeletion.ts +162 -0
  217. package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +6 -1
  218. package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +6 -2
  219. package/frontend-vue/layers/dms-layout/app/custom-layouts/SettingsLayout.vue +6 -2
  220. package/frontend-vue/layers/dms-layout/app/custom-pages/modules-store.vue +291 -0
  221. package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +87 -474
  222. package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +3 -0
  223. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +13 -22
  224. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-navigation.client.ts +11 -10
  225. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-personal-pages.client.ts +22 -52
  226. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-quick-actions.client.ts +7 -7
  227. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-session.client.ts +11 -10
  228. package/frontend-vue/layers/dms-layout/app/plugins/dms-dev-reload.client.ts +14 -9
  229. package/frontend-vue/layers/dms-layout/app/theme/actions.ts +5 -4
  230. package/frontend-vue/layers/dms-layout/app/theme/form-controls.ts +9 -4
  231. package/frontend-vue/layers/dms-layout/app/theme/surfaces.ts +22 -6
  232. package/frontend-vue/layers/dms-layout/app/types/module-store.ts +9 -0
  233. package/frontend-vue/layers/dms-layout/app/types/page.ts +15 -1
  234. package/frontend-vue/layers/dms-layout/app/utils/account-menu.ts +11 -0
  235. package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +57 -0
  236. package/frontend-vue/layers/dms-layout/app/utils/pageQuery.ts +26 -0
  237. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +45 -18
  238. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +44 -17
  239. package/frontend-vue/layers/dms-ui/app/build/components/containers/TargetComponentTree.vue +31 -0
  240. package/frontend-vue/layers/dms-ui/app/build/components/containers/constants.ts +25 -0
  241. package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +244 -15
  242. package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +42 -25
  243. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowHeader.vue +153 -0
  244. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowNotFound.vue +44 -0
  245. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/drawerRowHeader.ts +35 -0
  246. package/frontend-vue/layers/dms-ui/app/build/components/form/FormEntries.vue +44 -9
  247. package/frontend-vue/layers/dms-ui/app/build/components/form/FormErrorSummary.vue +11 -4
  248. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldControl.vue +38 -5
  249. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldNotes.vue +29 -5
  250. package/frontend-vue/layers/dms-ui/app/build/components/form/FormLoadError.vue +5 -1
  251. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSectionNav.vue +3 -1
  252. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSections.vue +106 -27
  253. package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +12 -4
  254. package/frontend-vue/layers/dms-ui/app/build/components/table/Empty.vue +4 -2
  255. package/frontend-vue/layers/dms-ui/app/build/components/table/FiltersRow.vue +48 -1
  256. package/frontend-vue/layers/dms-ui/app/build/components/table/Pagination.vue +55 -54
  257. package/frontend-vue/layers/dms-ui/app/build/components/table/RowSelection.vue +2 -1
  258. package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +396 -288
  259. package/frontend-vue/layers/dms-ui/app/build/components/table/Tabs.vue +2 -1
  260. package/frontend-vue/layers/dms-ui/app/build/components/table/Views.vue +2 -1
  261. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardAvatar.vue +59 -0
  262. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardGridSkeleton.vue +19 -2
  263. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardRowActions.vue +38 -0
  264. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardsDisplay.vue +148 -104
  265. package/frontend-vue/layers/dms-ui/app/build/components/table-view/LiveHoldBanner.vue +17 -2
  266. package/frontend-vue/layers/dms-ui/app/build/composables/actions/bulkSelection.ts +43 -9
  267. package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowComponentOptions.ts +76 -0
  268. package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowNavigation.ts +172 -27
  269. package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +270 -46
  270. package/frontend-vue/layers/dms-ui/app/build/composables/confirm/actionError.ts +6 -1
  271. package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +2 -0
  272. package/frontend-vue/layers/dms-ui/app/build/composables/containers/closingLayers.ts +186 -0
  273. package/frontend-vue/layers/dms-ui/app/build/composables/containers/context.ts +14 -0
  274. package/frontend-vue/layers/dms-ui/app/build/composables/containers/drawerSize.ts +229 -0
  275. package/frontend-vue/layers/dms-ui/app/build/composables/containers/useResizableSize.ts +175 -0
  276. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/metricCells.ts +18 -35
  277. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/numberSuffix.ts +44 -0
  278. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +123 -27
  279. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerFieldTypes.ts +2 -1
  280. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationFilter.ts +75 -0
  281. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationRows.ts +92 -0
  282. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationValue.ts +39 -0
  283. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/twoLineCell.ts +3 -3
  284. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/useColumnValueRenderer.ts +6 -2
  285. package/frontend-vue/layers/dms-ui/app/build/composables/form/createFormFields.ts +15 -0
  286. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldSaved.ts +40 -0
  287. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldValueRule.ts +62 -0
  288. package/frontend-vue/layers/dms-ui/app/build/composables/form/formEntryContext.ts +5 -0
  289. package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +42 -7
  290. package/frontend-vue/layers/dms-ui/app/build/composables/form/formLayout.ts +106 -4
  291. package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +11 -5
  292. package/frontend-vue/layers/dms-ui/app/build/composables/form/formSections.ts +32 -0
  293. package/frontend-vue/layers/dms-ui/app/build/composables/form/formValidation.ts +36 -4
  294. package/frontend-vue/layers/dms-ui/app/build/composables/form/readonlyValue.ts +19 -0
  295. package/frontend-vue/layers/dms-ui/app/build/composables/form/registerFormFunctions.ts +27 -0
  296. package/frontend-vue/layers/dms-ui/app/build/composables/form/repeaterTable.ts +78 -0
  297. package/frontend-vue/layers/dms-ui/app/build/composables/form/serverFieldErrors.ts +49 -0
  298. package/frontend-vue/layers/dms-ui/app/build/composables/form/toasterClearance.ts +118 -0
  299. package/frontend-vue/layers/dms-ui/app/build/composables/form/useInstantForm.ts +12 -1
  300. package/frontend-vue/layers/dms-ui/app/build/composables/live/currentPageLive.ts +41 -3
  301. package/frontend-vue/layers/dms-ui/app/build/composables/live/livePoller.ts +10 -2
  302. package/frontend-vue/layers/dms-ui/app/build/composables/live/pageReadClock.ts +28 -0
  303. package/frontend-vue/layers/dms-ui/app/build/composables/live/shortcuts.ts +1 -1
  304. package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageLiveHost.ts +17 -3
  305. package/frontend-vue/layers/dms-ui/app/build/composables/live/useUpdatedLabel.ts +38 -0
  306. package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/pageToolbarContext.ts +75 -0
  307. package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/toolbarControlHeight.ts +29 -0
  308. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodComparison.ts +52 -0
  309. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +21 -0
  310. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodShortLabels.ts +28 -0
  311. package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodDraft.ts +1 -0
  312. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRelationFilterLabels.ts +97 -0
  313. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRowScrollAnchor.ts +106 -0
  314. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +9 -0
  315. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTableColumns.ts +1 -2
  316. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/archivedCount.ts +31 -0
  317. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/filterEditorValue.ts +38 -0
  318. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/formatFilterValue.ts +33 -2
  319. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/scrollEdges.ts +31 -0
  320. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useCardFields.ts +33 -0
  321. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useMobileDisplay.ts +48 -0
  322. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useScrolledPastTop.ts +41 -18
  323. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +3 -2
  324. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLinkedVisit.ts +78 -0
  325. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLiveHold.ts +135 -22
  326. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +56 -25
  327. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +90 -35
  328. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +86 -10
  329. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/bulkActions.ts +14 -4
  330. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/card.ts +127 -2
  331. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/compareValues.ts +13 -0
  332. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/expandedRows.ts +28 -0
  333. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/groupedRows.ts +114 -1
  334. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/linkedArrival.ts +308 -0
  335. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/locateRow.ts +106 -0
  336. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pageArrival.ts +34 -0
  337. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pinnedRows.ts +17 -0
  338. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/quickFilters.ts +41 -33
  339. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/recordLink.ts +24 -8
  340. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableCounts.ts +113 -0
  341. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableQuery.ts +27 -4
  342. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/views.ts +22 -6
  343. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/writeError.ts +21 -0
  344. package/frontend-vue/layers/dms-ui/app/build/utils/addressSuggestions.ts +49 -0
  345. package/frontend-vue/layers/dms-ui/app/build/utils/codeHighlight.ts +67 -29
  346. package/frontend-vue/layers/dms-ui/app/build/utils/eyebrow.ts +12 -0
  347. package/frontend-vue/layers/dms-ui/app/build/utils/fieldOptionsI18n.ts +51 -11
  348. package/frontend-vue/layers/dms-ui/app/build/utils/htmlGrammar.ts +79 -0
  349. package/frontend-vue/layers/dms-ui/app/build/utils/inputWidth.ts +53 -0
  350. package/frontend-vue/layers/dms-ui/app/build/utils/numberAdjustment.ts +6 -3
  351. package/frontend-vue/layers/dms-ui/app/build/utils/relativeDate.ts +19 -5
  352. package/frontend-vue/layers/dms-ui/app/build/utils/scrollWithin.ts +99 -0
  353. package/frontend-vue/layers/dms-ui/app/build/utils/shellParser.ts +1 -1
  354. package/frontend-vue/layers/dms-ui/app/build/utils/streamHighlight.ts +90 -0
  355. package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +36 -14
  356. package/frontend-vue/layers/dms-ui/app/components/PasswordStrength.vue +1 -1
  357. package/frontend-vue/layers/dms-ui/app/components/RecursiveComponent.vue +9 -1
  358. package/frontend-vue/layers/dms-ui/app/components/blocks/LiveToggleBlock.vue +80 -32
  359. package/frontend-vue/layers/dms-ui/app/components/blocks/PageToolbarBlock.vue +137 -0
  360. package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +5 -0
  361. package/frontend-vue/layers/dms-ui/app/components/chart/ApexChartHost.vue +16 -2
  362. package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +24 -5
  363. package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +41 -23
  364. package/frontend-vue/layers/dms-ui/app/components/code-snippet/CodeSnippet.vue +23 -12
  365. package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +1 -1
  366. package/frontend-vue/layers/dms-ui/app/components/container-footer/ContainerFooter.vue +34 -0
  367. package/frontend-vue/layers/dms-ui/app/components/field-row/FieldRow.vue +25 -2
  368. package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +2 -2
  369. package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +119 -36
  370. package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +53 -22
  371. package/frontend-vue/layers/dms-ui/app/components/form/components/DatePicker.vue +20 -1
  372. package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +13 -1
  373. package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +8 -3
  374. package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +53 -5
  375. package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +29 -5
  376. package/frontend-vue/layers/dms-ui/app/components/form/components/InputNumber.vue +45 -8
  377. package/frontend-vue/layers/dms-ui/app/components/form/components/InputPercentage.vue +28 -5
  378. package/frontend-vue/layers/dms-ui/app/components/form/components/InputSecret.vue +5 -0
  379. package/frontend-vue/layers/dms-ui/app/components/form/components/InputText.vue +19 -2
  380. package/frontend-vue/layers/dms-ui/app/components/form/components/InputTime.vue +2 -1
  381. package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +226 -65
  382. package/frontend-vue/layers/dms-ui/app/components/form/components/Repeater.vue +80 -30
  383. package/frontend-vue/layers/dms-ui/app/components/form/components/RepeaterTable.vue +210 -0
  384. package/frontend-vue/layers/dms-ui/app/components/form/components/Select.vue +4 -2
  385. package/frontend-vue/layers/dms-ui/app/components/grid/GridRow.vue +11 -2
  386. package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +79 -70
  387. package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +117 -21
  388. package/frontend-vue/layers/dms-ui/app/components/meter/Meter.vue +2 -1
  389. package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +113 -34
  390. package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +14 -2
  391. package/frontend-vue/layers/dms-ui/app/components/section-header/Eyebrow.vue +6 -1
  392. package/frontend-vue/layers/dms-ui/app/components/segmented/Segmented.vue +13 -0
  393. package/frontend-vue/layers/dms-ui/app/components/stat-group/StatGroup.vue +52 -3
  394. package/frontend-vue/layers/dms-ui/app/components/tab/Tab.vue +2 -1
  395. package/frontend-vue/layers/dms-ui/app/components/table-view/CascaderPath.vue +74 -25
  396. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +48 -44
  397. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanDisplay.vue +4 -1
  398. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +426 -97
  399. package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +33 -5
  400. package/frontend-vue/layers/dms-ui/app/composables/chart/apexCategoryLabels.ts +68 -0
  401. package/frontend-vue/layers/dms-ui/app/composables/chart/apexDatetimeBuckets.ts +306 -0
  402. package/frontend-vue/layers/dms-ui/app/composables/chart/formatValue.ts +4 -3
  403. package/frontend-vue/layers/dms-ui/app/composables/chart/heatmapColors.ts +93 -0
  404. package/frontend-vue/layers/dms-ui/app/composables/chart/localizeSeries.ts +62 -0
  405. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.ts +116 -8
  406. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.types.ts +7 -0
  407. package/frontend-vue/layers/dms-ui/app/composables/containers/types.ts +84 -1
  408. package/frontend-vue/layers/dms-ui/app/composables/containers/useDrawer.ts +69 -8
  409. package/frontend-vue/layers/dms-ui/app/composables/form/types/field-saved.ts +11 -0
  410. package/frontend-vue/layers/dms-ui/app/composables/form/types/field.ts +16 -0
  411. package/frontend-vue/layers/dms-ui/app/composables/form/types/functions.ts +1 -0
  412. package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +189 -28
  413. package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +29 -12
  414. package/frontend-vue/layers/dms-ui/app/composables/page-toolbar/usePageToolbarSize.ts +30 -0
  415. package/frontend-vue/layers/dms-ui/app/composables/table-view/kanban.ts +4 -2
  416. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +22 -3
  417. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +4 -0
  418. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/display.ts +24 -1
  419. package/frontend-vue/layers/dms-ui/app/plugins/closing-layers.client.ts +10 -0
  420. package/frontend-vue/layers/dms-ui/app/plugins/live-pause-source.ts +15 -0
  421. package/frontend-vue/layers/dms-ui/app/plugins/register.ts +2 -0
  422. package/frontend-vue/layers/dms-ui/app/types/input-number.ts +3 -2
  423. package/frontend-vue/layers/dms-ui/app/types/page-toolbar.ts +7 -0
  424. package/frontend-vue/layers/dms-ui/app/types/row-action.ts +0 -2
  425. package/frontend-vue/layers/dms-ui/app/types/row-navigation.ts +17 -0
  426. package/frontend-vue/layers/dms-ui/app/types/table/query.ts +2 -1
  427. package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +3 -1
  428. package/frontend-vue/layers/dms-ui/app/utils/cascader.ts +94 -3
  429. package/frontend-vue/layers/dms-ui/app/utils/componentTree.ts +24 -0
  430. package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +20 -5
  431. package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +20 -5
  432. package/frontend-vue/package.json +6 -2
  433. package/package.json +11 -10
  434. package/skills/dms/SKILL.md +1 -1
  435. package/skills/dms-auth/SKILL.md +5 -3
  436. package/skills/dms-best-practices/REFERENCE.md +2 -0
  437. package/skills/dms-best-practices/SKILL.md +4 -0
  438. package/skills/dms-module/SKILL.md +3 -2
  439. package/skills/dms-pages/REFERENCE.md +129 -16
  440. package/skills/dms-pages/SKILL.md +1 -1
  441. package/skills/dms-project/DEV-SERVERS.md +23 -1
  442. package/skills/dms-project/REFERENCE.md +1 -1
  443. package/skills/dms-project/SKILL.md +4 -1
  444. package/skills/dms-project/scripts/dms-dev.sh +205 -17
  445. package/dist/hooks/tenant-instances.d.ts +0 -1
  446. package/dist/hooks/tenant-instances.js +0 -13
  447. package/dist/hooks/tenant-instances.js.map +0 -1
  448. package/dist/routes/startup-gate.d.ts +0 -7
  449. package/dist/routes/startup-gate.js +0 -39
  450. package/dist/routes/startup-gate.js.map +0 -1
  451. package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +0 -42
@@ -8,6 +8,7 @@ import type {
8
8
  import type { FormField } from "../../composables/form/types/field";
9
9
  import {
10
10
  cloneFormValue,
11
+ fieldValuesOfSubmitDefaults,
11
12
  type FormServerFieldError,
12
13
  formShowsActions,
13
14
  isAcceptanceField,
@@ -18,6 +19,7 @@ import {
18
19
  type FormValidator,
19
20
  } from "../../composables/form/types/validation";
20
21
  import { FORM_FIELD_LOADING_KEY } from "../../composables/form/types/field-loading";
22
+ import { FORM_FIELD_SAVED_KEY } from "../../composables/form/types/field-saved";
21
23
  import {
22
24
  FORM_HANDLE_KEY,
23
25
  linkedFieldId,
@@ -42,6 +44,7 @@ import {
42
44
  fieldErrorPattern,
43
45
  } from "../../build/composables/form/formEntryContext";
44
46
  import {
47
+ CONTAINER_LAYOUT_CLASSES,
45
48
  FORM_LAYOUT_CLASSES,
46
49
  FORM_SURFACE_CLASSES,
47
50
  type FormSurface,
@@ -58,7 +61,15 @@ import {
58
61
  sectionState,
59
62
  } from "../../build/composables/form/formSections";
60
63
  import { FORM_CONTROL_ERRORS_KEY } from "../../build/composables/form/formControlErrors";
64
+ import {
65
+ changedServerErrorFields,
66
+ keptServerErrors,
67
+ serverErrorValue,
68
+ type KeptServerError,
69
+ type PlacedServerError,
70
+ } from "../../build/composables/form/serverFieldErrors";
61
71
  import { useInstantForm } from "../../build/composables/form/useInstantForm";
72
+ import { fieldSavedMarker } from "../../build/composables/form/fieldSaved";
62
73
  import { useInstantSaveHeader } from "#dms-layout/app/composables/layout/useInstantSaveHeader";
63
74
  import DmsFormEntries from "../../build/components/form/FormEntries.vue";
64
75
  import DmsFormErrorSummary from "../../build/components/form/FormErrorSummary.vue";
@@ -70,6 +81,8 @@ import {
70
81
  } from "../../build/composables/form/formRecordLoad";
71
82
  import DmsFormRequiredLegend from "./FormRequiredLegend.vue";
72
83
  import DmsFormSections from "../../build/components/form/FormSections.vue";
84
+ import { scrollWithin } from "../../build/utils/scrollWithin";
85
+ import { isHiddenOnCreate } from "../../build/composables/form/createFormFields";
73
86
 
74
87
  const REALTIME_PRESENCE_FLAG = "_presence=1";
75
88
  const REALTIME_ACQUIRE_PATH = "/api/realtime/acquire";
@@ -97,10 +110,11 @@ const FormWrapper =
97
110
 
98
111
  const { processI18n, processApiMessage } = useTranslation();
99
112
 
100
- // Server errors stay on their field until its value changes (UForm only
101
- // re-validates a field once it was left, and its schema knows nothing of
102
- // them): the value each field's were raised against, by field id.
103
- const serverErrorValues = new Map<string, string>();
113
+ // Server errors stay on their field until its value changes or the form is
114
+ // saved, whatever re-validates the field in between: UForm's `validate`
115
+ // answers them with its schema's (see `keptServerErrors`). Each one shown,
116
+ // with the value it was raised against, by field id.
117
+ const serverErrors = new Map<string, KeptServerError>();
104
118
 
105
119
  /** The control of a field, by its id, inside this form only. */
106
120
  function fieldControl(name: string | undefined): HTMLElement | null {
@@ -171,13 +185,14 @@ async function focusField(name: string | undefined): Promise<void> {
171
185
  target.focus();
172
186
  return;
173
187
  }
174
- row?.scrollIntoView({ block: "center" });
188
+ if (row) scrollWithin(row, { block: "center" });
175
189
  }
176
190
 
177
191
  /** Brings a field into view and focuses it: a link's anchor, a field's request. */
178
192
  async function revealField(fieldId: string): Promise<void> {
179
193
  await formSettled();
180
- fieldRow(fieldId)?.scrollIntoView({ block: "center" });
194
+ const row = fieldRow(fieldId);
195
+ if (row) scrollWithin(row, { block: "center" });
181
196
  await focusField(fieldId);
182
197
  }
183
198
 
@@ -193,14 +208,19 @@ function showFieldErrors(errors: FormServerFieldError[]): boolean {
193
208
  // instant form) replaces its own only.
194
209
  if (!isInstant) {
195
210
  target.setErrors([]);
196
- serverErrorValues.clear();
211
+ serverErrors.clear();
197
212
  }
198
213
  for (const error of errors) placeServerError(target, error);
199
214
  const shown = errors.filter(
200
215
  (error) => target.getErrors(fieldErrorPattern(error.name)).length,
201
216
  );
202
217
  for (const error of shown) {
203
- serverErrorValues.set(error.name, JSON.stringify(state.value[error.name]));
218
+ serverErrors.set(error.name, {
219
+ value: serverErrorValue(state.value[error.name]),
220
+ errors: target
221
+ .getErrors(fieldErrorPattern(error.name))
222
+ .map(({ name, message }: PlacedServerError) => ({ name, message })),
223
+ });
204
224
  }
205
225
  showsErrorSummary.value = shown.length > 0;
206
226
  void focusField(target.errors[0]?.name);
@@ -229,7 +249,9 @@ function onValidationError(event: { errors: Array<{ name?: string }> }): void {
229
249
  }
230
250
 
231
251
  // Once a submit is refused, the form lists what to fix above its fields
232
- // until it is saved.
252
+ // until it is saved. The last fix empties the list, not the place it holds:
253
+ // the fields, and the submit button with them, would move up under the
254
+ // pointer on its way to it, and the click would land beside it.
233
255
  const showsErrorSummary = ref(false);
234
256
 
235
257
  const {
@@ -251,20 +273,33 @@ const {
251
273
  isFieldGroup,
252
274
  submitSucceeded,
253
275
  resolvedFetchUrl,
276
+ createsRecord,
277
+ effectiveSubmitDefaults,
254
278
  } = useForm(props, { showFieldErrors, onSaved: markFormClean });
255
279
 
256
280
  watch(
257
281
  state,
258
282
  (current) => {
259
- for (const [name, value] of serverErrorValues) {
260
- if (JSON.stringify(current[name]) === value) continue;
261
- serverErrorValues.delete(name);
283
+ for (const name of changedServerErrorFields(serverErrors, current)) {
284
+ serverErrors.delete(name);
262
285
  form.value?.clear(fieldErrorPattern(name));
263
286
  }
264
287
  },
265
288
  { deep: true },
266
289
  );
267
290
 
291
+ // UForm is busy while it validates a submit: the kept errors stay out of it.
292
+ const validateServerErrors = (current: Record<string, unknown>) =>
293
+ keptServerErrors(serverErrors, current, !!form.value?.loading);
294
+
295
+ /** A saved form: what the server refused before no longer stands. */
296
+ function clearServerErrors(): void {
297
+ for (const name of serverErrors.keys()) {
298
+ form.value?.clear(fieldErrorPattern(name));
299
+ }
300
+ serverErrors.clear();
301
+ }
302
+
268
303
  const allFieldIds = computed(() => toValue(allFields).map((field) => field.id));
269
304
 
270
305
  const showActions = computed(() => formShowsActions(props, toValue(allFields)));
@@ -291,6 +326,7 @@ const localizedSchema = computed(() => {
291
326
  id: field.id,
292
327
  type: field.type,
293
328
  localized: field.localized,
329
+ fallbackLocale: field.fallbackLocale,
294
330
  required: isFieldRequired(field),
295
331
  acceptance: isAcceptanceField(field),
296
332
  }));
@@ -335,7 +371,7 @@ watch(
335
371
  const previous = fieldValueSnapshots.get(field.id);
336
372
  fieldValueSnapshots.set(field.id, snapshot);
337
373
  if (previous === undefined || previous === snapshot) continue;
338
- if (!form.value || serverErrorValues.has(field.id)) continue;
374
+ if (!form.value || serverErrors.has(field.id)) continue;
339
375
  const pattern = fieldErrorPattern(field.id);
340
376
  if (!form.value.getErrors(pattern).length) continue;
341
377
  void form.value.validate({ name: errorNames(field), silent: true });
@@ -346,10 +382,12 @@ watch(
346
382
 
347
383
  const { t, locale, locales } = useI18n();
348
384
 
385
+ // A new row is created, not "changes" saved.
386
+ const DEFAULT_SUBMIT_LABEL = createsRecord
387
+ ? "dms.button.create"
388
+ : "dms.button.save_changes";
349
389
  const submitButtonLabel = computed(() =>
350
- props.submitLabel
351
- ? processI18n(props.submitLabel)
352
- : t("dms.button.save_changes"),
390
+ props.submitLabel ? processI18n(props.submitLabel) : t(DEFAULT_SUBMIT_LABEL),
353
391
  );
354
392
 
355
393
  watch(disabledFields, (newDisabled, oldDisabled) => {
@@ -413,6 +451,7 @@ onMounted(() => {
413
451
  function markFormClean(): void {
414
452
  isTouched = false;
415
453
  showsErrorSummary.value = false;
454
+ clearServerErrors();
416
455
  formDirty.markClean();
417
456
  instantSave?.start();
418
457
  }
@@ -441,11 +480,20 @@ const isDirty = computed(
441
480
 
442
481
  useUnsavedChanges({ dirty: isDirty, containerId, element: form });
443
482
 
444
- // A form offers Cancel while clean when it has somewhere to go back to: its
445
- // drawer or modal, or `backTo` on a page. An action form (send, invite, run)
446
- // saves from its footer band, never from the floating bar.
483
+ // A form offers Cancel when it has somewhere to go back to: its drawer or
484
+ // modal, `backTo` on a page, or the page of the app the user came from. An
485
+ // action form (send, invite, run) saves from its footer band, never from the
486
+ // floating bar.
447
487
  const isActionForm = props.kind === "action";
448
- const canCancel = inFormContainer || !!container || !!props.backTo;
488
+ // Read once mounted: the server renders no history.
489
+ const cameFromAppPage = ref(false);
490
+ onMounted(() => {
491
+ cameFromAppPage.value = !!previousAppPage();
492
+ });
493
+ const canCancel = computed(
494
+ () =>
495
+ inFormContainer || !!container || !!props.backTo || cameFromAppPage.value,
496
+ );
449
497
  const usesSaveBar = saveMode === "bar" && !inFormContainer && !isActionForm;
450
498
 
451
499
  /** Validates one field alone: whether it holds no error. */
@@ -479,19 +527,30 @@ const hasChangeableFields = computed(() =>
479
527
  const router = useDmsRouter();
480
528
  const route = useDmsRoute();
481
529
  /**
482
- * Cancel, with nothing to save: closes the drawer or modal, or leaves a page
483
- * form for the page the user came from (its list, when opened from one), or
484
- * its `backTo` when the form was opened directly.
530
+ * The page of the app this one was reached from, which the router records
531
+ * in the history entry: none for a page opened from a link outside the app,
532
+ * a bookmark or a new tab, where going back would leave the app.
533
+ */
534
+ function previousAppPage(): string | undefined {
535
+ const back = (window.history.state as { back?: unknown } | null)?.back;
536
+ return typeof back === "string" ? back : undefined;
537
+ }
538
+
539
+ /**
540
+ * Cancel: drops what was changed, then closes the drawer or modal, or leaves
541
+ * a page form for the page of the app the user came from (its list, when
542
+ * opened from one), or its `backTo` when the form was opened directly.
543
+ * Discarding first means leaving never asks about changes the user just chose
544
+ * to drop.
485
545
  */
486
546
  function cancelForm(): void {
547
+ if (isDirty.value) discardChanges();
487
548
  if (container) {
488
549
  void container.close();
489
550
  return;
490
551
  }
491
- const navigation = (window as { navigation?: { canGoBack?: boolean } })
492
- .navigation;
493
- if (navigation?.canGoBack ?? window.history.length > 1) {
494
- window.history.back();
552
+ if (previousAppPage()) {
553
+ router.back();
495
554
  return;
496
555
  }
497
556
  if (props.backTo) void router.push(pageLink(props.backTo));
@@ -537,6 +596,15 @@ provide(FORM_HANDLE_KEY, {
537
596
  revealField,
538
597
  });
539
598
  provide(FORM_FIELD_LOADING_KEY, setFieldLoading);
599
+ provide(
600
+ FORM_FIELD_SAVED_KEY,
601
+ fieldSavedMarker({
602
+ hasField: (id) => toValue(allFields).some((field) => field.id === id),
603
+ initialValues,
604
+ dirty: formDirty,
605
+ instantSave,
606
+ }),
607
+ );
540
608
  provide(FORM_CONTENT_LANGUAGE_KEY, "*");
541
609
 
542
610
  allFields.value.forEach((field) => {
@@ -547,6 +615,15 @@ allFields.value.forEach((field) => {
547
615
  ) as FormFieldValue;
548
616
  });
549
617
 
618
+ // A field the form submits a default under (the parent a table is filtered
619
+ // on) holds it from the start: it shows, and a mandatory one is filled.
620
+ for (const [id, value] of Object.entries(
621
+ fieldValuesOfSubmitDefaults(allFields.value, effectiveSubmitDefaults.value),
622
+ )) {
623
+ state.value[id] = cloneFormValue(value);
624
+ initialValues.value[id] = cloneFormValue(value) as FormFieldValue;
625
+ }
626
+
550
627
  let recordLoad: FormRecordLoad | undefined;
551
628
  if (props.fetchUrl) {
552
629
  // Keyed on the record too: the load outlives a drawer or modal, and the
@@ -648,7 +725,10 @@ function layoutClasses(inSectionCard: boolean) {
648
725
  if ((inSectionCard || inSection) && isHorizontal) {
649
726
  return SECTION_LAYOUT_CLASSES;
650
727
  }
651
- return FORM_LAYOUT_CLASSES[isHorizontal ? "horizontal" : "vertical"];
728
+ const orientation = isHorizontal ? "horizontal" : "vertical";
729
+ return inFormContainer
730
+ ? CONTAINER_LAYOUT_CLASSES[orientation]
731
+ : FORM_LAYOUT_CLASSES[orientation];
652
732
  }
653
733
 
654
734
  function isFieldDisabled(field: FormField): boolean {
@@ -657,7 +737,8 @@ function isFieldDisabled(field: FormField): boolean {
657
737
  }
658
738
 
659
739
  function isFieldHidden(field: FormField): boolean {
660
- return hiddenFields.value?.has(field.id) ?? false;
740
+ if (hiddenFields.value?.has(field.id)) return true;
741
+ return createsRecord && isHiddenOnCreate(field, state.value[field.id]);
661
742
  }
662
743
 
663
744
  function isFieldRequired(field: FormField): boolean {
@@ -684,7 +765,10 @@ provide(FORM_ENTRY_CONTEXT_KEY, {
684
765
  isFieldHidden,
685
766
  isFieldDisabled,
686
767
  isFieldRequired,
687
- showsChanges: !isInstant && !isActionForm,
768
+ isReadOnly: computed(() => !hasChangeableFields.value),
769
+ // A new row has no earlier value to compare with: "Changed · was empty"
770
+ // on every field it fills would say nothing.
771
+ showsChanges: !isInstant && !isActionForm && !createsRecord,
688
772
  isFieldChanged: (fieldId) => changedFieldIds.value.has(fieldId),
689
773
  baselineValue: (fieldId) => formDirty.baseline.value[fieldId],
690
774
  fieldSaveState: (fieldId) => instantSave?.states[fieldId] ?? "idle",
@@ -739,10 +823,6 @@ const sectionOfField = computed(() => {
739
823
  return owners;
740
824
  });
741
825
 
742
- watch(invalidFields, (invalid) => {
743
- if (invalid.size === 0) showsErrorSummary.value = false;
744
- });
745
-
746
826
  // One entry per row to fix: the unlabelled fields of a group share its
747
827
  // label, and its first invalid field stands for them.
748
828
  const errorSummaryItems = computed(() => {
@@ -916,6 +996,7 @@ onUnmounted(async () => {
916
996
  ref="form"
917
997
  :state
918
998
  :schema="localizedSchema"
999
+ :validate="validateServerErrors"
919
1000
  :disabled="allFields.every((field) => field.disabled)"
920
1001
  @submit="onSubmit"
921
1002
  @error="onValidationError"
@@ -963,7 +1044,7 @@ onUnmounted(async () => {
963
1044
  />
964
1045
 
965
1046
  <DmsFormErrorSummary
966
- v-if="errorSummaryItems.length"
1047
+ v-if="showsErrorSummary"
967
1048
  :items="errorSummaryItems"
968
1049
  :class="hasSections ? 'mb-5' : 'mt-4'"
969
1050
  @select="focusField"
@@ -1014,7 +1095,9 @@ onUnmounted(async () => {
1014
1095
  :saving="loading || isAnyFieldLoading"
1015
1096
  :changes="changedFieldLabels"
1016
1097
  :form="formElementId"
1017
- :save-label="usesSaveBar ? props.submitLabel : submitButtonLabel"
1098
+ :save-label="
1099
+ usesSaveBar && !createsRecord ? props.submitLabel : submitButtonLabel
1100
+ "
1018
1101
  :cancellable="canCancel"
1019
1102
  :resettable="hasChangeableFields"
1020
1103
  :class="usesSaveBar ? 'mx-3' : surfaceClasses.foot"
@@ -6,7 +6,10 @@ import {
6
6
  findCascaderNode,
7
7
  formatCascaderPath,
8
8
  isCascaderNodeSelectable,
9
+ readCascaderAncestry,
9
10
  resolveCascaderKeyMapping,
11
+ buildCascaderPathLabels,
12
+ CASCADER_PATH_SEPARATOR,
10
13
  searchCascaderTree,
11
14
  type CascaderKeyMapping,
12
15
  type CascaderNode,
@@ -18,6 +21,7 @@ import {
18
21
  FIELD_TRIGGER_INVALID_CLASS,
19
22
  FIELD_TRIGGER_UI,
20
23
  } from "../../../build/utils/fieldTrigger";
24
+ import { useRelationRows } from "../../../build/composables/data-types/relationRows";
21
25
 
22
26
  interface CascaderProps {
23
27
  searchUrl: string;
@@ -88,6 +92,36 @@ const { data, status, execute } = await useDmsAsyncData(
88
92
  );
89
93
 
90
94
  const keyMapping = computed(() => resolveCascaderKeyMapping(props.keyMapping));
95
+ const selectedValues = computed<string[]>(() => {
96
+ if (Array.isArray(props.modelValue)) {
97
+ return props.modelValue.map(String);
98
+ }
99
+ return props.modelValue ? [String(props.modelValue)] : [];
100
+ });
101
+
102
+ // The picked rows and their ancestors, read by `?ids=` level by level: the
103
+ // field names its value before the menu is opened, whatever the tree's size.
104
+ const { readRelationRows } = useRelationRows();
105
+ const { data: ancestry } = await useDmsAsyncData(
106
+ () => `cascader-value-${instanceId}-${selectedValues.value.join(",")}`,
107
+ () =>
108
+ selectedValues.value.length > 0
109
+ ? readCascaderAncestry(
110
+ (ids) =>
111
+ readRelationRows(
112
+ {
113
+ searchUrl: props.searchUrl,
114
+ keyMapping: { value: keyMapping.value.value },
115
+ },
116
+ ids,
117
+ ),
118
+ keyMapping.value,
119
+ selectedValues.value,
120
+ )
121
+ : Promise.resolve(undefined),
122
+ // The field's label, not the page's data.
123
+ { lazy: true, refreshOnPageRefresh: false },
124
+ );
91
125
  const rows = computed(() => data.value?.results ?? []);
92
126
  const isPartial = computed(
93
127
  () => (data.value?.total ?? 0) > rows.value.length && rows.value.length > 0,
@@ -100,12 +134,6 @@ const selectionRules = computed(() => ({
100
134
  maxDepth: props.maxDepth,
101
135
  }));
102
136
 
103
- const selectedValues = computed<string[]>(() => {
104
- if (Array.isArray(props.modelValue)) {
105
- return props.modelValue.map(String);
106
- }
107
- return props.modelValue ? [String(props.modelValue)] : [];
108
- });
109
137
  const selectedSet = computed(() => new Set(selectedValues.value));
110
138
 
111
139
  const open = ref(false);
@@ -134,14 +162,11 @@ const columns = computed<CascaderNode[][]>(() => {
134
162
  return result;
135
163
  });
136
164
 
137
- function initialValueLabel(): string | undefined {
138
- const source = Array.isArray(props.initialValue)
139
- ? props.initialValue[0]
140
- : props.initialValue;
141
- if (!source || typeof source !== "object") return undefined;
142
- const label = (source as Record<string, unknown>)[keyMapping.value.label];
143
- return label === undefined || label === null ? undefined : String(label);
144
- }
165
+ /** The picked id no row answers (deleted, out of reach), if it is one. */
166
+ const notFoundValue = computed(() => {
167
+ const value = selectedValues.value[0];
168
+ return value && ancestry.value?.notFound.includes(value) ? value : undefined;
169
+ });
145
170
 
146
171
  const triggerLabel = computed(() => {
147
172
  if (selectedValues.value.length === 0) return "";
@@ -151,9 +176,15 @@ const triggerLabel = computed(() => {
151
176
  });
152
177
  }
153
178
  const value = selectedValues.value[0]!;
179
+ if (notFoundValue.value) return t("dms.form.relation.not_found");
154
180
  const node = findCascaderNode(roots.value, value);
155
181
  if (node) return formatCascaderPath(node);
156
- return initialValueLabel() ?? value;
182
+ const path = buildCascaderPathLabels(
183
+ ancestry.value?.rows ?? [],
184
+ keyMapping.value,
185
+ value,
186
+ );
187
+ return path.length > 0 ? path.join(CASCADER_PATH_SEPARATOR) : value;
157
188
  });
158
189
 
159
190
  const placeholderLabel = computed(() =>
@@ -339,12 +370,6 @@ watch(searchTerm, () => {
339
370
  };
340
371
  });
341
372
 
342
- onMounted(() => {
343
- if (selectedValues.value.length > 0 || props.initialValue) {
344
- void execute();
345
- }
346
- });
347
-
348
373
  function nodeClasses(node: CascaderNode, isHighlighted: boolean): string[] {
349
374
  return [
350
375
  "flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm",
@@ -383,7 +408,13 @@ function nodeClasses(node: CascaderNode, isHighlighted: boolean): string[] {
383
408
  :aria-label="placeholderLabel"
384
409
  v-bind="ariaAttrs"
385
410
  >
386
- <span v-if="triggerLabel" class="truncate">{{ triggerLabel }}</span>
411
+ <span
412
+ v-if="triggerLabel"
413
+ :class="['truncate', notFoundValue && 'text-muted']"
414
+ :title="notFoundValue ?? triggerLabel"
415
+ >
416
+ {{ triggerLabel }}
417
+ </span>
387
418
  <span v-else class="text-dimmed truncate">{{ placeholderLabel }}</span>
388
419
  </UButton>
389
420
 
@@ -176,7 +176,21 @@ function trackRangePick(value: unknown): void {
176
176
  if (!isHalfPicked) report(undefined);
177
177
  }
178
178
 
179
+ const isPickerOpen = ref(false);
180
+
181
+ /**
182
+ * Whether a pick completes the field: a day, or a range's end. A multiple
183
+ * picker stays open for the next day, a range for its end.
184
+ */
185
+ function isPickComplete(value: unknown): boolean {
186
+ if (!value || props.multiple) return false;
187
+ if (!props.range) return true;
188
+ const ends = value as Partial<StrictDateRange>;
189
+ return !!ends.start && !!ends.end;
190
+ }
191
+
179
192
  function onPickerToggle(open: boolean): void {
193
+ isPickerOpen.value = open;
180
194
  if (open) return;
181
195
  if (props.range && isHalfPicked) report(RANGE_INCOMPLETE);
182
196
  emitFormBlur();
@@ -214,6 +228,10 @@ const wrappedEmits = (event: EmitEvent, value: EmitValue) => {
214
228
  if (event === "update:modelValue" && props.range) trackRangePick(value);
215
229
  // After the update: the field re-validates its new value.
216
230
  if (event === "update:modelValue") emitFormChange();
231
+ // The day picked, the calendar closes, as it does when left.
232
+ if (event === "update:modelValue" && isPickComplete(value)) {
233
+ onPickerToggle(false);
234
+ }
217
235
  };
218
236
 
219
237
  function forwardEmit(event: EmitEvent, value: EmitValue): void {
@@ -286,10 +304,11 @@ const label = computed(() => {
286
304
  </script>
287
305
 
288
306
  <template>
289
- <UPopover @update:open="onPickerToggle">
307
+ <UPopover :open="isPickerOpen" @update:open="onPickerToggle">
290
308
  <UButton
291
309
  :id="props.id"
292
310
  :label="label ?? t('dms.form.select_date')"
311
+ :title="label"
293
312
  :ui="{
294
313
  ...FIELD_TRIGGER_UI,
295
314
  label: label ? 'truncate text-highlighted' : 'truncate text-dimmed',
@@ -1,10 +1,15 @@
1
1
  <script setup lang="ts">
2
2
  import { isVNode } from "vue";
3
+ import { NUMERIC_TYPE_IDS } from "../../../build/composables/data-types/useColumnValueRenderer";
3
4
 
4
5
  interface DisplayProps {
5
6
  modelValue?: unknown;
6
7
  type?: string;
7
8
  loading?: boolean;
9
+ /** The field's label, which names the value where it is not text (an image). */
10
+ label?: string;
11
+ /** The record the value belongs to, read as a grid cell reads its row. */
12
+ row?: Record<string, unknown>;
8
13
  }
9
14
 
10
15
  const props = defineProps<DisplayProps>();
@@ -33,10 +38,16 @@ const formattedValue = computed(() => {
33
38
  if (isEmptyValue(props.modelValue)) return EMPTY_FALLBACK;
34
39
  const formatter = dataType.value?.formatter?.default;
35
40
  if (!formatter) return String(props.modelValue);
36
- return formatter(props.modelValue, locale.value, options);
41
+ const formatterOptions = props.label
42
+ ? { ...options, fieldLabel: props.label }
43
+ : options;
44
+ return formatter(props.modelValue, locale.value, formatterOptions, props.row);
37
45
  });
38
46
 
39
47
  const isVNodeValue = computed(() => isVNode(formattedValue.value));
48
+ // Amounts read in tabular figures, so a read-only list's amounts and the
49
+ // totals under it line up in one font.
50
+ const isNumeric = computed(() => NUMERIC_TYPE_IDS.has(props.type ?? ""));
40
51
  </script>
41
52
 
42
53
  <template>
@@ -56,6 +67,7 @@ const isVNodeValue = computed(() => isVNode(formattedValue.value));
56
67
  <div
57
68
  v-else
58
69
  class="text-default flex min-h-9 items-center py-1.5 text-sm"
70
+ :class="isNumeric && 'tabular-nums'"
59
71
  :title="typeof formattedValue === 'string' ? formattedValue : undefined"
60
72
  >
61
73
  <component :is="formattedValue" v-if="isVNodeValue" />
@@ -536,7 +536,9 @@ onBeforeUnmount(() => {
536
536
  </script>
537
537
 
538
538
  <template>
539
- <div @paste="onPaste">
539
+ <!-- A size container: the preview sits beside its details once the field
540
+ itself has room (a drawer, a form column), not the window. -->
541
+ <div class="@container" @paste="onPaste">
540
542
  <input
541
543
  ref="fileInput"
542
544
  type="file"
@@ -740,9 +742,12 @@ onBeforeUnmount(() => {
740
742
  </span>
741
743
  </button>
742
744
 
743
- <div v-else class="flex flex-col gap-4 sm:flex-row sm:items-stretch">
745
+ <div
746
+ v-else
747
+ class="flex flex-col gap-4 @min-[480px]:flex-row @min-[480px]:items-stretch"
748
+ >
744
749
  <div
745
- class="group relative aspect-[4/3] w-full flex-none overflow-hidden rounded-xl shadow-sm sm:w-58"
750
+ class="group relative aspect-[4/3] w-full flex-none overflow-hidden rounded-xl shadow-sm @min-[480px]:w-58"
746
751
  :class="{
747
752
  'ring-primary ring-2': isDraggingOver,
748
753
  'ring-error ring-2': !isDraggingOver && invalid,