@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
@@ -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
+ };
@@ -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
+ }
@@ -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
  }
@@ -0,0 +1,65 @@
1
+ import type {
2
+ DataType,
3
+ DataTypeSelectSource,
4
+ } from "#dms-core/app/composables/data-types/useDataType";
5
+ import type { RenderableColumn } from "../data-types/useColumnValueRenderer";
6
+
7
+ /** A column of a read-only list: a field of its rows. */
8
+ export interface RepeaterTableColumn {
9
+ id: string;
10
+ label?: string;
11
+ type?: string;
12
+ component: ComponentInfo;
13
+ }
14
+
15
+ /** Where a relation column reads the rows its values name. */
16
+ export interface RelationLabelSource extends DataTypeSelectSource {
17
+ multiple: boolean;
18
+ }
19
+
20
+ /**
21
+ * The column as the grid's cell renderer reads it: its data type, with the
22
+ * options of its control (a select's items, a relation's key mapping).
23
+ */
24
+ export function renderableColumn(
25
+ column: RepeaterTableColumn,
26
+ ): RenderableColumn {
27
+ const type: DataTypeConfig = {
28
+ id: column.type ?? "",
29
+ compareModes: [],
30
+ filterComponents: {},
31
+ // The column came from the form's JSON: its options are JSON values.
32
+ inputComponent: column.component as ComponentInfo<ComponentOptionsData>,
33
+ };
34
+ return { id: column.id, type };
35
+ }
36
+
37
+ /**
38
+ * The rows a relation column names its values from, if its data type reads
39
+ * its labels from a `select` route ({@link DataType.selectSource}).
40
+ */
41
+ export function relationLabelSource(
42
+ column: RepeaterTableColumn,
43
+ dataType: Pick<DataType, "selectSource"> | undefined,
44
+ ): RelationLabelSource | undefined {
45
+ const options = column.component.options as
46
+ | { multiple?: unknown }
47
+ | undefined;
48
+ const source = dataType?.selectSource?.(options);
49
+ return source ? { ...source, multiple: !!options?.multiple } : undefined;
50
+ }
51
+
52
+ /**
53
+ * A relation value as its formatter draws it: the related row in place of
54
+ * each id it holds. An id no row matches (a deleted one) stays as it is; a
55
+ * value the server already joined is kept.
56
+ */
57
+ export function joinRelationValue(
58
+ value: unknown,
59
+ rowsByValue: ReadonlyMap<unknown, Record<string, unknown>>,
60
+ multiple: boolean,
61
+ ): unknown {
62
+ const join = (item: unknown) => rowsByValue.get(item) ?? item;
63
+ if (multiple && Array.isArray(value)) return value.map(join);
64
+ return join(value);
65
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * A server error as UForm holds it once placed: on the field, or on the part
3
+ * of it the error names (`address.street`).
4
+ */
5
+ export interface PlacedServerError {
6
+ name?: string;
7
+ message: string;
8
+ }
9
+
10
+ /** A refused submit's errors a field shows, and the value they were raised against. */
11
+ export interface KeptServerError {
12
+ /** The field's value when the server refused it, as JSON. */
13
+ value: string;
14
+ errors: PlacedServerError[];
15
+ }
16
+
17
+ /** The value of a field as a kept error compares it. */
18
+ export function serverErrorValue(value: unknown): string {
19
+ return JSON.stringify(value) ?? "";
20
+ }
21
+
22
+ /** The fields whose value moved off the one their kept error was raised against. */
23
+ export function changedServerErrorFields(
24
+ kept: ReadonlyMap<string, KeptServerError>,
25
+ state: Readonly<Record<string, unknown>>,
26
+ ): string[] {
27
+ return [...kept]
28
+ .filter(([name, entry]) => serverErrorValue(state[name]) !== entry.value)
29
+ .map(([name]) => name);
30
+ }
31
+
32
+ /**
33
+ * The kept server errors, as UForm's `validate` answers them. UForm runs it
34
+ * with its schema on every validation and replaces a field's errors with what
35
+ * both answer: a number input commits its value as it loses focus, and the
36
+ * input event, landing 300 ms later, re-validates the field after a refused
37
+ * submit placed its error. A field still holding the refused value keeps it.
38
+ * A submit validates without them: the server judges the values again.
39
+ */
40
+ export function keptServerErrors(
41
+ kept: ReadonlyMap<string, KeptServerError>,
42
+ state: Readonly<Record<string, unknown>>,
43
+ isSubmitting: boolean,
44
+ ): PlacedServerError[] {
45
+ if (isSubmitting) return [];
46
+ return [...kept]
47
+ .filter(([name, entry]) => serverErrorValue(state[name]) === entry.value)
48
+ .flatMap(([, entry]) => entry.errors);
49
+ }