@conduction/nextcloud-vue 2.79.0 → 2.81.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 (390) hide show
  1. package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js.map +1 -1
  2. package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js +3 -1
  3. package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js.map +1 -1
  4. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +32 -3
  5. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  6. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +13 -11
  7. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  8. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +80 -4
  9. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  10. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +95 -91
  11. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  12. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +179 -13
  13. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  14. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +52 -16
  15. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  16. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +96 -4
  17. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  18. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +158 -110
  19. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  20. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js +10 -0
  21. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js.map +1 -0
  22. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js +252 -0
  23. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js.map +1 -0
  24. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js +140 -0
  25. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js.map +1 -0
  26. package/dist/esm/components/CnChipsFieldWidget/index.js +26 -0
  27. package/dist/esm/components/CnChipsFieldWidget/index.js.map +1 -0
  28. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js +10 -0
  29. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js.map +1 -0
  30. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js +354 -0
  31. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js.map +1 -0
  32. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js +172 -0
  33. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js.map +1 -0
  34. package/dist/esm/components/CnChoicePickerWidget/index.js +27 -0
  35. package/dist/esm/components/CnChoicePickerWidget/index.js.map +1 -0
  36. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +1 -1
  37. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
  38. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
  39. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +2 -2
  40. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
  41. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
  42. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +65 -5
  43. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  44. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -1
  45. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  46. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +19 -11
  47. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  48. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +4 -2
  49. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  50. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +134 -4
  51. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  52. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +21 -4
  53. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  54. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +24 -0
  55. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  56. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +43 -4
  57. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  58. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +1 -1
  59. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -1
  60. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -1
  61. package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js +7 -7
  62. package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js.map +1 -1
  63. package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue3.js.map +1 -1
  64. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +11 -0
  65. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js.map +1 -0
  66. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +867 -0
  67. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -0
  68. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +238 -0
  69. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -0
  70. package/dist/esm/components/CnFactsWidget/index.js +30 -0
  71. package/dist/esm/components/CnFactsWidget/index.js.map +1 -0
  72. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js +10 -0
  73. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js.map +1 -0
  74. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +626 -0
  75. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -0
  76. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +396 -0
  77. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -0
  78. package/dist/esm/components/CnFieldListWidget/index.js +29 -0
  79. package/dist/esm/components/CnFieldListWidget/index.js.map +1 -0
  80. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +302 -4
  81. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  82. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +417 -244
  83. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  84. package/dist/esm/components/CnFilesBrowser/filesBrowser.js +101 -1
  85. package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
  86. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +141 -1
  87. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
  88. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +65 -17
  89. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  90. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +4 -4
  91. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  92. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +78 -3
  93. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
  94. package/dist/esm/components/CnFormPage/CnFormPage.vue3.js +74 -49
  95. package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
  96. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +333 -73
  97. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  98. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +79 -17
  99. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  100. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +10 -0
  101. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js.map +1 -0
  102. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +99 -0
  103. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -0
  104. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +48 -0
  105. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -0
  106. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +7 -7
  107. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
  108. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
  109. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +1 -1
  110. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -1
  111. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +65 -2
  112. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  113. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +6 -2
  114. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  115. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +63 -1
  116. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  117. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +8 -1
  118. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  119. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +14 -0
  120. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
  121. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js +5 -1
  122. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
  123. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js +29 -1
  124. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
  125. package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
  126. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js +10 -0
  127. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js.map +1 -0
  128. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js +174 -0
  129. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js.map +1 -0
  130. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js +75 -0
  131. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js.map +1 -0
  132. package/dist/esm/components/CnRadioCardsWidget/index.js +28 -0
  133. package/dist/esm/components/CnRadioCardsWidget/index.js.map +1 -0
  134. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +10 -0
  135. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js.map +1 -0
  136. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +411 -0
  137. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -0
  138. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +178 -0
  139. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -0
  140. package/dist/esm/components/CnRecordListWidget/index.js +31 -0
  141. package/dist/esm/components/CnRecordListWidget/index.js.map +1 -0
  142. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js +10 -0
  143. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js.map +1 -0
  144. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js +222 -0
  145. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js.map +1 -0
  146. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js +64 -0
  147. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js.map +1 -0
  148. package/dist/esm/components/CnSavedViewsWidget/index.js +30 -0
  149. package/dist/esm/components/CnSavedViewsWidget/index.js.map +1 -0
  150. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js +10 -0
  151. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js.map +1 -0
  152. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js +351 -0
  153. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js.map +1 -0
  154. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js +71 -0
  155. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js.map +1 -0
  156. package/dist/esm/components/CnSectionsWidget/index.js +35 -0
  157. package/dist/esm/components/CnSectionsWidget/index.js.map +1 -0
  158. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
  159. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
  160. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js +10 -0
  161. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js.map +1 -0
  162. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js +347 -0
  163. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js.map +1 -0
  164. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js +184 -0
  165. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js.map +1 -0
  166. package/dist/esm/components/CnSettingsFormWidget/index.js +27 -0
  167. package/dist/esm/components/CnSettingsFormWidget/index.js.map +1 -0
  168. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +81 -2
  169. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
  170. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
  171. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +87 -6
  172. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  173. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +1 -0
  174. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  175. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +84 -1
  176. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
  177. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +85 -41
  178. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
  179. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js +10 -0
  180. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js.map +1 -0
  181. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js +159 -0
  182. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js.map +1 -0
  183. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js +60 -0
  184. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js.map +1 -0
  185. package/dist/esm/components/CnSwitchRowsWidget/index.js +26 -0
  186. package/dist/esm/components/CnSwitchRowsWidget/index.js.map +1 -0
  187. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +168 -7
  188. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  189. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +86 -23
  190. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  191. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +80 -1
  192. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
  193. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +75 -32
  194. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
  195. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js +10 -0
  196. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js.map +1 -0
  197. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +269 -0
  198. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -0
  199. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js +106 -0
  200. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -0
  201. package/dist/esm/components/CnToggleTableWidget/index.js +27 -0
  202. package/dist/esm/components/CnToggleTableWidget/index.js.map +1 -0
  203. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +11 -0
  204. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  205. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +7 -2
  206. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  207. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  208. package/dist/esm/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
  209. package/dist/esm/components/CnWidgetObjectTable/dashboardRegistration.js.map +1 -1
  210. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js +301 -6
  211. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
  212. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +153 -55
  213. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
  214. package/dist/esm/composables/useFormDraft.js +140 -9
  215. package/dist/esm/composables/useFormDraft.js.map +1 -1
  216. package/dist/esm/composables/useListView.js +4 -4
  217. package/dist/esm/composables/useListView.js.map +1 -1
  218. package/dist/esm/index.js +21 -1
  219. package/dist/esm/index.js.map +1 -1
  220. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js +28 -20
  221. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue2.js.map +1 -1
  222. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js +9 -0
  223. package/dist/esm/integrations/builtin/field-inspection/CnFieldInspectionCard.vue3.js.map +1 -1
  224. package/dist/esm/integrations/builtin/field-inspection.js +21 -0
  225. package/dist/esm/integrations/builtin/field-inspection.js.map +1 -1
  226. package/dist/esm/integrations/offline/checklistSubmission.js +141 -0
  227. package/dist/esm/integrations/offline/checklistSubmission.js.map +1 -0
  228. package/dist/esm/integrations/offline/fieldCollectionHelpers.js +70 -5
  229. package/dist/esm/integrations/offline/fieldCollectionHelpers.js.map +1 -1
  230. package/dist/esm/integrations/offline/index.js +2 -1
  231. package/dist/esm/integrations/offline/index.js.map +1 -1
  232. package/dist/esm/integrations/offline/offlineDb.js +6 -1
  233. package/dist/esm/integrations/offline/offlineDb.js.map +1 -1
  234. package/dist/esm/integrations/offline/syncReplayService.js +8 -2
  235. package/dist/esm/integrations/offline/syncReplayService.js.map +1 -1
  236. package/dist/esm/l10n/en.json.js +20 -1
  237. package/dist/esm/l10n/en.json.js.map +1 -1
  238. package/dist/esm/l10n/nl.json.js +70 -2
  239. package/dist/esm/l10n/nl.json.js.map +1 -1
  240. package/dist/esm/nextcloud-vue.css +2192 -222
  241. package/dist/esm/utils/contentHeaderLink.js +51 -0
  242. package/dist/esm/utils/contentHeaderLink.js.map +1 -0
  243. package/dist/esm/utils/detailActionModel.js +60 -3
  244. package/dist/esm/utils/detailActionModel.js.map +1 -1
  245. package/dist/esm/utils/facets.js +75 -3
  246. package/dist/esm/utils/facets.js.map +1 -1
  247. package/dist/esm/utils/fetchFilterCounts.js +40 -5
  248. package/dist/esm/utils/fetchFilterCounts.js.map +1 -1
  249. package/dist/esm/utils/fillTemplate.js +120 -0
  250. package/dist/esm/utils/fillTemplate.js.map +1 -0
  251. package/dist/esm/utils/initials.js +32 -0
  252. package/dist/esm/utils/initials.js.map +1 -0
  253. package/dist/esm/utils/libraryWidgetKeys.js +11 -0
  254. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  255. package/dist/esm/utils/objectWrite.js +59 -0
  256. package/dist/esm/utils/objectWrite.js.map +1 -0
  257. package/dist/esm/utils/pageControls.js +81 -0
  258. package/dist/esm/utils/pageControls.js.map +1 -0
  259. package/dist/esm/utils/personalLenses.js +40 -3
  260. package/dist/esm/utils/personalLenses.js.map +1 -1
  261. package/dist/esm/utils/queryPresets.js +96 -0
  262. package/dist/esm/utils/queryPresets.js.map +1 -0
  263. package/dist/esm/utils/resolveFilterTokens.js +12 -1
  264. package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
  265. package/dist/esm/utils/resolveManifestTokens.js +2 -0
  266. package/dist/esm/utils/resolveManifestTokens.js.map +1 -1
  267. package/dist/esm/utils/schema.js +21 -0
  268. package/dist/esm/utils/schema.js.map +1 -1
  269. package/dist/esm/utils/sentinelTokens.js +22 -4
  270. package/dist/esm/utils/sentinelTokens.js.map +1 -1
  271. package/dist/esm/utils/userGroupsPeek.js +91 -0
  272. package/dist/esm/utils/userGroupsPeek.js.map +1 -0
  273. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  274. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  275. package/dist/esm/utils/visibleWhen.js +57 -5
  276. package/dist/esm/utils/visibleWhen.js.map +1 -1
  277. package/dist/esm/utils/widgetDispatch.js +7 -1
  278. package/dist/esm/utils/widgetDispatch.js.map +1 -1
  279. package/dist/esm/utils/widgetVisibility.js +4 -57
  280. package/dist/esm/utils/widgetVisibility.js.map +1 -1
  281. package/dist/nextcloud-vue.cjs.js +19708 -9301
  282. package/dist/nextcloud-vue.cjs.js.map +2 -2
  283. package/dist/nextcloud-vue.css +2192 -222
  284. package/l10n/en.json +20 -1
  285. package/l10n/nl.json +70 -2
  286. package/package.json +2 -1
  287. package/scripts/check-docs-mdx.mjs +108 -0
  288. package/src/components/CnActionButtons/CnActionButtons.vue +2 -0
  289. package/src/components/CnActionsBar/CnActionsBar.md +4 -0
  290. package/src/components/CnActionsBar/CnActionsBar.vue +48 -11
  291. package/src/components/CnAppRoot/CnAppRoot.md +4 -2
  292. package/src/components/CnAppRoot/CnAppRoot.vue +151 -59
  293. package/src/components/CnBannerWidget/CnBannerWidget.vue +244 -23
  294. package/src/components/CnCellRenderer/CnCellRenderer.md +2 -0
  295. package/src/components/CnCellRenderer/CnCellRenderer.vue +235 -5
  296. package/src/components/CnChipsFieldWidget/CnChipsFieldWidget.vue +433 -0
  297. package/src/components/CnChipsFieldWidget/index.js +25 -0
  298. package/src/components/CnChoicePickerWidget/CnChoicePickerWidget.vue +556 -0
  299. package/src/components/CnChoicePickerWidget/index.js +26 -0
  300. package/src/components/CnConversationThread/CnConversationWidget.vue +1 -1
  301. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +2 -2
  302. package/src/components/CnDashboardPage/CnDashboardPage.vue +78 -5
  303. package/src/components/CnDataTable/CnDataTable.vue +22 -12
  304. package/src/components/CnDetailPage/CnDetailPage.md +1 -1
  305. package/src/components/CnDetailPage/CnDetailPage.vue +152 -6
  306. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +66 -0
  307. package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +1 -1
  308. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +7 -7
  309. package/src/components/CnFactsWidget/CnFactsWidget.vue +1190 -0
  310. package/src/components/CnFactsWidget/index.js +29 -0
  311. package/src/components/CnFieldListWidget/CnFieldListWidget.vue +1008 -0
  312. package/src/components/CnFieldListWidget/index.js +28 -0
  313. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +588 -117
  314. package/src/components/CnFilesBrowser/filesBrowser.js +100 -0
  315. package/src/components/CnFilesBrowser/filesBrowserColumns.js +140 -0
  316. package/src/components/CnFormDialog/CnFormDialog.md +9 -3
  317. package/src/components/CnFormDialog/CnFormDialog.vue +69 -21
  318. package/src/components/CnFormPage/CnFormPage.vue +99 -3
  319. package/src/components/CnIndexPage/CnIndexPage.vue +309 -9
  320. package/src/components/CnIndexPage/CnIndexPageWidgets.vue +169 -0
  321. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +7 -7
  322. package/src/components/CnNextStepCard/CnNextStepWidget.vue +1 -1
  323. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +69 -2
  324. package/src/components/CnObjectSidebar/CnFilesTab.vue +70 -1
  325. package/src/components/CnObjectSidebar/CnNotesTab.vue +16 -0
  326. package/src/components/CnPageRenderer/CnPageRenderer.vue +29 -1
  327. package/src/components/CnRadioCardsWidget/CnRadioCardsWidget.vue +313 -0
  328. package/src/components/CnRadioCardsWidget/index.js +27 -0
  329. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +652 -0
  330. package/src/components/CnRecordListWidget/index.js +30 -0
  331. package/src/components/CnSavedViewsWidget/CnSavedViewsWidget.vue +296 -0
  332. package/src/components/CnSavedViewsWidget/index.js +29 -0
  333. package/src/components/CnSectionsWidget/CnSectionsWidget.vue +489 -0
  334. package/src/components/CnSectionsWidget/index.js +34 -0
  335. package/src/components/CnSegmentedControl/CnSegmentedControl.vue +13 -0
  336. package/src/components/CnSettingsFormWidget/CnSettingsFormWidget.vue +555 -0
  337. package/src/components/CnSettingsFormWidget/index.js +26 -0
  338. package/src/components/CnStagesWidget/CnStagesWidget.vue +81 -2
  339. package/src/components/CnStatWidget/CnStatWidget.vue +88 -7
  340. package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +180 -3
  341. package/src/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue +226 -0
  342. package/src/components/CnSwitchRowsWidget/index.js +25 -0
  343. package/src/components/CnTabsWidget/CnTabsWidget.vue +263 -8
  344. package/src/components/CnTasksWidget/CnTasksWidget.vue +175 -15
  345. package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +386 -0
  346. package/src/components/CnToggleTableWidget/index.js +26 -0
  347. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +11 -0
  348. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +7 -2
  349. package/src/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
  350. package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +493 -11
  351. package/src/components/index.js +19 -0
  352. package/src/composables/__tests__/useFormDraft.spec.js +69 -0
  353. package/src/composables/useFormDraft.js +139 -8
  354. package/src/composables/useListView.js +4 -4
  355. package/src/css/form-field.css +14 -0
  356. package/src/css/index-page.css +71 -0
  357. package/src/css/kpi-card.css +32 -0
  358. package/src/css/look-board-detail.css +113 -2
  359. package/src/css/look-board-index.css +90 -8
  360. package/src/css/look-board.css +109 -2
  361. package/src/css/table.css +19 -0
  362. package/src/index.js +19 -0
  363. package/src/integrations/builtin/field-inspection/CnFieldInspectionCard.vue +35 -20
  364. package/src/integrations/builtin/field-inspection.js +21 -0
  365. package/src/integrations/offline/checklistSubmission.js +137 -0
  366. package/src/integrations/offline/fieldCollectionHelpers.js +69 -4
  367. package/src/integrations/offline/index.js +3 -0
  368. package/src/integrations/offline/offlineDb.js +6 -1
  369. package/src/integrations/offline/syncReplayService.js +8 -2
  370. package/src/schemas/app-manifest-v2.schema.json +196 -13
  371. package/src/utils/contentHeaderLink.js +48 -0
  372. package/src/utils/detailActionModel.js +59 -2
  373. package/src/utils/facets.js +74 -2
  374. package/src/utils/fetchFilterCounts.js +39 -4
  375. package/src/utils/fillTemplate.js +116 -0
  376. package/src/utils/initials.js +29 -0
  377. package/src/utils/libraryWidgetKeys.js +11 -0
  378. package/src/utils/objectWrite.js +55 -0
  379. package/src/utils/pageControls.js +78 -0
  380. package/src/utils/personalLenses.js +39 -2
  381. package/src/utils/queryPresets.js +93 -0
  382. package/src/utils/resolveFilterTokens.js +14 -1
  383. package/src/utils/resolveManifestTokens.js +2 -0
  384. package/src/utils/schema.js +21 -0
  385. package/src/utils/sentinelTokens.js +22 -4
  386. package/src/utils/userGroupsPeek.js +87 -0
  387. package/src/utils/validateManifestV2.compiled.js +3 -3
  388. package/src/utils/visibleWhen.js +56 -4
  389. package/src/utils/widgetDispatch.js +7 -1
  390. package/src/utils/widgetVisibility.js +7 -56
