@conduction/nextcloud-vue 2.78.0 → 2.80.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 (259) hide show
  1. package/dist/bundled-packages.json +0 -2
  2. package/dist/esm/bundled-packages.json +0 -2
  3. package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js.map +1 -1
  4. package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js +3 -1
  5. package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js.map +1 -1
  6. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +32 -3
  7. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  8. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +115 -104
  9. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  10. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +12 -2
  11. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  12. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  13. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +39 -3
  14. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  15. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  16. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +158 -0
  17. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  18. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +157 -96
  19. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  20. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +139 -1
  21. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  22. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +140 -92
  23. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  24. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +77 -0
  25. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  26. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +5 -2
  27. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  28. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +62 -4
  29. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  30. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  31. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +85 -1
  32. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  33. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +43 -4
  34. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  35. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +11 -0
  36. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js.map +1 -0
  37. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +736 -0
  38. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -0
  39. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +238 -0
  40. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -0
  41. package/dist/esm/components/CnFactsWidget/index.js +30 -0
  42. package/dist/esm/components/CnFactsWidget/index.js.map +1 -0
  43. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js +10 -0
  44. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js.map +1 -0
  45. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +551 -0
  46. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -0
  47. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +183 -0
  48. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -0
  49. package/dist/esm/components/CnFieldListWidget/index.js +29 -0
  50. package/dist/esm/components/CnFieldListWidget/index.js.map +1 -0
  51. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +173 -5
  52. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  53. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +181 -99
  54. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  55. package/dist/esm/components/CnFilesBrowser/filesBrowser.js +29 -11
  56. package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
  57. package/dist/esm/components/CnFilesBrowser/openRegisterSource.js +252 -0
  58. package/dist/esm/components/CnFilesBrowser/openRegisterSource.js.map +1 -0
  59. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +66 -18
  60. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  61. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +4 -4
  62. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  63. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +18 -3
  64. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
  65. package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
  66. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +330 -74
  67. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  68. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +34 -15
  69. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  70. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +10 -0
  71. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js.map +1 -0
  72. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +99 -0
  73. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -0
  74. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +48 -0
  75. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -0
  76. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +100 -3
  77. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  78. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +4 -2
  79. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  80. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js +29 -1
  81. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
  82. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
  83. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +3 -2
  84. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
  85. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
  86. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +10 -0
  87. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js.map +1 -0
  88. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +387 -0
  89. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -0
  90. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +169 -0
  91. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -0
  92. package/dist/esm/components/CnRecordListWidget/index.js +31 -0
  93. package/dist/esm/components/CnRecordListWidget/index.js.map +1 -0
  94. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
  95. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js +1 -1
  96. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
  97. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js +10 -0
  98. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js.map +1 -0
  99. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js +351 -0
  100. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js.map +1 -0
  101. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js +71 -0
  102. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js.map +1 -0
  103. package/dist/esm/components/CnSectionsWidget/index.js +35 -0
  104. package/dist/esm/components/CnSectionsWidget/index.js.map +1 -0
  105. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +81 -2
  106. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
  107. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
  108. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  109. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +1 -0
  110. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  111. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +152 -8
  112. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  113. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +84 -23
  114. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  115. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +4 -0
  116. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  117. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +13 -0
  118. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  119. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  120. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js +106 -2
  121. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
  122. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +105 -53
  123. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
  124. package/dist/esm/composables/useAppManifest.js +8 -3
  125. package/dist/esm/composables/useAppManifest.js.map +1 -1
  126. package/dist/esm/composables/useFormDraft.js +140 -9
  127. package/dist/esm/composables/useFormDraft.js.map +1 -1
  128. package/dist/esm/index.js +5 -0
  129. package/dist/esm/index.js.map +1 -1
  130. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js +28 -20
  131. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js.map +1 -1
  132. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js +9 -0
  133. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js.map +1 -1
  134. package/dist/esm/integrations/builtin/field-inspection.js +21 -0
  135. package/dist/esm/integrations/builtin/field-inspection.js.map +1 -1
  136. package/dist/esm/integrations/offline/checklistSubmission.js +141 -0
  137. package/dist/esm/integrations/offline/checklistSubmission.js.map +1 -0
  138. package/dist/esm/integrations/offline/fieldCollectionHelpers.js +70 -5
  139. package/dist/esm/integrations/offline/fieldCollectionHelpers.js.map +1 -1
  140. package/dist/esm/integrations/offline/index.js +2 -1
  141. package/dist/esm/integrations/offline/index.js.map +1 -1
  142. package/dist/esm/integrations/offline/offlineDb.js +6 -1
  143. package/dist/esm/integrations/offline/offlineDb.js.map +1 -1
  144. package/dist/esm/integrations/offline/syncReplayService.js +8 -2
  145. package/dist/esm/integrations/offline/syncReplayService.js.map +1 -1
  146. package/dist/esm/l10n/en.json.js +72 -1
  147. package/dist/esm/l10n/en.json.js.map +1 -1
  148. package/dist/esm/l10n/nl.json.js +120 -2
  149. package/dist/esm/l10n/nl.json.js.map +1 -1
  150. package/dist/esm/nextcloud-vue.css +1314 -74
  151. package/dist/esm/utils/boardDate.js +193 -0
  152. package/dist/esm/utils/boardDate.js.map +1 -0
  153. package/dist/esm/utils/detailActionModel.js +60 -3
  154. package/dist/esm/utils/detailActionModel.js.map +1 -1
  155. package/dist/esm/utils/dynamicProperties.js +5 -1
  156. package/dist/esm/utils/dynamicProperties.js.map +1 -1
  157. package/dist/esm/utils/fillTemplate.js +114 -0
  158. package/dist/esm/utils/fillTemplate.js.map +1 -0
  159. package/dist/esm/utils/libraryWidgetKeys.js +4 -0
  160. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  161. package/dist/esm/utils/queryPresets.js +96 -0
  162. package/dist/esm/utils/queryPresets.js.map +1 -0
  163. package/dist/esm/utils/resolveFilterTokens.js +11 -0
  164. package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
  165. package/dist/esm/utils/schema.js +21 -0
  166. package/dist/esm/utils/schema.js.map +1 -1
  167. package/dist/esm/utils/sentinelTokens.js +3 -3
  168. package/dist/esm/utils/sentinelTokens.js.map +1 -1
  169. package/dist/esm/utils/validateManifestV2.compiled.js +57 -6
  170. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  171. package/dist/esm/utils/widgetDispatch.js +7 -1
  172. package/dist/esm/utils/widgetDispatch.js.map +1 -1
  173. package/dist/nextcloud-vue.cjs.js +14278 -8169
  174. package/dist/nextcloud-vue.cjs.js.map +2 -2
  175. package/dist/nextcloud-vue.css +1314 -74
  176. package/l10n/en.json +72 -1
  177. package/l10n/nl.json +120 -2
  178. package/package.json +1 -1
  179. package/scripts/build-validators.js +26 -0
  180. package/src/components/CnActionButtons/CnActionButtons.vue +2 -0
  181. package/src/components/CnActionsBar/CnActionsBar.md +4 -0
  182. package/src/components/CnActionsBar/CnActionsBar.vue +83 -42
  183. package/src/components/CnAppRoot/CnAppRoot.vue +12 -2
  184. package/src/components/CnBannerWidget/CnBannerWidget.vue +39 -3
  185. package/src/components/CnCellRenderer/CnCellRenderer.vue +306 -2
  186. package/src/components/CnDashboardPage/CnDashboardPage.vue +219 -4
  187. package/src/components/CnDataTable/CnDataTable.md +2 -0
  188. package/src/components/CnDataTable/CnDataTable.vue +82 -2
  189. package/src/components/CnDetailPage/CnDetailPage.md +1 -1
  190. package/src/components/CnDetailPage/CnDetailPage.vue +63 -3
  191. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +127 -1
  192. package/src/components/CnFactsWidget/CnFactsWidget.vue +1050 -0
  193. package/src/components/CnFactsWidget/index.js +29 -0
  194. package/src/components/CnFieldListWidget/CnFieldListWidget.vue +746 -0
  195. package/src/components/CnFieldListWidget/index.js +28 -0
  196. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +232 -11
  197. package/src/components/CnFilesBrowser/filesBrowser.js +43 -2
  198. package/src/components/CnFilesBrowser/index.js +1 -0
  199. package/src/components/CnFilesBrowser/openRegisterSource.js +248 -0
  200. package/src/components/CnFormDialog/CnFormDialog.md +9 -3
  201. package/src/components/CnFormDialog/CnFormDialog.vue +70 -22
  202. package/src/components/CnFormPage/CnFormPage.vue +18 -3
  203. package/src/components/CnIndexPage/CnIndexPage.vue +281 -9
  204. package/src/components/CnIndexPage/CnIndexPageWidgets.vue +169 -0
  205. package/src/components/CnObjectSidebar/CnFilesTab.vue +103 -4
  206. package/src/components/CnPageRenderer/CnPageRenderer.vue +29 -1
  207. package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +3 -2
  208. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +603 -0
  209. package/src/components/CnRecordListWidget/index.js +30 -0
  210. package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +1 -1
  211. package/src/components/CnSectionsWidget/CnSectionsWidget.vue +489 -0
  212. package/src/components/CnSectionsWidget/index.js +34 -0
  213. package/src/components/CnStagesWidget/CnStagesWidget.vue +81 -2
  214. package/src/components/CnStatWidget/CnStatWidget.vue +1 -1
  215. package/src/components/CnTabsWidget/CnTabsWidget.vue +214 -9
  216. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +4 -0
  217. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +13 -0
  218. package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +188 -4
  219. package/src/components/index.js +5 -1
  220. package/src/composables/__tests__/useFormDraft.spec.js +69 -0
  221. package/src/composables/useAppManifest.js +8 -3
  222. package/src/composables/useFormDraft.js +139 -8
  223. package/src/css/form-field.css +14 -0
  224. package/src/css/index-page.css +71 -0
  225. package/src/css/kpi-card.css +32 -0
  226. package/src/css/look-board-detail.css +87 -0
  227. package/src/css/look-board-index.css +143 -14
  228. package/src/css/look-board.css +114 -2
  229. package/src/css/table.css +56 -0
  230. package/src/index.js +8 -0
  231. package/src/integrations/builtin/field-inspection/CnFieldInspectionCard.vue +35 -20
  232. package/src/integrations/builtin/field-inspection.js +21 -0
  233. package/src/integrations/offline/checklistSubmission.js +137 -0
  234. package/src/integrations/offline/fieldCollectionHelpers.js +69 -4
  235. package/src/integrations/offline/index.js +3 -0
  236. package/src/integrations/offline/offlineDb.js +6 -1
  237. package/src/integrations/offline/syncReplayService.js +8 -2
  238. package/src/schemas/app-manifest-v2.schema.json +149 -5
  239. package/src/utils/boardDate.js +189 -0
  240. package/src/utils/detailActionModel.js +59 -2
  241. package/src/utils/dynamicProperties.js +5 -1
  242. package/src/utils/fillTemplate.js +110 -0
  243. package/src/utils/libraryWidgetKeys.js +4 -0
  244. package/src/utils/queryPresets.js +93 -0
  245. package/src/utils/resolveFilterTokens.js +11 -0
  246. package/src/utils/schema.js +21 -0
  247. package/src/utils/sentinelTokens.js +3 -3
  248. package/src/utils/validateManifestV2.compiled.js +57 -2
  249. package/src/utils/widgetDispatch.js +7 -1
  250. package/dist/esm/_virtual/equal.js +0 -4
  251. package/dist/esm/_virtual/equal.js.map +0 -1
  252. package/dist/esm/_virtual/ucs2length.js +0 -4
  253. package/dist/esm/_virtual/ucs2length.js.map +0 -1
  254. package/dist/esm/node_modules/ajv/dist/runtime/equal.js +0 -9
  255. package/dist/esm/node_modules/ajv/dist/runtime/equal.js.map +0 -1
  256. package/dist/esm/node_modules/ajv/dist/runtime/ucs2length.js +0 -25
  257. package/dist/esm/node_modules/ajv/dist/runtime/ucs2length.js.map +0 -1
  258. package/dist/esm/node_modules/fast-deep-equal/index.js +0 -47
  259. package/dist/esm/node_modules/fast-deep-equal/index.js.map +0 -1
