@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,19 +1,31 @@
1
- import { resolveComponent, openBlock, createElementBlock, normalizeClass, createElementVNode, Fragment, renderList, createBlock, resolveDynamicComponent, createVNode, createCommentVNode, renderSlot } from 'vue';
1
+ import { resolveComponent, openBlock, createElementBlock, normalizeClass, createElementVNode, Fragment, renderList, createBlock, resolveDynamicComponent, createTextVNode, toDisplayString, createCommentVNode, createVNode, withCtx, renderSlot } from 'vue';
2
2
 
3
3
  const _hoisted_1 = { class: "cn-tabs__bar" };
4
4
  const _hoisted_2 = ["aria-label"];
5
5
  const _hoisted_3 = ["id", "aria-selected", "aria-controls", "tabindex", "disabled", "onClick"];
6
6
  const _hoisted_4 = {
7
7
  key: 0,
8
+ class: "cn-tabs__count",
9
+ "data-testid": "cn-tabs-count"
10
+ };
11
+ const _hoisted_5 = {
12
+ key: 0,
13
+ class: "cn-tabs__more"
14
+ };
15
+ const _hoisted_6 = {
16
+ key: 1,
8
17
  class: "cn-tabs__nav-end"
9
18
  };
10
19
 
11
20
  function render(_ctx, _cache, $props, $setup, $data, $options) {
12
21
  const _component_ChevronLeft = resolveComponent("ChevronLeft");
13
22
  const _component_ChevronRight = resolveComponent("ChevronRight");
23
+ const _component_ChevronDown = resolveComponent("ChevronDown");
24
+ const _component_NcActionButton = resolveComponent("NcActionButton");
25
+ const _component_NcActions = resolveComponent("NcActions");
14
26
 
15
27
  return (openBlock(), createElementBlock("div", {
16
- class: normalizeClass(["cn-tabs", { 'cn-tabs--card': _ctx.card }])
28
+ class: normalizeClass(["cn-tabs", { 'cn-tabs--card': _ctx.card, 'cn-tabs--segmented': _ctx.variant === 'segmented' }])
17
29
  }, [
18
30
  createElementVNode("div", _hoisted_1, [
19
31
  createElementVNode("div", {
@@ -28,7 +40,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
28
40
  onWheel: _cache[1] || (_cache[1] = (...args) => (_ctx.onNavWheel && _ctx.onNavWheel(...args))),
29
41
  onScroll: _cache[2] || (_cache[2] = (...args) => (_ctx.measureOverflow && _ctx.measureOverflow(...args)))
30
42
  }, [
31
- (openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.tabs, (tab) => {
43
+ (openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.visibleTabs, (tab) => {
32
44
  return (openBlock(), createElementBlock("button", {
33
45
  id: tab.tabId,
34
46
  key: tab.uid,
@@ -43,7 +55,11 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
43
55
  disabled: tab.disabled || null,
44
56
  onClick: $event => (tab.onActivate())
45
57
  }, [
46
- (openBlock(), createBlock(resolveDynamicComponent(tab.titleRender)))
58
+ (openBlock(), createBlock(resolveDynamicComponent(tab.titleRender))),
59
+ _cache[5] || (_cache[5] = createTextVNode()),
60
+ (_ctx.hasCount(tab))
61
+ ? (openBlock(), createElementBlock("span", _hoisted_4, toDisplayString(tab.count), 1 /* TEXT */))
62
+ : createCommentVNode("v-if", true)
47
63
  ], 10 /* CLASS, PROPS */, _hoisted_3))
48
64
  }), 128 /* KEYED_FRAGMENT */))
49
65
  ], 42 /* CLASS, PROPS, NEED_HYDRATION */, _hoisted_2),
@@ -72,8 +88,44 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
72
88
  ]))
73
89
  : createCommentVNode("v-if", true)
74
90
  ], 2 /* CLASS */),
91
+ (_ctx.overflowTabs.length > 0)
92
+ ? (openBlock(), createElementBlock("div", _hoisted_5, [
93
+ createVNode(_component_NcActions, {
94
+ forceMenu: true,
95
+ forceName: true,
96
+ menuName: _ctx.moreLabel,
97
+ "aria-label": _ctx.moreAriaLabel,
98
+ variant: "tertiary",
99
+ "data-testid": "cn-tabs-more"
100
+ }, {
101
+ icon: withCtx(() => [
102
+ createVNode(_component_ChevronDown, { size: 20 })
103
+ ]),
104
+ default: withCtx(() => [
105
+ (openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.overflowTabs, (tab) => {
106
+ return (openBlock(), createBlock(_component_NcActionButton, {
107
+ key: tab.uid,
108
+ disabled: tab.disabled || null,
109
+ closeAfterClick: true,
110
+ "data-testid": "cn-tabs-more-item",
111
+ onClick: $event => (_ctx.onOverflowPick(tab))
112
+ }, {
113
+ icon: withCtx(() => [...(_cache[6] || (_cache[6] = [
114
+ createElementVNode("span", { class: "cn-tabs__more-spacer" }, null, -1 /* CACHED */)
115
+ ]))]),
116
+ default: withCtx(() => [
117
+ createTextVNode(" " + toDisplayString(_ctx.overflowLabel(tab)), 1 /* TEXT */)
118
+ ]),
119
+ _: 2 /* DYNAMIC */
120
+ }, 1032 /* PROPS, DYNAMIC_SLOTS */, ["disabled", "onClick"]))
121
+ }), 128 /* KEYED_FRAGMENT */))
122
+ ]),
123
+ _: 1 /* STABLE */
124
+ }, 8 /* PROPS */, ["menuName", "aria-label"])
125
+ ]))
126
+ : createCommentVNode("v-if", true),
75
127
  (_ctx.$slots['nav-end'])
