@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
@@ -64,3 +64,42 @@ export function sameRelationValue(
64
64
  };
65
65
  return sameFormValue(comparable(left), comparable(right));
66
66
  }
67
+
68
+ /**
69
+ * What a relation field asks its target's `select` route for to label the
70
+ * ids it holds: those rows alone, matched on the field's value key (the
71
+ * route falls back to the row id when none is mapped).
72
+ */
73
+ export interface RelationIdsQuery {
74
+ ids: string[];
75
+ idKey?: string;
76
+ limit: number;
77
+ }
78
+
79
+ const isIdValue = (value: unknown): value is string | number =>
80
+ (typeof value === "string" && value !== "") || typeof value === "number";
81
+
82
+ /**
83
+ * The ids a relation value holds that no known row labels yet — a form whose
84
+ * read answers the id (`"u-5"`) rather than the joined row. A joined row
85
+ * labels itself, so only plain ids are returned, each once.
86
+ */
87
+ export function unlabeledRelationIds(
88
+ value: unknown,
89
+ labeledIds: ReadonlySet<string>,
90
+ ): string[] {
91
+ const ids = [value].flat().filter(isIdValue).map(String);
92
+ return [...new Set(ids)].filter((id) => !labeledIds.has(id));
93
+ }
94
+
95
+ /** The `select` query answering the rows of `ids`. */
96
+ export function relationIdsQuery(
97
+ ids: string[],
98
+ keyMapping?: RelationKeyMapping,
99
+ ): RelationIdsQuery {
100
+ return {
101
+ ids,
102
+ ...(keyMapping?.value ? { idKey: keyMapping.value } : {}),
103
+ limit: ids.length,
104
+ };
105
+ }
@@ -1,6 +1,6 @@
1
1
  import { h, type VNodeChild } from "vue";
2
2
  import type { DataType } from "#dms-core/app/composables/data-types/useDataType";