@@ -80,10 +80,64 @@
80
80
  </span>
81
81
  </template>
82
82
 
83
+ <!-- A stack (`widgetIds`): every widget is a titled card; the
84
+ first joins the open tab, the next ones stand below it. -->
85
+ <div
86
+ v-if="entry.widget && entry.stack.length"
87
+ class="cn-tabs-widget__stack"
88
+ data-testid="cn-tabs-widget-stack">
89
+ <div class="cn-tabs-widget__stack-first">
90
+ <CnDetailWidgetHost
91
+ :widget="entry.widget"
92
+ chrome="card"
93
+ :showCardTitle="true"
94
+ :showActions="false"
95
+ :availableWidgets="availableWidgets"
96
+ :objectId="objectId"
97
+ :object="objectData"
98
+ :objectType="objectType"
99
+ :schemaObject="schemaObject"
100
+ :register="register"
101
+ :schema="schema"
102
+ :store="store"
103
+ :surface="surface"
104
+ :integrationContext="integrationContext"
105
+ :cnRegistry="cnRegistry"
106
+ @geoSaved="onGeoSaved"
107
+ @openIntegration="onOpenIntegration"
108
+ @selectObject="onSelectObject" />
109
+ </div>
110
+ <div
111
+ v-for="child in entry.stack"
112
+ :key="`${entry.key}-${child.id}`"
113
+ class="cn-tabs-widget__stack-item"
114
+ :data-testid="`cn-tabs-widget-stack-${child.id}`">
115
+ <CnDetailWidgetHost
116
+ :widget="child"
117
+ chrome="card"
118
+ :showCardTitle="true"
119
+ :showActions="false"
120
+ :availableWidgets="availableWidgets"
121
+ :objectId="objectId"
122
+ :object="objectData"
123
+ :objectType="objectType"
124
+ :schemaObject="schemaObject"
125
+ :register="register"
126
+ :schema="schema"
127
+ :store="store"
128
+ :surface="surface"
129
+ :integrationContext="integrationContext"
130
+ :cnRegistry="cnRegistry"
131
+ @geoSaved="onGeoSaved"
132
+ @openIntegration="onOpenIntegration"
133
+ @selectObject="onSelectObject" />
134
+ </div>
135
+ </div>
83
136
  <CnDetailWidgetHost
