@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
@@ -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)));
@@ -335,7 +370,7 @@ watch(
335
370
  const previous = fieldValueSnapshots.get(field.id);
336
371
  fieldValueSnapshots.set(field.id, snapshot);
337
372
  if (previous === undefined || previous === snapshot) continue;
338
- if (!form.value || serverErrorValues.has(field.id)) continue;
373
+ if (!form.value || serverErrors.has(field.id)) continue;
339
374
  const pattern = fieldErrorPattern(field.id);
340
375
  if (!form.value.getErrors(pattern).length) continue;
341
376
  void form.value.validate({ name: errorNames(field), silent: true });
@@ -346,10 +381,12 @@ watch(
346
381
 
347
382
  const { t, locale, locales } = useI18n();
348
383
 
384
+ // A new row is created, not "changes" saved.
385
+ const DEFAULT_SUBMIT_LABEL = createsRecord
386
+ ? "dms.button.create"
387
+ : "dms.button.save_changes";
349
388
  const submitButtonLabel = computed(() =>
350
- props.submitLabel
351
- ? processI18n(props.submitLabel)
352
- : t("dms.button.save_changes"),
389
+ props.submitLabel ? processI18n(props.submitLabel) : t(DEFAULT_SUBMIT_LABEL),
353
390
  );
354
391
 
355
392
  watch(disabledFields, (newDisabled, oldDisabled) => {
@@ -413,6 +450,7 @@ onMounted(() => {
413
450
  function markFormClean(): void {
414
451
  isTouched = false;
415
452
  showsErrorSummary.value = false;
453
+ clearServerErrors();
416
454
  formDirty.markClean();
417
455
  instantSave?.start();
418
456
  }
@@ -441,11 +479,20 @@ const isDirty = computed(
441
479
 
442
480
  useUnsavedChanges({ dirty: isDirty, containerId, element: form });
443
481
 
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.
482
+ // A form offers Cancel when it has somewhere to go back to: its drawer or
483
+ // modal, `backTo` on a page, or the page of the app the user came from. An
484
+ // action form (send, invite, run) saves from its footer band, never from the
485
+ // floating bar.
447
486
  const isActionForm = props.kind === "action";
448
- const canCancel = inFormContainer || !!container || !!props.backTo;
487
+ // Read once mounted: the server renders no history.
488
+ const cameFromAppPage = ref(false);
489
+ onMounted(() => {
490
+ cameFromAppPage.value = !!previousAppPage();
491
+ });
492
+ const canCancel = computed(
493
+ () =>
494
+ inFormContainer || !!container || !!props.backTo || cameFromAppPage.value,
495
+ );
449
496
  const usesSaveBar = saveMode === "bar" && !inFormContainer && !isActionForm;
450
497
 
451
498
  /** Validates one field alone: whether it holds no error. */
@@ -479,19 +526,30 @@ const hasChangeableFields = computed(() =>
479
526
  const router = useDmsRouter();
480
527
  const route = useDmsRoute();
481
528
  /**
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.
529
+ * The page of the app this one was reached from, which the router records
530
+ * in the history entry: none for a page opened from a link outside the app,
531
+ * a bookmark or a new tab, where going back would leave the app.
532
+ */
533
+ function previousAppPage(): string | undefined {
534
+ const back = (window.history.state as { back?: unknown } | null)?.back;
535
+ return typeof back === "string" ? back : undefined;
536
+ }
537
+
538
+ /**
539
+ * Cancel: drops what was changed, then closes the drawer or modal, or leaves
540
+ * a page form for the page of the app the user came from (its list, when
541
+ * opened from one), or its `backTo` when the form was opened directly.
542
+ * Discarding first means leaving never asks about changes the user just chose
543
+ * to drop.
485
544
  */
486
545
  function cancelForm(): void {
546
+ if (isDirty.value) discardChanges();
487
547
  if (container) {
488
548
  void container.close();
489
549
  return;
490
550
  }
491
- const navigation = (window as { navigation?: { canGoBack?: boolean } })
492
- .navigation;
493
- if (navigation?.canGoBack ?? window.history.length > 1) {
494
- window.history.back();
551
+ if (previousAppPage()) {
552
+ router.back();
495
553
  return;
496
554
  }
497
555
  if (props.backTo) void router.push(pageLink(props.backTo));
@@ -537,6 +595,15 @@ provide(FORM_HANDLE_KEY, {
537
595
  revealField,
538
596
  });
539
597
  provide(FORM_FIELD_LOADING_KEY, setFieldLoading);
598
+ provide(
599
+ FORM_FIELD_SAVED_KEY,
600
+ fieldSavedMarker({
601
+ hasField: (id) => toValue(allFields).some((field) => field.id === id),
602
+ initialValues,
603
+ dirty: formDirty,
604
+ instantSave,
605
+ }),
606
+ );
540
607
  provide(FORM_CONTENT_LANGUAGE_KEY, "*");
541
608
 
542
609
  allFields.value.forEach((field) => {
@@ -547,6 +614,15 @@ allFields.value.forEach((field) => {
547
614
  ) as FormFieldValue;
548
615
  });
549
616
 
617
+ // A field the form submits a default under (the parent a table is filtered
618
+ // on) holds it from the start: it shows, and a mandatory one is filled.
619
+ for (const [id, value] of Object.entries(
620
+ fieldValuesOfSubmitDefaults(allFields.value, effectiveSubmitDefaults.value),
621
+ )) {
622
+ state.value[id] = cloneFormValue(value);
623
+ initialValues.value[id] = cloneFormValue(value) as FormFieldValue;
624
+ }
625
+
550
626
  let recordLoad: FormRecordLoad | undefined;
551
627
  if (props.fetchUrl) {
552
628
  // Keyed on the record too: the load outlives a drawer or modal, and the
@@ -648,7 +724,10 @@ function layoutClasses(inSectionCard: boolean) {
648
724
  if ((inSectionCard || inSection) && isHorizontal) {
649
725
  return SECTION_LAYOUT_CLASSES;
650
726
  }
651
- return FORM_LAYOUT_CLASSES[isHorizontal ? "horizontal" : "vertical"];
727
+ const orientation = isHorizontal ? "horizontal" : "vertical";
728
+ return inFormContainer
729
+ ? CONTAINER_LAYOUT_CLASSES[orientation]
730
+ : FORM_LAYOUT_CLASSES[orientation];
652
731
  }
653
732
 
654
733
  function isFieldDisabled(field: FormField): boolean {
@@ -657,7 +736,8 @@ function isFieldDisabled(field: FormField): boolean {
657
736
  }
658
737
 
659
738
  function isFieldHidden(field: FormField): boolean {
660
- return hiddenFields.value?.has(field.id) ?? false;
739
+ if (hiddenFields.value?.has(field.id)) return true;
740
+ return createsRecord && isHiddenOnCreate(field, state.value[field.id]);
661
741
  }
662
742
 
663
743
  function isFieldRequired(field: FormField): boolean {
@@ -684,7 +764,10 @@ provide(FORM_ENTRY_CONTEXT_KEY, {
684
764
  isFieldHidden,
685
765
  isFieldDisabled,
686
766
  isFieldRequired,
687
- showsChanges: !isInstant && !isActionForm,
767
+ isReadOnly: computed(() => !hasChangeableFields.value),
768
+ // A new row has no earlier value to compare with: "Changed · was empty"
769
+ // on every field it fills would say nothing.
770
+ showsChanges: !isInstant && !isActionForm && !createsRecord,
688
771
  isFieldChanged: (fieldId) => changedFieldIds.value.has(fieldId),
689
772
  baselineValue: (fieldId) => formDirty.baseline.value[fieldId],
690
773
  fieldSaveState: (fieldId) => instantSave?.states[fieldId] ?? "idle",
@@ -739,10 +822,6 @@ const sectionOfField = computed(() => {
739
822
  return owners;
740
823
  });
741
824
 
742
- watch(invalidFields, (invalid) => {
743
- if (invalid.size === 0) showsErrorSummary.value = false;
744
- });
745
-
746
825
  // One entry per row to fix: the unlabelled fields of a group share its
747
826
  // label, and its first invalid field stands for them.
748
827
  const errorSummaryItems = computed(() => {
@@ -916,6 +995,7 @@ onUnmounted(async () => {
916
995
  ref="form"
917
996
  :state
918
997
  :schema="localizedSchema"
998
+ :validate="validateServerErrors"
919
999
  :disabled="allFields.every((field) => field.disabled)"
920
1000
  @submit="onSubmit"
921
1001
  @error="onValidationError"
@@ -963,7 +1043,7 @@ onUnmounted(async () => {
963
1043
  />
964
1044
 
965
1045
  <DmsFormErrorSummary
966
- v-if="errorSummaryItems.length"
1046
+ v-if="showsErrorSummary"
967
1047
  :items="errorSummaryItems"
968
1048
  :class="hasSections ? 'mb-5' : 'mt-4'"
969
1049
  @select="focusField"
@@ -1014,7 +1094,9 @@ onUnmounted(async () => {
1014
1094
  :saving="loading || isAnyFieldLoading"
1015
1095
  :changes="changedFieldLabels"
1016
1096
  :form="formElementId"
1017
- :save-label="usesSaveBar ? props.submitLabel : submitButtonLabel"
1097
+ :save-label="
1098
+ usesSaveBar && !createsRecord ? props.submitLabel : submitButtonLabel
1099
+ "
1018
1100
  :cancellable="canCancel"
1019
1101
  :resettable="hasChangeableFields"
1020
1102
  :class="usesSaveBar ? 'mx-3' : surfaceClasses.foot"
@@ -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,
@@ -2,6 +2,10 @@
2
2
  import { pageRequestUrl } from "../../../build/composables/page/pageUrlValues";
3
3
  import type { SelectMenuItem } from "@nuxt/ui";
4
4
  import { refDebounced } from "@vueuse/core";
5
+ import {
6
+ rankAddressSuggestions,
7
+ suggestionPoolSize,
8
+ } from "../../../build/utils/addressSuggestions";
5
9
  import {
6
10
  formErrorsInjectionKey,
7
11
  formInputsInjectionKey,
@@ -48,6 +52,12 @@ interface AddressProps {
48
52
  label?: string;
49
53
  value?: string;
50
54
  };
55
+ /**
56
+ * ISO 3166-1 alpha-2 code ("BE") the country shows while the address has
57
+ * none, written with the first part typed, and the country whose places
58
+ * the street suggestions list first.
59
+ */
60
+ defaultCountry?: string;
51
61
  }
52
62
 
53
63
  const props = defineProps<AddressProps>();
@@ -68,7 +78,18 @@ const defaultAddress: AddressValue = {
68
78
  countryCode: "",
69
79
  };
70
80
 
71
- const safeModelValue = computed(() => modelValue.value ?? defaultAddress);
81
+ const defaultCountryCode = computed(
82
+ () => props.defaultCountry?.trim().toUpperCase() ?? "",
83
+ );
84
+
85
+ // An address with no country shows the default one; it is written with the
86
+ // first part typed, so an address left empty stays empty.
87
+ const safeModelValue = computed<AddressValue>(() => {
88
+ const value = modelValue.value ?? defaultAddress;
89
+ return value.countryCode || !defaultCountryCode.value
90
+ ? value
91
+ : { ...value, countryCode: defaultCountryCode.value };
92
+ });
72
93
 
73
94
  /* -------------------------------------------------------------------------- */
74
95
  /* Field state: which parts show the field's error */
@@ -308,6 +329,7 @@ interface PhotonResponse {
308
329
  interface AddressSuggestion {
309
330
  id: string;
310
331
  label: string;
332
+ countryCode?: string;
311
333
  patch: Partial<AddressValue>;
312
334
  }
313
335
 
@@ -329,6 +351,9 @@ const streetQueryDebounced = refDebounced(streetQuery, SEARCH_DEBOUNCE_MS);
329
351
  const suggestions = ref<AddressSuggestion[]>([]);
330
352
  const isSearching = ref(false);
331
353
  const showSuggestions = ref(false);
354
+ // Suggestions only open under the street field while it has the focus: an
355
+ // answer arriving once the user left it must not cover the next fields.
356
+ const isStreetFocused = ref(false);
332
357
 
333
358
  // Photon/OSM exposes a single `housenumber`. We only pull a box/unit out when it
334
359
  // is *explicitly* marked (BE "bte", NL "bus", "box", or a "/" separator). A bare
@@ -403,22 +428,31 @@ watch(streetQueryDebounced, async (query) => {
403
428
  params: {
404
429
  q: query,
405
430
  lang: photonLang.value,
406
- limit: photonLimit.value,
431
+ // A wider pool, so the places of the address's country can come
432
+ // first and the duplicates can go.
433
+ limit: suggestionPoolSize(photonLimit.value),
407
434
  },
408
435
  });
409
436
 
410
437
  if (seq !== searchSeq) return;
411
438
 
412
- suggestions.value = (response.features ?? [])
439
+ const found = (response.features ?? [])
413
440
  .filter(
414
441
  (feature) => feature.properties?.street || feature.properties?.name,
415
442
  )
416
443
  .map((feature, index) => ({
417
444
  id: String(index),
418
445
  label: formatPhotonLabel(feature.properties),
446
+ countryCode: feature.properties.countrycode?.toUpperCase(),
419
447
  patch: mapPhotonFeature(feature.properties),
420
448
  }));
421
- showSuggestions.value = suggestions.value.length > 0;
449
+ suggestions.value = rankAddressSuggestions(
450
+ found,
451
+ safeModelValue.value.countryCode,
452
+ photonLimit.value,
453
+ );
454
+ showSuggestions.value =
455
+ isStreetFocused.value && suggestions.value.length > 0;
422
456
  } catch {
423
457
  if (seq === searchSeq) suggestions.value = [];
424
458
  } finally {
@@ -438,7 +472,9 @@ const streetModel = computed({
438
472
  });
439
473
 
440
474
  const applySuggestion = (suggestion: AddressSuggestion) => {
441
- patchFields(suggestion.patch);
475
+ // A place the geocoder gives no country keeps the one shown.
476
+ const { countryCode, ...located } = suggestion.patch;
477
+ patchFields(countryCode ? suggestion.patch : located);
442
478
  // Bump the sequence so any still-in-flight request is discarded and cannot
443
479
  // repopulate the list / reopen the dropdown after the user picked a result.
444
480
  searchSeq++;
@@ -449,17 +485,27 @@ const applySuggestion = (suggestion: AddressSuggestion) => {
449
485
  let blurTimer: ReturnType<typeof setTimeout> | undefined;
450
486
 
451
487
  const onStreetFocus = () => {
488
+ isStreetFocused.value = true;
452
489
  if (autocompleteEnabled.value && suggestions.value.length > 0) {
453
490
  showSuggestions.value = true;
454
491
  }
455
492
  };
456
493
 
457
494
  const onStreetBlur = () => {
495
+ isStreetFocused.value = false;
458
496
  blurTimer = setTimeout(() => {
459
497
  showSuggestions.value = false;
460
498
  }, BLUR_CLOSE_DELAY_MS);
461
499
  };
462
500
 
501
+ /** Escape closes the suggestions, as it would any list; typing reopens it. */
502
+ const onStreetEscape = (event: KeyboardEvent) => {
503
+ if (!showSuggestions.value) return;
504
+ // Only the list closes, not the drawer or modal around the form.
505
+ event.stopPropagation();
506
+ showSuggestions.value = false;
507
+ };
508
+
463
509
  onBeforeUnmount(() => {
464
510
  if (blurTimer) clearTimeout(blurTimer);
465
511
  });
@@ -532,6 +578,7 @@ const streetPlaceholder = computed(() => {
532
578
  autocomplete="off"
533
579
  @focus="onStreetFocus"
534
580
  @blur="onStreetBlur"
581
+ @keydown.esc="onStreetEscape"
535
582
  />
536
583
  <div
537
584
  v-if="autocompleteEnabled && showSuggestions && suggestions.length"
@@ -635,6 +682,7 @@ const streetPlaceholder = computed(() => {
635
682
  value-key="value"
636
683
  label-key="label"
637
684
  searchable
685
+ :aria-label="t('dms.a11y.show_options')"
638
686
  @update:model-value="updateField('countryCode', $event)"
639
687
  />
640
688
  </div>
@@ -21,6 +21,12 @@ interface InputCodeProps {
21
21
  completions?: CodeEditorCompletion[];
22
22
  placeholder?: string;
23
23
  disabled?: boolean;
24
+ /**
25
+ * Fills the height its flex parent leaves it (a drawer's body, a stacked
26
+ * `DmsFieldRow fill`) and scrolls inside it: `minLines` stays its floor
27
+ * (the parent scrolls below it), `maxLines` is ignored.
28
+ */
29
+ fill?: boolean;
24
30
  }
25
31
 
26
32
  interface CompletionsResponse {
@@ -35,6 +41,7 @@ const props = withDefaults(defineProps<InputCodeProps>(), {
35
41
  completions: undefined,
36
42
  placeholder: undefined,
37
43
  id: undefined,
44
+ fill: false,
38
45
  });
39
46
  const model = defineModel<string | null | undefined>();
40
47
  const { t } = useI18n();
@@ -152,13 +159,18 @@ onBeforeUnmount(() => {
152
159
  </script>
153
160
 
154
161
  <template>
162
+ <!-- Filling, the root clips without scrolling (`overflow-clip`): its
163
+ minimum stays its content's, the editor's floor, and the parent scrolls
164
+ below it. The editor's area holds no size of its own (`contain-size`): a
165
+ long text scrolls in it instead of growing the root. -->
155
166
  <div
156
- class="border-accented overflow-hidden rounded-md border focus-within:ring-2"
157
- :class="
167
+ class="border-accented rounded-md border focus-within:ring-2"
168
+ :class="[
158
169
  isInvalid
159
170
  ? 'border-error focus-within:ring-error/25'
160
- : 'focus-within:border-primary focus-within:ring-primary/25'
161
- "
171
+ : 'focus-within:border-primary focus-within:ring-primary/25',
172
+ fill ? 'flex flex-1 flex-col overflow-clip' : 'overflow-hidden',
173
+ ]"
162
174
  :style="sizeStyle"
163
175
  @focusout="emitFormBlur"
164
176
  >
@@ -167,11 +179,23 @@ onBeforeUnmount(() => {
167
179
  <pre
168
180
  v-if="!isReady"
169
181
  class="text-highlighted min-h-(--dms-code-min-height) overflow-auto px-3 py-2 font-mono text-[12.5px] whitespace-pre-wrap"
182
+ :class="fill && 'flex-1 contain-size'"
170
183
  >{{ model }}</pre
171
184
  >
185
+ <!-- Important: CodeMirror's unlayered `.cm-content { min-height: 100% }`
186
+ outranks a utility, and `min-lines` would never apply. -->
172
187
  <div
173
188
  ref="host"
174
- class="[&_.cm-content]:min-h-(--dms-code-min-height) [&_.cm-scroller]:max-h-(--dms-code-max-height) [&_.cm-scroller]:overflow-auto"
189
+ data-dms-code-host
190
+ class="[&_.cm-content]:min-h-(--dms-code-min-height)! [&_.cm-scroller]:overflow-auto"
191
+ :class="
192
+ fill
193
+ ? [
194
+ 'flex flex-col contain-size [&_.cm-editor]:min-h-0 [&_.cm-editor]:flex-1 [&_.cm-scroller]:min-h-0 [&_.cm-scroller]:flex-1',
195
+ isReady && 'min-h-(--dms-code-min-height) flex-1',
196
+ ]
197
+ : '[&_.cm-scroller]:max-h-(--dms-code-max-height)'
198
+ "
175
199
  />
176
200
  <div
177
201
  class="border-default text-dimmed flex items-center gap-3 border-t bg-(--dms-bg-muted) px-3 py-1 font-mono text-[11px]"