@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
@@ -0,0 +1,414 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ SPDX-License-Identifier: EUPL-1.2
4
+ -->
5
+ <template>
6
+ <div class="cn-stacked-bar" data-testid="cn-stacked-bar">
7
+ <div v-if="loading" class="cn-stacked-bar__state" role="status">
8
+ <NcLoadingIcon :size="20" />
9
+ <span>{{ loadingLabel }}</span>
10
+ </div>
11
+ <p v-else-if="error" class="cn-stacked-bar__state" role="status">
12
+ {{ errorLabel }}
13
+ </p>
14
+ <p v-else-if="total === 0" class="cn-stacked-bar__state" data-testid="cn-stacked-bar-empty">
15
+ {{ emptyLabel }}
16
+ </p>
17
+ <template v-else>
18
+ <!-- The bar is a picture of the legend below it. The legend holds
19
+ every label and number, so the bar itself is hidden from
20
+ assistive technology rather than described twice. -->
21
+ <div class="cn-stacked-bar__bar" aria-hidden="true">
22
+ <span
23
+ v-for="segment in barSegments"
24
+ :key="segment.key"
25
+ class="cn-stacked-bar__segment"
26
+ :style="{ flexGrow: segment.count, backgroundColor: segment.color }" />
27
+ </div>
28
+ <ul class="cn-stacked-bar__legend">
29
+ <li
30
+ v-for="segment in segments"
31
+ :key="segment.key"
32
+ class="cn-stacked-bar__legend-item"
33
+ data-testid="cn-stacked-bar-legend-item">
34
+ <span class="cn-stacked-bar__label">
35
+ <span
36
+ class="cn-stacked-bar__dot"
37
+ aria-hidden="true"
38
+ :style="{ backgroundColor: segment.color }" />
39
+ {{ segment.label }}
40
+ </span>
41
+ <!-- The leading space keeps label and count two words for a
42
+ screen reader; it is not drawn in this flex column. -->
43
+ <strong class="cn-stacked-bar__count">{{ ' ' + segment.count }}</strong>
44
+ </li>
45
+ </ul>
46
+ </template>
47
+ </div>
48
+ </template>
49
+
50
+ <script>
51
+ import { translate as t } from '@nextcloud/l10n'
52
+ import { NcLoadingIcon } from '@nextcloud/vue'
53
+ import { fetchGroupedCounts } from '../../utils/fetchAggregate.js'
54
+
55
+ /** The lightest step keeps this share of the primary colour, so it still stands out from the page. */
56
+ const LIGHTEST_PRIMARY_SHARE = 70
57
+
58
+ /**
59
+ * The colour of step `index` out of `count`, on one ramp that runs from a
60
+ * light tint of the primary colour, through the primary colour, to the text
61
+ * colour. Steps differ in LIGHTNESS, so they stay apart for somebody who
62
+ * cannot tell hues apart, and the ramp follows the theme because it is built
63
+ * from Nextcloud's own variables.
64
+ *
65
+ * @param {number} index The step, 0-based.
66
+ * @param {number} count The number of steps.
67
+ * @return {string} A CSS colour.
68
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget
69
+ */
70
+ export function rampColor(index, count) {
71
+ if (count <= 1) {
72
+ return 'var(--color-primary-element)'
73
+ }
74
+ const position = index / (count - 1)
75
+ if (position < 0.5) {
76
+ const share = Math.round(LIGHTEST_PRIMARY_SHARE + (position / 0.5) * (100 - LIGHTEST_PRIMARY_SHARE))
77
+ return `color-mix(in srgb, var(--color-primary-element) ${share}%, var(--color-main-background))`
78
+ }
79
+ const textShare = Math.round(((position - 0.5) / 0.5) * 100)
80
+ if (textShare === 0) {
81
+ return 'var(--color-primary-element)'
82
+ }
83
+ if (textShare === 100) {
84
+ return 'var(--color-main-text)'
85
+ }
86
+ return `color-mix(in srgb, var(--color-main-text) ${textShare}%, var(--color-primary-element))`
87
+ }
88
+
89
+ /**
90
+ * CnStackedBarWidget shows how a total divides over a few groups: ONE bar cut
91
+ * into segments, with a legend below it that names every segment and gives
92
+ * its count. Use it for "my cases per step" or "requests per channel".
93
+ *
94
+ * Segments come from one grouped count request against an OpenRegister
95
+ * source (`source.groupBy`), or from static `segments`. `order` fixes the
96
+ * order of the segments, and `labels` gives a group key a readable name.
97
+ * Colours come from one ramp built on the theme's primary colour; the steps
98
+ * differ in lightness, never in hue alone. Resolved by its registry type key
99
+ * `stacked-bar`.
100
+ *
101
+ * ```json
102
+ * {
103
+ * "widgetKey": "stacked-bar",
104
+ * "props": {
105
+ * "content": {
106
+ * "source": { "register": "dossiq", "schema": "case", "groupBy": "status", "filter": { "assignee": "@me" } },
107
+ * "order": ["received", "in_progress", "decision", "publish"],
108
+ * "labels": { "received": "Received", "in_progress": "In progress", "decision": "Decision", "publish": "Publish" }
109
+ * }
110
+ * }
111
+ * }
112
+ * ```
113
+ */
114
+ export default {
115
+ name: 'CnStackedBarWidget',
116
+
117
+ components: { NcLoadingIcon },
118
+
119
+ inject: {
120
+ /**
121
+ * Host translate function provided by CnAppRoot. Segment labels and
122
+ * the empty text run through it. Identity by default.
123
+ */
124
+ cnTranslate: { default: () => (key) => key },
125
+ },
126
+
127
+ props: {
128
+ /**
129
+ * The widget's configuration. `source` names the OpenRegister
130
+ * register and schema, the `groupBy` field and an optional `filter`
131
+ * (the shared @-token grammar). `segments` are static entries
132
+ * `{ key?, label, value }` used when there is no source. `order` lists
133
+ * group keys in the order they render; groups it does not list follow
134
+ * in the order they arrive, and a listed key without records renders
135
+ * with a count of 0. `labels` maps a group key to its label.
136
+ * `emptyText` is shown when there is nothing to count.
137
+ *
138
+ * @type {{source?: {register?: string, schema?: string, groupBy?: string, filter?: object}, segments?: Array<{key?: string, label: string, value: number}>, order?: Array<string>, labels?: {[key: string]: string}, emptyText?: string}}
139
+ */
140
+ content: {
141
+ type: Object,
142
+ default: () => ({}),
143
+ },
144
+
145
+ /**
146
+ * Translate function. Falls back to the injected `cnTranslate`. Pass it
147
+ * when mounting the widget outside a CnAppRoot.
148
+ *
149
+ * @type {((key: string) => string)|null}
150
+ */
151
+ translate: {
152
+ type: Function,
153
+ default: null,
154
+ },
155
+ },
156
+
157
+ data() {
158
+ return {
159
+ groups: [],
160
+ loading: false,
161
+ error: '',
162
+ }
163
+ },
164
+
165
+ computed: {
166
+ /**
167
+ * The translate function in use.
168
+ *
169
+ * @return {(key: string) => string}
170
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget
171
+ */
172
+ effectiveTranslate() {
173
+ return this.translate ?? this.cnTranslate
174
+ },
175
+
176
+ /**
177
+ * Whether the segments come from OpenRegister.
178
+ *
179
+ * @return {boolean}
180
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget
181
+ */
182
+ hasSource() {
183
+ const s = this.content.source || {}
184
+ return Boolean(s.register && s.schema && s.groupBy)
185
+ },
186
+
187
+ /**
188
+ * The raw groups, from the source or the static list, as `{ key, label, count }`.
189
+ *
190
+ * @return {Array<{key: string, label: string, count: number}>}
191
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget
192
+ */
193
+ rawGroups() {
194
+ if (this.hasSource) {
195
+ return this.groups.map((group) => ({ key: group.key, label: group.key, count: group.count }))
196
+ }
197
+ const list = Array.isArray(this.content.segments) ? this.content.segments : []
198
+ return list
199
+ .filter((segment) => segment && typeof segment === 'object')
200
+ .map((segment, index) => {
201
+ const count = Number(segment.value)
202
+ return {
203
+ key: String(segment.key ?? segment.label ?? index),
204
+ label: String(segment.label ?? segment.key ?? ''),
205
+ count: Number.isFinite(count) && count > 0 ? count : 0,
206
+ }
207
+ })
208
+ },
209
+
210
+ /**
211
+ * The segments in render order, each with its label and colour.
212
+ *
213
+ * @return {Array<{key: string, label: string, count: number, color: string}>}
214
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget
215
+ */
216
+ segments() {
217
+ const order = Array.isArray(this.content.order) ? this.content.order.map(String) : []
218
+ const labels = (this.content.labels && typeof this.content.labels === 'object') ? this.content.labels : {}
219
+ const byKey = {}
220
+ for (const group of this.rawGroups) {
221
+ byKey[group.key] = group
222
+ }
223
+ const ordered = []
224
+ for (const key of order) {
225
+ ordered.push(byKey[key] || { key, label: key, count: 0 })
226
+ }
227
+ for (const group of this.rawGroups) {
228
+ if (!order.includes(group.key)) {
229
+ ordered.push(group)
230
+ }
231
+ }
232
+ return ordered.map((group, index) => ({
233
+ key: group.key,
234
+ label: this.effectiveTranslate(typeof labels[group.key] === 'string' ? labels[group.key] : group.label),
235
+ count: group.count,
236
+ color: rampColor(index, ordered.length),
237
+ }))
238
+ },
239
+
240
+ /**
241
+ * The segments that take up room in the bar.
242
+ *
243
+ * @return {Array<object>}
244
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget
245
+ */
246
+ barSegments() {
247
+ return this.segments.filter((segment) => segment.count > 0)
248
+ },
249
+
250
+ /**
251
+ * The sum of all counts.
252
+ *
253
+ * @return {number}
254
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget
255
+ */
256
+ total() {
257
+ return this.segments.reduce((sum, segment) => sum + segment.count, 0)
258
+ },
259
+
260
+ /**
261
+ * The text shown when there is nothing to count.
262
+ *
263
+ * @return {string}
264
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget
265
+ */
266
+ emptyLabel() {
267
+ const text = this.content.emptyText
268
+ return (typeof text === 'string' && text !== '')
269
+ ? this.effectiveTranslate(text)
270
+ : t('nextcloud-vue', 'Nothing to show yet')
271
+ },
272
+
273
+ /**
274
+ * @return {string} The loading text.
275
+ *
276
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget
277
+ */
278
+ loadingLabel() {
279
+ return t('nextcloud-vue', 'Loading…')
280
+ },
281
+
282
+ /**
283
+ * @return {string} The text shown when the counts could not be loaded.
284
+ *
285
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget
286
+ */
287
+ errorLabel() {
288
+ return t('nextcloud-vue', 'The numbers could not be loaded.')
289
+ },
290
+
291
+ /**
292
+ * Changes whenever a new fetch is needed.
293
+ *
294
+ * @return {string}
295
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget
296
+ */
297
+ sourceKey() {
298
+ return JSON.stringify(this.content.source || {})
299
+ },
300
+ },
301
+
302
+ watch: {
303
+ /** @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget */
304
+ sourceKey() {
305
+ this.fetchGroups()
306
+ },
307
+ },
308
+
309
+ mounted() {
310
+ this.fetchGroups()
311
+ },
312
+
313
+ methods: {
314
+ /**
315
+ * Load the count per group in one request.
316
+ *
317
+ * @return {Promise<void>}
318
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget
319
+ */
320
+ async fetchGroups() {
321
+ if (!this.hasSource) {
322
+ this.groups = []
323
+ this.error = ''
324
+ return
325
+ }
326
+ const requestKey = this.sourceKey
327
+ this.loading = true
328
+ this.error = ''
329
+ try {
330
+ const groups = await fetchGroupedCounts(this.content.source)
331
+ if (requestKey !== this.sourceKey) {
332
+ return
333
+ }
334
+ this.groups = groups
335
+ } catch (e) {
336
+ // eslint-disable-next-line no-console
337
+ console.warn('[CnStackedBarWidget] failed to load counts:', e)
338
+ this.error = (e && e.message) || 'error'
339
+ this.groups = []
340
+ } finally {
341
+ this.loading = false
342
+ }
343
+ },
344
+ },
345
+ }
346
+ </script>
347
+
348
+ <style scoped>
349
+ .cn-stacked-bar {
350
+ display: flex;
351
+ flex-direction: column;
352
+ gap: 16px;
353
+ width: 100%;
354
+ }
355
+
356
+ .cn-stacked-bar__state {
357
+ display: flex;
358
+ align-items: center;
359
+ gap: 8px;
360
+ margin: 0;
361
+ color: var(--color-text-maxcontrast);
362
+ }
363
+
364
+ .cn-stacked-bar__bar {
365
+ display: flex;
366
+ gap: 3px;
367
+ height: 12px;
368
+ }
369
+
370
+ .cn-stacked-bar__segment {
371
+ flex-basis: 0;
372
+ min-width: 4px;
373
+ border-radius: 6px;
374
+ }
375
+
376
+ .cn-stacked-bar__legend {
377
+ display: grid;
378
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
379
+ gap: 12px;
380
+ margin: 0;
381
+ padding: 0;
382
+ list-style: none;
383
+ }
384
+
385
+ .cn-stacked-bar__legend-item {
386
+ display: flex;
387
+ flex-direction: column;
388
+ gap: 2px;
389
+ min-width: 0;
390
+ }
391
+
392
+ .cn-stacked-bar__label {
393
+ display: flex;
394
+ align-items: center;
395
+ gap: 8px;
396
+ font-size: 0.95em;
397
+ color: var(--color-text-maxcontrast);
398
+ overflow-wrap: anywhere;
399
+ }
400
+
401
+ .cn-stacked-bar__dot {
402
+ flex: none;
403
+ width: 10px;
404
+ height: 10px;
405
+ border-radius: 3px;
406
+ }
407
+
408
+ .cn-stacked-bar__count {
409
+ font-size: 1.5em;
410
+ font-weight: 700;
411
+ line-height: 1.2;
412
+ color: var(--color-main-text);
413
+ }
414
+ </style>
@@ -0,0 +1,14 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * CnStackedBarWidget: the `stacked-bar` dashboard widget (one segmented bar
6
+ * with a legend that carries the numbers). It is registered in
7
+ * `CnWidgetGrid/registerDashboardWidgets.js`, inline, because a bare
8
+ * side-effect import of this module may be tree-shaken away.
9
+ */
10
+
11
+ import CnStackedBarWidget from './CnStackedBarWidget.vue'
12
+
13
+ export { CnStackedBarWidget }
14
+ export default CnStackedBarWidget
@@ -0,0 +1,224 @@
1
+ <!--
2
+ - SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ - SPDX-License-Identifier: EUPL-1.2
4
+ -->
5
+
6
+ <template>
7
+ <div class="cn-stacked-bar-form">
8
+ <div class="cn-stacked-bar-form__row2">
9
+ <CnRegisterSchemaSelect
10
+ :register="source.register"
11
+ :schema="source.schema"
12
+ @update:register="updateSource('register', $event)"
13
+ @update:schema="updateSource('schema', $event)" />
14
+ </div>
15
+
16
+ <CnFieldPicker
17
+ :value="groupBy"
18
+ :label="t('nextcloud-vue', 'Field to group by')"
19
+ :options="availableFields"
20
+ placeholder="status"
21
+ @update="updateField('groupBy', $event)" />
22
+
23
+ <CnFilterRowsEditor :value="filterRows" :fields="availableFields" @input="onFilterRows" />
24
+
25
+ <NcTextField
26
+ :modelValue="orderText"
27
+ :label="t('nextcloud-vue', 'Order of the segments (values, comma separated)')"
28
+ placeholder="received, in_progress, decision"
29
+ @update:modelValue="updateField('orderText', $event)" />
30
+
31
+ <NcTextField
32
+ :modelValue="emptyText"
33
+ :label="t('nextcloud-vue', 'Text when there is nothing to count')"
34
+ @update:modelValue="updateField('emptyText', $event)" />
35
+ </div>
36
+ </template>
37
+
38
+ <script>
39
+ import { translate as t } from '@nextcloud/l10n'
40
+ import { NcTextField } from '@nextcloud/vue'
41
+ import CnFieldPicker from '../CnFieldPicker/CnFieldPicker.vue'
42
+ import CnFilterRowsEditor from '../CnFilterRowsEditor/CnFilterRowsEditor.vue'
43
+ import CnRegisterSchemaSelect from '../CnRegisterSchemaSelect/CnRegisterSchemaSelect.vue'
44
+ import { fetchSchemaProperties } from '../../utils/fetchSchemaProperties.js'
45
+ import { filterToRows, rowsToFilter } from '../CnFilterRowsEditor/filterRows.js'
46
+
47
+ /**
48
+ * CnStackedBarWidgetForm is the settings form of a `stacked-bar` widget. It
49
+ * edits the source (register, schema, filter), the field the records are
50
+ * grouped by, the order of the segments and the empty text. Static `segments`
51
+ * and the `labels` map are written in the manifest and pass through
52
+ * untouched. Emits `update:content` on every change; `validate()` asks for a
53
+ * register, a schema and a group field unless the widget has static segments.
54
+ */
55
+ export default {
56
+ name: 'CnStackedBarWidgetForm',
57
+
58
+ components: { NcTextField, CnFieldPicker, CnFilterRowsEditor, CnRegisterSchemaSelect },
59
+
60
+ props: {
61
+ /**
62
+ * The placement being edited (pre-fills from `editingWidget.content`), or null.
63
+ *
64
+ * @type {{content: object}|null}
65
+ */
66
+ editingWidget: { type: Object, default: null },
67
+ /**
68
+ * Initial content values when not editing (registry defaults).
69
+ *
70
+ * @type {object}
71
+ */
72
+ value: { type: Object, default: () => ({}) },
73
+ },
74
+
75
+ emits: [
76
+ /**
77
+ * Emitted with the assembled content blob on every field change.
78
+ *
79
+ * @event update:content
80
+ * @type {object}
81
+ */
82
+ 'update:content',
83
+ ],
84
+
85
+ data() {
86
+ const initial = this.editingWidget?.content || this.value || {}
87
+ const src = initial.source || {}
88
+ return {
89
+ initial,
90
+ source: { register: src.register ?? '', schema: src.schema ?? '' },
91
+ groupBy: src.groupBy ?? '',
92
+ filterRows: filterToRows(src.filter || {}),
93
+ orderText: Array.isArray(initial.order) ? initial.order.join(', ') : '',
94
+ emptyText: initial.emptyText ?? '',
95
+ availableFields: [],
96
+ }
97
+ },
98
+
99
+ computed: {
100
+ /**
101
+ * The assembled content blob. Keys the form does not edit (static
102
+ * segments, labels) are carried over from the initial content.
103
+ *
104
+ * @return {object}
105
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget
106
+ */
107
+ assembledContent() {
108
+ return {
109
+ ...this.initial,
110
+ source: {
111
+ register: this.source.register,
112
+ schema: this.source.schema,
113
+ groupBy: this.groupBy,
114
+ filter: rowsToFilter(this.filterRows),
115
+ },
116
+
117
+ order: this.orderText.split(',').map((key) => key.trim()).filter((key) => key !== ''),
118
+ emptyText: this.emptyText,
119
+ }
120
+ },
121
+ },
122
+
123
+ watch: {
124
+ 'source.register': 'loadFields',
125
+ 'source.schema': 'loadFields',
126
+ },
127
+
128
+ mounted() {
129
+ this.loadFields()
130
+ },
131
+
132
+ methods: {
133
+ t,
134
+
135
+ /**
136
+ * Load the schema's field names for the picker.
137
+ *
138
+ * @return {Promise<void>}
139
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget
140
+ */
141
+ async loadFields() {
142
+ this.availableFields = await fetchSchemaProperties(this.source.register, this.source.schema)
143
+ },
144
+
145
+ /**
146
+ * Set one field and emit the assembled content.
147
+ *
148
+ * @param {string} field The data key to write.
149
+ * @param {string} value The new value.
150
+ * @return {void}
151
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget
152
+ */
153
+ updateField(field, value) {
154
+ this[field] = value
155
+ this.$emit('update:content', this.assembledContent)
156
+ },
157
+
158
+ /**
159
+ * Set the register or the schema and emit.
160
+ *
161
+ * @param {'register'|'schema'} field The source key to write.
162
+ * @param {string} value The chosen slug.
163
+ * @return {void}
164
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget
165
+ */
166
+ updateSource(field, value) {
167
+ this.source[field] = value
168
+ this.$emit('update:content', this.assembledContent)
169
+ },
170
+
171
+ /**
172
+ * Take the filter editor's rows and emit.
173
+ *
174
+ * @param {Array<{key: string, op: string, value: string}>} rows The rows.
175
+ * @return {void}
176
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget
177
+ */
178
+ onFilterRows(rows) {
179
+ this.filterRows = rows
180
+ this.$emit('update:content', this.assembledContent)
181
+ },
182
+
183
+ /**
184
+ * Validate the form; an empty array means valid.
185
+ *
186
+ * @return {string[]} The validation errors.
187
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget
188
+ */
189
+ validate() {
190
+ const hasStatic = Array.isArray(this.initial.segments) && this.initial.segments.length > 0
191
+ if (hasStatic && !this.source.register && !this.source.schema) {
192
+ return []
193
+ }
194
+ const errors = []
195
+ if (!this.source.register || !this.source.schema) {
196
+ errors.push(t('nextcloud-vue', 'A register and schema are required'))
197
+ }
198
+ if (!this.groupBy) {
199
+ errors.push(t('nextcloud-vue', 'A field to group by is required'))
200
+ }
201
+ return errors
202
+ },
203
+ },
204
+ }
205
+ </script>
206
+
207
+ <style scoped>
208
+ .cn-stacked-bar-form {
209
+ display: flex;
210
+ flex-direction: column;
211
+ gap: 12px;
212
+ }
213
+
214
+ .cn-stacked-bar-form__row2 {
215
+ display: flex;
216
+ gap: 12px;
217
+ align-items: flex-end;
218
+ }
219
+
220
+ .cn-stacked-bar-form__row2 > * {
221
+ flex: 1;
222
+ min-width: 0;
223
+ }
224
+ </style>
@@ -0,0 +1,4 @@
1
+ import CnStackedBarWidgetForm from './CnStackedBarWidgetForm.vue'
2
+
3
+ export default CnStackedBarWidgetForm
4
+ export { CnStackedBarWidgetForm }
@@ -89,6 +89,28 @@ export default defineComponent({
89
89
  type: Boolean,
90
90
  default: false,
91
91
  },
92
+
93
+ /**
94
+ * A count shown after the title, for example the number of documents
95
+ * behind the tab. `null` (the default) shows none; `0` is shown.
96
+ *
97
+ * @type {number|string|null}
98
+ */
99
+ count: {
100
+ type: [Number, String],
101
+ default: null,
102
+ },
103
+
104
+ /**
105
+ * List this tab under the strip's "More" menu instead of in the strip.
106
+ * Picking it there selects it, and while it is the selected tab it
107
+ * shows in the strip. Give an overflow tab a `title`: the menu needs
108
+ * plain text, and a `#title` slot is markup.
109
+ */
110
+ overflow: {
111
+ type: Boolean,
112
+ default: false,
113
+ },
92
114
  },
93
115
 
94
116
  emits: [
@@ -125,6 +147,19 @@ export default defineComponent({
125
147
  get disabled() {
126
148
  return props.disabled
127
149
  },
150
+
151
+ get count() {
152
+ return props.count
153
+ },
154
+
155
+ get overflow() {
156
+ return props.overflow
157
+ },
158
+
159
+ // Plain text for the "More" menu, where a `#title` slot cannot go.
160
+ get label() {
161
+ return props.title
162
+ },
128
163
  }
129
164
 
130
165
  onMounted(() => tabsApi?.register(entry))