84
- v-if="entry.widget"
137
+ v-else-if="entry.widget"
85
138
  :widget="entry.widget"
86
139
  chrome="bare"
140
+ :availableWidgets="availableWidgets"
87
141
  :objectId="objectId"
88
142
  :object="objectData"
89
143
  :objectType="objectType"
@@ -114,6 +168,7 @@ import CnTab from '../CnTabs/CnTab.vue'
114
168
  import CnTabs from '../CnTabs/CnTabs.vue'
115
169
  import { normalizeLook } from '../../composables/useLook.js'
116
170
  import { resolveTabCount } from '../../utils/detailActionModel.js'
171
+ import { fetchListTotal } from '../../utils/fetchFilterCounts.js'
117
172
  import { isActivityWidget } from '../../utils/headerMeta.js'
118
173
  import { PANEL_ACTION_SINK } from '../../utils/panelActions.js'
119
174
  import { evaluateVisibleWhen, evaluateVisibleWhenLocal, isLocallyDecidableVisibleWhen } from '../../utils/visibleWhen.js'
@@ -254,6 +309,11 @@ export default {
254
309
  cnLook: { default: 'nextcloud' },
255
310
  /** The tab list's name under the board look, provided by CnDetailPage (`config.tabsLabel`). */
256
311
  cnDetailTabsLabel: { default: null },
312
+ /**
313
+ * The host's translate (CnAppRoot provides `cnTranslate`), so a tab
314
+ * label written in the manifest reads in the user's language.
315
+ */
316
+ cnTranslate: { default: () => (key) => key },
257
317
  },
258
318
 