3
- import { useComposedText } from "../../../../../dms-core/app/composables/translation/useComposedText";
3
+ import { getAppComposedText } from "../../../../../dms-core/app/composables/translation/useComposedText";
4
4
  import {
5
5
  type ComposableText,
6
6
  isComposedText,
@@ -53,7 +53,7 @@ function readPrimary(
53
53
  opts: TwoLineOptions,
54
54
  row: Row,
55
55
  ): VNodeChild | undefined {
56
- const { processText } = useComposedText();
56
+ const { processText } = getAppComposedText();
57
57
  if (opts.primaryField) {
58
58
  const text = processText(
59
59
  readRowField(row, opts.primaryField) as ComposableText,
@@ -76,7 +76,7 @@ function renderTwoLine(
76
76
  options: unknown,
77
77
  row: Row,
78
78
  ) {
79
- const { processText } = useComposedText();
79
+ const { processText } = getAppComposedText();
80
80
  const opts = (options ?? {}) as TwoLineOptions;
81
81
  const primary = readPrimary(value, locale, opts, row);
82
82
  const sub = readSubline(readRowField(row, opts.subField), {
@@ -20,12 +20,14 @@ export interface RenderableColumn {
20
20
  }
21
21
 
22
22
  // v2 cell anatomy: amounts are right-aligned mono figures in the ink color,
23
- // dates 12px muted mono. A data type can give cells a shorter rendering
23
+ // dates 12px muted mono. Where a record is read (the detail surface), amounts
24
+ // keep the form's own font with tabular figures, as its read-only values do. A data type can give cells a shorter rendering
24
25
  // through a `cell` formatter (the date type does); others use `default`.
25
26
  export const NUMERIC_TYPE_IDS = new Set(["number", "price", "percentage"]);
26
27
  const DATE_TYPE_IDS = new Set(["date"]);
27
28
  const NUMERIC_CELL_CLASS =
28
29
  "block text-right font-mono tabular-nums text-highlighted";
30
+ const DETAIL_NUMERIC_CELL_CLASS = "block text-right tabular-nums";
29
31
  const NUMERIC_EMPTY_CELL_CLASS = "block text-right text-dimmed";
30
32
  const DATE_CELL_CLASS = "font-mono text-xs text-muted tabular-nums";
31
33
  const DEFAULT_FALLBACK = "-";
@@ -101,7 +103,9 @@ export function useColumnValueRenderer() {
101
103
  return formatted as VNodeChild;
102
104
  }
103
105
  if (isNumeric) {
104
- return h("span", { class: NUMERIC_CELL_CLASS }, String(formatted));
106
+ const numericClass =
107
+ surface === "detail" ? DETAIL_NUMERIC_CELL_CLASS : NUMERIC_CELL_CLASS;
108
+ return h("span", { class: numericClass }, String(formatted));
105
109
  }
106
110
  if (!column.display && typeId && DATE_TYPE_IDS.has(typeId)) {
107
111
  return h("span", { class: DATE_CELL_CLASS }, String(formatted));
@@ -0,0 +1,15 @@
1
+ import type { FormField } from "../../../composables/form/types/field";
2
+ import { isEmptyFormValue } from "../unsaved-changes/formValue";
3
+
4
+ /**
5
+ * Whether a form creating a record leaves a field out: a read-only one that
6
+ * holds nothing yet (a number, a date, a total the server sets once the
7
+ * record exists) would only read "—". It shows once it holds a value (a
8
+ * default, one a rule computed), and a field declaring `showEmptyOnCreate`
9
+ * keeps its place regardless.
10
+ */
11
+ export function isHiddenOnCreate(field: FormField, value: unknown): boolean {
12
+ if (field.showEmptyOnCreate) return false;
13
+ if (!field.disabled && !field.readonly) return false;
14
+ return isEmptyFormValue(value);
15
+ }
@@ -0,0 +1,40 @@
1
+ import type { Ref } from "vue";
2
+ import type { MarkFieldSaved } from "../../../composables/form/types/field-saved";
3
+ import type {
4
+ FormData,
5
+ FormFieldValue,
6
+ } from "../../../composables/form/types/value";
7
+ import { cloneFormValue } from "../../../composables/form/useForm";
8
+ import type { UseFormDirtyReturn } from "../../../composables/unsaved-changes/useFormDirty";
9
+ import type { InstantForm } from "./useInstantForm";
10
+
11
+ type FormValues = Record<string, unknown>;
12
+
13
+ /** What a field the server saved on its own updates in its form. */
14
+ export interface FieldSavedTargets {
15
+ /** Whether the form has the field: another control's id is left alone. */
16
+ hasField: (id: string) => boolean;
17
+ /** The values "Discard" puts back. */
18
+ initialValues: Ref<FormData>;
19
+ /** The values the form reads as unchanged. */
20
+ dirty: Pick<UseFormDirtyReturn<FormValues>, "baseline" | "markClean">;
21
+ /** The instant save of a form saving as it goes. */
22
+ instantSave?: Pick<InstantForm, "confirmField">;
23
+ }
24
+
25
+ /**
26
+ * The form's answer to a field whose value the server stored on its own (a
27
+ * rotated secret): that value becomes the one the field is compared with and
28
+ * the one "Discard" puts back, and an instant form does not save it again.
29
+ */
30
+ export function fieldSavedMarker(targets: FieldSavedTargets): MarkFieldSaved {
31
+ return (id, value) => {
32
+ if (!targets.hasField(id)) return;
33
+ targets.initialValues.value = {
34
+ ...targets.initialValues.value,
35
+ [id]: cloneFormValue(value as FormFieldValue) as FormFieldValue,
36
+ };
37
+ targets.dirty.markClean({ ...targets.dirty.baseline.value, [id]: value });
38
+ targets.instantSave?.confirmField(id, value);
39
+ };
40
+ }
@@ -0,0 +1,62 @@
1
+ /** What a `SetFieldValue` watch writes, and where. */
2
+ export interface SetFieldValueParams {
3
+ targetField: string;
4
+ value?: unknown;
5
+ valueMap?: Record<string, unknown>;
6
+ }
7
+
8
+ /** The payload of a form's `FieldChange` event. */
9
+ export interface FieldChangePayload {
10
+ fieldId?: string;
11
+ value?: unknown;
12
+ /** The field still holds the value the form started from. */
13
+ initial?: boolean;
14
+ }
15
+
16
+ /** A value to write, or nothing to write. */
17
+ export type FieldValueWrite = { value: unknown } | undefined;
18
+
19
+ type MapKey = string | number | boolean;
20
+
21
+ function isMapKey(value: unknown): value is MapKey {
22
+ return ["string", "number", "boolean"].includes(typeof value);
23
+ }
24
+
25
+ /**
26
+ * What a `SetFieldValue` watch writes for a field change: its `value`, or the
27
+ * entry of its `valueMap` named by the changed field's value. Nothing on a
28
+ * value the form started from — the record keeps what it stored — nor for a
29
+ * value the map has no entry for.
30
+ */
31
+ export function resolveFieldValueWrite(
32
+ params: SetFieldValueParams,
33
+ payload: FieldChangePayload | undefined,
34
+ ): FieldValueWrite {
35
+ if (payload?.initial) return undefined;
36
+ if (params.valueMap) {
37
+ const key = payload?.value;
38
+ if (!isMapKey(key) || !Object.hasOwn(params.valueMap, String(key))) {
39
+ return undefined;
40
+ }
41
+ return { value: params.valueMap[String(key)] };
42
+ }
43
+ if (!Object.hasOwn(params, "value")) return undefined;
44
+ return { value: params.value };
45
+ }
46
+
47
+ /**
48
+ * Tells, for each `FieldChange` a form sends, whether the field still holds
49
+ * the value it started from and never held another: once a field changed,
50
+ * coming back to that value is a change too.
51
+ */
52
+ export function createStartingValueTracker(
53
+ isStartingValue: (fieldId: string, value: unknown) => boolean,
54
+ ): (fieldId: string, value: unknown) => boolean {
55
+ const changedFieldIds = new Set<string>();
56
+ return (fieldId, value) => {
57
+ if (changedFieldIds.has(fieldId)) return false;
58
+ if (isStartingValue(fieldId, value)) return true;
59
+ changedFieldIds.add(fieldId);
60
+ return false;
61
+ };
62
+ }
@@ -20,6 +20,11 @@ export interface FormEntryContext {
20
20
  isFieldHidden: (field: FormField) => boolean;
21
21
  isFieldDisabled: (field: FormField) => boolean;
22
22
  isFieldRequired: (field: FormField) => boolean;
23
+ /**
24
+ * Whether nothing in the form can be changed (a record's details): its
25
+ * rows show values without the descriptions that tell how to fill them.
26
+ */
27
+ isReadOnly: Readonly<Ref<boolean>>;
23
28
  /**
24
29
  * Whether a field points out a change waiting to be saved: not in an
25
30
  * instant form (nothing waits) nor in an action form (nothing was saved).
@@ -3,15 +3,19 @@
3
3
  *
4
4
  * - `record` (default): a record, a settings page, a table view's form. It
5
5
  * keeps its values once saved; Cancel while there is nothing to save when
6
- * it has somewhere to go back to, then "Unsaved changes" with Discard and
7
- * Save.
6
+ * it has somewhere to go back to, then "Unsaved changes" with Save and,
7
+ * beside it, Cancel again (discard and leave) or, with nowhere to go back
8
+ * to, Discard.
9
+ * - `create`: a form that makes a new record (a table view's add form, the
10
+ * duplicate it opens). Its submit from the start, no "Unsaved changes"
11
+ * list, since all it holds is new.
8
12
  * - `action`: a form that does something each time (send a notification,
9
13
  * invite someone, run a job). Its own submit label, always ready: what it
10
14
  * holds untouched, its defaults included, is a payload too. Beside it,
11
15
  * Reset once a value changed, Cancel before that in a drawer or a modal.
12
16
  * It empties after a successful submit.
13
17
  */
14
- export type FormKind = "record" | "action";
18
+ export type FormKind = "record" | "create" | "action";
15
19
 
16
20
  /** How a form offers to save (interface-dms `FormSaveMode`). */
17
21
  export type FormSaveMode = "bar" | "footer" | "none" | "instant";
@@ -39,7 +43,10 @@ export interface SaveBarContext {
39
43
  resettable: boolean;
40
44
  }
41
45
 
42
- /** The button beside the submit: Discard / Reset, or Cancel. */
46
+ /**
47
+ * The button beside the submit: Discard / Reset (values back, the form
48
+ * stays), or Cancel (values back, the form is left).
49
+ */
43
50
  export type SaveBarSecondary = "discard" | "cancel";
44
51
 
45
52
  /** What a form's save bar shows. */
@@ -67,11 +74,20 @@ const HIDDEN_BAR: SaveBarState = {
67
74
  };
68
75
 
69
76
  /**
70
- * A record form's bar: "Unsaved changes" with Discard and Save once a value
71
- * changed, Cancel before that when it has somewhere to go back to.
77
+ * A record form's bar: "Unsaved changes" and Save once a value changed, Cancel
78
+ * before that when it has somewhere to go back to. Beside Save, a form with
79
+ * somewhere to go back to keeps Cancel, which drops the changes and leaves,
80
+ * the same as before any change: one click, never a reverted form left open.
81
+ * A form with nowhere to go back to offers Discard.
72
82
  */
73
83
  function recordSaveBar(context: SaveBarContext): SaveBarState {
74
- if (context.dirty) return { ...HIDDEN_BAR, isHidden: false };
84
+ if (context.dirty) {
85
+ return {
86
+ ...HIDDEN_BAR,
87
+ secondary: context.cancellable ? "cancel" : "discard",
88
+ isHidden: false,
89
+ };
90
+ }
75
91
  if (!context.cancellable) return HIDDEN_BAR;
76
92
  return {
77
93
  showsStatus: false,
@@ -82,6 +98,24 @@ function recordSaveBar(context: SaveBarContext): SaveBarState {
82
98
  };
83
99
  }
84
100
 
101
+ /**
102
+ * A create form's bar: its submit from the start, since a duplicate (or a
103
+ * row its defaults fill) is saved as it is, with no "Unsaved changes" list,
104
+ * since all it holds is new. Beside it, Cancel when it has somewhere to go
105
+ * back to, Discard once a value changed otherwise.
106
+ */
107
+ function createSaveBar(context: SaveBarContext): SaveBarState {
108
+ const bar: SaveBarState = {
109
+ showsStatus: false,
110
+ showsSubmit: true,
111
+ isSubmitHeld: false,
112
+ isHidden: false,
113
+ };
114
+ if (context.cancellable) return { ...bar, secondary: "cancel" };
115
+ if (context.dirty) return { ...bar, secondary: "discard" };
116
+ return bar;
117
+ }
118
+
85
119
  /**
86
120
  * An action form's bar: its submit, never held, so a pre-filled form sends as
87
121
  * it is. Beside it, Reset once a value changed; before that, Cancel in a
@@ -106,6 +140,7 @@ function actionSaveBar(context: SaveBarContext): SaveBarState {
106
140
 
107
141
  const SAVE_BARS: Record<FormKind, (context: SaveBarContext) => SaveBarState> = {
108
142
  record: recordSaveBar,
143
+ create: createSaveBar,
109
144
  action: actionSaveBar,
110
145
  };
111
146
 
@@ -2,6 +2,11 @@ import type {
2
2
  FieldRowLayout,
3
3
  FieldRowSpacing,
4
4
  } from "../../../components/field-row/FieldRow.vue";
5
+ import {
6
+ type FormField,
7
+ type FormFieldOrGroup,
8
+ isFieldGroup,
9
+ } from "../../../composables/form/types/field";
5
10
 
6
11
  export type FormOrientation = "horizontal" | "vertical";
7
12
  export type FormSurface = "card" | "container" | "section" | "sections";
@@ -41,6 +46,25 @@ export const FORM_LAYOUT_CLASSES: Record<FormOrientation, FormLayoutClasses> = {
41
46
  },
42
47
  };
43
48
 
49
+ // A form in a drawer or a modal: the same rows on a tighter rhythm, the
50
+ // container's width being scarcer than a page's. A row with one line of
51
+ // value is 57px beside its label; stacked (a phone, a narrow drawer) the
52
+ // label sits 6px above its control. Read-only details and editable forms
53
+ // share the rows, so both read alike.
54
+ export const CONTAINER_LAYOUT_CLASSES: Record<
55
+ FormOrientation,
56
+ FormLayoutClasses
57
+ > = {
58
+ horizontal: {
59
+ rows: "flex flex-col",
60
+ row: { layout: "form", inset: false, spacing: "dense" },
61
+ },
62
+ vertical: {
63
+ rows: "flex flex-col gap-3 py-3",
64
+ row: { layout: "stack", inset: false, spacing: "list" },
65
+ },
66
+ };
67
+
44
68
  // A card form draws its own head band and sticky footer band; inside a
45
69
  // modal or drawer the container is the surface and already pads the body.
46
70
  // The footer sticks to the bottom of the scroll area while the form runs
@@ -82,10 +106,88 @@ export const SECTION_LAYOUT_CLASSES: FormLayoutClasses = {
82
106
  row: { layout: "form", inset: true, spacing: "row" },
83
107
  };
84
108
 
85
- // Grouped controls sit side by side once the row is wide enough for its
86
- // label column (FieldRow's 560px container step), whatever the viewport: a
87
- // narrow modal or drawer form stacks them.
109
+ // Grouped controls share the row while each keeps a usable width (11rem: a
110
+ // date, a select showing its value), and wrap onto the next line once they
111
+ // would shrink below it, so a group of five in a modal, a drawer or on a
112
+ // phone wraps instead of drawing its controls over one another. A member
113
+ // alone on its line takes the whole width; `min(100%, ...)` keeps a row
114
+ // narrower than the minimum from overflowing.
88
115
  export const GROUP_FIELDS_CLASSES: Record<FormOrientation, string> = {
89
- horizontal: "flex flex-col gap-2.5 @min-[560px]:flex-row",
116
+ horizontal:
117
+ "grid grid-cols-[repeat(auto-fit,minmax(min(100%,11rem),1fr))] gap-x-3 gap-y-2.5",
90
118
  vertical: "flex flex-col gap-2.5",
91
119
  };
120
+
121
+ // Fields whose value is a block of its own (a table of rows, pairs, code,
122
+ // formatted text, a gallery): beside a 240px label they get what is left of
123
+ // a drawer, where a repeater falls into its stacked cards. They take the
124
+ // row's full width under their label instead, whatever the form's layout.
125
+ const WIDE_FIELD_COMPONENTS: ReadonlySet<string> = new Set([
126
+ "dms-repeater",
127
+ "dms-key-value",
128
+ "dms-input-code",
129
+ "dms-rich-text",
130
+ ]);
131
+ // The same values shown read-only, drawn from the field's type.
132
+ const WIDE_FIELD_TYPES: ReadonlySet<string> = new Set([
133
+ "array",
134
+ "key_value",
135
+ "code",
136
+ "rich_text",
137
+ ]);
138
+ // Files and images drop their list or gallery under the control when
139
+ // several are allowed.
140
+ const MULTIPLE_FILE_COMPONENTS: ReadonlySet<string> = new Set([
141
+ "dms-file",
142
+ "dms-image",
143
+ ]);
144
+ // A textarea taller than the browser's three rows is a long text.
145
+ const SHORT_TEXTAREA_ROWS = 3;
146
+
147
+ /** The options of a field's control that make it wide. */
148
+ interface WideFieldOptions {
149
+ multiple?: boolean;
150
+ rows?: number;
151
+ }
152
+
153
+ /** Whether a field takes the row's full width, its label above it. */
154
+ export function isWideField(field: FormField): boolean {
155
+ if (field.type && WIDE_FIELD_TYPES.has(field.type)) return true;
156
+ const name = field.component.componentName ?? "";
157
+ if (WIDE_FIELD_COMPONENTS.has(name)) return true;
158
+ const options: WideFieldOptions = field.component.options ?? {};
159
+ if (MULTIPLE_FILE_COMPONENTS.has(name)) return options.multiple === true;
160
+ if (name !== "dms-textarea") return false;
161
+ return (options.rows ?? 0) > SHORT_TEXTAREA_ROWS;
162
+ }
163
+
164
+ /**
165
+ * How a form row is drawn: a wide field, or a group holding one, stacks its
166
+ * label above it where the form would set it beside.
167
+ */
168
+ export function entryRowLayout(
169
+ row: FormRowLayout,
170
+ fields: readonly FormField[],
171
+ ): FormRowLayout {
172
+ if (row.layout !== "form" || !fields.some(isWideField)) return row;
173
+ return { ...row, layout: "stack" };
174
+ }
175
+
176
+ /** The fields a form entry draws: its own, or its group's. */
177
+ export function entryFields(entry: FormFieldOrGroup): FormField[] {
178
+ return isFieldGroup(entry) ? entry.fields : [entry];
179
+ }
180
+
181
+ /** Classes of a grouped field: its own label above its control. */
182
+ export interface GroupMemberClasses {
183
+ root: string;
184
+ label: string;
185
+ }
186
+
187
+ // A member's label is a step below the group's (FieldRow's 13px semibold
188
+ // label), which heads the cluster.
189
+ export const GROUP_MEMBER_CLASSES: GroupMemberClasses = {
190
+ root: "grid min-w-0 flex-1 content-start gap-1.5",
191
+ label:
192
+ "flex flex-wrap items-center gap-1 text-[12.5px] font-medium text-toned",
193
+ };
@@ -1,15 +1,19 @@
1
1
  import { type Ref, ref, watch } from "vue";
2
2
  import { apiErrorStatus } from "#dms-core/app/composables/useFieldErrors";
3
- import { HTTP_FORBIDDEN } from "#dms-core/app/utils/http-status";
3
+ import {
4
+ HTTP_FORBIDDEN,
5
+ HTTP_NOT_FOUND,
6
+ } from "#dms-core/app/utils/http-status";
4
7
 
5
8
  /**
6
9
  * Why a form could not load the record it edits: `forbidden` when the server
7
10
  * refused the caller the read (a role that may edit rows but not view them),
8
11
  * `missing` when its address lacks the record's id (a page reached without
9
- * its `?id=`), `error` for anything else (a network failure, a server error,
10
- * a row gone).
12
+ * its `?id=`), `not_found` when the server answered 404 (the record was
13
+ * deleted, or never existed), `error` for anything else (a network failure,
14
+ * a server error).
11
15
  */
12
- export type FormLoadFailure = "forbidden" | "missing" | "error";
16
+ export type FormLoadFailure = "forbidden" | "missing" | "not_found" | "error";
13
17
 
14
18
  /** What a form knows of the record it is about, as far as `missing` goes. */
15
19
  export interface FormRecordAddress {
@@ -51,7 +55,9 @@ export interface FormRecordLoad {
51
55
  /** The failure a load error stands for; `null` when there is none. */
52
56
  export function formLoadFailure(error: unknown): FormLoadFailure | null {
53
57
  if (!error) return null;
54
- return apiErrorStatus(error) === HTTP_FORBIDDEN ? "forbidden" : "error";
58
+ const status = apiErrorStatus(error);
59
+ if (status === HTTP_FORBIDDEN) return "forbidden";
60
+ return status === HTTP_NOT_FOUND ? "not_found" : "error";
55
61
  }
56
62
 
57
63
  /** How a form takes the answers of its record's later reads. */
@@ -133,3 +133,35 @@ export function sectionState(
133
133
  invalidCount: section.fieldIds.filter((id) => invalid.has(id)).length,
134
134
  };
135
135
  }
136
+
137
+ /** Where a section's card starts, from the top of the visible area. */
138
+ export interface SectionOffset {
139
+ id: string;
140
+ top: number;
141
+ }
142
+
143
+ /** What the side list reads to tell which section is being read. */
144
+ export interface SectionSpyView {
145
+ /** The sections, in order. */
146
+ sections: SectionOffset[];
147
+ /** Offset of the reading line from the top of the visible area. */
148
+ readingLine: number;
149
+ /** Scrolled to the end: nothing below can rise to the reading line. */
150
+ isAtEnd: boolean;
151
+ }
152
+
153
+ /**
154
+ * The section being read: the last one whose top has passed the reading
155
+ * line, the first before any has, and the last once the form is scrolled to
156
+ * its end — the sections at the bottom of a long form never rise that far,
157
+ * and would otherwise never be current. One section at a time.
158
+ */
159
+ export function currentSectionId(view: SectionSpyView): string | undefined {
160
+ if (view.isAtEnd) return view.sections.at(-1)?.id;
161
+ let current = view.sections[0]?.id;
162
+ for (const section of view.sections) {
163
+ if (section.top > view.readingLine) break;
164
+ current = section.id;
165
+ }
166
+ return current;
167
+ }
@@ -22,6 +22,11 @@ export interface FormValidationField {
22
22
  type?: string;
23
23
  /** Holds one value per content language. */
24
24
  localized?: boolean;
25
+ /**
26
+ * The language a localized field falls back to: required as soon as the
27
+ * field holds any language, and where its required error goes.
28
+ */
29
+ fallbackLocale?: string;
25
30
  /** A box to tick: unticked, it is missing. */
26
31
  acceptance?: boolean;
27
32
  /**
@@ -63,6 +68,32 @@ function issueField(issue: { path: PropertyKey[] }): PropertyKey | undefined {
63
68
  return issue.path[0];
64
69
  }
65
70
 
71
+ /** Whether a localized value fills some language but not its fallback one. */
72
+ function lacksFallbackLocale(
73
+ field: FormValidationField,
74
+ value: unknown,
75
+ ): boolean {
76
+ if (!field.localized || !field.fallbackLocale) return false;
77
+ if (typeof value !== "object" || value === null) return false;
78
+ const languages = value as Record<string, unknown>;
79
+ const isAnyFilled = Object.values(languages).some(
80
+ (text) => !isBlankValue(text, field.type),
81
+ );
82
+ return (
83
+ isAnyFilled && isBlankValue(languages[field.fallbackLocale], field.type)
84
+ );
85
+ }
86
+
87
+ /** Where a localized field's required error goes: the language to fill. */
88
+ function requiredPath(
89
+ field: FormValidationField,
90
+ locale: string | undefined,
91
+ ): PropertyKey[] {
92
+ if (!field.localized) return [field.id];
93
+ const language = field.fallbackLocale ?? locale;
94
+ return language ? [field.id, language] : [field.id];
95
+ }
96
+
66
97
  /** An issue saying the value is not of a branch's type at all. */
67
98
  function isTypeMismatch(issue: z.ZodIssue, path: PropertyKey[]): boolean {
68
99
  return issue.code === "invalid_type" && issue.path.length === path.length;
@@ -117,6 +148,10 @@ export async function validateFormState(
117
148
  for (const field of context.fields) {
118
149
  const value = state[field.id];
119
150
  if (reported.has(field.id)) continue;
151
+ if (lacksFallbackLocale(field, value)) {
152
+ missing.add(field.id);
153
+ continue;
154
+ }
120
155
  const isBlank = field.acceptance
121
156
  ? value !== true
122
157
  : isBlankValue(value, field.type);
@@ -159,10 +194,7 @@ export async function validateFormState(
159
194
  if (!missing.has(field.id)) continue;
160
195
  issues.push({
161
196
  message: context.requiredMessage,
162
- path:
163
- field.localized && context.locale
164
- ? [field.id, context.locale]
165
- : [field.id],
197
+ path: requiredPath(field, context.locale),
166
198
  });
167
199
  }
168
200
 
@@ -0,0 +1,19 @@
1
+ import type { DataType } from "#dms-core/app/composables/data-types/useDataType";
2
+
3
+ /**
4
+ * What a read-only field shows: for a type whose form state is mapped from
5
+ * the value the server sent (a relation loaded as its row, held as its id),
6
+ * that value while the state still references it, so the field reads as the
7
+ * table cell does — the row's label and avatar rather than its id. Any other
8
+ * value, or one the form changed, is shown as the state holds it.
9
+ */
10
+ export function readonlyFieldValue(
11
+ value: unknown,
12
+ loaded: unknown,
13
+ dataType: DataType | undefined,
14
+ options?: unknown,
15
+ ): unknown {
16
+ if (!dataType?.beforeStateMapper || !dataType.isSameValue) return value;
17
+ if (loaded === undefined || loaded === null) return value;
18
+ return dataType.isSameValue(value, loaded, options) ? loaded : value;
19
+ }
@@ -1,3 +1,9 @@
1
+ import {
2
+ type FieldChangePayload,
3
+ type SetFieldValueParams,
4
+ resolveFieldValueWrite,
5
+ } from "./fieldValueRule";
6
+
1
7
  type FieldSetStateKey = "disabledFields" | "hiddenFields" | "requiredFields";
2
8
 
3
9
  function createFieldSetMutator(stateKey: FieldSetStateKey, flagKey: string) {
@@ -23,6 +29,25 @@ function createFieldSetMutator(stateKey: FieldSetStateKey, flagKey: string) {
23
29
  };
24
30
  }
25
31
 
32
+ function setFieldValue(
33
+ action: WatchAction,
34
+ eventData: ComponentEventData,
35
+ componentState: Ref<Record<string, unknown>>,
36
+ ) {
37
+ const params = action.params as unknown as SetFieldValueParams;
38
+ const write = resolveFieldValueWrite(
39
+ params,
40
+ eventData.data as FieldChangePayload | undefined,
41
+ );
42
+ if (!write) return;
43
+ // A copy: a map entry holding a list or an object is shared by every write
44
+ // (and may reach here as a reactive proxy, which structuredClone refuses).
45
+ componentState.value[params.targetField] =
46
+ write.value === undefined
47
+ ? undefined
48
+ : JSON.parse(JSON.stringify(write.value));
49
+ }
50
+
26
51
  export function registerFormFunctions() {
27
52
  const { registerFunction } = useDefinedFunctions();
28
53
 
@@ -40,4 +65,6 @@ export function registerFormFunctions() {
40
65
  FormFunctions.SET_FIELD_REQUIRED,
41
66
  createFieldSetMutator("requiredFields", "setRequired"),
42
67
  );
68
+
69
+ registerFunction(FormFunctions.SET_FIELD_VALUE, setFieldValue);
43
70
  }