@conduction/nextcloud-vue 2.59.0 → 2.61.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 (254) hide show
  1. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js +14 -1
  2. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
  4. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +80 -1
  5. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  6. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +46 -4
  7. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  8. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +232 -10
  9. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  10. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +79 -25
  11. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  12. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue2.js +42 -3
  13. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue2.js.map +1 -1
  14. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue3.js +39 -6
  15. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue3.js.map +1 -1
  16. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +46 -0
  17. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  18. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +11 -4
  19. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  20. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue.js +10 -0
  21. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue.js.map +1 -0
  22. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js +39 -0
  23. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js.map +1 -0
  24. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js +16 -0
  25. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js.map +1 -0
  26. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +117 -1
  27. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  28. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +147 -89
  29. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  30. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +10 -0
  31. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js.map +1 -0
  32. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +259 -0
  33. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -0
  34. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +131 -0
  35. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -0
  36. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +10 -0
  37. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js.map +1 -0
  38. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +116 -0
  39. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -0
  40. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +24 -0
  41. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -0
  42. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +10 -2
  43. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  44. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  45. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +503 -28
  46. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  47. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +303 -144
  48. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  49. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js +10 -0
  50. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js.map +1 -0
  51. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js +237 -0
  52. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js.map +1 -0
  53. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js +120 -0
  54. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js.map +1 -0
  55. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js +10 -0
  56. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js.map +1 -0
  57. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +77 -0
  58. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -0
  59. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js +27 -0
  60. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -0
  61. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +171 -12
  62. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
  63. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +15 -7
  64. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
  65. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js +37 -3
  66. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js.map +1 -1
  67. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js +44 -16
  68. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js.map +1 -1
  69. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +132 -3
  70. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  71. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +6 -3
  72. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  73. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue.js +10 -0
  74. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue.js.map +1 -0
  75. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js +316 -0
  76. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js.map +1 -0
  77. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js +105 -0
  78. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js.map +1 -0
  79. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js +10 -0
  80. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js.map +1 -0
  81. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js +150 -0
  82. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js.map +1 -0
  83. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js +106 -0
  84. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js.map +1 -0
  85. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js +9 -0
  86. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js.map +1 -0
  87. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +47 -0
  88. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -0
  89. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js +17 -0
  90. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js.map +1 -0
  91. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +49 -0
  92. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  93. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  94. package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js +6 -0
  95. package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js.map +1 -1
  96. package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue3.js.map +1 -1
  97. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js +46 -0
  98. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js.map +1 -1
  99. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js +15 -3
  100. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js.map +1 -1
  101. package/dist/esm/components/CnPageRenderer/pageTypes.js +1 -0
  102. package/dist/esm/components/CnPageRenderer/pageTypes.js.map +1 -1
  103. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +37 -1
  104. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
  105. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +29 -13
  106. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
  107. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js +50 -3
  108. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
  109. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
  110. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js +10 -0
  111. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js.map +1 -0
  112. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +186 -0
  113. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -0
  114. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js +46 -0
  115. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -0
  116. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js +11 -0
  117. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js.map +1 -0
  118. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +298 -0
  119. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -0
  120. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +77 -0
  121. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -0
  122. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js +10 -0
  123. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js.map +1 -0
  124. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js +169 -0
  125. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js.map +1 -0
  126. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js +48 -0
  127. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js.map +1 -0
  128. package/dist/esm/components/CnTabs/CnTab.vue2.js +35 -0
  129. package/dist/esm/components/CnTabs/CnTab.vue2.js.map +1 -1
  130. package/dist/esm/components/CnTabs/CnTab.vue3.js.map +1 -1
  131. package/dist/esm/components/CnTabs/CnTabs.vue2.js +120 -7
  132. package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
  133. package/dist/esm/components/CnTabs/CnTabs.vue3.js +57 -5
  134. package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
  135. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +59 -4
  136. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  137. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +6 -2
  138. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  139. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js +10 -0
  140. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js.map +1 -0
  141. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +484 -0
  142. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -0
  143. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js +115 -0
  144. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -0
  145. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js +10 -0
  146. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js.map +1 -0
  147. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js +189 -0
  148. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js.map +1 -0
  149. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js +74 -0
  150. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js.map +1 -0
  151. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +82 -8
  152. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  153. package/dist/esm/index.js +16 -0
  154. package/dist/esm/index.js.map +1 -1
  155. package/dist/esm/l10n/en.json.js +55 -1
  156. package/dist/esm/l10n/en.json.js.map +1 -1
  157. package/dist/esm/l10n/nl.json.js +55 -1
  158. package/dist/esm/l10n/nl.json.js.map +1 -1
  159. package/dist/esm/nextcloud-vue.css +1036 -19
  160. package/dist/esm/utils/dateVariant.js +124 -0
  161. package/dist/esm/utils/dateVariant.js.map +1 -0
  162. package/dist/esm/utils/detailActionModel.js +260 -0
  163. package/dist/esm/utils/detailActionModel.js.map +1 -0
  164. package/dist/esm/utils/dueRule.js +84 -0
  165. package/dist/esm/utils/dueRule.js.map +1 -0
  166. package/dist/esm/utils/fetchAggregate.js +37 -1
  167. package/dist/esm/utils/fetchAggregate.js.map +1 -1
  168. package/dist/esm/utils/fetchFilterCounts.js +186 -0
  169. package/dist/esm/utils/fetchFilterCounts.js.map +1 -0
  170. package/dist/esm/utils/headers.js +28 -2
  171. package/dist/esm/utils/headers.js.map +1 -1
  172. package/dist/esm/utils/libraryWidgetKeys.js +5 -0
  173. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  174. package/dist/esm/utils/readPath.js +30 -0
  175. package/dist/esm/utils/readPath.js.map +1 -0
  176. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  177. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  178. package/dist/nextcloud-vue.cjs.js +16019 -9528
  179. package/dist/nextcloud-vue.cjs.js.map +2 -2
  180. package/dist/nextcloud-vue.css +1036 -19
  181. package/l10n/en.json +55 -1
  182. package/l10n/nl.json +55 -1
  183. package/package.json +1 -1
  184. package/scripts/.jsdoc-baselines.json +4 -0
  185. package/src/components/CnActionsMenu/CnActionsMenu.vue +14 -1
  186. package/src/components/CnAppNav/CnAppNav.md +26 -0
  187. package/src/components/CnAppNav/CnAppNav.vue +145 -2
  188. package/src/components/CnBannerWidget/CnBannerWidget.vue +373 -11
  189. package/src/components/CnBannerWidgetForm/CnBannerWidgetForm.vue +65 -3
  190. package/src/components/CnBoardView/CnBoardView.vue +77 -0
  191. package/src/components/CnBrandStripe/CnBrandStripe.md +22 -0
  192. package/src/components/CnBrandStripe/CnBrandStripe.vue +100 -0
  193. package/src/components/CnBrandStripe/index.js +4 -0
  194. package/src/components/CnCellRenderer/CnCellRenderer.vue +207 -1
  195. package/src/components/CnConversationThread/CnConversationThread.md +51 -0
  196. package/src/components/CnConversationThread/CnConversationThread.vue +457 -0
  197. package/src/components/CnConversationThread/CnConversationWidget.vue +144 -0
  198. package/src/components/CnConversationThread/index.js +4 -0
  199. package/src/components/CnDashboardPage/CnDashboardPage.vue +10 -2
  200. package/src/components/CnDetailPage/CnDetailPage.md +16 -0
  201. package/src/components/CnDetailPage/CnDetailPage.vue +687 -49
  202. package/src/components/CnDocumentReviewList/CnDocumentReviewList.md +55 -0
  203. package/src/components/CnDocumentReviewList/CnDocumentReviewList.vue +384 -0
  204. package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +108 -0
  205. package/src/components/CnDocumentReviewList/index.js +4 -0
  206. package/src/components/CnHeaderWidget/CnHeaderWidget.vue +191 -14
  207. package/src/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue +53 -3
  208. package/src/components/CnIndexPage/CnIndexPage.vue +135 -3
  209. package/src/components/CnLinkCardsPage/CnLinkCardsPage.vue +484 -0
  210. package/src/components/CnLinkCardsPage/index.js +4 -0
  211. package/src/components/CnNextStepCard/CnNextStepCard.md +40 -0
  212. package/src/components/CnNextStepCard/CnNextStepCard.vue +319 -0
  213. package/src/components/CnNextStepCard/CnNextStepWidget.vue +59 -0
  214. package/src/components/CnNextStepCard/index.js +4 -0
  215. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +49 -0
  216. package/src/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue +6 -0
  217. package/src/components/CnObjectKanban/CnObjectKanban.vue +86 -2
  218. package/src/components/CnPageRenderer/pageTypes.js +1 -0
  219. package/src/components/CnQuickFilterBar/CnQuickFilterBar.md +3 -0
  220. package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +60 -1
  221. package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +50 -3
  222. package/src/components/CnSegmentedControl/CnSegmentedControl.md +46 -0
  223. package/src/components/CnSegmentedControl/CnSegmentedControl.vue +291 -0
  224. package/src/components/CnSegmentedControl/index.js +4 -0
  225. package/src/components/CnStackedBarWidget/CnStackedBarWidget.md +15 -0
  226. package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +414 -0
  227. package/src/components/CnStackedBarWidget/index.js +14 -0
  228. package/src/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue +224 -0
  229. package/src/components/CnStackedBarWidgetForm/index.js +4 -0
  230. package/src/components/CnTabs/CnTab.vue +35 -0
  231. package/src/components/CnTabs/CnTabs.vue +244 -10
  232. package/src/components/CnTabsWidget/CnTabsWidget.vue +63 -4
  233. package/src/components/CnWeekStripWidget/CnWeekStripWidget.md +31 -0
  234. package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +668 -0
  235. package/src/components/CnWeekStripWidget/index.js +14 -0
  236. package/src/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue +265 -0
  237. package/src/components/CnWeekStripWidgetForm/index.js +4 -0
  238. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +74 -0
  239. package/src/components/index.js +12 -0
  240. package/src/css/detail-page.css +57 -2
  241. package/src/index.js +16 -0
  242. package/src/schemas/app-manifest-v2.schema.json +433 -3
  243. package/src/schemas/app-manifest.schema.json +2 -1
  244. package/src/types/manifest.d.ts +1 -1
  245. package/src/utils/dateVariant.js +120 -0
  246. package/src/utils/detailActionModel.js +256 -0
  247. package/src/utils/dueRule.js +94 -0
  248. package/src/utils/fetchAggregate.js +36 -0
  249. package/src/utils/fetchFilterCounts.js +182 -0
  250. package/src/utils/headers.js +28 -2
  251. package/src/utils/index.js +1 -0
  252. package/src/utils/libraryWidgetKeys.js +5 -0
  253. package/src/utils/readPath.js +27 -0
  254. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="cn-tabs" :class="{ 'cn-tabs--card': card }">