76
- ? (openBlock(), createElementBlock("div", _hoisted_4, [
128
+ ? (openBlock(), createElementBlock("div", _hoisted_6, [
77
129
  renderSlot(_ctx.$slots, "nav-end", { activeIndex: _ctx.activeIndex })
78
130
  ]))
79
131
  : createCommentVNode("v-if", true)
@@ -1 +1 @@
1
- {"version":3,"file":"CnTabs.vue3.js","sources":["../../../../src/components/CnTabs/CnTabs.vue?vue&type=template&id=5c1a056f&lang.js"],"sourcesContent":["<template>\n\t<div class=\"cn-tabs\" :class=\"{ 'cn-tabs--card': card }\">\n\t\t<div class=\"cn-tabs__bar\">\n\t\t\t<div\n\t\t\t\tclass=\"cn-tabs__strip\"\n\t\t\t\t:class=\"{ 'cn-tabs__strip--more-start': moreStart, 'cn-tabs__strip--more-end': moreEnd }\">\n\t\t\t\t<div\n\t\t\t\t\tref=\"nav\"\n\t\t\t\t\tclass=\"cn-tabs__nav\"\n\t\t\t\t\t:class=\"{ 'cn-tabs__nav--justified': justified }\"\n\t\t\t\t\trole=\"tablist\"\n\t\t\t\t\t:aria-label=\"ariaLabel || null\"\n\t\t\t\t\t@keydown=\"onNavKeydown\"\n\t\t\t\t\t@wheel=\"onNavWheel\"\n\t\t\t\t\t@scroll=\"measureOverflow\">\n\t\t\t\t\t<button\n\t\t\t\t\t\tv-for=\"tab in tabs\"\n\t\t\t\t\t\t:id=\"tab.tabId\"\n\t\t\t\t\t\t:key=\"tab.uid\"\n\t\t\t\t\t\tref=\"navButtons\"\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\trole=\"tab\"\n\t\t\t\t\t\tclass=\"cn-tabs__nav-item\"\n\t\t\t\t\t\t:class=\"{ 'cn-tabs__nav-item--active': isActive(tab.uid) }\"\n\t\t\t\t\t\t:aria-selected=\"isActive(tab.uid) ? 'true' : 'false'\"\n\t\t\t\t\t\t:aria-controls=\"tab.panelId\"\n\t\t\t\t\t\t:tabindex=\"isActive(tab.uid) ? 0 : -1\"\n\t\t\t\t\t\t:disabled=\"tab.disabled || null\"\n\t\t\t\t\t\t@click=\"tab.onActivate()\">\n\t\t\t\t\t\t<component :is=\"tab.titleRender\" />\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t\t<!-- Mouse-only affordances: keyboard users already have the arrow\n\t\t\t\t keys, and the tabs themselves stay in the accessibility tree. -->\n\t\t\t\t<button\n\t\t\t\t\tv-if=\"moreStart\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-tabs__scroll cn-tabs__scroll--start\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\t@click=\"scrollStrip(-1)\">\n\t\t\t\t\t<ChevronLeft :size=\"20\" />\n\t\t\t\t</button>\n\t\t\t\t<button\n\t\t\t\t\tv-if=\"moreEnd\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-tabs__scroll cn-tabs__scroll--end\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\t@click=\"scrollStrip(1)\">\n\t\t\t\t\t<ChevronRight :size=\"20\" />\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<div v-if=\"$slots['nav-end']\" class=\"cn-tabs__nav-end\">\n\t\t\t\t<!-- @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. -->\n\t\t\t\t<!-- @binding {number} active-index Index of the currently selected tab. -->\n\t\t\t\t<slot name=\"nav-end\" :activeIndex=\"activeIndex\" />\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"cn-tabs__content\" :class=\"contentClass\">\n\t\t\t<!-- @slot The CnTab children. Anything else is rendered into the panel area untouched. -->\n\t\t\t<slot />\n\t\t</div>\n\t</div>\n</template>\n\n<script>\n/**\n * CnTabs — a generic tab strip.\n *\n * Neither `@nextcloud/vue@9` nor this library previously shipped one: the only\n * tab component in either is `NcAppSidebarTab`, which is meaningless outside an\n * `NcAppSidebar`. Apps that needed a plain tab strip in a page, a dialog or a\n * detail view were on `bootstrap-vue@2` — a Vue-2-only package with no Vue 3\n * release (`bootstrap-vue-next` is a different package with a different API) —\n * and pulling a second UI framework in alongside `@nextcloud/vue` cuts against\n * the fleet rule that apps render with Nextcloud components. So zaakafhandelapp\n * wrote a local `Tabs`/`Tab` pair for eight views and opencatalogi and\n * openregister flagged the same gap. This is that pair, lifted and hardened.\n *\n * ```vue\n * <CnTabs aria-label=\"Case details\" justified>\n * <CnTab title=\"Documents\">…</CnTab>\n * <CnTab title=\"Tasks\" :active=\"showTasks\" @click=\"onTasks\">…</CnTab>\n * <CnTab>\n * <template #title><CnIcon name=\"close\" /> Draft</template>\n * …\n * </CnTab>\n * </CnTabs>\n * ```\n *\n * ## Putting controls beside the strip\n *\n * `#nav-end` fills the right-hand end of the tab bar. `CnTabsWidget` uses it to\n * carry the active child's Actions menu, so one menu serves every tab instead\n * of each panel drawing its own header:\n *\n * ```vue\n * <CnTabs aria-label=\"Case details\">\n * <template #nav-end><CnActionsMenu :title=\"activeTitle\" /></template>\n * <CnTab title=\"Notes\">…</CnTab>\n * </CnTabs>\n * ```\n *\n * The slot renders OUTSIDE the `role=\"tablist\"` element on purpose. See the\n * comment on it in the template.\n *\n * ## Migrating from `bootstrap-vue`\n *\n * `<BTabs>`/`<BTab>` map across directly for the subset apps actually used:\n * `content-class`, `justified` and `card` on the strip; `title`, the `#title`\n * slot, `active` and `@click` on a tab. Anything else (`lazy`, `no-fade`,\n * `pills`, `<b-tab disabled>`'s tooltip behaviour) is not reimplemented.\n *\n * ## How registration works\n *\n * Children register themselves on mount and hand back a *render function* for\n * their title rather than a plain string. That is what lets a child's `#title`\n * slot be rendered inside the parent's nav strip, and it keeps a computed title\n * reactive: the parent invokes it inside its own render effect, so a title\n * change re-renders the nav.\n *\n * Mount order is document order for both static children and `v-for`-generated\n * ones, so the nav strip matches the source. A tab that unmounts (a closable\n * tab) hands the selection to its neighbour.\n *\n * ## Accessibility\n *\n * Implements the WAI-ARIA tabs pattern: `role=\"tablist\"` / `role=\"tab\"` /\n * `role=\"tabpanel\"`, `aria-selected`, `aria-controls` ↔ `aria-labelledby`\n * wiring, a roving `tabindex` (only the selected tab is in the tab order), and\n * Left/Right/Home/End keyboard navigation within the strip. Pass `aria-label`\n * (or `ariaLabel`) so screen-reader users hear what the strip is for.\n */\nimport { computed, defineComponent, onBeforeUnmount, onMounted, onUpdated, provide, reactive, ref, watch } from 'vue'\nimport ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'\nimport ChevronRight from 'vue-material-design-icons/ChevronRight.vue'\nimport { CN_TABS_INJECTION_KEY } from './tabsKey.js'\n\nexport default defineComponent({\n\tname: 'CnTabs',\n\n\tcomponents: {\n\t\tChevronLeft,\n\t\tChevronRight,\n\t},\n\n\tprops: {\n\t\t/** Extra class applied to the panel container (bootstrap-vue's `content-class`). */\n\t\tcontentClass: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Stretch the nav items to fill the strip (bootstrap-vue's `justified`). */\n\t\tjustified: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Card-style chrome (border + padding) around the panel area. */\n\t\tcard: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Accessible name for the tab strip, applied to the `role=\"tablist\"`\n\t\t * element. Screen readers announce it when focus enters the strip.\n\t\t */\n\t\tariaLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\temits: [\n\t\t/** The selected tab changed. Payload: the new active tab's index. */\n\t\t'update:activeIndex',\n\t],\n\n\tsetup(props, { emit }) {\n\t\t// Registered children, in mount order.\n\t\tconst tabs = reactive([])\n\t\tconst activeUid = ref(null)\n\t\tconst navButtons = ref([])\n\n\t\t/**\n\t\t * Register a child tab. The first child to register wins the initial\n\t\t * selection unless a later one declares itself `active`. Disabled tabs\n\t\t * never take the initial selection.\n\t\t *\n\t\t * @param {object} tab Child descriptor.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction register(tab) {\n\t\t\ttabs.push(tab)\n\t\t\tif (tab.disabled) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (activeUid.value === null || tab.active) {\n\t\t\t\tactiveUid.value = tab.uid\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Drop a child that is unmounting, moving the selection to its nearest\n\t\t * remaining neighbour when it was the active one. Closable tabs rely on\n\t\t * this; without it the strip would render with nothing selected.\n\t\t *\n\t\t * @param {number} uid The child's instance uid.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction unregister(uid) {\n\t\t\tconst index = tabs.findIndex((tab) => tab.uid === uid)\n\t\t\tif (index !== -1) {\n\t\t\t\ttabs.splice(index, 1)\n\t\t\t}\n\t\t\tif (activeUid.value === uid) {\n\t\t\t\tconst neighbour = tabs[index] || tabs[index - 1] || tabs[0] || null\n\t\t\t\tactiveUid.value = neighbour ? neighbour.uid : null\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Make a child the visible one.\n\t\t *\n\t\t * @param {number} uid The child's instance uid.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction select(uid) {\n\t\t\tif (activeUid.value === uid) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tactiveUid.value = uid\n\t\t\temit('update:activeIndex', tabs.findIndex((tab) => tab.uid === uid))\n\t\t}\n\n\t\t/**\n\t\t * Whether a child is the visible one.\n\t\t *\n\t\t * @param {number} uid The child's instance uid.\n\t\t *\n\t\t * @return {boolean} True when this tab's panel should render.\n\t\t */\n\t\tfunction isActive(uid) {\n\t\t\treturn activeUid.value === uid\n\t\t}\n\n\t\tconst activeIndex = computed(() => tabs.findIndex((tab) => tab.uid === activeUid.value))\n\n\t\t/**\n\t\t * Move the selection and DOM focus to a tab by index, skipping disabled\n\t\t * ones. Focus has to move with the selection: in the WAI-ARIA tabs\n\t\t * pattern only the selected tab is in the tab order, so leaving focus on\n\t\t * a now-`tabindex=\"-1\"` button strands the keyboard user.\n\t\t *\n\t\t * @param {number} index Target index (clamped to the enabled tabs).\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction focusTab(index) {\n\t\t\tconst enabled = tabs.filter((tab) => !tab.disabled)\n\t\t\tif (enabled.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst wrapped = ((index % enabled.length) + enabled.length) % enabled.length\n\t\t\tconst target = enabled[wrapped]\n\t\t\tselect(target.uid)\n\t\t\tconst position = tabs.indexOf(target)\n\t\t\tconst button = navButtons.value?.[position]\n\t\t\tif (button && typeof button.focus === 'function') {\n\t\t\t\tbutton.focus()\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Left/Right/Home/End navigation inside the strip.\n\t\t *\n\t\t * @param {KeyboardEvent} event The keydown event.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction onNavKeydown(event) {\n\t\t\tconst enabled = tabs.filter((tab) => !tab.disabled)\n\t\t\tconst current = enabled.findIndex((tab) => tab.uid === activeUid.value)\n\t\t\tlet next = null\n\n\t\t\tif (event.key === 'ArrowRight') {\n\t\t\t\tnext = current + 1\n\t\t\t} else if (event.key === 'ArrowLeft') {\n\t\t\t\tnext = current - 1\n\t\t\t} else if (event.key === 'Home') {\n\t\t\t\tnext = 0\n\t\t\t} else if (event.key === 'End') {\n\t\t\t\tnext = enabled.length - 1\n\t\t\t}\n\n\t\t\tif (next === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tfocusTab(next)\n\t\t}\n\n\t\tconst nav = ref(null)\n\n\t\t/**\n\t\t * Scroll the strip so the selected tab is wholly in view.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction revealActiveTab() {\n\t\t\tconst strip = nav.value\n\t\t\tconst button = strip?.querySelector('[role=\"tab\"][aria-selected=\"true\"]')\n\t\t\tif (!strip || !button) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst stripRect = strip.getBoundingClientRect()\n\t\t\tconst rect = button.getBoundingClientRect()\n\t\t\tif (rect.left < stripRect.left) {\n\t\t\t\tstrip.scrollLeft -= stripRect.left - rect.left\n\t\t\t} else if (rect.right > stripRect.right) {\n\t\t\t\tstrip.scrollLeft += rect.right - stripRect.right\n\t\t\t}\n\t\t}\n\n\t\twatch(activeUid, revealActiveTab, { flush: 'post' })\n\n\t\t// Whether tabs are hidden past the start or end edge of the strip.\n\t\tconst moreStart = ref(false)\n\t\tconst moreEnd = ref(false)\n\n\t\t/**\n\t\t * Recompute which edges hide tabs; 1px of tolerance absorbs sub-pixel rounding.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction measureOverflow() {\n\t\t\tconst strip = nav.value\n\t\t\tif (!strip) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tmoreStart.value = strip.scrollLeft > 1\n\t\t\tmoreEnd.value = strip.scrollWidth - strip.clientWidth - strip.scrollLeft > 1\n\t\t}\n\n\t\tlet resizeObserver = null\n\n\t\tonMounted(() => {\n\t\t\tmeasureOverflow()\n\t\t\tif (typeof ResizeObserver === 'undefined' || !nav.value) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tresizeObserver = new ResizeObserver(measureOverflow)\n\t\t\tresizeObserver.observe(nav.value)\n\t\t})\n\n\t\t// A title change or a tab added can tip the strip into overflowing without resizing it.\n\t\tonUpdated(measureOverflow)\n\n\t\tonBeforeUnmount(() => {\n\t\t\tresizeObserver?.disconnect()\n\t\t\tresizeObserver = null\n\t\t})\n\n\t\t/**\n\t\t * Scroll the strip most of a viewport towards one edge.\n\t\t *\n\t\t * @param {number} direction -1 towards the start, 1 towards the end.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction scrollStrip(direction) {\n\t\t\tconst strip = nav.value\n\t\t\tif (!strip) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tstrip.scrollLeft += direction * Math.max(1, Math.round(strip.clientWidth * 0.6))\n\t\t}\n\n\t\t/**\n\t\t * Turn a vertical wheel into a horizontal scroll while the strip overflows.\n\t\t *\n\t\t * @param {WheelEvent} event The wheel event.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction onNavWheel(event) {\n\t\t\tconst strip = event.currentTarget\n\t\t\tif (strip.scrollWidth <= strip.clientWidth || event.deltaX !== 0 || event.deltaY === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tstrip.scrollLeft += event.deltaY\n\t\t}\n\n\t\tprovide(CN_TABS_INJECTION_KEY, { register, unregister, select, isActive })\n\n\t\treturn {\n\t\t\ttabs,\n\t\t\tisActive,\n\t\t\tactiveIndex,\n\t\t\tnav,\n\t\t\tnavButtons,\n\t\t\tmoreStart,\n\t\t\tmoreEnd,\n\t\t\tmeasureOverflow,\n\t\t\tscrollStrip,\n\t\t\tonNavKeydown,\n\t\t\tonNavWheel,\n\t\t}\n\t},\n})\n</script>\n\n<style scoped>\n/* The bar owns the rule under the strip so a `#nav-end` surface sits ON the\n line rather than above a line that stops where the last tab does. With no\n nav-end content the nav is the bar's only child and the result is pixel\n identical to the rule living on the nav itself. */\n.cn-tabs__bar {\n\t/* Top-aligned so the `#nav-end` control sits on the tab row, not below it,\n\t should the two sides ever differ in height. */\n\talign-items: flex-start;\n\tborder-bottom: 1px solid var(--color-border);\n\tdisplay: flex;\n\tgap: 8px;\n}\n\n/* The strip holds the scrolling nav and the edge affordances drawn over it. */\n.cn-tabs__strip {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n\tposition: relative;\n}\n\n.cn-tabs__nav {\n\tdisplay: flex;\n\tgap: 4px;\n\t/* One row that scrolls, never a wrap: in a narrow sidebar a nine-tab strip\n\t wrapped into a stack taller than the panel under it. The scrollbar is\n\t hidden because it would sit between the tabs and the bar's rule and break\n\t the open tab's join with its panel; a fade and a chevron on the clipped\n\t edge say there is more, and the strip follows the wheel. */\n\tflex-wrap: nowrap;\n\toverflow-x: auto;\n\toverflow-y: hidden;\n\tscroll-behavior: smooth;\n\tscrollbar-width: none;\n\t/* Overhang the bar's 1px rule so the open tab's own bottom edge, drawn in\n\t the panel's colour, covers it and the tab joins its panel. */\n\tmargin-bottom: -1px;\n}\n\n.cn-tabs__nav::-webkit-scrollbar {\n\tdisplay: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n\t.cn-tabs__nav {\n\t\tscroll-behavior: auto;\n\t}\n}\n\n/* A fade over the clipped edge, only on the side that hides tabs. Stops a\n pixel above the bar's rule so the rule keeps its full colour.\n\n z-index 2 so the fades outrank a lifted tab: CnTabsWidget puts the open tab\n at 1, and a tie goes to DOM order, which would draw that tab over the start\n fade (it precedes the tabs) while the end fade still covered it. */\n.cn-tabs__strip::before,\n.cn-tabs__strip::after {\n\tcontent: '';\n\tdisplay: none;\n\tinset-block: 0 1px;\n\tpointer-events: none;\n\tposition: absolute;\n\twidth: 56px;\n\tz-index: 2;\n}\n\n.cn-tabs__strip::before {\n\tbackground: linear-gradient(to right, var(--color-main-background) 30%, transparent);\n\tinset-inline-start: 0;\n}\n\n.cn-tabs__strip::after {\n\tbackground: linear-gradient(to left, var(--color-main-background) 30%, transparent);\n\tinset-inline-end: 0;\n}\n\n.cn-tabs__strip--more-start::before,\n.cn-tabs__strip--more-end::after {\n\tdisplay: block;\n}\n\n/* Doubled selector for the same reason as `.cn-tabs__nav .cn-tabs__nav-item`:\n Nextcloud's own `button` rule would otherwise give this a border, a fill\n and a margin. */\n.cn-tabs__strip .cn-tabs__scroll {\n\talign-items: center;\n\tbackground: none;\n\tborder: 0;\n\tborder-radius: 0;\n\tcolor: var(--color-main-text);\n\tcursor: pointer;\n\tdisplay: flex;\n\t/* Pushed down a little so the chevron sits level with the tab labels. */\n\tinset-block: 2px 0;\n\tjustify-content: center;\n\tmargin: 0;\n\tmin-height: 0;\n\tpadding: 0;\n\tposition: absolute;\n\twidth: 28px;\n\tz-index: 3;\n}\n\n/* Nextcloud's `button:hover` rule would otherwise fill the chevron. */\n.cn-tabs__strip .cn-tabs__scroll:hover,\n.cn-tabs__strip .cn-tabs__scroll:focus {\n\tbackground: none;\n}\n\n.cn-tabs__scroll--start {\n\tinset-inline-start: 0;\n}\n\n.cn-tabs__scroll--end {\n\tinset-inline-end: 0;\n}\n\n.cn-tabs__nav-end {\n\talign-items: center;\n\tdisplay: flex;\n\tflex: 0 0 auto;\n\tgap: 4px;\n\t/* No min-height. This used to hard-code 38px to mirror the old nav item's\n\t vertical box, but the folder-tab restyle changed that box to 33px, and\n\t because the bar is `align-items: flex-start` the taller nav-end then drove\n\t the BAR's height. The bar's bottom rule sank 6px below the tab row, so the\n\t open tab no longer met the panel it is drawn as joined to. Letting the\n\t control size to its own content keeps the rule on the tab row whatever the\n\t nav item's box becomes, so the two cannot drift apart again. */\n\tmin-height: 0;\n}\n\n/* Folder tabs, not underlined labels. An underline is the weakest possible\n affordance on a strip that already carries icons: with nine tabs on a dossiq\n case the row read as a sentence of links, and only the 2px rule said which\n one was open. Giving the inactive tabs their own darker surface makes the\n strip legible as a control at a glance, and the active tab, sharing the\n panel's background and punching a hole in the bar's rule, reads as the sheet\n in front. */\n.cn-tabs__nav-item {\n\tbackground-color: var(--color-background-dark);\n\t/* `--color-border-dark`, not `--color-border`: the latter is the same\n\t #ededed as `--color-background-dark` in the stock light theme, so a tab\n\t drawn with it has no visible edge against its own fill. */\n\tborder: 1px solid var(--color-border-dark);\n\tborder-bottom: none;\n\tborder-radius: var(--border-radius-large, 8px) var(--border-radius-large, 8px) 0 0;\n\t/* Full-contrast label, not `--color-text-maxcontrast`. On the old\n\t transparent strip maxcontrast sat on white at 5.3:1; on this darker\n\t surface the same pair measures 4.55:1, which clears WCAG AA by 0.05 and\n\t would not survive a themed instance shifting either token. A real tab\n\t strip carries its selection in the surface and the weight anyway, so\n\t nothing is lost by giving every label the readable colour. */\n\tcolor: var(--color-main-text);\n\tcursor: pointer;\n\tfont-weight: normal;\n\tpadding: 8px 12px;\n\twhite-space: nowrap;\n\t/* Match the `#nav-end` control's own height. The bar is a flex row, so\n\t whichever side is taller sets the bar's height and therefore where its\n\t bottom rule lands. When the control was the taller one the rule sat below\n\t the tab row and showed as a line under the open tab, breaking the join to\n\t the panel. Sizing both sides the same is what keeps the rule ON the tab\n\t row; `inline-flex` centres the label inside the taller box. */\n\talign-items: center;\n\tbox-sizing: border-box;\n\tdisplay: inline-flex;\n\tmin-height: var(--default-clickable-area, 34px);\n}\n\n/* Nextcloud's server stylesheet gives every plain `button` a 3px bottom margin\n with a selector that outscores the scoped rule above, which floated the tabs\n above the bar's rule inside a real page. The parent class wins on\n specificity rather than on `!important` or source order. */\n.cn-tabs__nav .cn-tabs__nav-item {\n\tmargin-block-end: 0;\n}\n\n.cn-tabs__nav--justified .cn-tabs__nav-item {\n\tflex: 1 1 0;\n}\n\n.cn-tabs__nav-item:hover,\n.cn-tabs__nav-item:focus-visible {\n\tbackground-color: var(--color-background-hover);\n\tcolor: var(--color-main-text);\n}\n\n/* Inside the box: the scrolling strip clips anything drawn outside a tab. */\n.cn-tabs__nav-item:focus-visible {\n\toutline-offset: -2px;\n}\n\n.cn-tabs__nav-item[disabled] {\n\tcursor: default;\n\topacity: 0.5;\n}\n\n.cn-tabs__nav-item[disabled]:hover {\n\tbackground-color: var(--color-background-dark);\n\tcolor: var(--color-main-text);\n}\n\n/* The open tab: the panel's own background, and a bottom border painted in\n that same background so it erases the bar's rule underneath and the tab\n joins the sheet below it. The primary-coloured top edge is what carries the\n selection at a glance, and it survives forced-colours mode, which drops\n background colours but keeps borders. */\n.cn-tabs__nav-item--active {\n\tbackground-color: var(--color-main-background);\n\tborder-bottom: 1px solid var(--color-main-background);\n\tborder-top: 2px solid var(--color-primary-element);\n\tcolor: var(--color-main-text);\n\tfont-weight: bold;\n\t/* Keep the text baseline identical to an inactive tab despite the 1px\n\t thicker top border, so the strip does not jog when the selection moves. */\n\tpadding-top: 7px;\n}\n\n.cn-tabs__content {\n\tpadding-top: 12px;\n}\n\n.cn-tabs--card .cn-tabs__content {\n\tborder: 1px solid var(--color-border);\n\tborder-top: none;\n\tpadding: 12px;\n}\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_createElementVNode","_Fragment","_renderList","_createBlock","_resolveDynamicComponent","_createVNode","_openBlock","_renderSlot"],"mappings":"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"}
1
+ {"version":3,"file":"CnTabs.vue3.js","sources":["../../../../src/components/CnTabs/CnTabs.vue?vue&type=template&id=5c1a056f&lang.js"],"sourcesContent":["<template>\n\t<div class=\"cn-tabs\" :class=\"{ 'cn-tabs--card': card, 'cn-tabs--segmented': variant === 'segmented' }\">\n\t\t<div class=\"cn-tabs__bar\">\n\t\t\t<div\n\t\t\t\tclass=\"cn-tabs__strip\"\n\t\t\t\t:class=\"{ 'cn-tabs__strip--more-start': moreStart, 'cn-tabs__strip--more-end': moreEnd }\">\n\t\t\t\t<div\n\t\t\t\t\tref=\"nav\"\n\t\t\t\t\tclass=\"cn-tabs__nav\"\n\t\t\t\t\t:class=\"{ 'cn-tabs__nav--justified': justified }\"\n\t\t\t\t\trole=\"tablist\"\n\t\t\t\t\t:aria-label=\"ariaLabel || null\"\n\t\t\t\t\t@keydown=\"onNavKeydown\"\n\t\t\t\t\t@wheel=\"onNavWheel\"\n\t\t\t\t\t@scroll=\"measureOverflow\">\n\t\t\t\t\t<button\n\t\t\t\t\t\tv-for=\"tab in visibleTabs\"\n\t\t\t\t\t\t:id=\"tab.tabId\"\n\t\t\t\t\t\t:key=\"tab.uid\"\n\t\t\t\t\t\tref=\"navButtons\"\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\trole=\"tab\"\n\t\t\t\t\t\tclass=\"cn-tabs__nav-item\"\n\t\t\t\t\t\t:class=\"{ 'cn-tabs__nav-item--active': isActive(tab.uid) }\"\n\t\t\t\t\t\t:aria-selected=\"isActive(tab.uid) ? 'true' : 'false'\"\n\t\t\t\t\t\t:aria-controls=\"tab.panelId\"\n\t\t\t\t\t\t:tabindex=\"isActive(tab.uid) ? 0 : -1\"\n\t\t\t\t\t\t:disabled=\"tab.disabled || null\"\n\t\t\t\t\t\t@click=\"tab.onActivate()\">\n\t\t\t\t\t\t<!-- The space before the count is load-bearing: it keeps\n\t\t\t\t\t\t the tab's accessible name \"Documents 5\" rather than\n\t\t\t\t\t\t \"Documents5\". The margin separates the two for the eye\n\t\t\t\t\t\t only, so keep both tags on one line. -->\n\t\t\t\t\t\t<component :is=\"tab.titleRender\" /> <span\n\t\t\t\t\t\t\tv-if=\"hasCount(tab)\"\n\t\t\t\t\t\t\tclass=\"cn-tabs__count\"\n\t\t\t\t\t\t\tdata-testid=\"cn-tabs-count\">{{ tab.count }}</span>\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t\t<!-- Mouse-only affordances: keyboard users already have the arrow\n\t\t\t\t keys, and the tabs themselves stay in the accessibility tree. -->\n\t\t\t\t<button\n\t\t\t\t\tv-if=\"moreStart\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-tabs__scroll cn-tabs__scroll--start\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\t@click=\"scrollStrip(-1)\">\n\t\t\t\t\t<ChevronLeft :size=\"20\" />\n\t\t\t\t</button>\n\t\t\t\t<button\n\t\t\t\t\tv-if=\"moreEnd\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-tabs__scroll cn-tabs__scroll--end\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\t@click=\"scrollStrip(1)\">\n\t\t\t\t\t<ChevronRight :size=\"20\" />\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<!-- The tabs that did not make the strip. OUTSIDE the tablist, like\n\t\t\t `#nav-end`: a menu button nested in it would be announced as\n\t\t\t one more tab. A tab picked here joins the strip while it is\n\t\t\t selected, so the open panel always has a real tab to be\n\t\t\t labelled by. -->\n\t\t\t<div v-if=\"overflowTabs.length > 0\" class=\"cn-tabs__more\">\n\t\t\t\t<NcActions\n\t\t\t\t\t:forceMenu=\"true\"\n\t\t\t\t\t:forceName=\"true\"\n\t\t\t\t\t:menuName=\"moreLabel\"\n\t\t\t\t\t:aria-label=\"moreAriaLabel\"\n\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\tdata-testid=\"cn-tabs-more\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<ChevronDown :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\tv-for=\"tab in overflowTabs\"\n\t\t\t\t\t\t:key=\"tab.uid\"\n\t\t\t\t\t\t:disabled=\"tab.disabled || null\"\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\tdata-testid=\"cn-tabs-more-item\"\n\t\t\t\t\t\t@click=\"onOverflowPick(tab)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<span class=\"cn-tabs__more-spacer\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ overflowLabel(tab) }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</NcActions>\n\t\t\t</div>\n\t\t\t<div v-if=\"$slots['nav-end']\" class=\"cn-tabs__nav-end\">\n\t\t\t\t<!-- @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. -->\n\t\t\t\t<!-- @binding {number} active-index Index of the currently selected tab. -->\n\t\t\t\t<slot name=\"nav-end\" :activeIndex=\"activeIndex\" />\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"cn-tabs__content\" :class=\"contentClass\">\n\t\t\t<!-- @slot The CnTab children. Anything else is rendered into the panel area untouched. -->\n\t\t\t<slot />\n\t\t</div>\n\t</div>\n</template>\n\n<script>\n/**\n * CnTabs — a generic tab strip.\n *\n * Neither `@nextcloud/vue@9` nor this library previously shipped one: the only\n * tab component in either is `NcAppSidebarTab`, which is meaningless outside an\n * `NcAppSidebar`. Apps that needed a plain tab strip in a page, a dialog or a\n * detail view were on `bootstrap-vue@2` — a Vue-2-only package with no Vue 3\n * release (`bootstrap-vue-next` is a different package with a different API) —\n * and pulling a second UI framework in alongside `@nextcloud/vue` cuts against\n * the fleet rule that apps render with Nextcloud components. So zaakafhandelapp\n * wrote a local `Tabs`/`Tab` pair for eight views and opencatalogi and\n * openregister flagged the same gap. This is that pair, lifted and hardened.\n *\n * ```vue\n * <CnTabs aria-label=\"Case details\" justified>\n * <CnTab title=\"Documents\">…</CnTab>\n * <CnTab title=\"Tasks\" :active=\"showTasks\" @click=\"onTasks\">…</CnTab>\n * <CnTab>\n * <template #title><CnIcon name=\"close\" /> Draft</template>\n * …\n * </CnTab>\n * </CnTabs>\n * ```\n *\n * ## Putting controls beside the strip\n *\n * `#nav-end` fills the right-hand end of the tab bar. `CnTabsWidget` uses it to\n * carry the active child's Actions menu, so one menu serves every tab instead\n * of each panel drawing its own header:\n *\n * ```vue\n * <CnTabs aria-label=\"Case details\">\n * <template #nav-end><CnActionsMenu :title=\"activeTitle\" /></template>\n * <CnTab title=\"Notes\">…</CnTab>\n * </CnTabs>\n * ```\n *\n * The slot renders OUTSIDE the `role=\"tablist\"` element on purpose. See the\n * comment on it in the template.\n *\n * ## Migrating from `bootstrap-vue`\n *\n * `<BTabs>`/`<BTab>` map across directly for the subset apps actually used:\n * `content-class`, `justified` and `card` on the strip; `title`, the `#title`\n * slot, `active` and `@click` on a tab. Anything else (`lazy`, `no-fade`,\n * `pills`, `<b-tab disabled>`'s tooltip behaviour) is not reimplemented.\n *\n * ## How registration works\n *\n * Children register themselves on mount and hand back a *render function* for\n * their title rather than a plain string. That is what lets a child's `#title`\n * slot be rendered inside the parent's nav strip, and it keeps a computed title\n * reactive: the parent invokes it inside its own render effect, so a title\n * change re-renders the nav.\n *\n * Mount order is document order for both static children and `v-for`-generated\n * ones, so the nav strip matches the source. A tab that unmounts (a closable\n * tab) hands the selection to its neighbour.\n *\n * ## Accessibility\n *\n * Implements the WAI-ARIA tabs pattern: `role=\"tablist\"` / `role=\"tab\"` /\n * `role=\"tabpanel\"`, `aria-selected`, `aria-controls` ↔ `aria-labelledby`\n * wiring, a roving `tabindex` (only the selected tab is in the tab order), and\n * Left/Right/Home/End keyboard navigation within the strip. Pass `aria-label`\n * (or `ariaLabel`) so screen-reader users hear what the strip is for.\n *\n * ## Counts and the \"More\" menu\n *\n * A `CnTab` with `count` shows that number after its title. A `CnTab` with\n * `overflow` is listed under a \"More\" menu beside the strip instead of in it:\n *\n * ```vue\n * <CnTabs aria-label=\"Case details\">\n * <CnTab title=\"Documents\" :count=\"5\">…</CnTab>\n * <CnTab title=\"Archiving\" overflow>…</CnTab>\n * </CnTabs>\n * ```\n *\n * Picking a tab from the menu selects it and shows it in the strip for as long\n * as it is selected. With no overflow tab the menu does not render, and the\n * strip is exactly what it was before these props existed.\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActions } from '@nextcloud/vue'\nimport { computed, defineComponent, nextTick, onBeforeUnmount, onMounted, onUpdated, provide, reactive, ref, watch } from 'vue'\nimport ChevronDown from 'vue-material-design-icons/ChevronDown.vue'\nimport ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'\nimport ChevronRight from 'vue-material-design-icons/ChevronRight.vue'\nimport { CN_TABS_INJECTION_KEY } from './tabsKey.js'\n\nexport default defineComponent({\n\tname: 'CnTabs',\n\n\tcomponents: {\n\t\tChevronDown,\n\t\tChevronLeft,\n\t\tChevronRight,\n\t\tNcActionButton,\n\t\tNcActions,\n\t},\n\n\tprops: {\n\t\t/** Extra class applied to the panel container (bootstrap-vue's `content-class`). */\n\t\tcontentClass: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Stretch the nav items to fill the strip (bootstrap-vue's `justified`). */\n\t\tjustified: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Card-style chrome (border + padding) around the panel area. */\n\t\tcard: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * How the strip is drawn: `line` (the default: tabs joined to the\n\t\t * panel below) or `segmented` (a compact pill switch, for a view\n\t\t * switch such as \"My work / My team\"). Roles and keyboard behaviour\n\t\t * are the same in both.\n\t\t *\n\t\t * @type {'line'|'segmented'}\n\t\t */\n\t\tvariant: {\n\t\t\ttype: String,\n\t\t\tdefault: 'line',\n\t\t\tvalidator: (v) => ['line', 'segmented'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Accessible name for the tab strip, applied to the `role=\"tablist\"`\n\t\t * element. Screen readers announce it when focus enters the strip.\n\t\t */\n\t\tariaLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Name of the menu that lists the `overflow` tabs. */\n\t\tmoreLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'More'),\n\t\t},\n\t},\n\n\temits: [\n\t\t/** The selected tab changed. Payload: the new active tab's index. */\n\t\t'update:activeIndex',\n\t],\n\n\tsetup(props, { emit }) {\n\t\t// Registered children, in mount order.\n\t\tconst tabs = reactive([])\n\t\tconst activeUid = ref(null)\n\t\tconst navButtons = ref([])\n\t\tconst nav = ref(null)\n\t\t// Whether the selection was chosen (a tab declared `active`, or the\n\t\t// user picked one) rather than defaulted to the first tab registered.\n\t\tlet selectionChosen = false\n\n\t\t/**\n\t\t * Register a child tab. The first child to register wins the initial\n\t\t * selection unless a later one declares itself `active`. Disabled tabs\n\t\t * never take the initial selection.\n\t\t *\n\t\t * @param {object} tab Child descriptor.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction register(tab) {\n\t\t\ttabs.push(tab)\n\t\t\tif (tab.disabled) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (tab.active) {\n\t\t\t\tactiveUid.value = tab.uid\n\t\t\t\tselectionChosen = true\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (activeUid.value === null) {\n\t\t\t\tactiveUid.value = tab.uid\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// A tab under \"More\" only held the default selection because it\n\t\t\t// registered first. The first tab that is actually in the strip\n\t\t\t// takes it over, unless somebody chose.\n\t\t\tif (!selectionChosen && !tab.overflow) {\n\t\t\t\tconst current = tabs.find((candidate) => candidate.uid === activeUid.value)\n\t\t\t\tif (current && current.overflow) {\n\t\t\t\t\tactiveUid.value = tab.uid\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Drop a child that is unmounting, moving the selection to its nearest\n\t\t * remaining neighbour when it was the active one. Closable tabs rely on\n\t\t * this; without it the strip would render with nothing selected.\n\t\t *\n\t\t * @param {number} uid The child's instance uid.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction unregister(uid) {\n\t\t\tconst index = tabs.findIndex((tab) => tab.uid === uid)\n\t\t\tif (index !== -1) {\n\t\t\t\ttabs.splice(index, 1)\n\t\t\t}\n\t\t\tif (activeUid.value === uid) {\n\t\t\t\tconst neighbour = tabs[index] || tabs[index - 1] || tabs[0] || null\n\t\t\t\tactiveUid.value = neighbour ? neighbour.uid : null\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Make a child the visible one.\n\t\t *\n\t\t * @param {number} uid The child's instance uid.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction select(uid) {\n\t\t\tselectionChosen = true\n\t\t\tif (activeUid.value === uid) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tactiveUid.value = uid\n\t\t\temit('update:activeIndex', tabs.findIndex((tab) => tab.uid === uid))\n\t\t}\n\n\t\t/**\n\t\t * Whether a child is the visible one.\n\t\t *\n\t\t * @param {number} uid The child's instance uid.\n\t\t *\n\t\t * @return {boolean} True when this tab's panel should render.\n\t\t */\n\t\tfunction isActive(uid) {\n\t\t\treturn activeUid.value === uid\n\t\t}\n\n\t\tconst activeIndex = computed(() => tabs.findIndex((tab) => tab.uid === activeUid.value))\n\n\t\t/**\n\t\t * The tabs drawn in the strip: every tab that is not `overflow`, plus\n\t\t * an overflow tab for as long as it is the selected one.\n\t\t *\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow\n\t\t */\n\t\tconst visibleTabs = computed(() => tabs.filter((tab) => !tab.overflow || tab.uid === activeUid.value))\n\n\t\t/**\n\t\t * The tabs listed under \"More\": overflow tabs that are not selected.\n\t\t *\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow\n\t\t */\n\t\tconst overflowTabs = computed(() => tabs.filter((tab) => tab.overflow && tab.uid !== activeUid.value))\n\n\t\t/**\n\t\t * Whether a tab declares a count. `0` is a count; null and '' are not.\n\t\t *\n\t\t * @param {object} tab Child descriptor.\n\t\t *\n\t\t * @return {boolean} True when the count renders.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow\n\t\t */\n\t\tfunction hasCount(tab) {\n\t\t\treturn tab.count !== null && tab.count !== undefined && tab.count !== ''\n\t\t}\n\n\t\t/**\n\t\t * A \"More\" entry's text: the tab's title, and its count when it has one.\n\t\t *\n\t\t * @param {object} tab Child descriptor.\n\t\t *\n\t\t * @return {string} The menu label.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow\n\t\t */\n\t\tfunction overflowLabel(tab) {\n\t\t\treturn hasCount(tab) ? `${tab.label} (${tab.count})` : tab.label\n\t\t}\n\n\t\t/**\n\t\t * Select a tab picked from \"More\" and move focus to it in the strip,\n\t\t * so a keyboard user lands on the tab they chose rather than on a menu\n\t\t * button that no longer lists it.\n\t\t *\n\t\t * @param {object} tab Child descriptor.\n\t\t *\n\t\t * @return {void}\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow\n\t\t */\n\t\tfunction onOverflowPick(tab) {\n\t\t\ttab.onActivate()\n\t\t\tnextTick(() => {\n\t\t\t\tconst button = nav.value?.querySelector(`#${tab.tabId}`)\n\t\t\t\tif (button && typeof button.focus === 'function') {\n\t\t\t\t\tbutton.focus()\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\n\t\tconst moreAriaLabel = computed(() => t('nextcloud-vue', 'More tabs'))\n\n\t\t/**\n\t\t * Move the selection and DOM focus to a tab by index, skipping disabled\n\t\t * ones. Focus has to move with the selection: in the WAI-ARIA tabs\n\t\t * pattern only the selected tab is in the tab order, so leaving focus on\n\t\t * a now-`tabindex=\"-1\"` button strands the keyboard user.\n\t\t *\n\t\t * @param {number} index Target index (clamped to the enabled tabs).\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction focusTab(index) {\n\t\t\t// The strip's own tabs only: the arrow keys walk what is drawn, and\n\t\t\t// the \"More\" menu has its own keyboard handling.\n\t\t\tconst enabled = visibleTabs.value.filter((tab) => !tab.disabled)\n\t\t\tif (enabled.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst wrapped = ((index % enabled.length) + enabled.length) % enabled.length\n\t\t\tconst target = enabled[wrapped]\n\t\t\tconst position = visibleTabs.value.indexOf(target)\n\t\t\tselect(target.uid)\n\t\t\tconst button = navButtons.value?.[position]\n\t\t\tif (button && typeof button.focus === 'function') {\n\t\t\t\tbutton.focus()\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Left/Right/Home/End navigation inside the strip.\n\t\t *\n\t\t * @param {KeyboardEvent} event The keydown event.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction onNavKeydown(event) {\n\t\t\tconst enabled = visibleTabs.value.filter((tab) => !tab.disabled)\n\t\t\tconst current = enabled.findIndex((tab) => tab.uid === activeUid.value)\n\t\t\tlet next = null\n\n\t\t\tif (event.key === 'ArrowRight') {\n\t\t\t\tnext = current + 1\n\t\t\t} else if (event.key === 'ArrowLeft') {\n\t\t\t\tnext = current - 1\n\t\t\t} else if (event.key === 'Home') {\n\t\t\t\tnext = 0\n\t\t\t} else if (event.key === 'End') {\n\t\t\t\tnext = enabled.length - 1\n\t\t\t}\n\n\t\t\tif (next === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tfocusTab(next)\n\t\t}\n\n\t\t/**\n\t\t * Scroll the strip so the selected tab is wholly in view.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction revealActiveTab() {\n\t\t\tconst strip = nav.value\n\t\t\tconst button = strip?.querySelector('[role=\"tab\"][aria-selected=\"true\"]')\n\t\t\tif (!strip || !button) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst stripRect = strip.getBoundingClientRect()\n\t\t\tconst rect = button.getBoundingClientRect()\n\t\t\tif (rect.left < stripRect.left) {\n\t\t\t\tstrip.scrollLeft -= stripRect.left - rect.left\n\t\t\t} else if (rect.right > stripRect.right) {\n\t\t\t\tstrip.scrollLeft += rect.right - stripRect.right\n\t\t\t}\n\t\t}\n\n\t\twatch(activeUid, revealActiveTab, { flush: 'post' })\n\n\t\t// Whether tabs are hidden past the start or end edge of the strip.\n\t\tconst moreStart = ref(false)\n\t\tconst moreEnd = ref(false)\n\n\t\t/**\n\t\t * Recompute which edges hide tabs; 1px of tolerance absorbs sub-pixel rounding.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction measureOverflow() {\n\t\t\tconst strip = nav.value\n\t\t\tif (!strip) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tmoreStart.value = strip.scrollLeft > 1\n\t\t\tmoreEnd.value = strip.scrollWidth - strip.clientWidth - strip.scrollLeft > 1\n\t\t}\n\n\t\tlet resizeObserver = null\n\n\t\tonMounted(() => {\n\t\t\tmeasureOverflow()\n\t\t\tif (typeof ResizeObserver === 'undefined' || !nav.value) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tresizeObserver = new ResizeObserver(measureOverflow)\n\t\t\tresizeObserver.observe(nav.value)\n\t\t})\n\n\t\t// A title change or a tab added can tip the strip into overflowing without resizing it.\n\t\tonUpdated(measureOverflow)\n\n\t\tonBeforeUnmount(() => {\n\t\t\tresizeObserver?.disconnect()\n\t\t\tresizeObserver = null\n\t\t})\n\n\t\t/**\n\t\t * Scroll the strip most of a viewport towards one edge.\n\t\t *\n\t\t * @param {number} direction -1 towards the start, 1 towards the end.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction scrollStrip(direction) {\n\t\t\tconst strip = nav.value\n\t\t\tif (!strip) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tstrip.scrollLeft += direction * Math.max(1, Math.round(strip.clientWidth * 0.6))\n\t\t}\n\n\t\t/**\n\t\t * Turn a vertical wheel into a horizontal scroll while the strip overflows.\n\t\t *\n\t\t * @param {WheelEvent} event The wheel event.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction onNavWheel(event) {\n\t\t\tconst strip = event.currentTarget\n\t\t\tif (strip.scrollWidth <= strip.clientWidth || event.deltaX !== 0 || event.deltaY === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tstrip.scrollLeft += event.deltaY\n\t\t}\n\n\t\tprovide(CN_TABS_INJECTION_KEY, { register, unregister, select, isActive })\n\n\t\treturn {\n\t\t\ttabs,\n\t\t\tvisibleTabs,\n\t\t\toverflowTabs,\n\t\t\thasCount,\n\t\t\toverflowLabel,\n\t\t\tonOverflowPick,\n\t\t\tmoreAriaLabel,\n\t\t\tisActive,\n\t\t\tactiveIndex,\n\t\t\tnav,\n\t\t\tnavButtons,\n\t\t\tmoreStart,\n\t\t\tmoreEnd,\n\t\t\tmeasureOverflow,\n\t\t\tscrollStrip,\n\t\t\tonNavKeydown,\n\t\t\tonNavWheel,\n\t\t}\n\t},\n})\n</script>\n\n<style scoped>\n/* The bar owns the rule under the strip so a `#nav-end` surface sits ON the\n line rather than above a line that stops where the last tab does. With no\n nav-end content the nav is the bar's only child and the result is pixel\n identical to the rule living on the nav itself. */\n.cn-tabs__bar {\n\t/* Top-aligned so the `#nav-end` control sits on the tab row, not below it,\n\t should the two sides ever differ in height. */\n\talign-items: flex-start;\n\tborder-bottom: 1px solid var(--color-border);\n\tdisplay: flex;\n\tgap: 8px;\n}\n\n/* The strip holds the scrolling nav and the edge affordances drawn over it. */\n.cn-tabs__strip {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n\tposition: relative;\n}\n\n.cn-tabs__nav {\n\tdisplay: flex;\n\tgap: 4px;\n\t/* One row that scrolls, never a wrap: in a narrow sidebar a nine-tab strip\n\t wrapped into a stack taller than the panel under it. The scrollbar is\n\t hidden because it would sit between the tabs and the bar's rule and break\n\t the open tab's join with its panel; a fade and a chevron on the clipped\n\t edge say there is more, and the strip follows the wheel. */\n\tflex-wrap: nowrap;\n\toverflow-x: auto;\n\toverflow-y: hidden;\n\tscroll-behavior: smooth;\n\tscrollbar-width: none;\n\t/* Overhang the bar's 1px rule so the open tab's own bottom edge, drawn in\n\t the panel's colour, covers it and the tab joins its panel. */\n\tmargin-bottom: -1px;\n}\n\n.cn-tabs__nav::-webkit-scrollbar {\n\tdisplay: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n\t.cn-tabs__nav {\n\t\tscroll-behavior: auto;\n\t}\n}\n\n/* A fade over the clipped edge, only on the side that hides tabs. Stops a\n pixel above the bar's rule so the rule keeps its full colour.\n\n z-index 2 so the fades outrank a lifted tab: CnTabsWidget puts the open tab\n at 1, and a tie goes to DOM order, which would draw that tab over the start\n fade (it precedes the tabs) while the end fade still covered it. */\n.cn-tabs__strip::before,\n.cn-tabs__strip::after {\n\tcontent: '';\n\tdisplay: none;\n\tinset-block: 0 1px;\n\tpointer-events: none;\n\tposition: absolute;\n\twidth: 56px;\n\tz-index: 2;\n}\n\n.cn-tabs__strip::before {\n\tbackground: linear-gradient(to right, var(--color-main-background) 30%, transparent);\n\tinset-inline-start: 0;\n}\n\n.cn-tabs__strip::after {\n\tbackground: linear-gradient(to left, var(--color-main-background) 30%, transparent);\n\tinset-inline-end: 0;\n}\n\n.cn-tabs__strip--more-start::before,\n.cn-tabs__strip--more-end::after {\n\tdisplay: block;\n}\n\n/* Doubled selector for the same reason as `.cn-tabs__nav .cn-tabs__nav-item`:\n Nextcloud's own `button` rule would otherwise give this a border, a fill\n and a margin. */\n.cn-tabs__strip .cn-tabs__scroll {\n\talign-items: center;\n\tbackground: none;\n\tborder: 0;\n\tborder-radius: 0;\n\tcolor: var(--color-main-text);\n\tcursor: pointer;\n\tdisplay: flex;\n\t/* Pushed down a little so the chevron sits level with the tab labels. */\n\tinset-block: 2px 0;\n\tjustify-content: center;\n\tmargin: 0;\n\tmin-height: 0;\n\tpadding: 0;\n\tposition: absolute;\n\twidth: 28px;\n\tz-index: 3;\n}\n\n/* Nextcloud's `button:hover` rule would otherwise fill the chevron. */\n.cn-tabs__strip .cn-tabs__scroll:hover,\n.cn-tabs__strip .cn-tabs__scroll:focus {\n\tbackground: none;\n}\n\n.cn-tabs__scroll--start {\n\tinset-inline-start: 0;\n}\n\n.cn-tabs__scroll--end {\n\tinset-inline-end: 0;\n}\n\n/* The count behind a tab's title. Full-contrast text on the page background,\n so it reads on the idle tab's darker fill and on the open tab alike. */\n.cn-tabs__count {\n\tbackground-color: var(--color-main-background);\n\tborder: 1px solid var(--color-border-dark);\n\tborder-radius: var(--border-radius-pill, 100px);\n\tcolor: var(--color-main-text);\n\tdisplay: inline-block;\n\tfont-size: 0.85em;\n\tline-height: 1.4;\n\tmargin-inline-start: 6px;\n\tmin-width: 1.4em;\n\tpadding: 0 6px;\n\ttext-align: center;\n}\n\n.cn-tabs__more {\n\talign-items: center;\n\tdisplay: flex;\n\tflex: 0 0 auto;\n}\n\n/* Keeps a \"More\" entry's text on the menu's text column without drawing an icon. */\n.cn-tabs__more-spacer {\n\tdisplay: inline-block;\n\twidth: 20px;\n}\n\n.cn-tabs__nav-end {\n\talign-items: center;\n\tdisplay: flex;\n\tflex: 0 0 auto;\n\tgap: 4px;\n\t/* No min-height. This used to hard-code 38px to mirror the old nav item's\n\t vertical box, but the folder-tab restyle changed that box to 33px, and\n\t because the bar is `align-items: flex-start` the taller nav-end then drove\n\t the BAR's height. The bar's bottom rule sank 6px below the tab row, so the\n\t open tab no longer met the panel it is drawn as joined to. Letting the\n\t control size to its own content keeps the rule on the tab row whatever the\n\t nav item's box becomes, so the two cannot drift apart again. */\n\tmin-height: 0;\n}\n\n/* Folder tabs, not underlined labels. An underline is the weakest possible\n affordance on a strip that already carries icons: with nine tabs on a dossiq\n case the row read as a sentence of links, and only the 2px rule said which\n one was open. Giving the inactive tabs their own darker surface makes the\n strip legible as a control at a glance, and the active tab, sharing the\n panel's background and punching a hole in the bar's rule, reads as the sheet\n in front. */\n.cn-tabs__nav-item {\n\tbackground-color: var(--color-background-dark);\n\t/* `--color-border-dark`, not `--color-border`: the latter is the same\n\t #ededed as `--color-background-dark` in the stock light theme, so a tab\n\t drawn with it has no visible edge against its own fill. */\n\tborder: 1px solid var(--color-border-dark);\n\tborder-bottom: none;\n\tborder-radius: var(--border-radius-large, 8px) var(--border-radius-large, 8px) 0 0;\n\t/* Full-contrast label, not `--color-text-maxcontrast`. On the old\n\t transparent strip maxcontrast sat on white at 5.3:1; on this darker\n\t surface the same pair measures 4.55:1, which clears WCAG AA by 0.05 and\n\t would not survive a themed instance shifting either token. A real tab\n\t strip carries its selection in the surface and the weight anyway, so\n\t nothing is lost by giving every label the readable colour. */\n\tcolor: var(--color-main-text);\n\tcursor: pointer;\n\tfont-weight: normal;\n\tpadding: 8px 12px;\n\twhite-space: nowrap;\n\t/* Match the `#nav-end` control's own height. The bar is a flex row, so\n\t whichever side is taller sets the bar's height and therefore where its\n\t bottom rule lands. When the control was the taller one the rule sat below\n\t the tab row and showed as a line under the open tab, breaking the join to\n\t the panel. Sizing both sides the same is what keeps the rule ON the tab\n\t row; `inline-flex` centres the label inside the taller box. */\n\talign-items: center;\n\tbox-sizing: border-box;\n\tdisplay: inline-flex;\n\tmin-height: var(--default-clickable-area, 34px);\n}\n\n/* Nextcloud's server stylesheet gives every plain `button` a 3px bottom margin\n with a selector that outscores the scoped rule above, which floated the tabs\n above the bar's rule inside a real page. The parent class wins on\n specificity rather than on `!important` or source order. */\n.cn-tabs__nav .cn-tabs__nav-item {\n\tmargin-block-end: 0;\n}\n\n.cn-tabs__nav--justified .cn-tabs__nav-item {\n\tflex: 1 1 0;\n}\n\n.cn-tabs__nav-item:hover,\n.cn-tabs__nav-item:focus-visible {\n\tbackground-color: var(--color-background-hover);\n\tcolor: var(--color-main-text);\n}\n\n/* Inside the box: the scrolling strip clips anything drawn outside a tab. */\n.cn-tabs__nav-item:focus-visible {\n\toutline-offset: -2px;\n}\n\n.cn-tabs__nav-item[disabled] {\n\tcursor: default;\n\topacity: 0.5;\n}\n\n.cn-tabs__nav-item[disabled]:hover {\n\tbackground-color: var(--color-background-dark);\n\tcolor: var(--color-main-text);\n}\n\n/* The open tab: the panel's own background, and a bottom border painted in\n that same background so it erases the bar's rule underneath and the tab\n joins the sheet below it. The primary-coloured top edge is what carries the\n selection at a glance, and it survives forced-colours mode, which drops\n background colours but keeps borders. */\n.cn-tabs__nav-item--active {\n\tbackground-color: var(--color-main-background);\n\tborder-bottom: 1px solid var(--color-main-background);\n\tborder-top: 2px solid var(--color-primary-element);\n\tcolor: var(--color-main-text);\n\tfont-weight: bold;\n\t/* Keep the text baseline identical to an inactive tab despite the 1px\n\t thicker top border, so the strip does not jog when the selection moves. */\n\tpadding-top: 7px;\n}\n\n.cn-tabs__content {\n\tpadding-top: 12px;\n}\n\n.cn-tabs--card .cn-tabs__content {\n\tborder: 1px solid var(--color-border);\n\tborder-top: none;\n\tpadding: 12px;\n}\n\n/* Segmented variant: the strip is a pill switch that stands on its own, so\n the bar's rule and the tab-joins-panel treatment are both dropped. */\n.cn-tabs--segmented .cn-tabs__bar {\n\tborder-bottom: none;\n}\n\n.cn-tabs--segmented .cn-tabs__strip {\n\tflex: 0 1 auto;\n}\n\n.cn-tabs--segmented .cn-tabs__nav {\n\tgap: 4px;\n\tmargin-bottom: 0;\n\tpadding: 4px;\n\tborder-radius: var(--border-radius-container, var(--border-radius-large, 10px));\n\tbackground-color: var(--color-background-dark);\n}\n\n.cn-tabs--segmented .cn-tabs__nav-item {\n\tborder: 1px solid transparent;\n\tborder-radius: var(--border-radius-element, var(--border-radius-large, 8px));\n\tbackground-color: transparent;\n\tfont-weight: 600;\n\tpadding: 6px 16px;\n}\n\n.cn-tabs--segmented .cn-tabs__nav-item:hover {\n\tbackground-color: var(--color-background-hover);\n}\n\n/* The open segment is the raised one. Its border survives forced-colours\n mode, which drops the background and the shadow. */\n.cn-tabs--segmented .cn-tabs__nav-item--active {\n\tborder: 1px solid var(--color-border-dark);\n\tbackground-color: var(--color-main-background);\n\tbox-shadow: 0 1px 2px var(--color-box-shadow);\n\tfont-weight: 600;\n\tpadding-top: 6px;\n}\n</style>\n"],"names":["_createElementBlock","_normalizeClass","_createElementVNode","_Fragment","_renderList","_createBlock","_resolveDynamicComponent","_openBlock","_toDisplayString","_createVNode","_renderSlot"],"mappings":"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"}
@@ -4,6 +4,7 @@ import __sfc_main$1 from '../CnDetailWidgetHost/CnDetailWidgetHost.vue.js';
4
4
  import __sfc_main$2 from '../CnIcon/CnIcon.vue.js';
5
5
  import __sfc_main$3 from '../CnTabs/CnTab.vue.js';
6
6
  import __sfc_main$4 from '../CnTabs/CnTabs.vue.js';
7
+ import { resolveTabCount } from '../../utils/detailActionModel.js';
7
8
  import { PANEL_ACTION_SINK } from '../../utils/panelActions.js';
8
9
  import { widgetTitleOf } from '../../utils/widgetDispatch.js';
9
10
  import __sfc_main from '../CnActionsMenu/CnActionsMenu.vue.js';
@@ -140,10 +141,17 @@ var script = {
140
141
  /**
141
142
  * The widget's config: `{ tabs, ariaLabel }`.
142
143
  *
143
- * `tabs[]` entries are `{ widgetId, label?, icon? }`. `label` and `icon`
144
- * fall back to the referenced widget's own title and icon.
144
+ * `tabs[]` entries are `{ widgetId, label?, icon?, count?, countField?, overflow? }`.
145
+ * `label` and `icon` fall back to the referenced widget's own title and
146
+ * icon. `count` is a number shown after the label; `countField` reads
147
+ * it off the record instead (a list counts its items). `overflow: true`
148
+ * lists the tab under "More".
145
149
  *
146
- * @type {{ tabs?: Array<{widgetId: string, label?: string, icon?: string}>, ariaLabel?: string }}
150
+ * `maxVisibleTabs` caps the strip: later tabs go under "More".
151
+ * `hideEmpty: true` moves a tab whose count is 0 there too.
152
+ * `moreLabel` names that menu.
153
+ *
154
+ * @type {{ tabs?: Array<{widgetId: string, label?: string, icon?: string, count?: number, countField?: string, overflow?: boolean}>, ariaLabel?: string, maxVisibleTabs?: number, hideEmpty?: boolean, moreLabel?: string }}
147
155
  */
148
156
  content: {
149
157
  type: Object,
@@ -281,23 +289,56 @@ var script = {
281
289
  /**
282
290
  * The configured tabs, each paired with the widget definition it names.
283
291
  *
284
- * @return {object[]} `{ key, widgetId, label, icon, widget }` per tab.
292
+ * A tab goes under the strip's "More" menu when it says `overflow`, when
293
+ * `content.hideEmpty` is set and its count is 0, or when
294
+ * `content.maxVisibleTabs` places are already taken. `count` is a
295
+ * literal, or read off the record through `countField`.
296
+ *
297
+ * @return {object[]} `{ key, widgetId, label, icon, widget, count, overflow }` per tab.
298
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
285
299
  */
286
300
  resolvedTabs() {
287
301
  const tabs = Array.isArray(this.content?.tabs) ? this.content.tabs : [];
302
+ const max = Number(this.content?.maxVisibleTabs);
303
+ const hideEmpty = this.content?.hideEmpty === true;
304
+ // How many tabs have taken a place in the strip so far. A tab that
305
+ // is already going under "More" does not use one up.
306
+ let placed = 0;
288
307
  return tabs.map((tab, index) => {
289
308
  const widgetId = typeof tab === 'string' ? tab : tab?.widgetId;
290
309
  const widget = this.availableWidgets.find((w) => w && w.id === widgetId) || null;
310
+ const count = resolveTabCount(typeof tab === 'object' ? tab : null, this.objectData);
311
+ let overflow = (tab && tab.overflow === true) || (hideEmpty && count === 0);
312
+ if (!overflow) {
313
+ if (Number.isFinite(max) && max > 0 && placed >= max) {
314
+ overflow = true;
315
+ } else {
316
+ placed += 1;
317
+ }
318
+ }
291
319
  return {
292
320
  key: `${widgetId || 'tab'}-${index}`,
293
321
  widgetId,
294
322
  label: (tab && tab.label) || widgetTitleOf(widget) || widgetId || '',
295
323
  icon: (tab && tab.icon) || widget?.icon || '',
296
324
  widget,
325
+ count,
326
+ overflow,
297
327
  }
298
328
  })
299
329
  },
300
330
 
331
+ /**
332
+ * Name of the menu that holds the tabs that are not in the strip.
333
+ *
334
+ * @return {string|undefined} The configured label, or undefined for the default.
335
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
336
+ */
337
+ moreLabel() {
338
+ const label = this.content?.moreLabel;
339
+ return typeof label === 'string' && label !== '' ? label : undefined
340
+ },
341
+
301
342
  /**
302
343
  * The tab currently showing.
303
344
  *
@@ -336,6 +377,20 @@ var script = {
336
377
  },
337
378
  },
338
379
 
380
+ /**
381
+ * Open on the first tab that is in the strip. Tab 0 is the default, and
382
+ * when tab 0 sits under "More" the strip would open on a tab the author
383
+ * chose to tuck away.
384
+ *
385
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
386
+ */
387
+ created() {
388
+ const first = this.resolvedTabs.findIndex((tab) => !tab.overflow);
389
+ if (first > 0) {
390
+ this.activeIndex = first;
391
+ }
392
+ },
393
+
339
394
  methods: {
340
395
  /**
341
396
  * Re-emit a geo child's save so the surface can reload the record.
@@ -1 +1 @@
1
- {"version":3,"file":"CnTabsWidget.vue2.js","sources":["../../../../src/components/CnTabsWidget/CnTabsWidget.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-tabs-widget\">\n\t\t<CnTabs\n\t\t\t:aria-label=\"stripLabel\"\n\t\t\tclass=\"cn-tabs-widget__tabs\"\n\t\t\t@update:activeIndex=\"onTabChange\">\n\t\t\t<!-- One Actions menu for the whole widget, bound to whichever child\n\t\t\t is showing. This is the point of the component: six tabbed\n\t\t\t widgets used to mean six card headers stacked down the page. -->\n\t\t\t<template #nav-end>\n\t\t\t\t<CnActionsMenu\n\t\t\t\t\t:showRefresh=\"showRefresh\"\n\t\t\t\t\t:showRequestFeature=\"showRequestFeature\"\n\t\t\t\t\t:showDocumentation=\"showDocumentation\"\n\t\t\t\t\t:documentationUrl=\"documentationUrl\"\n\t\t\t\t\t:widgetId=\"activeWidgetId\"\n\t\t\t\t\t:title=\"activeTitle\"\n\t\t\t\t\t:surface=\"`widget:${activeWidgetId}`\"\n\t\t\t\t\trefreshChannel=\"cn:widget:refresh\"\n\t\t\t\t\ttestidBase=\"cn-tabs-widget\">\n\t\t\t\t\t<!-- The open panel's own items, below the built-in trio.\n\t\t\t\t\t A panel draws no header, so these would otherwise have\n\t\t\t\t\t nowhere to go: the data widget's Metadata and full edit\n\t\t\t\t\t dialog vanished outright when the panel stopped drawing\n\t\t\t\t\t a card. They are rendered here rather than rebuilt here\n\t\t\t\t\t because `run` closes over the widget that published it,\n\t\t\t\t\t which is what keeps the dialog's per-widget config and\n\t\t\t\t\t the widget's own save path. See utils/panelActions.js.\n\n\t\t\t\t\t Only the ACTIVE panel's items. `lazy` keeps a visited\n\t\t\t\t\t tab mounted, so several panels publish at once and an\n\t\t\t\t\t unfiltered list would offer actions for a sheet nobody\n\t\t\t\t\t is looking at. -->\n\t\t\t\t\t<template v-if=\"activePanelActions.length\" #action-items>\n\t\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\t\tv-for=\"action in activePanelActions\"\n\t\t\t\t\t\t\t:key=\"action.key\"\n\t\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t\t@click=\"action.run()\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<CnIcon :name=\"action.icon\" :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ action.label }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t</template>\n\t\t\t\t</CnActionsMenu>\n\t\t\t</template>\n\n\t\t\t<CnTab\n\t\t\t\tv-for=\"(entry, index) in resolvedTabs\"\n\t\t\t\t:key=\"entry.key\"\n\t\t\t\t:active=\"index === activeIndex\"\n\t\t\t\tlazy\n\t\t\t\t@click=\"activeIndex = index\">\n\t\t\t\t<template #title>\n\t\t\t\t\t<span class=\"cn-tabs-widget__title\">\n\t\t\t\t\t\t<CnIcon\n\t\t\t\t\t\t\tv-if=\"entry.icon\"\n\t\t\t\t\t\t\t:name=\"entry.icon\"\n\t\t\t\t\t\t\t:size=\"18\"\n\t\t\t\t\t\t\tclass=\"cn-tabs-widget__title-icon\" />\n\t\t\t\t\t\t{{ entry.label }}\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\n\t\t\t\t<CnDetailWidgetHost\n\t\t\t\t\tv-if=\"entry.widget\"\n\t\t\t\t\t:widget=\"entry.widget\"\n\t\t\t\t\tchrome=\"bare\"\n\t\t\t\t\t:objectId=\"objectId\"\n\t\t\t\t\t:object=\"objectData\"\n\t\t\t\t\t:objectType=\"objectType\"\n\t\t\t\t\t:schemaObject=\"schemaObject\"\n\t\t\t\t\t:register=\"register\"\n\t\t\t\t\t:schema=\"schema\"\n\t\t\t\t\t:store=\"store\"\n\t\t\t\t\t:surface=\"surface\"\n\t\t\t\t\t:integrationContext=\"integrationContext\"\n\t\t\t\t\t:cnRegistry=\"cnRegistry\"\n\t\t\t\t\t@geoSaved=\"onGeoSaved\"\n\t\t\t\t\t@openIntegration=\"onOpenIntegration\" />\n\t\t\t\t<NcEmptyContent v-else :name=\"missingLabel(entry)\" />\n\t\t\t</CnTab>\n\t\t</CnTabs>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcEmptyContent } from '@nextcloud/vue'\nimport CnDetailWidgetHost from '../CnDetailWidgetHost/CnDetailWidgetHost.vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport CnTab from '../CnTabs/CnTab.vue'\nimport CnTabs from '../CnTabs/CnTabs.vue'\nimport { PANEL_ACTION_SINK } from '../../utils/panelActions.js'\nimport { widgetTitleOf } from '../../utils/widgetDispatch.js'\nimport { CnActionsMenu } from '../CnActionsMenu/index.js'\n\n/**\n * CnTabsWidget — a widget that holds other widgets, one per tab.\n *\n * A detail page that carries notes, files, related records, sub-records, mail\n * and appointments renders six cards, each with its own header and its own\n * Actions menu, stacked down the page. All six say roughly the same thing about\n * one record, and only one of them is being read at a time. This widget puts\n * them behind a tab strip instead.\n *\n * ## What the tabs take over\n *\n * The strip owns the title and the card. A child renders through\n * `CnDetailWidgetHost` with `chrome=\"bare\"`, so it draws no header and no\n * border of its own, and its content fills the panel exactly as it would have\n * filled its card. The tab label is what the widget's card header used to say.\n *\n * The Actions menu moves OUT of the panels and into the bar, beside the strip\n * rather than inside it, and rebinds to whichever child is showing. So there is\n * one menu, and it always acts on what you are looking at.\n *\n * ## Configuring it\n *\n * `content.tabs[]` names the children and their labels, so a deployment can\n * relabel or reorder tabs, or drop one, without touching code:\n *\n * ```js\n * content: {\n * tabs: [\n * { widgetId: 'case-notes', label: 'Notes', icon: 'NoteTextOutline' },\n * { widgetId: 'case-files', label: 'Files and attachments' },\n * ],\n * }\n * ```\n *\n * `label` and `icon` are optional and fall back to the child widget's own\n * title and icon, so the common case is a list of `widgetId`s.\n *\n * ## Panels are lazy, and stay mounted\n *\n * Each panel is a `CnTab` with `lazy`, so a child only mounts when its tab is\n * first opened. Six eager panels that each fetch on `mounted()` would fire six\n * requests on page load to answer five questions nobody asked. Once opened, a\n * panel stays mounted, so switching back never refetches.\n *\n * ## A named tab whose widget is missing\n *\n * The tab still renders, and its panel says which widget id did not resolve.\n * Dropping the tab would be worse: `content.tabs[]` is hand-authored config,\n * and a typo that silently removes a tab is a typo nobody finds.\n */\nexport default {\n\tname: 'CnTabsWidget',\n\n\tcomponents: {\n\t\tCnActionsMenu,\n\t\tCnDetailWidgetHost,\n\t\tCnIcon,\n\t\tCnTab,\n\t\tCnTabs,\n\t\tNcActionButton,\n\t\tNcEmptyContent,\n\t},\n\n\t/**\n\t * Offer the panels a way to put their own menu items in the strip's menu.\n\t *\n\t * Re-provided one level down by `CnDetailWidgetHost`, which knows the id to\n\t * key by; see utils/panelActions.js for why the items travel up instead of\n\t * being rebuilt here.\n\t *\n\t * @return {object} The provided sink.\n\t */\n\tprovide() {\n\t\treturn {\n\t\t\t[PANEL_ACTION_SINK]: {\n\t\t\t\t/**\n\t\t\t\t * Publish a panel's items, under one SOURCE.\n\t\t\t\t *\n\t\t\t\t * A panel has two possible publishers: the host, for an action\n\t\t\t\t * the host itself would have drawn (the catalog Add), and the\n\t\t\t\t * widget inside it, for its own menu items. Keyed by source so\n\t\t\t\t * the two coexist; a single slot per widget meant whichever\n\t\t\t\t * published last silently replaced the other.\n\t\t\t\t *\n\t\t\t\t * Replaces the map rather than mutating it so the computed that\n\t\t\t\t * reads it re-evaluates on any change.\n\t\t\t\t *\n\t\t\t\t * @param {string} id The publishing widget's id.\n\t\t\t\t * @param {object[]} items Its PanelAction descriptors.\n\t\t\t\t * @param {string} [source] Who is publishing: `host` or `widget`.\n\t\t\t\t * @return {void}\n\t\t\t\t */\n\t\t\t\tset: (id, items, source = 'widget') => {\n\t\t\t\t\t// `activePanelActions` spreads what is stored, so a\n\t\t\t\t\t// non-array becomes one menu item per character.\n\t\t\t\t\tif (!id || !Array.isArray(items)) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tconst forId = { ...(this.panelActionsByWidget[id] || {}), [source]: items }\n\t\t\t\t\tthis.panelActionsByWidget = { ...this.panelActionsByWidget, [id]: forId }\n\t\t\t\t},\n\n\t\t\t\t/**\n\t\t\t\t * Withdraw one source's items, on unmount or when that\n\t\t\t\t * publisher's own menu comes back. The other source's items\n\t\t\t\t * stay.\n\t\t\t\t *\n\t\t\t\t * @param {string} id The publishing widget's id.\n\t\t\t\t * @param {string} [source] Who is withdrawing.\n\t\t\t\t * @return {void}\n\t\t\t\t */\n\t\t\t\tclear: (id, source = 'widget') => {\n\t\t\t\t\tconst forId = this.panelActionsByWidget[id]\n\t\t\t\t\tif (!forId || !(source in forId)) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tconst { [source]: _removed, ...keptSources } = forId\n\t\t\t\t\tif (Object.keys(keptSources).length) {\n\t\t\t\t\t\tthis.panelActionsByWidget = { ...this.panelActionsByWidget, [id]: keptSources }\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tconst { [id]: _gone, ...rest } = this.panelActionsByWidget\n\t\t\t\t\tthis.panelActionsByWidget = rest\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget's config: `{ tabs, ariaLabel }`.\n\t\t *\n\t\t * `tabs[]` entries are `{ widgetId, label?, icon? }`. `label` and `icon`\n\t\t * fall back to the referenced widget's own title and icon.\n\t\t *\n\t\t * @type {{ tabs?: Array<{widgetId: string, label?: string, icon?: string}>, ariaLabel?: string }}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Every widget definition available on the surface, for `content.tabs[]`\n\t\t * to reference by id.\n\t\t *\n\t\t * The surface passes its whole list rather than the resolved children\n\t\t * because a tab may name a widget that does not exist, and this component\n\t\t * has to be able to say so.\n\t\t *\n\t\t * @type {object[]}\n\t\t */\n\t\tavailableWidgets: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** The bound record's id. */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The loaded record, or null while it is still being fetched. */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The resolved object-type slug. */\n\t\tobjectType: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The resolved JSON Schema object, needed by a `data` child. */\n\t\tschemaObject: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** OpenRegister register slug of the surface. */\n\t\tregister: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** OpenRegister schema slug of the surface. */\n\t\tschema: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The effective object store. */\n\t\tstore: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Rendering surface forwarded to integration children (AD-19). */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t},\n\n\t\t/** Object context forwarded to integration children. */\n\t\tintegrationContext: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The consumer's component registry, for custom child widget types. */\n\t\tcnRegistry: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Show the Refresh entry in the hoisted Actions menu. */\n\t\tshowRefresh: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Show the Request-a-feature entry in the hoisted Actions menu. */\n\t\tshowRequestFeature: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Show the Documentation entry in the hoisted Actions menu. */\n\t\tshowDocumentation: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Documentation URL for the hoisted Actions menu. */\n\t\tdocumentationUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\temits: ['geo-saved', 'open-integration'],\n\n\tdata() {\n\t\treturn {\n\t\t\tactiveIndex: 0,\n\t\t\t// Items published by the panels, keyed by widget id. Keyed rather\n\t\t\t// than a flat list because `lazy` keeps a visited tab mounted, so\n\t\t\t// more than one panel publishes at a time.\n\t\t\tpanelActionsByWidget: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The open panel's own menu items, or an empty list.\n\t\t *\n\t\t * @return {object[]} PanelAction descriptors for the active tab.\n\t\t */\n\t\tactivePanelActions() {\n\t\t\tconst forId = this.panelActionsByWidget[this.activeWidgetId]\n\t\t\tif (!forId) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\t// Host first, then the widget's own: the catalog Add is about the\n\t\t\t// panel as a whole, the widget's items about what is in it.\n\t\t\treturn [...(forId.host || []), ...(forId.widget || [])]\n\t\t},\n\n\t\t/**\n\t\t * The configured tabs, each paired with the widget definition it names.\n\t\t *\n\t\t * @return {object[]} `{ key, widgetId, label, icon, widget }` per tab.\n\t\t */\n\t\tresolvedTabs() {\n\t\t\tconst tabs = Array.isArray(this.content?.tabs) ? this.content.tabs : []\n\t\t\treturn tabs.map((tab, index) => {\n\t\t\t\tconst widgetId = typeof tab === 'string' ? tab : tab?.widgetId\n\t\t\t\tconst widget = this.availableWidgets.find((w) => w && w.id === widgetId) || null\n\t\t\t\treturn {\n\t\t\t\t\tkey: `${widgetId || 'tab'}-${index}`,\n\t\t\t\t\twidgetId,\n\t\t\t\t\tlabel: (tab && tab.label) || widgetTitleOf(widget) || widgetId || '',\n\t\t\t\t\ticon: (tab && tab.icon) || widget?.icon || '',\n\t\t\t\t\twidget,\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The tab currently showing.\n\t\t *\n\t\t * @return {object|null} The resolved tab, or null when there are none.\n\t\t */\n\t\tactiveTab() {\n\t\t\treturn this.resolvedTabs[this.activeIndex] || null\n\t\t},\n\n\t\t/**\n\t\t * The active child's id, so the hoisted Refresh reaches THAT child's\n\t\t * fetch over `cn:widget:refresh` rather than a sibling's.\n\t\t *\n\t\t * @return {string} The active widget id.\n\t\t */\n\t\tactiveWidgetId() {\n\t\t\treturn this.activeTab?.widgetId || ''\n\t\t},\n\n\t\t/**\n\t\t * The active child's label, so the Actions menu names what it acts on.\n\t\t *\n\t\t * @return {string} The active tab's label.\n\t\t */\n\t\tactiveTitle() {\n\t\t\treturn this.activeTab?.label || ''\n\t\t},\n\n\t\t/**\n\t\t * Accessible name for the tab strip.\n\t\t *\n\t\t * @return {string} The aria-label.\n\t\t */\n\t\tstripLabel() {\n\t\t\treturn this.content?.ariaLabel || t('nextcloud-vue', 'Details')\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Re-emit a geo child's save so the surface can reload the record.\n\t\t *\n\t\t * @param {object} geo The saved geometry.\n\t\t * @return {void}\n\t\t */\n\t\tonGeoSaved(geo) {\n\t\t\t/**\n\t\t\t * @event geo-saved Re-emitted from a geo child that saved a geometry.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('geo-saved', geo)\n\t\t},\n\n\t\t/**\n\t\t * Re-emit a related child's request to open an integration.\n\t\t *\n\t\t * @param {string} integrationId The integration to open.\n\t\t * @return {void}\n\t\t */\n\t\tonOpenIntegration(integrationId) {\n\t\t\t/**\n\t\t\t * @event open-integration Re-emitted from a related child asking to open an integration.\n\t\t\t * @type {string}\n\t\t\t */\n\t\t\tthis.$emit('open-integration', integrationId)\n\t\t},\n\n\t\t/**\n\t\t * Follow the strip's own selection, so keyboard navigation moves the\n\t\t * hoisted Actions menu too.\n\t\t *\n\t\t * @param {number} index The newly selected tab index.\n\t\t * @return {void}\n\t\t */\n\t\tonTabChange(index) {\n\t\t\tif (typeof index === 'number' && index >= 0) {\n\t\t\t\tthis.activeIndex = index\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Empty-state text for a tab whose widget id resolves to nothing.\n\t\t *\n\t\t * @param {object} entry The resolved tab.\n\t\t * @return {string} The message.\n\t\t */\n\t\tmissingLabel(entry) {\n\t\t\treturn t('nextcloud-vue', 'No widget found for \"{id}\"', { id: entry.widgetId || '' })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/* The panel is the card; the strip is not inside it. There is no title bar\n above the tabs either: the open tab names the panel, so a title row would\n say the same thing twice and cost a row of height on a card that is mostly\n content.\n\n The card chrome used to wrap the strip as well, which drew a border and a\n pair of rounded corners ABOVE the tabs and boxed them in. Folder tabs are\n drawn as the edge of the sheet they open, so a second edge around them\n reads as a header the widget does not have. Border, radius and background\n now live on the panel below, and the strip sits bare on the page. */\n/* The class is doubled on purpose, and the four properties below are RESET\n rather than simply omitted.\n\n Nextcloud serves every enabled app's assets on every page, each app bundles\n this library's compiled CSS, and the Vue scope id is derived from the file\n PATH, so `data-v-1dbd6122` is byte-identical across library versions. An app\n still on an older release therefore ships a rule with exactly this selector\n and the OLD declarations, and it lands on the pages of an app already on the\n new one. Measured on a dossiq case page: three stale copies of the pre-fix\n rule, one from hermiq's `companion.css` (its companion bundle loads on every\n page by design) and two inline from other apps' bundles. Same specificity,\n so source order decided, and the card border came back around the strip.\n\n Dropping a declaration cannot beat a rule that sets it, so `border` and the\n rest are explicitly zeroed. Doubling the class takes this selector to\n (0,3,0) against the stale (0,2,0), which wins on specificity rather than on\n `!important` or on load order this library does not control. The same\n technique, for the same reason, is used on `.cn-tabs__nav .cn-tabs__nav-item`\n in CnTabs to beat Nextcloud's own button margin. */\n.cn-tabs-widget.cn-tabs-widget {\n\tbackground-color: transparent;\n\tborder: 0;\n\tborder-radius: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\theight: 100%;\n\tmin-height: 0;\n\toverflow: visible;\n\tpadding: 0;\n}\n\n/* No inset, so the first tab starts at the panel's own left edge. The 8px\n here was there to clear the card's rounded top corner; that corner is gone\n from the strip now, and the inset it existed for left the first tab floating\n 8px inside the sheet it opens.\n\n The bar's own bottom rule is switched off and redrawn as a pseudo-element\n that stops one corner radius short of the right edge. CnTabs draws its rule\n across the bar's whole width, which is the sheet's top edge everywhere\n except at the far right, where the panel's top-right corner curves away\n below it: the rule ran straight on to the widget's edge and read as a\n border sticking out past a rounded corner (Ruben, 2026-09-13). Ending it\n where the curve begins makes the rule and the curve one continuous edge. */\n.cn-tabs-widget__tabs :deep(.cn-tabs__bar) {\n\tborder-bottom: none;\n\tpadding: 0;\n\tposition: relative;\n}\n\n.cn-tabs-widget__tabs :deep(.cn-tabs__bar::after) {\n\tborder-bottom: 1px solid var(--color-border);\n\tcontent: '';\n\tinset-block-end: 0;\n\tinset-inline: 0 var(--border-radius-large);\n\tpointer-events: none;\n\tposition: absolute;\n}\n\n/* The open tab erases the slice of the rule directly above the panel with its\n own background-coloured bottom edge, which only works if it paints ABOVE the\n pseudo-element: a positioned pseudo-element otherwise paints over every\n in-flow sibling, rule included. */\n.cn-tabs-widget__tabs :deep(.cn-tabs__nav-item--active) {\n\tposition: relative;\n\tz-index: 1;\n}\n\n.cn-tabs-widget__tabs {\n\tdisplay: flex;\n\tflex-direction: column;\n\t/* Grow to the widget's height, so the panel fills the grid cell the\n\t layout gave it. Without this the strip plus the open panel took only\n\t the height of the panel's content, and a cell of nine rows ended in\n\t 130px of nothing under a sheet that stopped short of its own card. */\n\tflex: 1 1 auto;\n\tmin-height: 0;\n}\n\n/* The panel area is the scroll region, so the strip stays put while a long\n child scrolls under it.\n\n Padded on all four sides. The top was once zeroed to protect the join with\n the open tab, but padding sits inside the painted box — only a margin would\n have opened that gap, and a zero top put every panel's content against the\n strip. */\n.cn-tabs-widget__tabs :deep(.cn-tabs__content) {\n\tbackground-color: var(--color-main-background);\n\t/* Three sides only: the bar's rule (redrawn above, stopping at the corner)\n\t is this sheet's top edge, and the open tab erases the slice of it\n\t directly above the panel so the two read as one surface. A border-top\n\t here would put a second line under that tab which the tab cannot paint\n\t over. */\n\tborder: 1px solid var(--color-border);\n\tborder-top: none;\n\t/* Three rounded corners. The top-left stays square because the first tab\n\t is drawn joined to the panel there, and a curve under it would curl the\n\t sheet away from that tab. The top-right has no tab above it: the strip\n\t ends where the last tab ends and the Actions menu floats free, so a\n\t square corner there read as a sheet cut off, not as a tab's edge. */\n\tborder-radius: 0 var(--border-radius-large) var(--border-radius-large) var(--border-radius-large);\n\tflex: 1 1 auto;\n\tmin-height: 0;\n\toverflow: auto;\n\tpadding: 12px;\n}\n\n.cn-tabs-widget__tabs :deep(.cn-tab) {\n\theight: 100%;\n}\n\n.cn-tabs-widget__title {\n\talign-items: center;\n\tdisplay: inline-flex;\n\tgap: 6px;\n}\n\n.cn-tabs-widget__title-icon {\n\tflex: 0 0 auto;\n}\n</style>\n"],"names":["CnActionsMenu","CnDetailWidgetHost","CnIcon","CnTab","CnTabs","t"],"mappings":"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"}
1
+ {"version":3,"file":"CnTabsWidget.vue2.js","sources":["../../../../src/components/CnTabsWidget/CnTabsWidget.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-tabs-widget\">\n\t\t<CnTabs\n\t\t\t:aria-label=\"stripLabel\"\n\t\t\t:moreLabel=\"moreLabel\"\n\t\t\tclass=\"cn-tabs-widget__tabs\"\n\t\t\t@update:activeIndex=\"onTabChange\">\n\t\t\t<!-- One Actions menu for the whole widget, bound to whichever child\n\t\t\t is showing. This is the point of the component: six tabbed\n\t\t\t widgets used to mean six card headers stacked down the page. -->\n\t\t\t<template #nav-end>\n\t\t\t\t<CnActionsMenu\n\t\t\t\t\t:showRefresh=\"showRefresh\"\n\t\t\t\t\t:showRequestFeature=\"showRequestFeature\"\n\t\t\t\t\t:showDocumentation=\"showDocumentation\"\n\t\t\t\t\t:documentationUrl=\"documentationUrl\"\n\t\t\t\t\t:widgetId=\"activeWidgetId\"\n\t\t\t\t\t:title=\"activeTitle\"\n\t\t\t\t\t:surface=\"`widget:${activeWidgetId}`\"\n\t\t\t\t\trefreshChannel=\"cn:widget:refresh\"\n\t\t\t\t\ttestidBase=\"cn-tabs-widget\">\n\t\t\t\t\t<!-- The open panel's own items, below the built-in trio.\n\t\t\t\t\t A panel draws no header, so these would otherwise have\n\t\t\t\t\t nowhere to go: the data widget's Metadata and full edit\n\t\t\t\t\t dialog vanished outright when the panel stopped drawing\n\t\t\t\t\t a card. They are rendered here rather than rebuilt here\n\t\t\t\t\t because `run` closes over the widget that published it,\n\t\t\t\t\t which is what keeps the dialog's per-widget config and\n\t\t\t\t\t the widget's own save path. See utils/panelActions.js.\n\n\t\t\t\t\t Only the ACTIVE panel's items. `lazy` keeps a visited\n\t\t\t\t\t tab mounted, so several panels publish at once and an\n\t\t\t\t\t unfiltered list would offer actions for a sheet nobody\n\t\t\t\t\t is looking at. -->\n\t\t\t\t\t<template v-if=\"activePanelActions.length\" #action-items>\n\t\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\t\tv-for=\"action in activePanelActions\"\n\t\t\t\t\t\t\t:key=\"action.key\"\n\t\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\t\t@click=\"action.run()\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<CnIcon :name=\"action.icon\" :size=\"20\" />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t{{ action.label }}\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t</template>\n\t\t\t\t</CnActionsMenu>\n\t\t\t</template>\n\n\t\t\t<CnTab\n\t\t\t\tv-for=\"(entry, index) in resolvedTabs\"\n\t\t\t\t:key=\"entry.key\"\n\t\t\t\t:active=\"index === activeIndex\"\n\t\t\t\t:title=\"entry.label\"\n\t\t\t\t:count=\"entry.count\"\n\t\t\t\t:overflow=\"entry.overflow\"\n\t\t\t\tlazy\n\t\t\t\t@click=\"activeIndex = index\">\n\t\t\t\t<template #title>\n\t\t\t\t\t<span class=\"cn-tabs-widget__title\">\n\t\t\t\t\t\t<CnIcon\n\t\t\t\t\t\t\tv-if=\"entry.icon\"\n\t\t\t\t\t\t\t:name=\"entry.icon\"\n\t\t\t\t\t\t\t:size=\"18\"\n\t\t\t\t\t\t\tclass=\"cn-tabs-widget__title-icon\" />\n\t\t\t\t\t\t{{ entry.label }}\n\t\t\t\t\t</span>\n\t\t\t\t</template>\n\n\t\t\t\t<CnDetailWidgetHost\n\t\t\t\t\tv-if=\"entry.widget\"\n\t\t\t\t\t:widget=\"entry.widget\"\n\t\t\t\t\tchrome=\"bare\"\n\t\t\t\t\t:objectId=\"objectId\"\n\t\t\t\t\t:object=\"objectData\"\n\t\t\t\t\t:objectType=\"objectType\"\n\t\t\t\t\t:schemaObject=\"schemaObject\"\n\t\t\t\t\t:register=\"register\"\n\t\t\t\t\t:schema=\"schema\"\n\t\t\t\t\t:store=\"store\"\n\t\t\t\t\t:surface=\"surface\"\n\t\t\t\t\t:integrationContext=\"integrationContext\"\n\t\t\t\t\t:cnRegistry=\"cnRegistry\"\n\t\t\t\t\t@geoSaved=\"onGeoSaved\"\n\t\t\t\t\t@openIntegration=\"onOpenIntegration\" />\n\t\t\t\t<NcEmptyContent v-else :name=\"missingLabel(entry)\" />\n\t\t\t</CnTab>\n\t\t</CnTabs>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcEmptyContent } from '@nextcloud/vue'\nimport CnDetailWidgetHost from '../CnDetailWidgetHost/CnDetailWidgetHost.vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport CnTab from '../CnTabs/CnTab.vue'\nimport CnTabs from '../CnTabs/CnTabs.vue'\nimport { resolveTabCount } from '../../utils/detailActionModel.js'\nimport { PANEL_ACTION_SINK } from '../../utils/panelActions.js'\nimport { widgetTitleOf } from '../../utils/widgetDispatch.js'\nimport { CnActionsMenu } from '../CnActionsMenu/index.js'\n\n/**\n * CnTabsWidget — a widget that holds other widgets, one per tab.\n *\n * A detail page that carries notes, files, related records, sub-records, mail\n * and appointments renders six cards, each with its own header and its own\n * Actions menu, stacked down the page. All six say roughly the same thing about\n * one record, and only one of them is being read at a time. This widget puts\n * them behind a tab strip instead.\n *\n * ## What the tabs take over\n *\n * The strip owns the title and the card. A child renders through\n * `CnDetailWidgetHost` with `chrome=\"bare\"`, so it draws no header and no\n * border of its own, and its content fills the panel exactly as it would have\n * filled its card. The tab label is what the widget's card header used to say.\n *\n * The Actions menu moves OUT of the panels and into the bar, beside the strip\n * rather than inside it, and rebinds to whichever child is showing. So there is\n * one menu, and it always acts on what you are looking at.\n *\n * ## Configuring it\n *\n * `content.tabs[]` names the children and their labels, so a deployment can\n * relabel or reorder tabs, or drop one, without touching code:\n *\n * ```js\n * content: {\n * tabs: [\n * { widgetId: 'case-notes', label: 'Notes', icon: 'NoteTextOutline' },\n * { widgetId: 'case-files', label: 'Files and attachments' },\n * ],\n * }\n * ```\n *\n * `label` and `icon` are optional and fall back to the child widget's own\n * title and icon, so the common case is a list of `widgetId`s.\n *\n * ## Panels are lazy, and stay mounted\n *\n * Each panel is a `CnTab` with `lazy`, so a child only mounts when its tab is\n * first opened. Six eager panels that each fetch on `mounted()` would fire six\n * requests on page load to answer five questions nobody asked. Once opened, a\n * panel stays mounted, so switching back never refetches.\n *\n * ## A named tab whose widget is missing\n *\n * The tab still renders, and its panel says which widget id did not resolve.\n * Dropping the tab would be worse: `content.tabs[]` is hand-authored config,\n * and a typo that silently removes a tab is a typo nobody finds.\n */\nexport default {\n\tname: 'CnTabsWidget',\n\n\tcomponents: {\n\t\tCnActionsMenu,\n\t\tCnDetailWidgetHost,\n\t\tCnIcon,\n\t\tCnTab,\n\t\tCnTabs,\n\t\tNcActionButton,\n\t\tNcEmptyContent,\n\t},\n\n\t/**\n\t * Offer the panels a way to put their own menu items in the strip's menu.\n\t *\n\t * Re-provided one level down by `CnDetailWidgetHost`, which knows the id to\n\t * key by; see utils/panelActions.js for why the items travel up instead of\n\t * being rebuilt here.\n\t *\n\t * @return {object} The provided sink.\n\t */\n\tprovide() {\n\t\treturn {\n\t\t\t[PANEL_ACTION_SINK]: {\n\t\t\t\t/**\n\t\t\t\t * Publish a panel's items, under one SOURCE.\n\t\t\t\t *\n\t\t\t\t * A panel has two possible publishers: the host, for an action\n\t\t\t\t * the host itself would have drawn (the catalog Add), and the\n\t\t\t\t * widget inside it, for its own menu items. Keyed by source so\n\t\t\t\t * the two coexist; a single slot per widget meant whichever\n\t\t\t\t * published last silently replaced the other.\n\t\t\t\t *\n\t\t\t\t * Replaces the map rather than mutating it so the computed that\n\t\t\t\t * reads it re-evaluates on any change.\n\t\t\t\t *\n\t\t\t\t * @param {string} id The publishing widget's id.\n\t\t\t\t * @param {object[]} items Its PanelAction descriptors.\n\t\t\t\t * @param {string} [source] Who is publishing: `host` or `widget`.\n\t\t\t\t * @return {void}\n\t\t\t\t */\n\t\t\t\tset: (id, items, source = 'widget') => {\n\t\t\t\t\t// `activePanelActions` spreads what is stored, so a\n\t\t\t\t\t// non-array becomes one menu item per character.\n\t\t\t\t\tif (!id || !Array.isArray(items)) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tconst forId = { ...(this.panelActionsByWidget[id] || {}), [source]: items }\n\t\t\t\t\tthis.panelActionsByWidget = { ...this.panelActionsByWidget, [id]: forId }\n\t\t\t\t},\n\n\t\t\t\t/**\n\t\t\t\t * Withdraw one source's items, on unmount or when that\n\t\t\t\t * publisher's own menu comes back. The other source's items\n\t\t\t\t * stay.\n\t\t\t\t *\n\t\t\t\t * @param {string} id The publishing widget's id.\n\t\t\t\t * @param {string} [source] Who is withdrawing.\n\t\t\t\t * @return {void}\n\t\t\t\t */\n\t\t\t\tclear: (id, source = 'widget') => {\n\t\t\t\t\tconst forId = this.panelActionsByWidget[id]\n\t\t\t\t\tif (!forId || !(source in forId)) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tconst { [source]: _removed, ...keptSources } = forId\n\t\t\t\t\tif (Object.keys(keptSources).length) {\n\t\t\t\t\t\tthis.panelActionsByWidget = { ...this.panelActionsByWidget, [id]: keptSources }\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tconst { [id]: _gone, ...rest } = this.panelActionsByWidget\n\t\t\t\t\tthis.panelActionsByWidget = rest\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget's config: `{ tabs, ariaLabel }`.\n\t\t *\n\t\t * `tabs[]` entries are `{ widgetId, label?, icon?, count?, countField?, overflow? }`.\n\t\t * `label` and `icon` fall back to the referenced widget's own title and\n\t\t * icon. `count` is a number shown after the label; `countField` reads\n\t\t * it off the record instead (a list counts its items). `overflow: true`\n\t\t * lists the tab under \"More\".\n\t\t *\n\t\t * `maxVisibleTabs` caps the strip: later tabs go under \"More\".\n\t\t * `hideEmpty: true` moves a tab whose count is 0 there too.\n\t\t * `moreLabel` names that menu.\n\t\t *\n\t\t * @type {{ tabs?: Array<{widgetId: string, label?: string, icon?: string, count?: number, countField?: string, overflow?: boolean}>, ariaLabel?: string, maxVisibleTabs?: number, hideEmpty?: boolean, moreLabel?: string }}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Every widget definition available on the surface, for `content.tabs[]`\n\t\t * to reference by id.\n\t\t *\n\t\t * The surface passes its whole list rather than the resolved children\n\t\t * because a tab may name a widget that does not exist, and this component\n\t\t * has to be able to say so.\n\t\t *\n\t\t * @type {object[]}\n\t\t */\n\t\tavailableWidgets: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** The bound record's id. */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The loaded record, or null while it is still being fetched. */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The resolved object-type slug. */\n\t\tobjectType: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The resolved JSON Schema object, needed by a `data` child. */\n\t\tschemaObject: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** OpenRegister register slug of the surface. */\n\t\tregister: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** OpenRegister schema slug of the surface. */\n\t\tschema: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The effective object store. */\n\t\tstore: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Rendering surface forwarded to integration children (AD-19). */\n\t\tsurface: {\n\t\t\ttype: String,\n\t\t\tdefault: 'detail-page',\n\t\t},\n\n\t\t/** Object context forwarded to integration children. */\n\t\tintegrationContext: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The consumer's component registry, for custom child widget types. */\n\t\tcnRegistry: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Show the Refresh entry in the hoisted Actions menu. */\n\t\tshowRefresh: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Show the Request-a-feature entry in the hoisted Actions menu. */\n\t\tshowRequestFeature: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Show the Documentation entry in the hoisted Actions menu. */\n\t\tshowDocumentation: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/** Documentation URL for the hoisted Actions menu. */\n\t\tdocumentationUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\temits: ['geo-saved', 'open-integration'],\n\n\tdata() {\n\t\treturn {\n\t\t\tactiveIndex: 0,\n\t\t\t// Items published by the panels, keyed by widget id. Keyed rather\n\t\t\t// than a flat list because `lazy` keeps a visited tab mounted, so\n\t\t\t// more than one panel publishes at a time.\n\t\t\tpanelActionsByWidget: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The open panel's own menu items, or an empty list.\n\t\t *\n\t\t * @return {object[]} PanelAction descriptors for the active tab.\n\t\t */\n\t\tactivePanelActions() {\n\t\t\tconst forId = this.panelActionsByWidget[this.activeWidgetId]\n\t\t\tif (!forId) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\t// Host first, then the widget's own: the catalog Add is about the\n\t\t\t// panel as a whole, the widget's items about what is in it.\n\t\t\treturn [...(forId.host || []), ...(forId.widget || [])]\n\t\t},\n\n\t\t/**\n\t\t * The configured tabs, each paired with the widget definition it names.\n\t\t *\n\t\t * A tab goes under the strip's \"More\" menu when it says `overflow`, when\n\t\t * `content.hideEmpty` is set and its count is 0, or when\n\t\t * `content.maxVisibleTabs` places are already taken. `count` is a\n\t\t * literal, or read off the record through `countField`.\n\t\t *\n\t\t * @return {object[]} `{ key, widgetId, label, icon, widget, count, overflow }` per tab.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow\n\t\t */\n\t\tresolvedTabs() {\n\t\t\tconst tabs = Array.isArray(this.content?.tabs) ? this.content.tabs : []\n\t\t\tconst max = Number(this.content?.maxVisibleTabs)\n\t\t\tconst hideEmpty = this.content?.hideEmpty === true\n\t\t\t// How many tabs have taken a place in the strip so far. A tab that\n\t\t\t// is already going under \"More\" does not use one up.\n\t\t\tlet placed = 0\n\t\t\treturn tabs.map((tab, index) => {\n\t\t\t\tconst widgetId = typeof tab === 'string' ? tab : tab?.widgetId\n\t\t\t\tconst widget = this.availableWidgets.find((w) => w && w.id === widgetId) || null\n\t\t\t\tconst count = resolveTabCount(typeof tab === 'object' ? tab : null, this.objectData)\n\t\t\t\tlet overflow = (tab && tab.overflow === true) || (hideEmpty && count === 0)\n\t\t\t\tif (!overflow) {\n\t\t\t\t\tif (Number.isFinite(max) && max > 0 && placed >= max) {\n\t\t\t\t\t\toverflow = true\n\t\t\t\t\t} else {\n\t\t\t\t\t\tplaced += 1\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tkey: `${widgetId || 'tab'}-${index}`,\n\t\t\t\t\twidgetId,\n\t\t\t\t\tlabel: (tab && tab.label) || widgetTitleOf(widget) || widgetId || '',\n\t\t\t\t\ticon: (tab && tab.icon) || widget?.icon || '',\n\t\t\t\t\twidget,\n\t\t\t\t\tcount,\n\t\t\t\t\toverflow,\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Name of the menu that holds the tabs that are not in the strip.\n\t\t *\n\t\t * @return {string|undefined} The configured label, or undefined for the default.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow\n\t\t */\n\t\tmoreLabel() {\n\t\t\tconst label = this.content?.moreLabel\n\t\t\treturn typeof label === 'string' && label !== '' ? label : undefined\n\t\t},\n\n\t\t/**\n\t\t * The tab currently showing.\n\t\t *\n\t\t * @return {object|null} The resolved tab, or null when there are none.\n\t\t */\n\t\tactiveTab() {\n\t\t\treturn this.resolvedTabs[this.activeIndex] || null\n\t\t},\n\n\t\t/**\n\t\t * The active child's id, so the hoisted Refresh reaches THAT child's\n\t\t * fetch over `cn:widget:refresh` rather than a sibling's.\n\t\t *\n\t\t * @return {string} The active widget id.\n\t\t */\n\t\tactiveWidgetId() {\n\t\t\treturn this.activeTab?.widgetId || ''\n\t\t},\n\n\t\t/**\n\t\t * The active child's label, so the Actions menu names what it acts on.\n\t\t *\n\t\t * @return {string} The active tab's label.\n\t\t */\n\t\tactiveTitle() {\n\t\t\treturn this.activeTab?.label || ''\n\t\t},\n\n\t\t/**\n\t\t * Accessible name for the tab strip.\n\t\t *\n\t\t * @return {string} The aria-label.\n\t\t */\n\t\tstripLabel() {\n\t\t\treturn this.content?.ariaLabel || t('nextcloud-vue', 'Details')\n\t\t},\n\t},\n\n\t/**\n\t * Open on the first tab that is in the strip. Tab 0 is the default, and\n\t * when tab 0 sits under \"More\" the strip would open on a tab the author\n\t * chose to tuck away.\n\t *\n\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow\n\t */\n\tcreated() {\n\t\tconst first = this.resolvedTabs.findIndex((tab) => !tab.overflow)\n\t\tif (first > 0) {\n\t\t\tthis.activeIndex = first\n\t\t}\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Re-emit a geo child's save so the surface can reload the record.\n\t\t *\n\t\t * @param {object} geo The saved geometry.\n\t\t * @return {void}\n\t\t */\n\t\tonGeoSaved(geo) {\n\t\t\t/**\n\t\t\t * @event geo-saved Re-emitted from a geo child that saved a geometry.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('geo-saved', geo)\n\t\t},\n\n\t\t/**\n\t\t * Re-emit a related child's request to open an integration.\n\t\t *\n\t\t * @param {string} integrationId The integration to open.\n\t\t * @return {void}\n\t\t */\n\t\tonOpenIntegration(integrationId) {\n\t\t\t/**\n\t\t\t * @event open-integration Re-emitted from a related child asking to open an integration.\n\t\t\t * @type {string}\n\t\t\t */\n\t\t\tthis.$emit('open-integration', integrationId)\n\t\t},\n\n\t\t/**\n\t\t * Follow the strip's own selection, so keyboard navigation moves the\n\t\t * hoisted Actions menu too.\n\t\t *\n\t\t * @param {number} index The newly selected tab index.\n\t\t * @return {void}\n\t\t */\n\t\tonTabChange(index) {\n\t\t\tif (typeof index === 'number' && index >= 0) {\n\t\t\t\tthis.activeIndex = index\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Empty-state text for a tab whose widget id resolves to nothing.\n\t\t *\n\t\t * @param {object} entry The resolved tab.\n\t\t * @return {string} The message.\n\t\t */\n\t\tmissingLabel(entry) {\n\t\t\treturn t('nextcloud-vue', 'No widget found for \"{id}\"', { id: entry.widgetId || '' })\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/* The panel is the card; the strip is not inside it. There is no title bar\n above the tabs either: the open tab names the panel, so a title row would\n say the same thing twice and cost a row of height on a card that is mostly\n content.\n\n The card chrome used to wrap the strip as well, which drew a border and a\n pair of rounded corners ABOVE the tabs and boxed them in. Folder tabs are\n drawn as the edge of the sheet they open, so a second edge around them\n reads as a header the widget does not have. Border, radius and background\n now live on the panel below, and the strip sits bare on the page. */\n/* The class is doubled on purpose, and the four properties below are RESET\n rather than simply omitted.\n\n Nextcloud serves every enabled app's assets on every page, each app bundles\n this library's compiled CSS, and the Vue scope id is derived from the file\n PATH, so `data-v-1dbd6122` is byte-identical across library versions. An app\n still on an older release therefore ships a rule with exactly this selector\n and the OLD declarations, and it lands on the pages of an app already on the\n new one. Measured on a dossiq case page: three stale copies of the pre-fix\n rule, one from hermiq's `companion.css` (its companion bundle loads on every\n page by design) and two inline from other apps' bundles. Same specificity,\n so source order decided, and the card border came back around the strip.\n\n Dropping a declaration cannot beat a rule that sets it, so `border` and the\n rest are explicitly zeroed. Doubling the class takes this selector to\n (0,3,0) against the stale (0,2,0), which wins on specificity rather than on\n `!important` or on load order this library does not control. The same\n technique, for the same reason, is used on `.cn-tabs__nav .cn-tabs__nav-item`\n in CnTabs to beat Nextcloud's own button margin. */\n.cn-tabs-widget.cn-tabs-widget {\n\tbackground-color: transparent;\n\tborder: 0;\n\tborder-radius: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\theight: 100%;\n\tmin-height: 0;\n\toverflow: visible;\n\tpadding: 0;\n}\n\n/* No inset, so the first tab starts at the panel's own left edge. The 8px\n here was there to clear the card's rounded top corner; that corner is gone\n from the strip now, and the inset it existed for left the first tab floating\n 8px inside the sheet it opens.\n\n The bar's own bottom rule is switched off and redrawn as a pseudo-element\n that stops one corner radius short of the right edge. CnTabs draws its rule\n across the bar's whole width, which is the sheet's top edge everywhere\n except at the far right, where the panel's top-right corner curves away\n below it: the rule ran straight on to the widget's edge and read as a\n border sticking out past a rounded corner (Ruben, 2026-09-13). Ending it\n where the curve begins makes the rule and the curve one continuous edge. */\n.cn-tabs-widget__tabs :deep(.cn-tabs__bar) {\n\tborder-bottom: none;\n\tpadding: 0;\n\tposition: relative;\n}\n\n.cn-tabs-widget__tabs :deep(.cn-tabs__bar::after) {\n\tborder-bottom: 1px solid var(--color-border);\n\tcontent: '';\n\tinset-block-end: 0;\n\tinset-inline: 0 var(--border-radius-large);\n\tpointer-events: none;\n\tposition: absolute;\n}\n\n/* The open tab erases the slice of the rule directly above the panel with its\n own background-coloured bottom edge, which only works if it paints ABOVE the\n pseudo-element: a positioned pseudo-element otherwise paints over every\n in-flow sibling, rule included. */\n.cn-tabs-widget__tabs :deep(.cn-tabs__nav-item--active) {\n\tposition: relative;\n\tz-index: 1;\n}\n\n.cn-tabs-widget__tabs {\n\tdisplay: flex;\n\tflex-direction: column;\n\t/* Grow to the widget's height, so the panel fills the grid cell the\n\t layout gave it. Without this the strip plus the open panel took only\n\t the height of the panel's content, and a cell of nine rows ended in\n\t 130px of nothing under a sheet that stopped short of its own card. */\n\tflex: 1 1 auto;\n\tmin-height: 0;\n}\n\n/* The panel area is the scroll region, so the strip stays put while a long\n child scrolls under it.\n\n Padded on all four sides. The top was once zeroed to protect the join with\n the open tab, but padding sits inside the painted box — only a margin would\n have opened that gap, and a zero top put every panel's content against the\n strip. */\n.cn-tabs-widget__tabs :deep(.cn-tabs__content) {\n\tbackground-color: var(--color-main-background);\n\t/* Three sides only: the bar's rule (redrawn above, stopping at the corner)\n\t is this sheet's top edge, and the open tab erases the slice of it\n\t directly above the panel so the two read as one surface. A border-top\n\t here would put a second line under that tab which the tab cannot paint\n\t over. */\n\tborder: 1px solid var(--color-border);\n\tborder-top: none;\n\t/* Three rounded corners. The top-left stays square because the first tab\n\t is drawn joined to the panel there, and a curve under it would curl the\n\t sheet away from that tab. The top-right has no tab above it: the strip\n\t ends where the last tab ends and the Actions menu floats free, so a\n\t square corner there read as a sheet cut off, not as a tab's edge. */\n\tborder-radius: 0 var(--border-radius-large) var(--border-radius-large) var(--border-radius-large);\n\tflex: 1 1 auto;\n\tmin-height: 0;\n\toverflow: auto;\n\tpadding: 12px;\n}\n\n.cn-tabs-widget__tabs :deep(.cn-tab) {\n\theight: 100%;\n}\n\n.cn-tabs-widget__title {\n\talign-items: center;\n\tdisplay: inline-flex;\n\tgap: 6px;\n}\n\n.cn-tabs-widget__title-icon {\n\tflex: 0 0 auto;\n}\n</style>\n"],"names":["CnActionsMenu","CnDetailWidgetHost","CnIcon","CnTab","CnTabs","t"],"mappings":"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"}
@@ -15,6 +15,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
15
15
  return (openBlock(), createElementBlock("div", _hoisted_1, [
16
16
  createVNode(_component_CnTabs, {
17
17
  "aria-label": $options.stripLabel,
18
+ moreLabel: $options.moreLabel,
18
19
  class: "cn-tabs-widget__tabs",
19
20
  "onUpdate:activeIndex": $options.onTabChange
20
21
  }, {
@@ -63,6 +64,9 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
63
64
  return (openBlock(), createBlock(_component_CnTab, {
64
65
  key: entry.key,
65
66
  active: index === $data.activeIndex,
67
+ title: entry.label,
68
+ count: entry.count,
69
+ overflow: entry.overflow,
66
70
  lazy: "",
67
71
  onClick: $event => ($data.activeIndex = index)
68
72
  }, {
@@ -104,11 +108,11 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
104
108
  }, null, 8 /* PROPS */, ["name"]))
105
109
  ]),
106
110
  _: 2 /* DYNAMIC */
107
- }, 1032 /* PROPS, DYNAMIC_SLOTS */, ["active", "onClick"]))
111
+ }, 1032 /* PROPS, DYNAMIC_SLOTS */, ["active", "title", "count", "overflow", "onClick"]))
108
112
  }), 128 /* KEYED_FRAGMENT */))
109
113
  ]),
110
114
  _: 1 /* STABLE */
111
- }, 8 /* PROPS */, ["aria-label", "onUpdate:activeIndex"])
115
+ }, 8 /* PROPS */, ["aria-label", "moreLabel", "onUpdate:activeIndex"])
112
116
  ]))
113
117
  }
114
118