259
319
  props: {
@@ -263,7 +323,11 @@ export default {
263
323
  * `tabs[]` entries are `{ widgetId, label?, icon?, count?, countField?, overflow? }`.
264
324
  * `label` and `icon` fall back to the referenced widget's own title and
265
325
  * icon. `count` is a number shown after the label; `countField` reads
266
- * it off the record instead (a list counts its items). `overflow: true`
326
+ * it off the record instead (a list counts its items). `countFrom`
327
+ * counts a list on the server: `"widget"` counts the child widget's
328
+ * own list (`content.register`, `schema`, `filter`, tokens such as
329
+ * `@objectId` resolved), an object `{ register, schema, filter? }`
330
+ * names the list; `count` and `countField` win over it. `overflow: true`
267
331
  * lists the tab under "More".
268
332
  *
269
333
  * `maxVisibleTabs` caps the strip: later tabs go under "More".
@@ -271,7 +335,7 @@ export default {
271
335
  * `moreLabel` names that menu. `variant: "segmented"` draws the strip
272
336
  * as a pill switch (CnTabs' segmented variant); `line` is the default.
273
337
  *
274
- * @type {{ tabs?: Array<{widgetId: string, label?: string, icon?: string, count?: number, countField?: string, overflow?: boolean}>, ariaLabel?: string, maxVisibleTabs?: number, hideEmpty?: boolean, moreLabel?: string, variant?: ('line'|'segmented') }}
338
+ * @type {{ tabs?: Array<{widgetId?: string, widgetIds?: string[], label?: string, icon?: string, count?: number, countField?: string, countFrom?: ('widget'|{register: string, schema: string, filter?: object}), overflow?: boolean}>, ariaLabel?: string, maxVisibleTabs?: number, hideEmpty?: boolean, moreLabel?: string, variant?: ('line'|'segmented') }}
275
339
  */
276
340
  content: {
277
341
  type: Object,
@@ -391,6 +455,9 @@ export default {
391
455
  sourceVisibility: {},
392
456
  // Bumped per evaluation round, so a slow answer from an earlier round is dropped.
393
457
  visibilitySeq: 0,
458
+ // Totals counted for tabs with `countFrom`, by position in `content.tabs`.
459
+ widgetCounts: {},
460
+ countSeq: 0,
394
461
  // Tab id named by the route hash that has not appeared yet (still pending).
395
462
  hashTargetId: '',
396
463
  // Items published by the panels, keyed by widget id. Keyed rather
@@ -433,7 +500,7 @@ export default {
433
500
  // whatever its place in the manifest. `index` stays the position in
434
501
  // `content.tabs`, which the source-mode conditions are keyed on.
435
502
  const isActivityTab = (tab) => {
436
- const id = typeof tab === 'string' ? tab : tab?.widgetId
503
+ const id = this.tabWidgetIds(tab)[0]
437
504
  return isActivityWidget(this.availableWidgets.find((w) => w && w.id === id))
438
505
  }
439
506
  const ordered = authored.map((tab, index) => ({ tab, index }))
@@ -455,9 +522,19 @@ export default {
455
522
  if (visibility === 'hidden') {
456
523
  return
457
524
  }
458
- const widgetId = typeof tab === 'string' ? tab : tab?.widgetId
459
- const widget = this.availableWidgets.find((w) => w && w.id === widgetId) || null
460
- const count = resolveTabCount(typeof tab === 'object' ? tab : null, this.objectData)
525
+ // A stack (`widgetIds`) resolves every id; one that names no
526
+ // widget is skipped. The first that resolves is the panel's.
527
+ const ids = this.tabWidgetIds(tab)
528
+ const widgets = ids.length > 1
529
+ ? ids.map((id) => this.availableWidgets.find((w) => w && w.id === id) || null).filter(Boolean)
530
+ : []
531
+ const widget = ids.length > 1
532
+ ? (widgets[0] || null)
533
+ : (this.availableWidgets.find((w) => w && w.id === ids[0]) || null)
534
+ const widgetId = widget ? widget.id : ids[0]
535
+ const declared = resolveTabCount(typeof tab === 'object' ? tab : null, this.objectData)
536
+ const counted = this.widgetCounts[index]
537
+ const count = declared === null && typeof counted === 'number' ? counted : declared
461
538
  // The board strip has no "More": every tab is in the strip.
462
539
  let overflow = !this.isBoard && ((tab && tab.overflow === true) || (hideEmpty && count === 0))
463
540
  if (!overflow && !this.isBoard) {
@@ -471,9 +548,10 @@ export default {
471
548
  key: `${widgetId || 'tab'}-${index}`,
472
549
  id: (tab && typeof tab === 'object' && tab.id) || widgetId || '',
473
550
  widgetId,
474
- label: (tab && tab.label) || (this.isBoard && isActivityWidget(widget) ? t('nextcloud-vue', 'History') : '') || widgetTitleOf(widget) || widgetId || '',
551
+ label: this.tabLabel(tab, widget, widgetId),
475
552
  icon: (tab && tab.icon) || widget?.icon || '',
476
553
  widget,
554
+ stack: widgets.slice(1),
477
555
  count,
478
556
  overflow,
479
557
  pending: visibility === 'pending',
@@ -492,6 +570,17 @@ export default {
492
570
  return t('nextcloud-vue', 'Loading …')
493
571
  },
494
572
 
573
+ /**
574
+ * The lists the tabs count (`countFrom`), serialised, to count again
575
+ * when they change.
576
+ *
577
+ * @return {string} The key.
578
+ */
579
+ countSourcesKey() {
580
+ const tabs = Array.isArray(this.content?.tabs) ? this.content.tabs : []
581
+ return JSON.stringify(tabs.map((tab) => this.countSourceOf(tab)))
582
+ },
583
+
495
584
  /** @return {string} The source-mode conditions, serialised, to re-evaluate when they change. */
496
585
  sourceConditionsKey() {
497
586
  const tabs = Array.isArray(this.content?.tabs) ? this.content.tabs : []
@@ -604,6 +693,12 @@ export default {
604
693
  objectId() {
605
694
  this.sourceVisibility = {}
606
695
  this.refreshSourceVisibility()
696
+ this.widgetCounts = {}
697
+ this.refreshWidgetCounts()
698
+ },
699
+
700
+ countSourcesKey() {
701
+ this.refreshWidgetCounts()
607
702
  },
608
703
 
609
704
  sourceConditionsKey() {
@@ -638,10 +733,14 @@ export default {
638
733
  }
639
734
  this.activeKey = this.resolvedTabs[this.activeIndex]?.key || ''
640
735
  this.refreshSourceVisibility()
736
+ this.refreshWidgetCounts()
641
737
  },
642
738
 
643
739
  mounted() {
644
- this._onRefresh = () => this.refreshSourceVisibility()
740
+ this._onRefresh = () => {
741
+ this.refreshSourceVisibility()
742
+ this.refreshWidgetCounts()
743
+ }
645
744
  subscribe('cn:page:refresh', this._onRefresh)
646
745
  subscribe('cn:widget:refresh', this._onRefresh)
647
746
  },
@@ -652,6 +751,51 @@ export default {
652
751
  },
653
752
 
654
753
  methods: {
754
+ /**
755
+ * The widget ids a tab names: `widgetIds` (a stack) when it holds at
756
+ * least one id, else `widgetId`, or the bare string.
757
+ *
758
+ * @param {object|string} tab The `content.tabs[]` entry.
759
+ * @return {string[]} The ids, at least one entry (possibly undefined).
760
+ * @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-tab-can-hold-a-stack-of-widgets
761
+ */
762
+ tabWidgetIds(tab) {
763
+ if (typeof tab === 'string') {
764
+ return [tab]
765
+ }
766
+ if (tab && Array.isArray(tab.widgetIds)) {
767
+ const ids = tab.widgetIds.filter((id) => typeof id === 'string' && id !== '')
768
+ if (ids.length) {
769
+ return ids
770
+ }
771
+ }
772
+ return [tab?.widgetId]
773
+ },
774
+
775
+ /**
776
+ * The label a tab shows: the authored `label`, else (board look) History
777
+ * for the activity widget, else the child widget's title, else its id.
778
+ * A manifest label or title goes through the host's translate; the
779
+ * library's own History is translated by the library catalogue.
780
+ *
781
+ * @param {object|string} tab The `content.tabs[]` entry.
782
+ * @param {object|null} widget The widget definition it names.
783
+ * @param {string} widgetId The widget id.
784
+ * @return {string} The label.
785
+ * @spec openspec/changes/screens-detail-labels-parity/specs/detail-labels-board-look/spec.md#requirement-detail-labels-read-in-the-users-language
786
+ */
787
+ tabLabel(tab, widget, widgetId) {
788
+ const translate = typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key
789
+ if (tab && typeof tab.label === 'string' && tab.label !== '') {
790
+ return translate(tab.label)
791
+ }
792
+ if (this.isBoard && isActivityWidget(widget)) {
793
+ return t('nextcloud-vue', 'History')
794
+ }
795
+ const title = widgetTitleOf(widget)
796
+ return title ? translate(title) : (widgetId || '')
797
+ },
798
+
655
799
  /**
656
800
  * Whether a tab shows: `visible`, `hidden`, or `pending` while its
657
801
  * condition cannot be decided yet (object not loaded, count not back).
@@ -704,6 +848,60 @@ export default {
704
848
  }))
705
849
  },
706
850
 
851
+ /**
852
+ * The list a tab counts (`countFrom`): `"widget"` counts the child
853
+ * widget's own list (its `content.register`, `content.schema` and
854
+ * `content.filter`); an object `{ register, schema, filter? }` names
855
+ * the list itself. Null for a tab that does not ask, or a child with
856
+ * no list.
857
+ *
858
+ * @param {object|string} tab The `content.tabs[]` entry.
859
+ * @return {{register: string, schema: string, filter: object}|null} The list.
860
+ * @spec openspec/changes/screens-tab-counts-parity/specs/tab-counts/spec.md#requirement-a-tab-can-count-its-list
861
+ */
862
+ countSourceOf(tab) {
863
+ const from = tab && typeof tab === 'object' ? tab.countFrom : null
864
+ let source = null
865
+ if (from === 'widget') {
866
+ // A stack counts its first widget that has a list.
867
+ const lists = this.tabWidgetIds(tab)
868
+ .map((id) => this.availableWidgets.find((w) => w && w.id === id))
869
+ .filter((w) => w && w.content && typeof w.content === 'object' && w.content.register && w.content.schema)
870
+ source = lists.length ? lists[0].content : null
871
+ } else if (from && typeof from === 'object') {
872
+ source = from
873
+ }
874
+ if (!source || typeof source.register !== 'string' || source.register === '' || typeof source.schema !== 'string' || source.schema === '') {
875
+ return null
876
+ }
877
+ const filter = source.filter && typeof source.filter === 'object' && !Array.isArray(source.filter) ? source.filter : {}
878
+ return { register: source.register, schema: source.schema, filter }
879
+ },
880
+
881
+ /**
882
+ * Count the list behind every tab with `countFrom`: once on mount,
883
+ * when the record changes and after a refresh. A tab keeps its last
884
+ * total while the new one is on its way; a failed count shows none.
885
+ *
886
+ * @return {Promise<void>}
887
+ * @spec openspec/changes/screens-tab-counts-parity/specs/tab-counts/spec.md#requirement-a-tab-can-count-its-list
888
+ */
889
+ async refreshWidgetCounts() {
890
+ const tabs = Array.isArray(this.content?.tabs) ? this.content.tabs : []
891
+ const seq = ++this.countSeq
892
+ const ctx = { objectId: this.objectId, object: this.objectData || undefined }
893
+ await Promise.all(tabs.map(async (tab, index) => {
894
+ const source = this.countSourceOf(tab)
895
+ if (!source) {
896
+ return
897
+ }
898
+ const total = await fetchListTotal(source.register, source.schema, source.filter, ctx).catch(() => null)
899
+ if (seq === this.countSeq) {
900
+ this.widgetCounts = { ...this.widgetCounts, [index]: total }
901
+ }
902
+ }))
903
+ },
904
+
707
905
  /** @return {string} The route hash without its `#`, or ''. */
708
906
  currentHash() {
709
907
  const fromRoute = this.$route && typeof this.$route.hash === 'string' ? this.$route.hash : ''
@@ -794,6 +992,13 @@ export default {
794
992
  </script>
795
993
 
796
994
  <style scoped>
995
+ /* A stack (`widgetIds`): the panel's widget, then cards 20px apart. */
996
+ .cn-tabs-widget__stack {
997
+ display: flex;
998
+ flex-direction: column;
999
+ gap: 20px;
1000
+ }
1001
+
797
1002
  /* The panel is the card; the strip is not inside it. There is no title bar
798
1003
  above the tabs either: the open tab names the panel, so a title row would
799
1004
  say the same thing twice and cost a row of height on a card that is mostly
@@ -72,6 +72,10 @@ import '../CnDeltaWidget/index.js'
72
72
  import '../CnGaugeWidget/index.js'
73
73
  import '../CnFlowRunsWidget/index.js'
74
74
  import '../CnTasksWidget/index.js'
75
+ import '../CnSectionsWidget/index.js'
76
+ import '../CnFactsWidget/index.js'
77
+ import '../CnFieldListWidget/index.js'
78
+ import '../CnRecordListWidget/index.js'
75
79
  import '../CnNotepadWidget/index.js'
76
80
  import '../CnObjectDataWidget/dashboardRegistration.js'
77
81
  import '../CnObjectGeoWidget/dashboardRegistration.js'
@@ -323,6 +323,19 @@ export default {
323
323
  default: false,
324
324
  },
325
325
 
326
+ /**
327
+ * Fit the table to the tile: the title column (a column's `grow: true`,
328
+ * else `title` or `name`, else the first) shrinks and ends in an
329
+ * ellipsis so the trailing columns stay visible. On by default; pass
330
+ * `false` for the table's own auto layout. Forwarded to CnDataTable.
331
+ *
332
+ * @spec openspec/changes/narrow-object-table-widgets/specs/narrow-object-table-widgets/spec.md#requirement-a-narrow-object-table-keeps-its-trailing-columns-in-view
333
+ */
334
+ fitWidth: {
335
+ type: Boolean,
336
+ default: true,
337
+ },
338
+
326
339
  /**
327
340
  * Route NAME for row-click navigation. Mapped onto CnDataTable's
328
341
  * `rowClickRoute` as `{ name: rowRoute, params: { id } }` with the
@@ -4,12 +4,43 @@
4
4
  -->
5
5
 
6
6
  <template>
7
- <div class="cn-workspace-filter-widget" data-testid="cn-workspace-filter-widget">
7
+ <div
8
+ class="cn-workspace-filter-widget"
9
+ :class="{ 'cn-workspace-filter-widget--start': isStart }"
10
+ data-testid="cn-workspace-filter-widget">
11
+ <!-- The start card (`layout: "start"`): a title and one line of
12
+ guidance beside the control, as the "Contact starten" card. -->
13
+ <div v-if="isStart && (title || description)" class="cn-workspace-filter-widget__text">
14
+ <h3 v-if="title" class="cn-workspace-filter-widget__title">
15
+ {{ title }}
16
+ </h3>
17
+ <p v-if="description" class="cn-workspace-filter-widget__description">
18
+ {{ description }}
19
+ </p>
20
+ </div>
21
+
8
22
  <p v-if="label" class="cn-workspace-filter-widget__label">
9
23
  {{ label }}
10
24
  </p>
11
25
 
12
- <div v-if="loading" class="cn-workspace-filter-widget__status">
26
+ <!-- An object picker (`objectSource`): searches OpenRegister on the
27
+ server, so it reaches every object, not a fetched page. -->
28
+ <CnResourceSelect
29
+ v-if="objectSource"
30
+ class="cn-workspace-filter-widget__control"
31
+ :register="objectSource.register"
32
+ :schema="objectSource.schema"
33
+ :labelField="objectSource.labelField || 'title'"
34
+ :modelValue="selected === undefined || selected === null ? '' : String(selected)"
35
+ :placeholder="tr(objectSource.placeholder || '')"
36
+ :ariaLabelCombobox="tr(objectSource.ariaLabel || '') || title || label || writeKey"
37
+ :preload="objectSource.preload !== false"
38
+ :clearable="Boolean(content.allLabel)"
39
+ :allowCreate="false"
40
+ data-testid="cn-workspace-filter-object"
41
+ @update:modelValue="applySelection($event || '')" />
42
+
43
+ <div v-else-if="loading" class="cn-workspace-filter-widget__status">
13
44
  {{ loadingLabel }}
14
45
  </div>
15
46
 
@@ -47,12 +78,23 @@
47
78
  </NcCheckboxRadioSwitch>
48
79
  </li>
49
80
  </ul>
81
+
82
+ <component
83
+ :is="startLink.to ? 'router-link' : 'a'"
84
+ v-if="startLink"
85
+ :to="startLink.to || undefined"
86
+ :href="startLink.href || undefined"
87
+ class="cn-workspace-filter-widget__link"
88
+ data-testid="cn-workspace-filter-link">
89
+ {{ startLink.label }}
90
+ </component>
50
91
  </div>
51
92
  </template>
52
93
 
53
94
  <script>
54
95
  import { translate as t } from '@nextcloud/l10n'
55
96
  import { NcCheckboxRadioSwitch, NcSelect } from '@nextcloud/vue'
97
+ import CnResourceSelect from '../CnResourceSelect/CnResourceSelect.vue'
56
98
  import { fetchEndpointSource } from '../../composables/useEndpointSource.js'
57
99
  import { resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
58
100
 
@@ -94,7 +136,7 @@ import { resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
94
136
  export default {
95
137
  name: 'CnWorkspaceFilterWidget',
96
138
 
97
- components: { NcSelect, NcCheckboxRadioSwitch },
139
+ components: { CnResourceSelect, NcSelect, NcCheckboxRadioSwitch },
98
140
 
99
141
  inject: {
100
142
  /**
@@ -104,13 +146,23 @@ export default {
104
146
  * Null on pages without one (the widget then just emits `@change`).
105
147
  */
106
148
  cnWorkspaceContext: { default: null },
149
+ /** Host translate function, for the start card's texts. */
150
+ cnTranslate: { default: () => (key) => key },
107
151
  },
108
152
 
109
153
  props: {
110
154
  /**
111
155
  * Persisted configuration blob.
112
156
  *
113
- * @type {{label?: string, writes?: string, style?: string, allLabel?: string, showCounts?: boolean, options?: Array, source?: object, endpointSource?: object, default?: (string|number)}}
157
+ * Start card keys (abstract-start-card): `layout: "start"` draws
158
+ * `title` and `description` beside the control, and `link`
159
+ * (`{ label, route? | href? }`) after it. `objectSource`
160
+ * (`{ register, schema, labelField?, placeholder?, ariaLabel?, preload? }`)
161
+ * makes the control an OpenRegister object picker that searches on the
162
+ * server and writes the picked object's id; without a selection and
163
+ * without `allLabel` it selects the first object.
164
+ *
165
+ * @type {{label?: string, writes?: string, style?: string, allLabel?: string, showCounts?: boolean, options?: Array, source?: object, endpointSource?: object, objectSource?: object, default?: (string|number), layout?: string, title?: string, description?: string, link?: object}}
114
166
  */
115
167
  content: {
116
168
  type: Object,
@@ -137,6 +189,50 @@ export default {
137
189
  return this.content.label || ''
138
190
  },
139
191
 
192
+ /** Whether the widget draws the start card. */
193
+ isStart() {
194
+ return this.content.layout === 'start'
195
+ },
196
+
197
+ /** The start card's title, translated. */
198
+ title() {
199
+ return this.tr(this.content.title || '')
200
+ },
201
+
202
+ /** The start card's guidance line, translated. */
203
+ description() {
204
+ return this.tr(this.content.description || '')
205
+ },
206
+
207
+ /**
208
+ * The link after the control, or null.
209
+ *
210
+ * @return {{label: string, to?: object, href?: string}|null}
211
+ */
212
+ startLink() {
213
+ const link = this.content.link
214
+ if (!link || typeof link !== 'object' || !link.label) {
215
+ return null
216
+ }
217
+ if (link.route && typeof link.route === 'object' && link.route.name) {
218
+ return { label: this.tr(link.label), to: link.route }
219
+ }
220
+ if (typeof link.href === 'string' && link.href !== '') {
221
+ return { label: this.tr(link.label), href: link.href }
222
+ }
223
+ return null
224
+ },
225
+
226
+ /**
227
+ * The object picker declaration, or null.
228
+ *
229
+ * @return {{register: string, schema: string, labelField?: string}|null}
230
+ */
231
+ objectSource() {
232
+ const src = this.content.objectSource
233
+ return src && typeof src === 'object' && src.register && src.schema ? src : null
234
+ },
235
+
140
236
  /** Choice style: 'select' or 'radio' (default). */
141
237
  style() {
142
238
  return this.content.style === 'select' ? 'select' : 'radio'
@@ -263,6 +359,10 @@ export default {
263
359
  * @return {Promise<void>}
264
360
  */
265
361
  async loadOptions() {
362
+ if (this.objectSource) {
363
+ await this.seedFirstObject()
364
+ return
365
+ }
266
366
  if (this.content.endpointSource && this.content.endpointSource.url) {
267
367
  await this.loadFromEndpoint()
268
368
  } else if (this.content.source && this.content.source.schema) {
@@ -281,6 +381,51 @@ export default {
281
381
  }
282
382
  },
283
383
 
384
+ /**
385
+ * Translate a manifest string through the host app.
386
+ *
387
+ * @param {string} text The text.
388
+ * @return {string}
389
+ */
390
+ tr(text) {
391
+ if (typeof text !== 'string' || text === '') {
392
+ return ''
393
+ }
394
+ return typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text
395
+ },
396
+
397
+ /**
398
+ * The object picker's default: with nothing selected, no `default` and
399
+ * no `allLabel`, select the first object the source lists, so the page
400
+ * always has one in focus. Fail-safe: a failed read selects nothing.
401
+ *
402
+ * @return {Promise<void>}
403
+ */
404
+ async seedFirstObject() {
405
+ if ((this.selected !== undefined && this.selected !== '') || this.content.allLabel || this.content.default !== undefined) {
406
+ return
407
+ }
408
+ const src = this.objectSource
409
+ try {
410
+ const [{ default: axios }, { generateUrl }] = await Promise.all([
411
+ import('@nextcloud/axios'),
412
+ import('@nextcloud/router'),
413
+ ])
414
+ const url = generateUrl('/apps/openregister/api/objects/{register}/{schema}', { register: src.register, schema: src.schema })
415
+ const res = await axios.get(url, { params: { _limit: 1 } })
416
+ const data = res && res.data
417
+ const rows = Array.isArray(data) ? data : ((data && data.results) || [])
418
+ const first = rows[0]
419
+ const id = first && (first.id || (first['@self'] && first['@self'].id))
420
+ if (id && (this.selected === undefined || this.selected === '') && this.readWorkspace() === undefined) {
421
+ this.selected = String(id)
422
+ this.writeWorkspace(String(id))
423
+ }
424
+ } catch {
425
+ // Nothing selected: the siblings wait for a pick.
426
+ }
427
+ },
428
+
284
429
  /**
285
430
  * Fetch options from an `endpointSource` (array of options at
286
431
  * responsePath). Fail-safe: a failed fetch leaves an empty list.
@@ -465,6 +610,45 @@ export default {
465
610
  gap: 2px;
466
611
  }
467
612
 
613
+ /* The start card: text left, control (and link) right, as PqDashboard's "Contact starten". */
614
+ .cn-workspace-filter-widget--start {
615
+ flex-direction: row;
616
+ flex-wrap: wrap;
617
+ align-items: center;
618
+ gap: 16px 24px;
619
+ }
620
+
621
+ .cn-workspace-filter-widget__text {
622
+ flex: 1 1 320px;
623
+ min-width: 0;
624
+ }
625
+
626
+ .cn-workspace-filter-widget__title {
627
+ margin: 0 0 4px;
628
+ font-size: 17px;
629
+ font-weight: 700;
630
+ color: var(--color-main-text);
631
+ }
632
+
633
+ .cn-workspace-filter-widget__description {
634
+ margin: 0;
635
+ max-width: 420px;
636
+ color: var(--color-text-maxcontrast);
637
+ }
638
+
639
+ .cn-workspace-filter-widget--start .cn-workspace-filter-widget__control,
640
+ .cn-workspace-filter-widget--start > .v-select {
641
+ flex: 0 1 480px;
642
+ min-width: 240px;
643
+ }
644
+
645
+ .cn-workspace-filter-widget__link {
646
+ font-weight: 600;
647
+ color: var(--color-primary-element);
648
+ text-decoration: underline;
649
+ white-space: nowrap;
650
+ }
651
+
468
652
  .cn-workspace-filter-widget__count {
469
653
  margin-inline-start: 6px;
470
654
  padding: 0 6px;
@@ -109,7 +109,7 @@ export { CnTreeView } from './CnTreeView/index.js'
109
109
  export { CnUserActionMenu } from './CnUserActionMenu/index.js'
110
110
  export { CnNotesCard } from './CnNotesCard/index.js'
111
111
  export { CnTasksCard } from './CnTasksCard/index.js'
112
- export { CnFilesBrowser } from './CnFilesBrowser/index.js'
112
+ export { CnFilesBrowser, createOpenRegisterSource } from './CnFilesBrowser/index.js'
113
113
  export { CnFilesCard } from './CnFilesCard/index.js'
114
114
  export { CnFileManager } from './CnFileManager/index.js'
115
115
  export { CnRelatedFiles } from './CnRelatedFiles/index.js'
@@ -216,6 +216,10 @@ export { CnFlowStepOutcomes } from './CnFlowStepOutcomes/index.js'
216
216
  export { CnFlowRunsWidgetForm } from './CnFlowRunsWidgetForm/index.js'
217
217
  export { CnTasksWidget } from './CnTasksWidget/index.js'
218
218
  export { CnTasksWidgetForm } from './CnTasksWidgetForm/index.js'
219
+ export { CnSectionsWidget } from './CnSectionsWidget/index.js'
220
+ export { CnFactsWidget } from './CnFactsWidget/index.js'
221
+ export { CnFieldListWidget } from './CnFieldListWidget/index.js'
222
+ export { CnRecordListWidget } from './CnRecordListWidget/index.js'
219
223
  export { CnFilePreview } from './CnFilePreview/index.js'
220
224
  export { CnTaskFormDialog } from './CnTaskFormDialog/index.js'
221
225
  export { CnChildRecordsField } from './CnChildRecordsField/index.js'