@@ -0,0 +1,226 @@
1
+ <!--
2
+ - SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ - SPDX-License-Identifier: EUPL-1.2
4
+ -->
5
+
6
+ <template>
7
+ <div class="cn-switch-rows" data-testid="cn-switch-rows">
8
+ <ul class="cn-switch-rows__list">
9
+ <li
10
+ v-for="row in rows"
11
+ :key="row.key"
12
+ class="cn-switch-rows__row"
13
+ :data-testid="`cn-switch-rows-row-${row.key}`">
14
+ <NcCheckboxRadioSwitch
15
+ :modelValue="values[row.key] === true"
16
+ type="switch"
17
+ :disabled="readOnly || busy === row.key"
18
+ @update:modelValue="toggle(row, $event)">
19
+ <span class="cn-switch-rows__label">{{ row.label }}</span>
20
+ <span v-if="row.hint" class="cn-switch-rows__hint">{{ row.hint }}</span>
21
+ </NcCheckboxRadioSwitch>
22
+ </li>
23
+ </ul>
24
+ <NcNoteCard v-if="notice" type="error" data-testid="cn-switch-rows-notice">
25
+ {{ notice }}
26
+ </NcNoteCard>
27
+ </div>
28
+ </template>
29
+
30
+ <script>
31
+ import { NcCheckboxRadioSwitch, NcNoteCard } from '@nextcloud/vue'
32
+ import { objectUrlOf, patchObject } from '../../utils/objectWrite.js'
33
+ import { readPath } from '../../utils/readPath.js'
34
+ import { serverErrorMessage } from '../../utils/serverErrorMessage.js'
35
+
36
+ /**
37
+ * CnSwitchRowsWidget — the `switch-rows` widget: a card of switches, each with
38
+ * a label and a hint, bound to boolean fields of the CURRENT object. A toggle
39
+ * saves at once with ONE PATCH of that field; a refused PATCH puts the switch
40
+ * back and shows the server's words.
41
+ *
42
+ * @spec openspec/changes/abstract-switch-rows/specs/switch-rows/spec.md
43
+ */
44
+ export default {
45
+ name: 'CnSwitchRowsWidget',
46
+
47
+ components: { NcCheckboxRadioSwitch, NcNoteCard },
48
+
49
+ inject: {
50
+ cnTranslate: { default: () => (key) => key },
51
+ },
52
+
53
+ props: {
54
+ /**
55
+ * The widget configuration.
56
+ *
57
+ * - `rows` (required) — `[{ key, label, hint?, invert? }]`; `key` is a top-level boolean field. `invert` shows the switch on when the field is false (a stored `disabled` read as "enabled").
58
+ * - `readOnly`.
59
+ *
60
+ * @type {object}
61
+ */
62
+ content: {
63
+ type: Object,
64
+ default: () => ({}),
65
+ },
66
+
67
+ /** The current detail-page object. */
68
+ objectData: {
69
+ type: Object,
70
+ default: null,
71
+ },
72
+
73
+ /** The current object id. */
74
+ objectId: {
75
+ type: [String, Number],
76
+ default: '',
77
+ },
78
+
79
+ /** OpenRegister register slug of the surface. */
80
+ register: {
81
+ type: [String, Object],
82
+ default: '',
83
+ },
84
+
85
+ /** OpenRegister schema slug of the surface. */
86
+ schema: {
87
+ type: [String, Object],
88
+ default: '',
89
+ },
90
+ },
91
+
92
+ emits: ['saved'],
93
+
94
+ data() {
95
+ return {
96
+ /** The switch states as last saved (by key, as the switch shows them). */
97
+ saved: {},
98
+ values: {},
99
+ busy: '',
100
+ notice: '',
101
+ }
102
+ },
103
+
104
+ computed: {
105
+ rows() {
106
+ const list = Array.isArray(this.content.rows) ? this.content.rows : []
107
+ return list
108
+ .filter((row) => row && row.key)
109
+ .map((row) => ({ ...row, label: this.tr(row.label || row.key), hint: this.tr(row.hint || '') }))
110
+ },
111
+
112
+ readOnly() {
113
+ return this.content.readOnly === true
114
+ },
115
+
116
+ objectUrl() {
117
+ return objectUrlOf(this.register, this.schema, this.objectId || (this.objectData && this.objectData.id))
118
+ },
119
+ },
120
+
121
+ watch: {
122
+ objectData() {
123
+ this.adopt()
124
+ },
125
+ },
126
+
127
+ created() {
128
+ this.adopt()
129
+ },
130
+
131
+ methods: {
132
+ tr(text) {
133
+ if (typeof text !== 'string' || text === '') {
134
+ return ''
135
+ }
136
+ return typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text
137
+ },
138
+
139
+ /** Read every switch off the object. */
140
+ adopt() {
141
+ const values = {}
142
+ for (const row of this.rows) {
143
+ const stored = readPath(this.objectData || {}, row.key) === true
144
+ values[row.key] = row.invert ? !stored : stored
145
+ }
146
+ this.saved = values
147
+ this.values = { ...values }
148
+ },
149
+
150
+ /**
151
+ * Flip one switch and save that one field.
152
+ *
153
+ * @param {object} row The row.
154
+ * @param {boolean} on The new switch state.
155
+ * @return {Promise<void>}
156
+ * @spec openspec/changes/abstract-switch-rows/specs/switch-rows/spec.md#requirement-a-toggle-saves-that-one-field
157
+ */
158
+ async toggle(row, on) {
159
+ if (!this.objectUrl) {
160
+ this.notice = this.tr('This switch cannot be saved here.')
161
+ return
162
+ }
163
+ this.notice = ''
164
+ this.busy = row.key
165
+ this.values = { ...this.values, [row.key]: on }
166
+ const stored = row.invert ? !on : on
167
+ try {
168
+ const data = await patchObject(this.objectUrl, { [row.key]: stored })
169
+ const written = data[row.key] !== undefined ? data[row.key] === true : stored
170
+ const shown = row.invert ? !written : written
171
+ this.saved = { ...this.saved, [row.key]: shown }
172
+ this.values = { ...this.values, [row.key]: shown }
173
+ /**
174
+ * @event saved A switch was written.
175
+ * @type {{field: string, value: boolean}}
176
+ */
177
+ this.$emit('saved', { field: row.key, value: written })
178
+ } catch (e) {
179
+ this.values = { ...this.values, [row.key]: this.saved[row.key] }
180
+ this.notice = serverErrorMessage(e)
181
+ } finally {
182
+ this.busy = ''
183
+ }
184
+ },
185
+ },
186
+ }
187
+ </script>
188
+
189
+ <style scoped>
190
+ .cn-switch-rows {
191
+ display: flex;
192
+ flex-direction: column;
193
+ gap: 8px;
194
+ min-width: 0;
195
+ }
196
+
197
+ .cn-switch-rows__list {
198
+ list-style: none;
199
+ margin: 0;
200
+ padding: 0;
201
+ display: flex;
202
+ flex-direction: column;
203
+ }
204
+
205
+ .cn-switch-rows__row {
206
+ padding: 10px 0;
207
+ border-top: 1px solid var(--cn-board-hairline, var(--color-border));
208
+ }
209
+
210
+ .cn-switch-rows__row:first-child {
211
+ border-top: none;
212
+ padding-top: 0;
213
+ }
214
+
215
+ .cn-switch-rows__label {
216
+ display: block;
217
+ font-weight: 600;
218
+ }
219
+
220
+ .cn-switch-rows__hint {
221
+ display: block;
222
+ font-size: 12px;
223
+ font-weight: 400;
224
+ color: var(--color-text-maxcontrast);
225
+ }
226
+ </style>
@@ -0,0 +1,25 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * CnSwitchRowsWidget — self-registers the `switch-rows` widget type (a card of
6
+ * switches, each with a label and a hint, bound to boolean fields of the
7
+ * current object and saved on toggle) into the shared dashboardWidgetRegistry.
8
+ */
9
+
10
+ import CnSwitchRowsWidget from './CnSwitchRowsWidget.vue'
11
+ import { registerDashboardWidget } from '../CnWidgetGrid/dashboardWidgetRegistry.js'
12
+
13
+ registerDashboardWidget('switch-rows', {
14
+ renderer: CnSwitchRowsWidget,
15
+ form: null,
16
+ defaultContent: {
17
+ rows: [],
18
+ },
19
+ displayName: 'Switch rows',
20
+ icon: 'ToggleSwitch',
21
+ surfaces: ['detail-page'],
22
+ })
23
+
24
+ export { CnSwitchRowsWidget }
25
+ export default CnSwitchRowsWidget
@@ -80,10 +80,66 @@
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
+ :class="{ 'cn-tabs-widget__stack--grid': !!entry.grid }"
89
+ data-testid="cn-tabs-widget-stack">
90
+ <div class="cn-tabs-widget__stack-first" :style="spanStyle(entry.grid, 0)">
91
+ <CnDetailWidgetHost
92
+ :widget="entry.widget"
93
+ chrome="card"
94
+ :showCardTitle="true"
95
+ :showActions="false"
96
+ :availableWidgets="availableWidgets"
97
+ :objectId="objectId"
98
+ :object="objectData"
99
+ :objectType="objectType"
100
+ :schemaObject="schemaObject"
101
+ :register="register"
102
+ :schema="schema"
103
+ :store="store"
104
+ :surface="surface"
105
+ :integrationContext="integrationContext"
106
+ :cnRegistry="cnRegistry"
107
+ @geoSaved="onGeoSaved"
108
+ @openIntegration="onOpenIntegration"
109
+ @selectObject="onSelectObject" />
110
+ </div>
111
+ <div
112
+ v-for="(child, childIndex) in entry.stack"
113
+ :key="`${entry.key}-${child.id}`"
114
+ class="cn-tabs-widget__stack-item"
115
+ :style="spanStyle(entry.grid, childIndex + 1)"
116
+ :data-testid="`cn-tabs-widget-stack-${child.id}`">
117
+ <CnDetailWidgetHost
118
+ :widget="child"
119
+ chrome="card"
120
+ :showCardTitle="true"
121
+ :showActions="false"
122
+ :availableWidgets="availableWidgets"
123
+ :objectId="objectId"
124
+ :object="objectData"
125
+ :objectType="objectType"
126
+ :schemaObject="schemaObject"
127
+ :register="register"
128
+ :schema="schema"
129
+ :store="store"
130
+ :surface="surface"
131
+ :integrationContext="integrationContext"
132
+ :cnRegistry="cnRegistry"
133
+ @geoSaved="onGeoSaved"
134
+ @openIntegration="onOpenIntegration"
135
+ @selectObject="onSelectObject" />
136
+ </div>
137
+ </div>
83
138
  <CnDetailWidgetHost
