@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
@@ -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
  }
@@ -0,0 +1,113 @@
1
+ import type { TableFilter } from "../../../components/table/Table.vue";
2
+ import type { TableCountRequest } from "../useTableListData";
3
+ import { buildTableQuery, isFilterEffective } from "./tableQuery";
4
+
5
+ /** A view's counter shares the tab counters' request, under its own id. */
6
+ export const VIEW_COUNT_ID_PREFIX = "view:";
7
+ /**
8
+ * So does the navigation badge of a tab while anything narrows its counter:
9
+ * the badge stands for the whole page, the counter for what the table shows.
10
+ */
11
+ export const NAV_BADGE_COUNT_ID_PREFIX = "nav:";
12
+
13
+ /** A tab that filters the table (a link tab counts its own list). */
14
+ export interface CountedTab {
15
+ id: string;
16
+ filters: TableFilter[];
17
+ hasNavBadge: boolean;
18
+ }
19
+
20
+ /** A view with a counter, as opening it sets the table. */
21
+ export interface CountedView {
22
+ id: string;
23
+ filters: TableFilter[];
24
+ search?: string;
25
+ }
26
+
27
+ /** What the table lists, besides its tab. */
28
+ export interface TableCountState {
29
+ /** The filters the page sets: its URL query and route parameters. */
30
+ scopeFilters: TableFilter[];
31
+ /** The open tab's filter. */
32
+ tabFilters: TableFilter[];
33
+ /** The filters on screen: the open view's, as the user edited them. */
34
+ columnFilters: TableFilter[];
35
+ search?: string;
36
+ }
37
+
38
+ export interface TableCountQueriesInput extends TableCountState {
39
+ tabs: CountedTab[];
40
+ views: CountedView[];
41
+ activeViewId?: string;
42
+ }
43
+
44
+ const countQuery = (
45
+ columnFilters: TableFilter[],
46
+ hiddenFilters: TableFilter[],
47
+ search?: string,
48
+ ) =>
49
+ buildTableQuery({
50
+ pagination: { pageIndex: 0, pageSize: 0 },
51
+ sorting: [],
52
+ columnFilters,
53
+ hiddenFilters,
54
+ globalFilter: search,
55
+ });
56
+
57
+ /** Whether a tab's counter counts fewer rows than its whole page holds. */
58
+ export const isCountNarrowed = (state: TableCountState): boolean =>
59
+ !!state.search ||
60
+ state.scopeFilters.some(isFilterEffective) ||
61
+ state.columnFilters.some(isFilterEffective);
62
+
63
+ /**
64
+ * The counters of a table's tabs and views, in one request: each counts what
65
+ * a click on it would list, the rest of the table's state kept. A tab counts
66
+ * under the filters and search on screen; a view under the tab open, with its
67
+ * own filters and search, which opening it applies; the open view counts the
68
+ * rows listed. The page's URL filters apply to all. A `navBadge` tab also gets
69
+ * a count of its filter alone, for the menu, while anything narrows its own.
70
+ */
71
+ export function buildTableCountQueries(
72
+ input: TableCountQueriesInput,
73
+ ): TableCountRequest[] {
74
+ const { scopeFilters, tabFilters, columnFilters, search } = input;
75
+ const viewScope = [...scopeFilters, ...tabFilters];
76
+ const isNarrowed = isCountNarrowed(input);
77
+ return [
78
+ ...input.tabs.map((tab) => ({
79
+ id: tab.id,
80
+ query: countQuery(
81
+ columnFilters,
82
+ [...scopeFilters, ...tab.filters],
83
+ search,
84
+ ),
85
+ })),
86
+ ...(isNarrowed ? input.tabs : [])
87
+ .filter((tab) => tab.hasNavBadge)
88
+ .map((tab) => ({
89
+ id: `${NAV_BADGE_COUNT_ID_PREFIX}${tab.id}`,
90
+ query: countQuery([], tab.filters),
91
+ })),
92
+ ...input.views.map((view) => ({
93
+ id: `${VIEW_COUNT_ID_PREFIX}${view.id}`,
94
+ query:
95
+ view.id === input.activeViewId
96
+ ? countQuery(columnFilters, viewScope, search)
97
+ : countQuery(view.filters, viewScope, view.search),
98
+ })),
99
+ ];
100
+ }
101
+
102
+ /**
103
+ * The menu badge of a `navBadge` tab, from the counts answered: its global
104
+ * count once asked for, its own counter while nothing narrows it, else none
105
+ * until the global count arrives.
106
+ */
107
+ export const navBadgeCount = (
108
+ counts: Record<string, number> | null | undefined,
109
+ tabId: string,
110
+ isNarrowed: boolean,
111
+ ): number | undefined =>
112
+ counts?.[`${NAV_BADGE_COUNT_ID_PREFIX}${tabId}`] ??
113
+ (isNarrowed ? undefined : counts?.[tabId]);
@@ -47,16 +47,39 @@ const buildFiltersQuery = (filters: TableFilter[]): Record<string, string> => {
47
47
  );