2
+ <div class="cn-tabs" :class="{ 'cn-tabs--card': card, 'cn-tabs--segmented': variant === 'segmented' }">
3
3
  <div class="cn-tabs__bar">
4
4
  <div
5
5
  class="cn-tabs__strip"
@@ -14,7 +14,7 @@
14
14
  @wheel="onNavWheel"
15
15
  @scroll="measureOverflow">
16
16
  <button
17
- v-for="tab in tabs"
17
+ v-for="tab in visibleTabs"
18
18
  :id="tab.tabId"
19
19
  :key="tab.uid"
20
20
  ref="navButtons"
@@ -27,7 +27,14 @@
27
27
  :tabindex="isActive(tab.uid) ? 0 : -1"
28
28
  :disabled="tab.disabled || null"
29
29
  @click="tab.onActivate()">
30
- <component :is="tab.titleRender" />
30
+ <!-- The space before the count is load-bearing: it keeps
31
+ the tab's accessible name "Documents 5" rather than
32
+ "Documents5". The margin separates the two for the eye
33
+ only, so keep both tags on one line. -->
34
+ <component :is="tab.titleRender" /> <span
35
+ v-if="hasCount(tab)"
36
+ class="cn-tabs__count"
37
+ data-testid="cn-tabs-count">{{ tab.count }}</span>
31
38
  </button>
