@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
@@ -0,0 +1,308 @@
1
+ import { nextTick } from "vue";
2
+ import {
3
+ hashText,
4
+ type TableField,
5
+ } from "#dms-core/app/build/composables/user/tablePreferences";
6
+ import type { QueryParamFilters } from "../../../../composables/table-view/types/config";
7
+ import type { TablePreferences } from "../useTablePreferences";
8
+ import { RECORD_URL_KEY } from "./recordLink";
9
+ import { readTableUrlKey, type TableUrlScope, writeTableUrlKey } from "./views";
10
+
11
+ /**
12
+ * The kept fields that decide which rows a table lists: a link that names
13
+ * part of that state replaces all of it, not only what it names.
14
+ */
15
+ const LINKED_STATE_FIELDS: ReadonlySet<TableField> = new Set<TableField>([
16
+ "activeView",
17
+ "activeTab",
18
+ "columnFilters",
19
+ "sorting",
20
+ "pagination",
21
+ "paginationScope",
22
+ ]);
23
+
24
+ /**
25
+ * The listing choices a user makes on a link's table (a view, a tab, a
26
+ * filter, a sort): the first one makes the state shown their own.
27
+ */
28
+ const USER_CHOICE_FIELDS: ReadonlySet<TableField> = new Set<TableField>([
29
+ "activeView",
30
+ "activeTab",
31
+ "columnFilters",
32
+ "sorting",
33
+ ]);
34
+
35
+ const VIEW_URL_KEY = "view";
36
+ const TAB_URL_KEY = "tab";
37
+
38
+ /**
39
+ * What `query` names as the table's view: an id, `""` for no view at all
40
+ * (`?view=`), or `undefined` when it names none.
41
+ */
42
+ const namedView = (
43
+ query: Record<string, unknown>,
44
+ urlScope: TableUrlScope,
45
+ ): string | undefined => {
46
+ const reading = readTableUrlKey(query, urlScope, VIEW_URL_KEY);
47
+ return reading.isCleared ? "" : reading.value;
48
+ };
49
+
50
+ /** What the URL a table arrives on may name, and how it was reached. */
51
+ export interface TableLinkArrival {
52
+ query: Record<string, unknown>;
53
+ urlScope: TableUrlScope;
54
+ queryParamFilters: QueryParamFilters | undefined;
55
+ /** The page was reached through a visit, not loaded or gone back to. */
56
+ isVisit: boolean;
57
+ /** The tab kept from the last visit. */
58
+ rememberedTab: string;
59
+ /** The URL filters the kept state was chosen under (`urlFilterScope`). */
60
+ keptLinkScope: string | undefined;
61
+ /** The link the table last showed, unclaimed by the user (`linkKeyScope`). */
62
+ shownLink: string | undefined;
63
+ }
64
+
65
+ const namesUrlFilter = (
66
+ query: Record<string, unknown>,
67
+ queryParamFilters: QueryParamFilters | undefined,
68
+ isNamed: (param: string) => boolean,
69
+ ): boolean =>
70
+ Object.keys(queryParamFilters ?? {}).some(
71
+ (param) => query[param] !== undefined && isNamed(param),
72
+ );
73
+
74
+ /**
75
+ * The `queryParamFilters` values `query` carries, as one key, or `undefined`
76
+ * when it carries none: what a kept state chosen under them is tied to.
77
+ */
78
+ export function urlFilterScope(
79
+ query: Record<string, unknown>,
80
+ queryParamFilters: QueryParamFilters | undefined,
81
+ ): string | undefined {
82
+ const values = Object.keys(queryParamFilters ?? {})
83
+ .filter((param) => query[param] !== undefined)
84
+ .sort()
85
+ .map((param) => [param, query[param]]);
86
+ return values.length > 0 ? hashText(JSON.stringify(values)) : undefined;
87
+ }
88
+
89
+ /**
90
+ * What `query` names for the table (its view, tab and row, and the filters
91
+ * declared in `queryParamFilters`), as one key, or `undefined` when it names
92
+ * none of them: a URL spelling a key short or prefixed reads the same.
93
+ */
94
+ export function linkKeyScope(
95
+ query: Record<string, unknown>,
96
+ urlScope: TableUrlScope,
97
+ queryParamFilters: QueryParamFilters | undefined,
98
+ ): string | undefined {
99
+ const named = [
100
+ [VIEW_URL_KEY, namedView(query, urlScope)],
101
+ ...[TAB_URL_KEY, RECORD_URL_KEY].map((key) => [
102
+ key,
103
+ readTableUrlKey(query, urlScope, key).value,
104
+ ]),
105
+ ].filter(([, value]) => value !== undefined);
106
+ const filters = Object.keys(queryParamFilters ?? {})
107
+ .filter((param) => query[param] !== undefined)
108
+ .sort()
109
+ .map((param) => [`?${param}`, query[param]]);
110
+ const values = [...named, ...filters];
111
+ return values.length > 0 ? hashText(JSON.stringify(values)) : undefined;
112
+ }
113
+
114
+ /**
115
+ * Whether the table was reached through a link that names what it lists: a
116
+ * view, a record, a filter declared in `queryParamFilters`, or a tab. Reached
117
+ * through a visit (a link clicked, a notification), it always is, even when
118
+ * what it names is what the user left. Loaded in full (a reload) or gone back
119
+ * to, the URL may be the one the table wrote for the user's own choices: it
120
+ * is a link again when it is the link the table last showed, and when it
121
+ * names a view or a record, or a tab or URL filters other than the kept
122
+ * ones. A server render cannot tell a reload from a URL typed or opened in a
123
+ * new browser tab, so a full load naming only the kept tab reads as one.
124
+ */
125
+ export function isLinkedArrival(arrival: TableLinkArrival): boolean {
126
+ const { query, urlScope, queryParamFilters } = arrival;
127
+ const linkScope = linkKeyScope(query, urlScope, queryParamFilters);
128
+ if (linkScope === undefined) return false;
129
+ if (arrival.isVisit || linkScope === arrival.shownLink) return true;
130
+ const names = (key: string) => !!readTableUrlKey(query, urlScope, key).value;
131
+ if (namedView(query, urlScope) !== undefined || names(RECORD_URL_KEY)) {
132
+ return true;
133
+ }
134
+ const filterScope = urlFilterScope(query, queryParamFilters);
135
+ if (filterScope !== undefined && filterScope !== arrival.keptLinkScope) {
136
+ return true;
137
+ }
138
+ const tab = readTableUrlKey(query, urlScope, TAB_URL_KEY).value;
139
+ return tab !== undefined && tab !== arrival.rememberedTab;
140
+ }
141
+
142
+ /**
143
+ * The table's preferences as a link arrival reads them: the listing state
144
+ * (`LINKED_STATE_FIELDS`) reads as its defaults, so the link applies over
145
+ * the table's own default state rather than over the last visit's. The
146
+ * kept state is only replaced once the user makes the state shown their own
147
+ * (`linkFollowingPreferences`).
148
+ */
149
+ export function linkedArrivalPreferences(
150
+ preferences: TablePreferences,
151
+ ): TablePreferences {
152
+ return {
153
+ ...preferences,
154
+ get: (field, defaultValue) =>
155
+ LINKED_STATE_FIELDS.has(field)
156
+ ? defaultValue
157
+ : preferences.get(field, defaultValue),
158
+ };
159
+ }
160
+
161
+ /** A visit to the page the table is already shown on. */
162
+ export interface TableLinkVisit {
163
+ query: Record<string, unknown>;
164
+ /**
165
+ * A link pushed on the page (one clicked, a notification): it is followed
166
+ * even when it names what the table shows.
167
+ */
168
+ isLinkClick: boolean;
169
+ /** The query of the visit the table was last reached through. */
170
+ previousQuery: Record<string, unknown>;
171
+ urlScope: TableUrlScope;
172
+ queryParamFilters: QueryParamFilters | undefined;
173
+ /** The view and the tab the table shows. */
174
+ activeView: string | undefined;
175
+ activeTab: string;
176
+ canOpenView: (id: string) => boolean;
177
+ canOpenTab: (id: string) => boolean;
178
+ }
179
+
180
+ /**
181
+ * Whether a visit that keeps the table mounted (a link to the page already
182
+ * shown, back and forward) is a link to follow as an arrival would. A link
183
+ * clicked on the page counts whenever it names a view or a tab the table can
184
+ * open, or a URL filter, even what the table already shows: the state around
185
+ * it may hide it. Otherwise (back and forward), the view and the tab are
186
+ * written or kept in place, so they count when they differ from what the
187
+ * table shows; a URL filter when it differs from the last visit. A row named
188
+ * (`?record=`) is opened on the table as it stands, its page included: it is
189
+ * no listing link, and a link naming one counts only for a view, a tab or a
190
+ * URL filter that changes what the table lists. A visit repeating what the
191
+ * table already reads (the one that mounted it) is not a link.
192
+ */
193
+ export function isLinkedVisit(visit: TableLinkVisit): boolean {
194
+ const { query, previousQuery, urlScope, isLinkClick } = visit;
195
+ const read = (from: Record<string, unknown>, key: string) =>
196
+ readTableUrlKey(from, urlScope, key).value;
197
+ // A link clicked counts even when it names what is already there, unless
198
+ // it opens a row: the table it is opened on stays as it stands.
199
+ const isRowLink = read(query, RECORD_URL_KEY) !== undefined;
200
+ const isNew = (value: unknown, current: unknown) =>
201
+ (isLinkClick && !isRowLink) || value !== current;
202
+ const view = namedView(query, urlScope);
203
+ if (view === "" && isNew(undefined, visit.activeView)) return true;
204
+ if (view && isNew(view, visit.activeView) && visit.canOpenView(view)) {
205
+ return true;
206
+ }
207
+ const tab = read(query, TAB_URL_KEY);
208
+ if (tab && isNew(tab, visit.activeTab) && visit.canOpenTab(tab)) return true;
209
+ return namesUrlFilter(query, visit.queryParamFilters, (param) =>
210
+ isNew(query[param], previousQuery[param]),
211
+ );
212
+ }
213
+
214
+ /** A kept field of the listing state, with its value shown and default. */
215
+ export type KeptListingField = [
216
+ field: TableField,
217
+ value: unknown,
218
+ defaultValue: unknown,
219
+ ];
220
+
221
+ /** What `linkFollowingPreferences` reads the table's link state from. */
222
+ export interface LinkFollowingOptions {
223
+ /** The table arrived on a link (`isLinkedArrival`). */
224
+ isLinkedArrival: boolean;
225
+ urlScope: TableUrlScope;
226
+ /** The URL filters the table lists under now (`urlFilterScope`). */
227
+ linkScope: () => string | undefined;
228
+ /** What the URL shown names for the table now (`linkKeyScope`). */
229
+ linkKeys: () => string | undefined;
230
+ /** The listing state shown, as it is kept (`LINKED_STATE_FIELDS`). */
231
+ listingState: () => KeptListingField[];
232
+ }
233
+
234
+ /** Preferences that leave a link's listing state unkept until it is the user's. */
235
+ export interface LinkFollowingPreferences extends TablePreferences {
236
+ /**
237
+ * Runs `change`, which opens a link on the table already shown: as on a
238
+ * linked arrival, the state it sets is not kept.
239
+ */
240
+ followLink: (change: () => void) => Promise<void>;
241
+ /** Called once the table is mounted: what changes from then is the user's. */
242
+ settleArrival: () => Promise<void>;
243
+ }
244
+
245
+ /**
246
+ * Wraps the table's preferences so the listing state a link opens (on
247
+ * arrival or on the table already shown) does not overwrite the kept one,
248
+ * which stays for the next plain visit. The table keeps its state from
249
+ * watchers, which run on the next flush: what the link and the arrival set
250
+ * is settled a tick later. The user's first choice from there (a view, a
251
+ * tab, a filter, a sort) makes the state shown theirs: it is kept whole,
252
+ * with the URL filters it was chosen under, and the view the link named
253
+ * leaves the URL, which then reads as a plain visit on reload. Until then,
254
+ * the link shown is kept too (`shownLink`), so a reload shows it again.
255
+ */
256
+ export function linkFollowingPreferences(
257
+ preferences: TablePreferences,
258
+ options: LinkFollowingOptions,
259
+ ): LinkFollowingPreferences {
260
+ let isShowingLink = options.isLinkedArrival;
261
+ let isSettled = false;
262
+
263
+ const keepLinkScope = () => {
264
+ preferences.set("linkScope", options.linkScope(), undefined);
265
+ preferences.set("shownLink", undefined, undefined);
266
+ };
267
+ const keepShownLink = () =>
268
+ preferences.set("shownLink", options.linkKeys(), undefined);
269
+
270
+ const takeOver = () => {
271
+ isShowingLink = false;
272
+ for (const [field, value, defaultValue] of options.listingState()) {
273
+ preferences.set(field, value, defaultValue);
274
+ }
275
+ keepLinkScope();
276
+ writeTableUrlKey(options.urlScope, VIEW_URL_KEY);
277
+ };
278
+
279
+ return {
280
+ ...preferences,
281
+ set: (field, value, defaultValue) => {
282
+ if (!LINKED_STATE_FIELDS.has(field)) {
283
+ preferences.set(field, value, defaultValue);
284
+ } else if (!isShowingLink) {
285
+ preferences.set(field, value, defaultValue);
286
+ keepLinkScope();
287
+ } else if (isSettled && USER_CHOICE_FIELDS.has(field)) {
288
+ takeOver();
289
+ }
290
+ },
291
+ followLink: async (change) => {
292
+ isShowingLink = true;
293
+ isSettled = false;
294
+ try {
295
+ change();
296
+ await nextTick();
297
+ keepShownLink();
298
+ } finally {
299
+ isSettled = true;
300
+ }
301
+ },
302
+ settleArrival: async () => {
303
+ await nextTick();
304
+ if (isShowingLink) keepShownLink();
305
+ isSettled = true;
306
+ },
307
+ };
308
+ }
@@ -0,0 +1,106 @@
1
+ import { get } from "@nuxt/ui/runtime/utils/index.js";
2
+ import type { RowLocation } from "../../actions/rowNavigation";
3
+
4
+ /** One window of the table's result, under its current query. */
5
+ export interface LocatedWindow {
6
+ results: Data[];
7
+ total: number;
8
+ }
9
+
10
+ export interface RowLocatorOptions {
11
+ /**
12
+ * Reads the rows from `offset`, `limit` of them, under the table's current
13
+ * query; `limit: 0` reads every row (`wholeRead`). `null` while it cannot.
14
+ */
15
+ list: (offset: number, limit: number) => Promise<LocatedWindow | null>;
16
+ rowIdKey: string;
17
+ /**
18
+ * The table pages the rows itself (a source answering every row): one read
19
+ * holds them all.
20
+ */
21
+ isWholeRead?: () => boolean;
22
+ }
23
+
24
+ /** Rows read per window while looking for a row. */
25
+ export const LOCATE_WINDOW = 200;
26
+ /**
27
+ * How far into the result a row is looked for: past it, a deep link opens
28
+ * without a position rather than reading the table to its end.
29
+ */
30
+ export const LOCATE_MAX_ROWS = 5_000;
31
+ const HALF_WINDOW = Math.floor(LOCATE_WINDOW / 2);
32
+ /** The row and one on each side. */
33
+ const NEIGHBOURHOOD = 3;
34
+
35
+ const idOf = (row: Data, rowIdKey: string): string =>
36
+ String(get(row, rowIdKey));
37
+
38
+ /** The location of the row at `local` in `rows`, read from `offset`. */
39
+ function locationIn(
40
+ window: LocatedWindow,
41
+ offset: number,
42
+ local: number,
43
+ ): RowLocation {
44
+ return {
45
+ index: offset + local,
46
+ total: Math.max(window.total, offset + local + 1),
47
+ prev: window.results[local - 1],
48
+ next: window.results[local + 1],
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Finds a row among all the rows a table lists, by id, reading its result
54
+ * a window at a time: a deep link to a row past the page loaded, or a row
55
+ * live updates pushed out of it, gets its position and its neighbours in the
56
+ * table's current order. Each window overlaps the previous one by a row, so a
57
+ * row found has its neighbours with it; one at a window's edge has them read
58
+ * around it.
59
+ */
60
+ export function rowLocator(options: RowLocatorOptions) {
61
+ const { list, rowIdKey } = options;
62
+ const find = (window: LocatedWindow, id: string) =>
63
+ window.results.findIndex((row) => idOf(row, rowIdKey) === id);
64
+
65
+ const withNeighbours = async (
66
+ location: RowLocation,
67
+ ): Promise<RowLocation> => {
68
+ const lacksPrev = !location.prev && location.index > 0;
69
+ const lacksNext = !location.next && location.index < location.total - 1;
70
+ if (!lacksPrev && !lacksNext) return location;
71
+ const offset = Math.max(0, location.index - 1);
72
+ const around = await list(offset, NEIGHBOURHOOD);
73
+ const local = around ? location.index - offset : -1;
74
+ if (!around || !around.results[local]) return location;
75
+ return { ...locationIn(around, offset, local), total: location.total };
76
+ };
77
+
78
+ return async (
79
+ id: string,
80
+ hint?: number,
81
+ ): Promise<RowLocation | undefined> => {
82
+ if (options.isWholeRead?.()) {
83
+ const all = await list(0, 0);
84
+ const local = all ? find(all, id) : -1;
85
+ return all && local >= 0 ? locationIn(all, 0, local) : undefined;
86
+ }
87
+ // The window around where the row last stood first, then the result
88
+ // from its start, until its end or the rows looked through run out.
89
+ const starts = hint === undefined ? [] : [Math.max(0, hint - HALF_WINDOW)];
90
+ let end = LOCATE_MAX_ROWS;
91
+ for (let start = 0; start < end; start += LOCATE_WINDOW) starts.push(start);
92
+ const read = new Set<number>();
93
+ for (const start of starts) {
94
+ if (read.has(start) || start >= end) continue;
95
+ read.add(start);
96
+ // One row before the window: its first row's previous.
97
+ const offset = Math.max(0, start - 1);
98
+ const window = await list(offset, LOCATE_WINDOW + (start > 0 ? 2 : 1));
99
+ if (!window) return undefined;
100
+ const local = find(window, id);
101
+ if (local >= 0) return withNeighbours(locationIn(window, offset, local));
102
+ end = Math.min(window.total, LOCATE_MAX_ROWS);
103
+ }
104
+ return undefined;
105
+ };
106
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * How the page shown was reached: a full load of the document (a reload, a
3
+ * URL typed or opened in a new browser tab), an Inertia visit (a link, a
4
+ * redirect), or Back and Forward.
5
+ */
6
+ export type PageArrivalKind = "load" | "visit" | "history";
7
+
8
+ interface InertiaBeforeDetail {
9
+ visit: { prefetch?: boolean };
10
+ }
11
+
12
+ let arrivalKind: PageArrivalKind = "load";
13
+ let isTracking = false;
14
+
15
+ /**
16
+ * Starts following how each page is reached, once, when the app starts: a
17
+ * table mounted later must still see the visit that brought it. A server
18
+ * render never tracks, so it and the hydrating client both read a full load.
19
+ */
20
+ export function trackPageArrivals(): void {
21
+ if (typeof document === "undefined" || isTracking) return;
22
+ isTracking = true;
23
+ // A prefetch (a link hovered) announces a visit that may never be made.
24
+ document.addEventListener("inertia:before", (event) => {
25
+ const { visit } = (event as CustomEvent<InertiaBeforeDetail>).detail;
26
+ if (!visit.prefetch) arrivalKind = "visit";
27
+ });
28
+ window.addEventListener("popstate", () => {
29
+ arrivalKind = "history";
30
+ });
31
+ }
32
+
33
+ /** How the page shown was reached (`trackPageArrivals`). */
34
+ export const pageArrivalKind = (): PageArrivalKind => arrivalKind;
@@ -27,3 +27,20 @@ export function pinRows<T extends Record<string, unknown>>(
27
27
  );
28
28
  return pinned.length === 0 ? rows : [...pinned, ...rest];
29
29
  }
30
+
31
+ /**
32
+ * `rows` as a table reading `span` of them at a time shows them: the pinned
33
+ * rows lead each read, not the whole list. `rows` starts at a read's first.
34
+ */
35
+ export function pinRowsBySpan<T extends Record<string, unknown>>(
36
+ rows: T[],
37
+ span: number,
38
+ pinnedRows: RowActionRule | undefined,
39
+ ): T[] {
40
+ if (!pinnedRows || span <= 0) return pinRows(rows, pinnedRows);
41
+ const spans: T[][] = [];
42
+ for (let start = 0; start < rows.length; start += span) {
43
+ spans.push(pinRows(rows.slice(start, start + span), pinnedRows));
44
+ }
45
+ return spans.flat();
46
+ }
@@ -1,6 +1,14 @@
1
1
  import type { TableViewColumn } from "../../../../composables/table-view/types/column";
2
2
  import type { TableViewQuickFilterMode } from "../../../../composables/table-view/types/config";
3
3
  import type { TableFilter } from "../../../components/table/Table.vue";
4
+ import type {
5
+ DataType,
6
+ DataTypeSelectSource,
7
+ } from "#dms-core/app/composables/data-types/useDataType";
8
+ import {
9
+ relationRowLabel,
10
+ relationRowValue,
11
+ } from "../../data-types/relationRows";
4
12
 
5
13
  /** A value a quick filter offers. */
6
14
  export interface QuickFilterItem {
@@ -20,22 +28,13 @@ export interface ResolvedQuickFilter {
20
28
  pending?: boolean;
21
29
  }
22
30
 
23
- /** Where a relation quick filter reads its values from. */
24
- export interface QuickFilterRelationSource {
25
- url: string;
26
- labelKey: string;
27
- valueKey: string;
28
- }
29
-
30
31
  interface ColumnInputOptions {
31
32
  items?: Array<{ value: unknown; label: string }>;
33
+ /** A tags column's suggested tags: the values it offers. */
34
+ suggestions?: string[];
32
35
  multiple?: boolean;
33
- searchUrl?: string;
34
- keyMapping?: { label?: string; value?: string };
35
36
  }
36
37
 
37
- const DEFAULT_LABEL_KEY = "name";
38
- const DEFAULT_VALUE_KEY = "_id";
39
38
  const ARRAY_MODE: TableViewQuickFilterMode = "array_contains_string";
40
39
  const SCALAR_MODE: TableViewQuickFilterMode = "is";
41
40
 
@@ -44,26 +43,30 @@ const inputOptions = (column: TableViewColumn | undefined) =>
44
43
 
45
44
  /**
46
45
  * The compare mode a quick filter applies: the declared one, else "contains"
47
- * for a multiple select or relation (the row holds a list) and equality
48
- * otherwise.
46
+ * when the row holds a list -- a multiple select or relation, or a type that
47
+ * filters on "contains" by default (tags) -- and equality otherwise.
49
48
  */
50
49
  export function quickFilterMode(
51
50
  column: TableViewColumn | undefined,
52
51
  declared?: TableViewQuickFilterMode,
53
52
  ): TableViewQuickFilterMode {
54
53
  if (declared) return declared;
55
- return inputOptions(column).multiple ? ARRAY_MODE : SCALAR_MODE;
54
+ const holdsList =
55
+ inputOptions(column).multiple ||
56
+ column?.type?.defaultCompareMode === ARRAY_MODE;
57
+ return holdsList ? ARRAY_MODE : SCALAR_MODE;
56
58
  }
57
59
 
58
60
  /**
59
- * The values a select or boolean column offers, known from its declaration;
60
- * `undefined` for a column whose values must be fetched (a relation).
61
+ * The values a select, boolean or tags column offers (a select's items, a
62
+ * tags column's `suggestions`), known from its declaration; `undefined` for a
63
+ * column that declares none (a relation's are fetched).
61
64
  */
62
65
  export function staticQuickFilterItems(
63
66
  column: TableViewColumn | undefined,
64
67
  translate: (key: string) => string,
65
68
  ): QuickFilterItem[] | undefined {
66
- if (!column) return [];
69
+ if (!column) return undefined;
67
70
  const typeId = column.type?.id;
68
71
  if (typeId === "boolean" || typeId === "status") {
69
72
  return [
@@ -74,41 +77,45 @@ export function staticQuickFilterItems(
74
77
  },
75
78
  ];
76
79
  }
77
- const items = inputOptions(column).items;
80
+ const { items, suggestions } = inputOptions(column);
78
81
  if (items) {
79
82
  return items.map((item) => ({
80
83
  value: String(item.value),
81
84
  label: translate(item.label),
82
85
  }));
83
86
  }
84
- return relationQuickFilterSource(column) ? undefined : [];
87
+ if (suggestions) {
88
+ return suggestions.map((tag) => ({ value: tag, label: tag }));
89
+ }
90
+ return undefined;
85
91
  }
86
92
 
87
- /** The `select` endpoint a relation column picks its rows from. */
93
+ /**
94
+ * Where a relation column's values are read: the `select` route its data
95
+ * type's {@link DataType.selectSource} names, as for its picker and its
96
+ * filter chips.
97
+ */
88
98
  export function relationQuickFilterSource(
89
99
  column: TableViewColumn | undefined,
90
- ): QuickFilterRelationSource | undefined {
91
- const options = inputOptions(column);
92
- if (!options.searchUrl) return undefined;
93
- return {
94
- url: options.searchUrl,
95
- labelKey: options.keyMapping?.label ?? DEFAULT_LABEL_KEY,
96
- valueKey: options.keyMapping?.value ?? DEFAULT_VALUE_KEY,
97
- };
100
+ getDataType: (id: string) => DataType | undefined,
101
+ ): DataTypeSelectSource | undefined {
102
+ const typeId = column?.type?.id;
103
+ if (!typeId) return undefined;
104
+ return getDataType(typeId)?.selectSource?.(inputOptions(column));
98
105
  }
99
106
 
100
- /** Rows of a relation's `select` endpoint as quick filter values. */
107
+ /** Rows of a relation's `select` route as quick filter values. */
101
108
  export function relationQuickFilterItems(
102
109
  rows: Array<Record<string, unknown>>,
103
- source: QuickFilterRelationSource,
110
+ source: DataTypeSelectSource,
104
111
  ): QuickFilterItem[] {
105
112
  return rows.flatMap((row) => {
106
- const value = row[source.valueKey];
113
+ const value = relationRowValue(source, row);
107
114
  if (value === undefined || value === null) return [];
108
115
  return [
109
116
  {
110
117
  value: String(value),
111
- label: String(row[source.labelKey] ?? value),
118
+ label: String(relationRowLabel(source, row) ?? value),
112
119
  },
113
120
  ];
114
121
  });
@@ -118,7 +125,8 @@ export function relationQuickFilterItems(
118
125
  const isQuickFilterOf =
119
126
  (quickFilter: Pick<ResolvedQuickFilter, "field" | "mode">) =>
120
127
  (filter: TableFilter): boolean =>
121
- filter.accessorKey === quickFilter.field && filter.mode === quickFilter.mode;
128
+ filter.accessorKey === quickFilter.field &&
129
+ filter.mode === quickFilter.mode;
122
130
 
123
131
  /**
124
132
  * The value a quick filter shows as picked: the one its column's filter holds
@@ -39,12 +39,28 @@ export function writeRecordId(
39
39
 
40
40
  /**
41
41
  * Whether an action keeps the row it opens in the URL: a side panel does
42
- * unless its target or the action sets `deepLink: false`, a drawer or a modal
43
- * only with the action's `deepLink: true`.
42
+ * unless its target or the action sets `deepLink: false`, a drawer only with
43
+ * its target's or the action's `deepLink: true`, a modal only with the
44
+ * action's.
44
45
  */
45
46
  export function isDeepLinkAction(action: CustomRowAction): boolean {
46
- if (action.target.type !== "sidePanel") return !!action.deepLink;
47
- return (action.deepLink ?? action.target.deepLink) !== false;
47
+ const { target } = action;
48
+ if (target.type === "sidePanel") {
49
+ return (action.deepLink ?? target.deepLink) !== false;
50
+ }
51
+ if (target.type === "drawer") return !!(action.deepLink ?? target.deepLink);
52
+ return !!action.deepLink;
53
+ }
54
+
55
+ /**
56
+ * Whether the action's container draws its own "no longer available" state
57
+ * on a row that is gone: a side panel always, a drawer once its target words
58
+ * it (`notFound`).
59
+ */
60
+ export function hasMissingRowState(action: CustomRowAction): boolean {
61
+ const { target } = action;
62
+ if (target.type === "sidePanel") return true;
63
+ return target.type === "drawer" && !!target.notFound;
48
64
  }
49
65
 
50
66
  /** The row a table wrote in the URL, and the page it wrote it on. */
@@ -114,8 +130,9 @@ export async function resolveDeepLinkedRow<T>(
114
130
 
115
131
  /**
116
132
  * What a table does with the row a deep link resolved to: open it, let a side
117
- * panel show its own "no longer available" state, tell the user the link led
118
- * nowhere (a drawer or a modal has no such state, and a table with no route to
133
+ * panel (or a drawer wording it) show its own "no longer available" state,
134
+ * tell the user the link led nowhere (any other container has no such state,
135
+ * and a table with no route to
119
136
  * read one row finds nothing either), or tell them the row could not be read
120
137
  * (a refusal, the network) without claiming it is gone.
121
138
  */
@@ -131,7 +148,6 @@ export function deepLinkOutcome(
131
148
  ): DeepLinkOutcome {
132
149
  if (found.row) return "open";
133
150
  if (found.isFailed) return "notifyFailed";
134
- if (found.isMissing && action.target.type === "sidePanel")
135
- return "missingPanel";
151
+ if (found.isMissing && hasMissingRowState(action)) return "missingPanel";
136
152
  return "notify";
137
153
  }