84
- v-if="entry.widget"
139
+ v-else-if="entry.widget"
85
140
  :widget="entry.widget"
86
141
  chrome="bare"
142
+ :availableWidgets="availableWidgets"
87
143
  :objectId="objectId"
88
144
  :object="objectData"
89
145
  :objectType="objectType"
@@ -114,6 +170,7 @@ import CnTab from '../CnTabs/CnTab.vue'
114
170
  import CnTabs from '../CnTabs/CnTabs.vue'
115
171
  import { normalizeLook } from '../../composables/useLook.js'
116
172
  import { resolveTabCount } from '../../utils/detailActionModel.js'
173
+ import { fetchListTotal } from '../../utils/fetchFilterCounts.js'
117
174
  import { isActivityWidget } from '../../utils/headerMeta.js'
118
175
  import { PANEL_ACTION_SINK } from '../../utils/panelActions.js'
119
176
  import { evaluateVisibleWhen, evaluateVisibleWhenLocal, isLocallyDecidableVisibleWhen } from '../../utils/visibleWhen.js'
@@ -268,15 +325,24 @@ export default {
268
325
  * `tabs[]` entries are `{ widgetId, label?, icon?, count?, countField?, overflow? }`.
269
326
  * `label` and `icon` fall back to the referenced widget's own title and
270
327
  * icon. `count` is a number shown after the label; `countField` reads
271
- * it off the record instead (a list counts its items). `overflow: true`
328
+ * it off the record instead (a list counts its items). `countFrom`
329
+ * counts a list on the server: `"widget"` counts the child widget's
330
+ * own list (`content.register`, `schema`, `filter`, tokens such as
331
+ * `@objectId` resolved), an object `{ register, schema, filter? }`
332
+ * names the list; `count` and `countField` win over it. `overflow: true`
272
333
  * lists the tab under "More".
273
334
  *
335
+ * A tab with `widgetIds` can lay them out in a grid instead of one under
336
+ * the other: `columns: 4` gives each a quarter of the row, and
337
+ * `layout: [{ widgetId, gridWidth }]` sets a width (of 12) per widget, as
338
+ * the page grid does. Widgets wrap onto the next row.
339
+ *
274
340
  * `maxVisibleTabs` caps the strip: later tabs go under "More".
275
341
  * `hideEmpty: true` moves a tab whose count is 0 there too.
276
342
  * `moreLabel` names that menu. `variant: "segmented"` draws the strip
277
343
  * as a pill switch (CnTabs' segmented variant); `line` is the default.
278
344
  *
279
- * @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') }}
345
+ * @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, columns?: number, layout?: Array<{widgetId: string, gridWidth: number}>}>, ariaLabel?: string, maxVisibleTabs?: number, hideEmpty?: boolean, moreLabel?: string, variant?: ('line'|'segmented') }}
280
346
  */