32
39
  </div>
33
40
  <!-- Mouse-only affordances: keyboard users already have the arrow
@@ -51,6 +58,36 @@
51
58
  <ChevronRight :size="20" />
52
59
  </button>
53
60
  </div>
61
+ <!-- The tabs that did not make the strip. OUTSIDE the tablist, like
62
+ `#nav-end`: a menu button nested in it would be announced as
63
+ one more tab. A tab picked here joins the strip while it is
64
+ selected, so the open panel always has a real tab to be
65
+ labelled by. -->
66
+ <div v-if="overflowTabs.length > 0" class="cn-tabs__more">
67
+ <NcActions
68
+ :forceMenu="true"
69
+ :forceName="true"
70
+ :menuName="moreLabel"
71
+ :aria-label="moreAriaLabel"
72
+ variant="tertiary"
73
+ data-testid="cn-tabs-more">
74
+ <template #icon>
75
+ <ChevronDown :size="20" />
76
+ </template>
77
+ <NcActionButton
78
+ v-for="tab in overflowTabs"
79
+ :key="tab.uid"
80
+ :disabled="tab.disabled || null"
81
+ :closeAfterClick="true"
82
+ data-testid="cn-tabs-more-item"
83
+ @click="onOverflowPick(tab)">
84
+ <template #icon>
85
+ <span class="cn-tabs__more-spacer" />
86
+ </template>
87
+ {{ overflowLabel(tab) }}
88
+ </NcActionButton>
89
+ </NcActions>
90
+ </div>
54
91
  <div v-if="$slots['nav-end']" class="cn-tabs__nav-end">