48
48
  };
49
49
 
50
+ /**
51
+ * The column filters (the filters row, a saved view) laid over the hidden
52
+ * ones (a tab, a route or query parameter). On a column both filter, both
53
+ * are sent: the list route keeps the rows passing every filter of a column
54
+ * (AND), so a tab never lists rows it excludes, and two filters no row can
55
+ * pass list nothing.
56
+ */
57
+ const combineFilters = (
58
+ hidden: Record<string, string>,
59
+ column: Record<string, string>,
60
+ ): Record<string, string | string[]> => {
61
+ const combined: Record<string, string | string[]> = { ...hidden };
62
+ for (const [key, value] of Object.entries(column)) {
63
+ const hiddenValue = hidden[key];
64
+ combined[key] =
65
+ hiddenValue === undefined || hiddenValue === value
66
+ ? value
67
+ : [hiddenValue, value];
68
+ }
69
+ return combined;
70
+ };
71
+
50
72
  export const buildTableQuery = (config: QueryConfig): TableQueryParams => {
51
73
  const pageSize = config.pagination.pageSize;
52
74
  const activeSort = config.sorting[0];
53
75
 
54
- const columnFilters = buildFiltersQuery(config.columnFilters);
55
- const hiddenFilters = buildFiltersQuery(config.hiddenFilters || []);
76
+ const filters = combineFilters(
77
+ buildFiltersQuery(config.hiddenFilters || []),
78
+ buildFiltersQuery(config.columnFilters),
79
+ );
56
80
 
57
81
  return {
58
- ...hiddenFilters,
59
- ...columnFilters,
82
+ ...filters,
60
83
  search: config.globalFilter ?? undefined,
61
84
  offset: config.pagination.pageIndex * pageSize,
62
85
  limit: pageSize,
@@ -53,7 +53,11 @@ export const newUserViewId = (): string =>
53
53
  .toString(ID_RADIX)
54
54
  .slice(ID_RANDOM_START)}`;
55
55
 
56
- function viewFilters(
56
+ /**
57
+ * The filters opening `view` sets: the table's pinned filters, at the view's
58
+ * value for those it names, then the view's other filters.
59
+ */
60
+ export function viewFilters(
57
61
  view: TableViewViewStateConfig,
58
62
  pinned: TableFilter[],
59
63
  ): TableFilter[] {
@@ -179,6 +183,8 @@ export interface TableUrlScope {
179
183
  /** What reading a key off the URL found. */
180
184
  export interface UrlKeyReading {
181
185
  value?: string;
186
+ /** The key is there, empty (`?view=`): it names nothing, explicitly. */
187
+ isCleared?: boolean;
182
188
  /** A short key named something the table may not claim. */
183
189
  ignoredShortKey?: boolean;
184
190
  }
@@ -208,14 +214,24 @@ export function writeTableUrlKey(
208
214
  const scopedKey = tableUrlKey(scope, key);
209
215
  if (value) url.searchParams.set(scopedKey, value);
210
216
  else url.searchParams.delete(scopedKey);
217
+ // The only table reads its prefixed key first (`readTableUrlKey`): a link
218
+ // naming it (`content.tab=`) would otherwise outlive the short key written
219
+ // over it, and win again on reload.
220
+ if (scope.tableId && scopedKey === key) {
221
+ url.searchParams.delete(`${scope.tableId}.${key}`);
222
+ }
211
223
  window.history.replaceState(window.history.state, "", url);
212
224
  return url;
213
225
  }
214
226
 
227
+ const readUrlValue = (value: string): UrlKeyReading =>
228
+ value ? { value } : { isCleared: true };
229
+
215
230
  /**
216
231
  * The value the URL gives `key` (`view`, `tab`) for this table: under its
217
232
  * prefixed key (`content.view`) or, when it is its page's only table, the
218
- * short one. A short key on a page with several tables is ignored.
233
+ * short one. A short key on a page with several tables is ignored. A key
234
+ * left empty reads as cleared, not as absent.
219
235
  */
220
236
  export function readTableUrlKey(
221
237
  query: Record<string, unknown>,
@@ -223,9 +239,9 @@ export function readTableUrlKey(
223
239
  key: string,
224
240
  ): UrlKeyReading {
225
241
  const prefixed = scope.tableId ? query[`${scope.tableId}.${key}`] : undefined;
226
- if (typeof prefixed === "string" && prefixed) return { value: prefixed };
242
+ if (typeof prefixed === "string") return readUrlValue(prefixed);
227
243
  const short = query[key];
228
- if (typeof short !== "string" || !short) return {};
229
- if (scope.isSoleTableView) return { value: short };
230
- return { ignoredShortKey: true };
244
+ if (typeof short !== "string") return {};
245
+ if (scope.isSoleTableView) return readUrlValue(short);
246
+ return short ? { ignoredShortKey: true } : {};
231
247
  }
@@ -0,0 +1,49 @@
1
+ // The geocoder answers the whole world in its own order, and the same place
2
+ // several times (one entry per OSM object: a square, its bus stop, its
3
+ // parking). The list a user picks from holds each place once, the ones in
4
+ // the address's country first.
5
+
6
+ /** How many results are asked for per suggestion shown, to rank from. */
7
+ export const SUGGESTION_POOL_FACTOR = 4;
8
+ /** The most results asked for at once. */
9
+ export const SUGGESTION_POOL_MAX = 20;
10
+
11
+ /** A suggestion as the address field lists it. */
12
+ export interface RankableSuggestion {
13
+ label: string;
14
+ /** ISO 3166-1 alpha-2 code of the suggestion's country, upper case. */
15
+ countryCode?: string;
16
+ }
17
+
18
+ /** How many results to ask the geocoder for, to show `limit`. */
19
+ export function suggestionPoolSize(limit: number): number {
20
+ return Math.min(SUGGESTION_POOL_MAX, limit * SUGGESTION_POOL_FACTOR);
21
+ }
22
+
23
+ /**
24
+ * The suggestions to list: each label once, those in `country` (when given)
25
+ * first and in the geocoder's order otherwise, at most `limit`.
26
+ *
27
+ * @internal
28
+ */
29
+ export function rankAddressSuggestions<T extends RankableSuggestion>(
30
+ suggestions: readonly T[],
31
+ country: string | undefined,
32
+ limit: number,
33
+ ): T[] {
34
+ const seen = new Set<string>();
35
+ const unique = suggestions.filter((suggestion) => {
36
+ const key = suggestion.label.trim().toLowerCase();
37
+ if (seen.has(key)) return false;
38
+ seen.add(key);
39
+ return true;
40
+ });
41
+ const preferred = country?.toUpperCase();
42
+ if (!preferred) return unique.slice(0, limit);
43
+ const isPreferred = (suggestion: T) =>
44
+ suggestion.countryCode?.toUpperCase() === preferred;
45
+ return [
46
+ ...unique.filter(isPreferred),
47
+ ...unique.filter((suggestion) => !isPreferred(suggestion)),
48
+ ].slice(0, limit);
49
+ }
@@ -5,3 +5,15 @@
5
5
  */
6
6
  export const EYEBROW_CLASS =
7
7
  "font-mono text-[10.5px] font-semibold tracking-[0.12em] uppercase";
8
+
9
+ /** U+2060: no line break on either side, and no glyph to draw. */
10
+ const WORD_JOINER = "⁠";
11
+ const INNER_HYPHEN = /(?<=\p{L})-(?=\p{L})/gu;
12
+
13
+ /**
14
+ * `label` with each hyphenated word kept on one line: a short label that wraps
15
+ * on a phone breaks between words, never as "MOIS-" over "CI".
16
+ */
17
+ export function keepHyphenatedWords(label: string): string {
18
+ return label.replace(INNER_HYPHEN, `-${WORD_JOINER}`);
19
+ }