281
347
  content: {
282
348
  type: Object,
@@ -396,6 +462,9 @@ export default {
396
462
  sourceVisibility: {},
397
463
  // Bumped per evaluation round, so a slow answer from an earlier round is dropped.
398
464
  visibilitySeq: 0,
465
+ // Totals counted for tabs with `countFrom`, by position in `content.tabs`.
466
+ widgetCounts: {},
467
+ countSeq: 0,
399
468
  // Tab id named by the route hash that has not appeared yet (still pending).
400
469
  hashTargetId: '',
401
470
  // Items published by the panels, keyed by widget id. Keyed rather
@@ -438,7 +507,7 @@ export default {
438
507
  // whatever its place in the manifest. `index` stays the position in
439
508
  // `content.tabs`, which the source-mode conditions are keyed on.
440
509
  const isActivityTab = (tab) => {
441
- const id = typeof tab === 'string' ? tab : tab?.widgetId
510
+ const id = this.tabWidgetIds(tab)[0]
442
511
  return isActivityWidget(this.availableWidgets.find((w) => w && w.id === id))
443
512
  }
444
513
  const ordered = authored.map((tab, index) => ({ tab, index }))
@@ -460,9 +529,19 @@ export default {
460
529
  if (visibility === 'hidden') {
461
530
  return
462
531
  }
463
- const widgetId = typeof tab === 'string' ? tab : tab?.widgetId
464
- const widget = this.availableWidgets.find((w) => w && w.id === widgetId) || null
465
- const count = resolveTabCount(typeof tab === 'object' ? tab : null, this.objectData)
532
+ // A stack (`widgetIds`) resolves every id; one that names no
533
+ // widget is skipped. The first that resolves is the panel's.
534
+ const ids = this.tabWidgetIds(tab)
535
+ const widgets = ids.length > 1
536
+ ? ids.map((id) => this.availableWidgets.find((w) => w && w.id === id) || null).filter(Boolean)
537
+ : []
538
+ const widget = ids.length > 1
539
+ ? (widgets[0] || null)
540
+ : (this.availableWidgets.find((w) => w && w.id === ids[0]) || null)
541
+ const widgetId = widget ? widget.id : ids[0]
542
+ const declared = resolveTabCount(typeof tab === 'object' ? tab : null, this.objectData)
543
+ const counted = this.widgetCounts[index]
544
+ const count = declared === null && typeof counted === 'number' ? counted : declared
466
545
  // The board strip has no "More": every tab is in the strip.
467
546
  let overflow = !this.isBoard && ((tab && tab.overflow === true) || (hideEmpty && count === 0))
468
547
  if (!overflow && !this.isBoard) {
@@ -479,6 +558,8 @@ export default {
479
558
  label: this.tabLabel(tab, widget, widgetId),
480
559
  icon: (tab && tab.icon) || widget?.icon || '',
481
560
  widget,
561
+ stack: widgets.slice(1),
562
+ grid: this.stackGrid(tab, widgets),
482
563
  count,
483
564
  overflow,
484
565
  pending: visibility === 'pending',
@@ -497,6 +578,17 @@ export default {
497
578
  return t('nextcloud-vue', 'Loading …')
498
579
  },
499
580
 
581
+ /**
582
+ * The lists the tabs count (`countFrom`), serialised, to count again
583
+ * when they change.
584
+ *
585
+ * @return {string} The key.
586
+ */
587
+ countSourcesKey() {
588
+ const tabs = Array.isArray(this.content?.tabs) ? this.content.tabs : []
589
+ return JSON.stringify(tabs.map((tab) => this.countSourceOf(tab)))
590
+ },
591
+
500
592
  /** @return {string} The source-mode conditions, serialised, to re-evaluate when they change. */
501
593
  sourceConditionsKey() {
502
594
  const tabs = Array.isArray(this.content?.tabs) ? this.content.tabs : []
@@ -609,6 +701,12 @@ export default {
609
701
  objectId() {
610
702
  this.sourceVisibility = {}
611
703
  this.refreshSourceVisibility()
704
+ this.widgetCounts = {}
705
+ this.refreshWidgetCounts()
706
+ },
707
+
708
+ countSourcesKey() {
709
+ this.refreshWidgetCounts()
612
710
  },
613
711
 
614
712
  sourceConditionsKey() {
@@ -643,10 +741,14 @@ export default {
643
741
  }
644
742
  this.activeKey = this.resolvedTabs[this.activeIndex]?.key || ''
645
743
  this.refreshSourceVisibility()
744
+ this.refreshWidgetCounts()
646
745
  },
647
746
 
648
747
  mounted() {
649
- this._onRefresh = () => this.refreshSourceVisibility()
748
+ this._onRefresh = () => {
749
+ this.refreshSourceVisibility()
750
+ this.refreshWidgetCounts()
751
+ }
650
752
  subscribe('cn:page:refresh', this._onRefresh)
651
753
  subscribe('cn:widget:refresh', this._onRefresh)
652
754
  },
@@ -657,6 +759,67 @@ export default {
657
759
  },
658
760
 
659
761
  methods: {
762
+ /**
763
+ * The grid a stack lays its widgets out in, as one column span (of 12)
764
+ * per widget in order, or null for the plain vertical stack. A tab says
765
+ * `columns: 4` (every widget spans a quarter of the row) and/or
766
+ * `layout: [{ widgetId, gridWidth }]`, the page grid's width key per
767
+ * widget (of 12; a widget it does not name takes the `columns` share, or
768
+ * the whole row). Widgets wrap onto the next row. Heights follow the
769
+ * content.
770
+ *
771
+ * @param {object|string} tab The `content.tabs[]` entry.
772
+ * @param {Array<object>} widgets The widgets of the stack, in order.
773
+ * @return {number[]|null} The column span of each widget, or null.
774
+ */
775
+ stackGrid(tab, widgets) {
776
+ if (!tab || typeof tab !== 'object' || widgets.length < 2) {
777
+ return null
778
+ }
779
+ const clamp = (value) => Math.min(12, Math.max(1, Math.floor(Number(value))))
780
+ const columns = Number(tab.columns)
781
+ const share = Number.isFinite(columns) && columns >= 1 ? clamp(12 / Math.min(12, Math.floor(columns))) : 12
782
+ const placed = new Map((Array.isArray(tab.layout) ? tab.layout : [])
783
+ .filter((entry) => entry && typeof entry.widgetId === 'string' && Number.isFinite(Number(entry.gridWidth)))
784
+ .map((entry) => [entry.widgetId, clamp(entry.gridWidth)]))
785
+ if (placed.size === 0 && share === 12) {
786
+ return null
787
+ }
788
+ return widgets.map((widget) => placed.get(widget.id) ?? share)
789
+ },
790
+
791
+ /**
792
+ * The inline column span of one widget of a gridded stack.
793
+ *
794
+ * @param {number[]|null} grid The spans of the stack, or null.
795
+ * @param {number} index The widget's position in the stack.
796
+ * @return {object|undefined} The style, or undefined without a grid.
797
+ */
798
+ spanStyle(grid, index) {
799
+ return grid ? { gridColumn: `span ${grid[index] || 12}` } : undefined
800
+ },
801
+
802
+ /**
803
+ * The widget ids a tab names: `widgetIds` (a stack) when it holds at
804
+ * least one id, else `widgetId`, or the bare string.
805
+ *
806
+ * @param {object|string} tab The `content.tabs[]` entry.
807
+ * @return {string[]} The ids, at least one entry (possibly undefined).
808
+ * @spec openspec/changes/screens-relation-pages/specs/relation-pages/spec.md#requirement-a-tab-can-hold-a-stack-of-widgets
809
+ */
810
+ tabWidgetIds(tab) {
811
+ if (typeof tab === 'string') {
812
+ return [tab]
813
+ }
814
+ if (tab && Array.isArray(tab.widgetIds)) {
815
+ const ids = tab.widgetIds.filter((id) => typeof id === 'string' && id !== '')
816
+ if (ids.length) {
817
+ return ids
818
+ }
819
+ }
820
+ return [tab?.widgetId]
821
+ },
822
+
660
823
  /**
661
824
  * The label a tab shows: the authored `label`, else (board look) History
662
825
  * for the activity widget, else the child widget's title, else its id.
@@ -733,6 +896,60 @@ export default {
733
896
  }))
734
897
  },
735
898
 
899
+ /**
900
+ * The list a tab counts (`countFrom`): `"widget"` counts the child
901
+ * widget's own list (its `content.register`, `content.schema` and
902
+ * `content.filter`); an object `{ register, schema, filter? }` names
903
+ * the list itself. Null for a tab that does not ask, or a child with
904
+ * no list.
905
+ *
906
+ * @param {object|string} tab The `content.tabs[]` entry.
907
+ * @return {{register: string, schema: string, filter: object}|null} The list.
908
+ * @spec openspec/changes/screens-tab-counts-parity/specs/tab-counts/spec.md#requirement-a-tab-can-count-its-list
909
+ */
910
+ countSourceOf(tab) {
911
+ const from = tab && typeof tab === 'object' ? tab.countFrom : null
912
+ let source = null
913
+ if (from === 'widget') {
914
+ // A stack counts its first widget that has a list.
915
+ const lists = this.tabWidgetIds(tab)
916
+ .map((id) => this.availableWidgets.find((w) => w && w.id === id))
917
+ .filter((w) => w && w.content && typeof w.content === 'object' && w.content.register && w.content.schema)
918
+ source = lists.length ? lists[0].content : null
919
+ } else if (from && typeof from === 'object') {
920
+ source = from
921
+ }
922
+ if (!source || typeof source.register !== 'string' || source.register === '' || typeof source.schema !== 'string' || source.schema === '') {
923
+ return null
924
+ }
925
+ const filter = source.filter && typeof source.filter === 'object' && !Array.isArray(source.filter) ? source.filter : {}
926
+ return { register: source.register, schema: source.schema, filter }
927
+ },
928
+
929
+ /**
930
+ * Count the list behind every tab with `countFrom`: once on mount,
931
+ * when the record changes and after a refresh. A tab keeps its last
932
+ * total while the new one is on its way; a failed count shows none.
933
+ *
934
+ * @return {Promise<void>}
935
+ * @spec openspec/changes/screens-tab-counts-parity/specs/tab-counts/spec.md#requirement-a-tab-can-count-its-list
936
+ */
937
+ async refreshWidgetCounts() {
938
+ const tabs = Array.isArray(this.content?.tabs) ? this.content.tabs : []
939
+ const seq = ++this.countSeq
940
+ const ctx = { objectId: this.objectId, object: this.objectData || undefined }
941
+ await Promise.all(tabs.map(async (tab, index) => {
942
+ const source = this.countSourceOf(tab)
943
+ if (!source) {
944
+ return
945
+ }
946
+ const total = await fetchListTotal(source.register, source.schema, source.filter, ctx).catch(() => null)
947
+ if (seq === this.countSeq) {
948
+ this.widgetCounts = { ...this.widgetCounts, [index]: total }
949
+ }
950
+ }))
951
+ },
952
+
736
953
  /** @return {string} The route hash without its `#`, or ''. */
737
954
  currentHash() {
738
955
  const fromRoute = this.$route && typeof this.$route.hash === 'string' ? this.$route.hash : ''
@@ -823,6 +1040,36 @@ export default {
823
1040
  </script>
824
1041
 
825
1042
  <style scoped>
1043
+ /* A stack (`widgetIds`): the panel's widget, then cards 20px apart. */
1044
+ .cn-tabs-widget__stack {
1045
+ display: flex;
1046
+ flex-direction: column;
1047
+ gap: 20px;
1048
+ }
1049
+
1050
+ /* A gridded stack (`columns` / `layout` on the tab): 12 columns, widgets wrap
1051
+ onto the next row, a row's cards as tall as the tallest. Narrow, it is the
1052
+ plain stack again. */
1053
+ .cn-tabs-widget__stack--grid {
1054
+ display: grid;
1055
+ grid-template-columns: repeat(12, minmax(0, 1fr));
1056
+ align-items: stretch;
1057
+ }
1058
+
1059
+ .cn-tabs-widget__stack--grid > * {
1060
+ min-width: 0;
1061
+ }
1062
+
1063
+ @media (max-width: 700px) {
1064
+ .cn-tabs-widget__stack--grid {
1065
+ grid-template-columns: minmax(0, 1fr);
1066
+ }
1067
+
1068
+ .cn-tabs-widget__stack--grid > * {
1069
+ grid-column: auto !important;
1070
+ }
1071
+ }
1072
+
826
1073
  /* The panel is the card; the strip is not inside it. There is no title bar
827
1074
  above the tabs either: the open tab names the panel, so a title row would
828
1075
  say the same thing twice and cost a row of height on a card that is mostly
@@ -943,6 +1190,14 @@ export default {
943
1190
  height: 100%;
944
1191
  }
945
1192
 
1193
+ /* A list widget's footer Add bleeds through its card's 16px content padding
1194
+ (margin 0 -16px -16px). The panel of a tab pads 12px, so on a tab that
1195
+ holds the list directly the footer stuck out 4px on the right and at the
1196
+ bottom and made the panel scroll. It bleeds exactly the panel's padding. */
1197
+ .cn-tabs-widget__tabs :deep(.cn-tabs__content > .cn-tab > .cn-detail-widget-host--bare .cn-object-list-widget__add) {
1198
+ margin: auto -12px -12px;
1199
+ }
1200
+
946
1201
  .cn-tabs-widget__title {
947
1202
  align-items: center;
948
1203
  display: inline-flex;