55
92
  <!-- @slot nav-end Rendered at the right-hand end of the tab bar, deliberately OUTSIDE the `role="tablist"` element. A widget Actions menu belongs beside the strip, not inside it: anything nested in the tablist is announced as one of the tabs, so a screen-reader user counting six tabs would hear seven. -->
56
93
  <!-- @binding {number} active-index Index of the currently selected tab. -->
@@ -131,8 +168,27 @@
131
168
  * wiring, a roving `tabindex` (only the selected tab is in the tab order), and
132
169
  * Left/Right/Home/End keyboard navigation within the strip. Pass `aria-label`
133
170
  * (or `ariaLabel`) so screen-reader users hear what the strip is for.
171
+ *
172
+ * ## Counts and the "More" menu
173
+ *
174
+ * A `CnTab` with `count` shows that number after its title. A `CnTab` with
175
+ * `overflow` is listed under a "More" menu beside the strip instead of in it:
176
+ *
177
+ * ```vue
178
+ * <CnTabs aria-label="Case details">
179
+ * <CnTab title="Documents" :count="5">…</CnTab>
180
+ * <CnTab title="Archiving" overflow>…</CnTab>
181
+ * </CnTabs>
182
+ * ```
183
+ *
184
+ * Picking a tab from the menu selects it and shows it in the strip for as long
185
+ * as it is selected. With no overflow tab the menu does not render, and the
186
+ * strip is exactly what it was before these props existed.
134
187
  */
135
- import { computed, defineComponent, onBeforeUnmount, onMounted, onUpdated, provide, reactive, ref, watch } from 'vue'
188
+ import { translate as t } from '@nextcloud/l10n'
189
+ import { NcActionButton, NcActions } from '@nextcloud/vue'
190
+ import { computed, defineComponent, nextTick, onBeforeUnmount, onMounted, onUpdated, provide, reactive, ref, watch } from 'vue'
191
+ import ChevronDown from 'vue-material-design-icons/ChevronDown.vue'
136
192
  import ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'
137
193
  import ChevronRight from 'vue-material-design-icons/ChevronRight.vue'
138
194
  import { CN_TABS_INJECTION_KEY } from './tabsKey.js'
@@ -141,8 +197,11 @@ export default defineComponent({
141
197
  name: 'CnTabs',
142
198
 
143
199
  components: {
200
+ ChevronDown,
144
201
  ChevronLeft,
145
202
  ChevronRight,
203
+ NcActionButton,
204
+ NcActions,
146
205
  },
147
206
 
148
207
  props: {
@@ -164,6 +223,20 @@ export default defineComponent({
164
223
  default: false,
165
224
  },
166
225
 
226
+ /**
227
+ * How the strip is drawn: `line` (the default: tabs joined to the
228
+ * panel below) or `segmented` (a compact pill switch, for a view
229
+ * switch such as "My work / My team"). Roles and keyboard behaviour
230
+ * are the same in both.
231
+ *
232
+ * @type {'line'|'segmented'}
233
+ */
234
+ variant: {
235
+ type: String,
236
+ default: 'line',
237
+ validator: (v) => ['line', 'segmented'].includes(v),
238
+ },
239
+
167
240
  /**
168
241
  * Accessible name for the tab strip, applied to the `role="tablist"`
169
242
  * element. Screen readers announce it when focus enters the strip.
@@ -172,6 +245,12 @@ export default defineComponent({
172
245
  type: String,
173
246
  default: '',
174
247
  },
248
+
249
+ /** Name of the menu that lists the `overflow` tabs. */
250
+ moreLabel: {
251
+ type: String,
252
+ default: () => t('nextcloud-vue', 'More'),
253
+ },
175
254
  },
176
255
 
177
256
  emits: [
@@ -184,6 +263,10 @@ export default defineComponent({
184
263
  const tabs = reactive([])
185
264
  const activeUid = ref(null)
186
265
  const navButtons = ref([])
266
+ const nav = ref(null)
267
+ // Whether the selection was chosen (a tab declared `active`, or the
268
+ // user picked one) rather than defaulted to the first tab registered.
269
+ let selectionChosen = false
187
270
 
188
271
  /**
189
272
  * Register a child tab. The first child to register wins the initial
@@ -199,8 +282,23 @@ export default defineComponent({
199
282
  if (tab.disabled) {
200
283
  return
201
284
  }
202
- if (activeUid.value === null || tab.active) {
285
+ if (tab.active) {
203
286
  activeUid.value = tab.uid
287
+ selectionChosen = true
288
+ return
289
+ }
290
+ if (activeUid.value === null) {
291
+ activeUid.value = tab.uid
292
+ return
293
+ }
294
+ // A tab under "More" only held the default selection because it
295
+ // registered first. The first tab that is actually in the strip
296
+ // takes it over, unless somebody chose.
297
+ if (!selectionChosen && !tab.overflow) {
298
+ const current = tabs.find((candidate) => candidate.uid === activeUid.value)
299
+ if (current && current.overflow) {
300
+ activeUid.value = tab.uid
301
+ }
204
302
  }
205
303
  }
206
304
 
@@ -232,6 +330,7 @@ export default defineComponent({
232
330
  * @return {void}
233
331
  */
234
332
  function select(uid) {
333
+ selectionChosen = true
235
334
  if (activeUid.value === uid) {
236
335
  return
237
336
  }
@@ -252,6 +351,67 @@ export default defineComponent({
252
351
 
253
352
  const activeIndex = computed(() => tabs.findIndex((tab) => tab.uid === activeUid.value))
254
353
 
354
+ /**
355
+ * The tabs drawn in the strip: every tab that is not `overflow`, plus
356
+ * an overflow tab for as long as it is the selected one.
357
+ *
358
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
359
+ */
360
+ const visibleTabs = computed(() => tabs.filter((tab) => !tab.overflow || tab.uid === activeUid.value))
361
+
362
+ /**
363
+ * The tabs listed under "More": overflow tabs that are not selected.
364
+ *
365
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
366
+ */
367
+ const overflowTabs = computed(() => tabs.filter((tab) => tab.overflow && tab.uid !== activeUid.value))
368
+
369
+ /**
370
+ * Whether a tab declares a count. `0` is a count; null and '' are not.
371
+ *
372
+ * @param {object} tab Child descriptor.
373
+ *
374
+ * @return {boolean} True when the count renders.
375
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
376
+ */
377
+ function hasCount(tab) {
378
+ return tab.count !== null && tab.count !== undefined && tab.count !== ''
379
+ }
380
+
381
+ /**
382
+ * A "More" entry's text: the tab's title, and its count when it has one.
383
+ *
384
+ * @param {object} tab Child descriptor.
385
+ *
386
+ * @return {string} The menu label.
387
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
388
+ */
389
+ function overflowLabel(tab) {
390
+ return hasCount(tab) ? `${tab.label} (${tab.count})` : tab.label
391
+ }
392
+
393
+ /**
394
+ * Select a tab picked from "More" and move focus to it in the strip,
395
+ * so a keyboard user lands on the tab they chose rather than on a menu
396
+ * button that no longer lists it.
397
+ *
398
+ * @param {object} tab Child descriptor.
399
+ *
400
+ * @return {void}
401
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
402
+ */
403
+ function onOverflowPick(tab) {
404
+ tab.onActivate()
405
+ nextTick(() => {
406
+ const button = nav.value?.querySelector(`#${tab.tabId}`)
407
+ if (button && typeof button.focus === 'function') {
408
+ button.focus()
409
+ }
410
+ })
411
+ }
412
+
413
+ const moreAriaLabel = computed(() => t('nextcloud-vue', 'More tabs'))
414
+
255
415
  /**
256
416
  * Move the selection and DOM focus to a tab by index, skipping disabled
257
417
  * ones. Focus has to move with the selection: in the WAI-ARIA tabs
@@ -263,14 +423,16 @@ export default defineComponent({
263
423
  * @return {void}
264
424
  */
265
425
  function focusTab(index) {
266
- const enabled = tabs.filter((tab) => !tab.disabled)
426
+ // The strip's own tabs only: the arrow keys walk what is drawn, and
427
+ // the "More" menu has its own keyboard handling.
428
+ const enabled = visibleTabs.value.filter((tab) => !tab.disabled)
267
429
  if (enabled.length === 0) {
268
430
  return
269
431
  }
270
432
  const wrapped = ((index % enabled.length) + enabled.length) % enabled.length
271
433
  const target = enabled[wrapped]
434
+ const position = visibleTabs.value.indexOf(target)
272
435
  select(target.uid)
273
- const position = tabs.indexOf(target)
274
436
  const button = navButtons.value?.[position]
275
437
  if (button && typeof button.focus === 'function') {
276
438
  button.focus()
@@ -285,7 +447,7 @@ export default defineComponent({
285
447
  * @return {void}
286
448
  */
287
449
  function onNavKeydown(event) {
288
- const enabled = tabs.filter((tab) => !tab.disabled)
450
+ const enabled = visibleTabs.value.filter((tab) => !tab.disabled)
289
451
  const current = enabled.findIndex((tab) => tab.uid === activeUid.value)
290
452
  let next = null
291
453
 
@@ -306,8 +468,6 @@ export default defineComponent({
306
468
  focusTab(next)
307
469
  }
308
470
 
309
- const nav = ref(null)
310
-
311
471
  /**
312
472
  * Scroll the strip so the selected tab is wholly in view.
313
473
  *
@@ -402,6 +562,12 @@ export default defineComponent({
402
562
 
403
563
  return {
404
564
  tabs,
565
+ visibleTabs,
566
+ overflowTabs,
567
+ hasCount,
568
+ overflowLabel,
569
+ onOverflowPick,
570
+ moreAriaLabel,
405
571
  isActive,
406
572
  activeIndex,
407
573
  nav,
@@ -534,6 +700,34 @@ export default defineComponent({
534
700
  inset-inline-end: 0;
535
701
  }
536
702
 
703
+ /* The count behind a tab's title. Full-contrast text on the page background,
704
+ so it reads on the idle tab's darker fill and on the open tab alike. */
705
+ .cn-tabs__count {
706
+ background-color: var(--color-main-background);
707
+ border: 1px solid var(--color-border-dark);
708
+ border-radius: var(--border-radius-pill, 100px);
709
+ color: var(--color-main-text);
710
+ display: inline-block;
711
+ font-size: 0.85em;
712
+ line-height: 1.4;
713
+ margin-inline-start: 6px;
714
+ min-width: 1.4em;
715
+ padding: 0 6px;
716
+ text-align: center;
717
+ }
718
+
719
+ .cn-tabs__more {
720
+ align-items: center;
721
+ display: flex;
722
+ flex: 0 0 auto;
723
+ }
724
+
725
+ /* Keeps a "More" entry's text on the menu's text column without drawing an icon. */
726
+ .cn-tabs__more-spacer {
727
+ display: inline-block;
728
+ width: 20px;
729
+ }
730
+
537
731
  .cn-tabs__nav-end {
538
732
  align-items: center;
539
733
  display: flex;
@@ -645,4 +839,44 @@ export default defineComponent({
645
839
  border-top: none;
646
840
  padding: 12px;
647
841
  }
842
+
843
+ /* Segmented variant: the strip is a pill switch that stands on its own, so
844
+ the bar's rule and the tab-joins-panel treatment are both dropped. */
845
+ .cn-tabs--segmented .cn-tabs__bar {
846
+ border-bottom: none;
847
+ }
848
+
849
+ .cn-tabs--segmented .cn-tabs__strip {
850
+ flex: 0 1 auto;
851
+ }
852
+
853
+ .cn-tabs--segmented .cn-tabs__nav {
854
+ gap: 4px;
855
+ margin-bottom: 0;
856
+ padding: 4px;
857
+ border-radius: var(--border-radius-container, var(--border-radius-large, 10px));
858
+ background-color: var(--color-background-dark);
859
+ }
860
+
861
+ .cn-tabs--segmented .cn-tabs__nav-item {
862
+ border: 1px solid transparent;
863
+ border-radius: var(--border-radius-element, var(--border-radius-large, 8px));
864
+ background-color: transparent;
865
+ font-weight: 600;
866
+ padding: 6px 16px;
867
+ }
868
+
869
+ .cn-tabs--segmented .cn-tabs__nav-item:hover {
870
+ background-color: var(--color-background-hover);
871
+ }
872
+
873
+ /* The open segment is the raised one. Its border survives forced-colours
874
+ mode, which drops the background and the shadow. */
875
+ .cn-tabs--segmented .cn-tabs__nav-item--active {
876
+ border: 1px solid var(--color-border-dark);
877
+ background-color: var(--color-main-background);
878
+ box-shadow: 0 1px 2px var(--color-box-shadow);
879
+ font-weight: 600;
880
+ padding-top: 6px;
881
+ }
648
882
  </style>
@@ -7,6 +7,7 @@
7
7
  <div class="cn-tabs-widget">
8
8
  <CnTabs
9
9
  :aria-label="stripLabel"
10
+ :moreLabel="moreLabel"
10
11
  class="cn-tabs-widget__tabs"
11
12
  @update:activeIndex="onTabChange">
12
13
  <!-- One Actions menu for the whole widget, bound to whichever child
@@ -55,6 +56,9 @@
55
56
  v-for="(entry, index) in resolvedTabs"
56
57
  :key="entry.key"
57
58
  :active="index === activeIndex"
59
+ :title="entry.label"
60
+ :count="entry.count"
61
+ :overflow="entry.overflow"
58
62
  lazy
59
63
  @click="activeIndex = index">
60
64
  <template #title>
@@ -97,6 +101,7 @@ import CnDetailWidgetHost from '../CnDetailWidgetHost/CnDetailWidgetHost.vue'
97
101
  import CnIcon from '../CnIcon/CnIcon.vue'
98
102
  import CnTab from '../CnTabs/CnTab.vue'
99
103
  import CnTabs from '../CnTabs/CnTabs.vue'
104
+ import { resolveTabCount } from '../../utils/detailActionModel.js'
100
105
  import { PANEL_ACTION_SINK } from '../../utils/panelActions.js'
101
106
  import { widgetTitleOf } from '../../utils/widgetDispatch.js'
102
107
  import { CnActionsMenu } from '../CnActionsMenu/index.js'
@@ -233,10 +238,17 @@ export default {
233
238
  /**
234
239
  * The widget's config: `{ tabs, ariaLabel }`.
235
240
  *
236
- * `tabs[]` entries are `{ widgetId, label?, icon? }`. `label` and `icon`
237
- * fall back to the referenced widget's own title and icon.
241
+ * `tabs[]` entries are `{ widgetId, label?, icon?, count?, countField?, overflow? }`.
242
+ * `label` and `icon` fall back to the referenced widget's own title and
243
+ * icon. `count` is a number shown after the label; `countField` reads
244
+ * it off the record instead (a list counts its items). `overflow: true`
245
+ * lists the tab under "More".
238
246
  *
239
- * @type {{ tabs?: Array<{widgetId: string, label?: string, icon?: string}>, ariaLabel?: string }}
247
+ * `maxVisibleTabs` caps the strip: later tabs go under "More".
248
+ * `hideEmpty: true` moves a tab whose count is 0 there too.
249
+ * `moreLabel` names that menu.
250
+ *
251
+ * @type {{ tabs?: Array<{widgetId: string, label?: string, icon?: string, count?: number, countField?: string, overflow?: boolean}>, ariaLabel?: string, maxVisibleTabs?: number, hideEmpty?: boolean, moreLabel?: string }}
240
252
  */
241
253
  content: {
242
254
  type: Object,
@@ -374,23 +386,56 @@ export default {
374
386
  /**
375
387
  * The configured tabs, each paired with the widget definition it names.
376
388
  *
377
- * @return {object[]} `{ key, widgetId, label, icon, widget }` per tab.
389
+ * A tab goes under the strip's "More" menu when it says `overflow`, when
390
+ * `content.hideEmpty` is set and its count is 0, or when
391
+ * `content.maxVisibleTabs` places are already taken. `count` is a
392
+ * literal, or read off the record through `countField`.
393
+ *
394
+ * @return {object[]} `{ key, widgetId, label, icon, widget, count, overflow }` per tab.
395
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
378
396
  */
379
397
  resolvedTabs() {
380
398
  const tabs = Array.isArray(this.content?.tabs) ? this.content.tabs : []
399
+ const max = Number(this.content?.maxVisibleTabs)
400
+ const hideEmpty = this.content?.hideEmpty === true
401
+ // How many tabs have taken a place in the strip so far. A tab that
402
+ // is already going under "More" does not use one up.
403
+ let placed = 0
381
404
  return tabs.map((tab, index) => {
382
405
  const widgetId = typeof tab === 'string' ? tab : tab?.widgetId
383
406
  const widget = this.availableWidgets.find((w) => w && w.id === widgetId) || null
407
+ const count = resolveTabCount(typeof tab === 'object' ? tab : null, this.objectData)
408
+ let overflow = (tab && tab.overflow === true) || (hideEmpty && count === 0)
409
+ if (!overflow) {
410
+ if (Number.isFinite(max) && max > 0 && placed >= max) {
411
+ overflow = true
412
+ } else {
413
+ placed += 1
414
+ }
415
+ }
384
416
  return {
385
417
  key: `${widgetId || 'tab'}-${index}`,
386
418
  widgetId,
387
419
  label: (tab && tab.label) || widgetTitleOf(widget) || widgetId || '',
388
420
  icon: (tab && tab.icon) || widget?.icon || '',
389
421
  widget,
422
+ count,
423
+ overflow,
390
424
  }
391
425
  })
392
426
  },
393
427
 
428
+ /**
429
+ * Name of the menu that holds the tabs that are not in the strip.
430
+ *
431
+ * @return {string|undefined} The configured label, or undefined for the default.
432
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
433
+ */
434
+ moreLabel() {
435
+ const label = this.content?.moreLabel
436
+ return typeof label === 'string' && label !== '' ? label : undefined
437
+ },
438
+
394
439
  /**
395
440
  * The tab currently showing.
396
441
  *
@@ -429,6 +474,20 @@ export default {
429
474
  },
430
475
  },
431
476
 
477
+ /**
478
+ * Open on the first tab that is in the strip. Tab 0 is the default, and
479
+ * when tab 0 sits under "More" the strip would open on a tab the author
480
+ * chose to tuck away.
481
+ *
482
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
483
+ */
484
+ created() {
485
+ const first = this.resolvedTabs.findIndex((tab) => !tab.overflow)
486
+ if (first > 0) {
487
+ this.activeIndex = first
488
+ }
489
+ },
490
+
432
491
  methods: {
433
492
  /**
434
493
  * Re-emit a geo child's save so the surface can reload the record.
@@ -0,0 +1,31 @@
1
+ The current week as day columns. This example uses static items; in an app the items usually come from an OpenRegister `source`.
2
+
3
+ ```vue
4
+ <template>
5
+ <CnWeekStripWidget :content="content" />
6
+ </template>
7
+
8
+ <script>
9
+ export default {
10
+ data() {
11
+ const day = (offset) => {
12
+ const now = new Date()
13
+ const monday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - ((now.getDay() + 6) % 7) + offset)
14
+ return [monday.getFullYear(), String(monday.getMonth() + 1).padStart(2, '0'), String(monday.getDate()).padStart(2, '0')].join('-')
15
+ }
16
+ return {
17
+ content: {
18
+ emptyText: 'No deadlines',
19
+ items: [
20
+ { title: 'Parking permits city centre', meta: '2026-0061 · Woo', date: day(0) },
21
+ { title: 'Objection parking fine', meta: '2026-0074 · Objection', date: day(2) },
22
+ { title: 'Tender youth care', meta: '2026-0058 · Woo', date: day(4) },
23
+ ],
24
+ },
25
+ }
26
+ },
27
+ }
28
+ </script>
29
+ ```
30
+
31
+ Props: `content` holds the configuration (see the component description), `translate` is an optional translate function for the texts in it, and `now` sets the moment the strip treats as now.