@conduction/nextcloud-vue 2.59.0 → 2.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js +14 -1
  2. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
  4. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +80 -1
  5. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  6. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +46 -4
  7. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  8. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +232 -10
  9. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  10. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +79 -25
  11. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  12. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue2.js +42 -3
  13. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue2.js.map +1 -1
  14. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue3.js +39 -6
  15. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue3.js.map +1 -1
  16. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +46 -0
  17. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  18. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +11 -4
  19. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  20. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue.js +10 -0
  21. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue.js.map +1 -0
  22. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js +39 -0
  23. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js.map +1 -0
  24. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js +16 -0
  25. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js.map +1 -0
  26. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +117 -1
  27. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  28. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +147 -89
  29. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  30. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +10 -0
  31. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js.map +1 -0
  32. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +259 -0
  33. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -0
  34. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +131 -0
  35. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -0
  36. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +10 -0
  37. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js.map +1 -0
  38. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +116 -0
  39. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -0
  40. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +24 -0
  41. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -0
  42. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +10 -2
  43. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  44. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  45. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +503 -28
  46. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  47. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +303 -144
  48. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  49. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js +10 -0
  50. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js.map +1 -0
  51. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js +237 -0
  52. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js.map +1 -0
  53. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js +120 -0
  54. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js.map +1 -0
  55. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js +10 -0
  56. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js.map +1 -0
  57. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +77 -0
  58. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -0
  59. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js +27 -0
  60. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -0
  61. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +171 -12
  62. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
  63. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +15 -7
  64. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
  65. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js +37 -3
  66. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js.map +1 -1
  67. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js +44 -16
  68. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js.map +1 -1
  69. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +132 -3
  70. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  71. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +6 -3
  72. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  73. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue.js +10 -0
  74. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue.js.map +1 -0
  75. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js +316 -0
  76. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js.map +1 -0
  77. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js +105 -0
  78. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js.map +1 -0
  79. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js +10 -0
  80. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js.map +1 -0
  81. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js +150 -0
  82. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js.map +1 -0
  83. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js +106 -0
  84. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js.map +1 -0
  85. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js +9 -0
  86. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js.map +1 -0
  87. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +47 -0
  88. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -0
  89. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js +17 -0
  90. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js.map +1 -0
  91. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +49 -0
  92. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  93. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  94. package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js +6 -0
  95. package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js.map +1 -1
  96. package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue3.js.map +1 -1
  97. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js +46 -0
  98. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js.map +1 -1
  99. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js +15 -3
  100. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js.map +1 -1
  101. package/dist/esm/components/CnPageRenderer/pageTypes.js +1 -0
  102. package/dist/esm/components/CnPageRenderer/pageTypes.js.map +1 -1
  103. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +37 -1
  104. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
  105. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +29 -13
  106. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
  107. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js +50 -3
  108. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
  109. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
  110. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js +10 -0
  111. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js.map +1 -0
  112. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +186 -0
  113. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -0
  114. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js +46 -0
  115. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -0
  116. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js +11 -0
  117. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js.map +1 -0
  118. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +298 -0
  119. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -0
  120. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +77 -0
  121. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -0
  122. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js +10 -0
  123. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js.map +1 -0
  124. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js +169 -0
  125. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js.map +1 -0
  126. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js +48 -0
  127. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js.map +1 -0
  128. package/dist/esm/components/CnTabs/CnTab.vue2.js +35 -0
  129. package/dist/esm/components/CnTabs/CnTab.vue2.js.map +1 -1
  130. package/dist/esm/components/CnTabs/CnTab.vue3.js.map +1 -1
  131. package/dist/esm/components/CnTabs/CnTabs.vue2.js +120 -7
  132. package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
  133. package/dist/esm/components/CnTabs/CnTabs.vue3.js +57 -5
  134. package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
  135. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +59 -4
  136. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  137. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +6 -2
  138. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  139. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js +10 -0
  140. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js.map +1 -0
  141. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +484 -0
  142. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -0
  143. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js +115 -0
  144. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -0
  145. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js +10 -0
  146. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js.map +1 -0
  147. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js +189 -0
  148. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js.map +1 -0
  149. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js +74 -0
  150. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js.map +1 -0
  151. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +82 -8
  152. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  153. package/dist/esm/index.js +16 -0
  154. package/dist/esm/index.js.map +1 -1
  155. package/dist/esm/l10n/en.json.js +55 -1
  156. package/dist/esm/l10n/en.json.js.map +1 -1
  157. package/dist/esm/l10n/nl.json.js +55 -1
  158. package/dist/esm/l10n/nl.json.js.map +1 -1
  159. package/dist/esm/nextcloud-vue.css +1036 -19
  160. package/dist/esm/utils/dateVariant.js +124 -0
  161. package/dist/esm/utils/dateVariant.js.map +1 -0
  162. package/dist/esm/utils/detailActionModel.js +260 -0
  163. package/dist/esm/utils/detailActionModel.js.map +1 -0
  164. package/dist/esm/utils/dueRule.js +84 -0
  165. package/dist/esm/utils/dueRule.js.map +1 -0
  166. package/dist/esm/utils/fetchAggregate.js +37 -1
  167. package/dist/esm/utils/fetchAggregate.js.map +1 -1
  168. package/dist/esm/utils/fetchFilterCounts.js +186 -0
  169. package/dist/esm/utils/fetchFilterCounts.js.map +1 -0
  170. package/dist/esm/utils/headers.js +28 -2
  171. package/dist/esm/utils/headers.js.map +1 -1
  172. package/dist/esm/utils/libraryWidgetKeys.js +5 -0
  173. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  174. package/dist/esm/utils/readPath.js +30 -0
  175. package/dist/esm/utils/readPath.js.map +1 -0
  176. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  177. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  178. package/dist/nextcloud-vue.cjs.js +16019 -9528
  179. package/dist/nextcloud-vue.cjs.js.map +2 -2
  180. package/dist/nextcloud-vue.css +1036 -19
  181. package/l10n/en.json +55 -1
  182. package/l10n/nl.json +55 -1
  183. package/package.json +1 -1
  184. package/scripts/.jsdoc-baselines.json +4 -0
  185. package/src/components/CnActionsMenu/CnActionsMenu.vue +14 -1
  186. package/src/components/CnAppNav/CnAppNav.md +26 -0
  187. package/src/components/CnAppNav/CnAppNav.vue +145 -2
  188. package/src/components/CnBannerWidget/CnBannerWidget.vue +373 -11
  189. package/src/components/CnBannerWidgetForm/CnBannerWidgetForm.vue +65 -3
  190. package/src/components/CnBoardView/CnBoardView.vue +77 -0
  191. package/src/components/CnBrandStripe/CnBrandStripe.md +22 -0
  192. package/src/components/CnBrandStripe/CnBrandStripe.vue +100 -0
  193. package/src/components/CnBrandStripe/index.js +4 -0
  194. package/src/components/CnCellRenderer/CnCellRenderer.vue +207 -1
  195. package/src/components/CnConversationThread/CnConversationThread.md +51 -0
  196. package/src/components/CnConversationThread/CnConversationThread.vue +457 -0
  197. package/src/components/CnConversationThread/CnConversationWidget.vue +144 -0
  198. package/src/components/CnConversationThread/index.js +4 -0
  199. package/src/components/CnDashboardPage/CnDashboardPage.vue +10 -2
  200. package/src/components/CnDetailPage/CnDetailPage.md +16 -0
  201. package/src/components/CnDetailPage/CnDetailPage.vue +687 -49
  202. package/src/components/CnDocumentReviewList/CnDocumentReviewList.md +55 -0
  203. package/src/components/CnDocumentReviewList/CnDocumentReviewList.vue +384 -0
  204. package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +108 -0
  205. package/src/components/CnDocumentReviewList/index.js +4 -0
  206. package/src/components/CnHeaderWidget/CnHeaderWidget.vue +191 -14
  207. package/src/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue +53 -3
  208. package/src/components/CnIndexPage/CnIndexPage.vue +135 -3
  209. package/src/components/CnLinkCardsPage/CnLinkCardsPage.vue +484 -0
  210. package/src/components/CnLinkCardsPage/index.js +4 -0
  211. package/src/components/CnNextStepCard/CnNextStepCard.md +40 -0
  212. package/src/components/CnNextStepCard/CnNextStepCard.vue +319 -0
  213. package/src/components/CnNextStepCard/CnNextStepWidget.vue +59 -0
  214. package/src/components/CnNextStepCard/index.js +4 -0
  215. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +49 -0
  216. package/src/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue +6 -0
  217. package/src/components/CnObjectKanban/CnObjectKanban.vue +86 -2
  218. package/src/components/CnPageRenderer/pageTypes.js +1 -0
  219. package/src/components/CnQuickFilterBar/CnQuickFilterBar.md +3 -0
  220. package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +60 -1
  221. package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +50 -3
  222. package/src/components/CnSegmentedControl/CnSegmentedControl.md +46 -0
  223. package/src/components/CnSegmentedControl/CnSegmentedControl.vue +291 -0
  224. package/src/components/CnSegmentedControl/index.js +4 -0
  225. package/src/components/CnStackedBarWidget/CnStackedBarWidget.md +15 -0
  226. package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +414 -0
  227. package/src/components/CnStackedBarWidget/index.js +14 -0
  228. package/src/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue +224 -0
  229. package/src/components/CnStackedBarWidgetForm/index.js +4 -0
  230. package/src/components/CnTabs/CnTab.vue +35 -0
  231. package/src/components/CnTabs/CnTabs.vue +244 -10
  232. package/src/components/CnTabsWidget/CnTabsWidget.vue +63 -4
  233. package/src/components/CnWeekStripWidget/CnWeekStripWidget.md +31 -0
  234. package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +668 -0
  235. package/src/components/CnWeekStripWidget/index.js +14 -0
  236. package/src/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue +265 -0
  237. package/src/components/CnWeekStripWidgetForm/index.js +4 -0
  238. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +74 -0
  239. package/src/components/index.js +12 -0
  240. package/src/css/detail-page.css +57 -2
  241. package/src/index.js +16 -0
  242. package/src/schemas/app-manifest-v2.schema.json +433 -3
  243. package/src/schemas/app-manifest.schema.json +2 -1
  244. package/src/types/manifest.d.ts +1 -1
  245. package/src/utils/dateVariant.js +120 -0
  246. package/src/utils/detailActionModel.js +256 -0
  247. package/src/utils/dueRule.js +94 -0
  248. package/src/utils/fetchAggregate.js +36 -0
  249. package/src/utils/fetchFilterCounts.js +182 -0
  250. package/src/utils/headers.js +28 -2
  251. package/src/utils/index.js +1 -0
  252. package/src/utils/libraryWidgetKeys.js +5 -0
  253. package/src/utils/readPath.js +27 -0
  254. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CnStackedBarWidgetForm.vue2.js","sources":["../../../../src/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.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-stacked-bar-form\">\n\t\t<div class=\"cn-stacked-bar-form__row2\">\n\t\t\t<CnRegisterSchemaSelect\n\t\t\t\t:register=\"source.register\"\n\t\t\t\t:schema=\"source.schema\"\n\t\t\t\t@update:register=\"updateSource('register', $event)\"\n\t\t\t\t@update:schema=\"updateSource('schema', $event)\" />\n\t\t</div>\n\n\t\t<CnFieldPicker\n\t\t\t:value=\"groupBy\"\n\t\t\t:label=\"t('nextcloud-vue', 'Field to group by')\"\n\t\t\t:options=\"availableFields\"\n\t\t\tplaceholder=\"status\"\n\t\t\t@update=\"updateField('groupBy', $event)\" />\n\n\t\t<CnFilterRowsEditor :value=\"filterRows\" :fields=\"availableFields\" @input=\"onFilterRows\" />\n\n\t\t<NcTextField\n\t\t\t:modelValue=\"orderText\"\n\t\t\t:label=\"t('nextcloud-vue', 'Order of the segments (values, comma separated)')\"\n\t\t\tplaceholder=\"received, in_progress, decision\"\n\t\t\t@update:modelValue=\"updateField('orderText', $event)\" />\n\n\t\t<NcTextField\n\t\t\t:modelValue=\"emptyText\"\n\t\t\t:label=\"t('nextcloud-vue', 'Text when there is nothing to count')\"\n\t\t\t@update:modelValue=\"updateField('emptyText', $event)\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcTextField } from '@nextcloud/vue'\nimport CnFieldPicker from '../CnFieldPicker/CnFieldPicker.vue'\nimport CnFilterRowsEditor from '../CnFilterRowsEditor/CnFilterRowsEditor.vue'\nimport CnRegisterSchemaSelect from '../CnRegisterSchemaSelect/CnRegisterSchemaSelect.vue'\nimport { fetchSchemaProperties } from '../../utils/fetchSchemaProperties.js'\nimport { filterToRows, rowsToFilter } from '../CnFilterRowsEditor/filterRows.js'\n\n/**\n * CnStackedBarWidgetForm is the settings form of a `stacked-bar` widget. It\n * edits the source (register, schema, filter), the field the records are\n * grouped by, the order of the segments and the empty text. Static `segments`\n * and the `labels` map are written in the manifest and pass through\n * untouched. Emits `update:content` on every change; `validate()` asks for a\n * register, a schema and a group field unless the widget has static segments.\n */\nexport default {\n\tname: 'CnStackedBarWidgetForm',\n\n\tcomponents: { NcTextField, CnFieldPicker, CnFilterRowsEditor, CnRegisterSchemaSelect },\n\n\tprops: {\n\t\t/**\n\t\t * The placement being edited (pre-fills from `editingWidget.content`), or null.\n\t\t *\n\t\t * @type {{content: object}|null}\n\t\t */\n\t\teditingWidget: { type: Object, default: null },\n\t\t/**\n\t\t * Initial content values when not editing (registry defaults).\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tvalue: { type: Object, default: () => ({}) },\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Emitted with the assembled content blob on every field change.\n\t\t *\n\t\t * @event update:content\n\t\t * @type {object}\n\t\t */\n\t\t'update:content',\n\t],\n\n\tdata() {\n\t\tconst initial = this.editingWidget?.content || this.value || {}\n\t\tconst src = initial.source || {}\n\t\treturn {\n\t\t\tinitial,\n\t\t\tsource: { register: src.register ?? '', schema: src.schema ?? '' },\n\t\t\tgroupBy: src.groupBy ?? '',\n\t\t\tfilterRows: filterToRows(src.filter || {}),\n\t\t\torderText: Array.isArray(initial.order) ? initial.order.join(', ') : '',\n\t\t\temptyText: initial.emptyText ?? '',\n\t\t\tavailableFields: [],\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The assembled content blob. Keys the form does not edit (static\n\t\t * segments, labels) are carried over from the initial content.\n\t\t *\n\t\t * @return {object}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\tassembledContent() {\n\t\t\treturn {\n\t\t\t\t...this.initial,\n\t\t\t\tsource: {\n\t\t\t\t\tregister: this.source.register,\n\t\t\t\t\tschema: this.source.schema,\n\t\t\t\t\tgroupBy: this.groupBy,\n\t\t\t\t\tfilter: rowsToFilter(this.filterRows),\n\t\t\t\t},\n\n\t\t\t\torder: this.orderText.split(',').map((key) => key.trim()).filter((key) => key !== ''),\n\t\t\t\temptyText: this.emptyText,\n\t\t\t}\n\t\t},\n\t},\n\n\twatch: {\n\t\t'source.register': 'loadFields',\n\t\t'source.schema': 'loadFields',\n\t},\n\n\tmounted() {\n\t\tthis.loadFields()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Load the schema's field names for the picker.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\tasync loadFields() {\n\t\t\tthis.availableFields = await fetchSchemaProperties(this.source.register, this.source.schema)\n\t\t},\n\n\t\t/**\n\t\t * Set one field and emit the assembled content.\n\t\t *\n\t\t * @param {string} field The data key to write.\n\t\t * @param {string} value The new value.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\tupdateField(field, value) {\n\t\t\tthis[field] = value\n\t\t\tthis.$emit('update:content', this.assembledContent)\n\t\t},\n\n\t\t/**\n\t\t * Set the register or the schema and emit.\n\t\t *\n\t\t * @param {'register'|'schema'} field The source key to write.\n\t\t * @param {string} value The chosen slug.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\tupdateSource(field, value) {\n\t\t\tthis.source[field] = value\n\t\t\tthis.$emit('update:content', this.assembledContent)\n\t\t},\n\n\t\t/**\n\t\t * Take the filter editor's rows and emit.\n\t\t *\n\t\t * @param {Array<{key: string, op: string, value: string}>} rows The rows.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\tonFilterRows(rows) {\n\t\t\tthis.filterRows = rows\n\t\t\tthis.$emit('update:content', this.assembledContent)\n\t\t},\n\n\t\t/**\n\t\t * Validate the form; an empty array means valid.\n\t\t *\n\t\t * @return {string[]} The validation errors.\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\tvalidate() {\n\t\t\tconst hasStatic = Array.isArray(this.initial.segments) && this.initial.segments.length > 0\n\t\t\tif (hasStatic && !this.source.register && !this.source.schema) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst errors = []\n\t\t\tif (!this.source.register || !this.source.schema) {\n\t\t\t\terrors.push(t('nextcloud-vue', 'A register and schema are required'))\n\t\t\t}\n\t\t\tif (!this.groupBy) {\n\t\t\t\terrors.push(t('nextcloud-vue', 'A field to group by is required'))\n\t\t\t}\n\t\t\treturn errors\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-stacked-bar-form {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-stacked-bar-form__row2 {\n\tdisplay: flex;\n\tgap: 12px;\n\talign-items: flex-end;\n}\n\n.cn-stacked-bar-form__row2 > * {\n\tflex: 1;\n\tmin-width: 0;\n}\n</style>\n"],"names":["CnFieldPicker","CnFilterRowsEditor","CnRegisterSchemaSelect","t"],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ import { resolveComponent, openBlock, createElementBlock, createElementVNode, createVNode } from 'vue';
2
+
3
+ const _hoisted_1 = { class: "cn-stacked-bar-form" };
4
+ const _hoisted_2 = { class: "cn-stacked-bar-form__row2" };
5
+
6
+ function render(_ctx, _cache, $props, $setup, $data, $options) {
7
+ const _component_CnRegisterSchemaSelect = resolveComponent("CnRegisterSchemaSelect");
8
+ const _component_CnFieldPicker = resolveComponent("CnFieldPicker");
9
+ const _component_CnFilterRowsEditor = resolveComponent("CnFilterRowsEditor");
10
+ const _component_NcTextField = resolveComponent("NcTextField");
11
+
12
+ return (openBlock(), createElementBlock("div", _hoisted_1, [
13
+ createElementVNode("div", _hoisted_2, [
14
+ createVNode(_component_CnRegisterSchemaSelect, {
15
+ register: $data.source.register,
16
+ schema: $data.source.schema,
17
+ "onUpdate:register": _cache[0] || (_cache[0] = $event => ($options.updateSource('register', $event))),
18
+ "onUpdate:schema": _cache[1] || (_cache[1] = $event => ($options.updateSource('schema', $event)))
19
+ }, null, 8 /* PROPS */, ["register", "schema"])
20
+ ]),
21
+ createVNode(_component_CnFieldPicker, {
22
+ value: $data.groupBy,
23
+ label: $options.t('nextcloud-vue', 'Field to group by'),
24
+ options: $data.availableFields,
25
+ placeholder: "status",
26
+ onUpdate: _cache[2] || (_cache[2] = $event => ($options.updateField('groupBy', $event)))
27
+ }, null, 8 /* PROPS */, ["value", "label", "options"]),
28
+ createVNode(_component_CnFilterRowsEditor, {
29
+ value: $data.filterRows,
30
+ fields: $data.availableFields,
31
+ onInput: $options.onFilterRows
32
+ }, null, 8 /* PROPS */, ["value", "fields", "onInput"]),
33
+ createVNode(_component_NcTextField, {
34
+ modelValue: $data.orderText,
35
+ label: $options.t('nextcloud-vue', 'Order of the segments (values, comma separated)'),
36
+ placeholder: "received, in_progress, decision",
37
+ "onUpdate:modelValue": _cache[3] || (_cache[3] = $event => ($options.updateField('orderText', $event)))
38
+ }, null, 8 /* PROPS */, ["modelValue", "label"]),
39
+ createVNode(_component_NcTextField, {
40
+ modelValue: $data.emptyText,
41
+ label: $options.t('nextcloud-vue', 'Text when there is nothing to count'),
42
+ "onUpdate:modelValue": _cache[4] || (_cache[4] = $event => ($options.updateField('emptyText', $event)))
43
+ }, null, 8 /* PROPS */, ["modelValue", "label"])
44
+ ]))
45
+ }
46
+
47
+ export { render };
48
+ //# sourceMappingURL=CnStackedBarWidgetForm.vue3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CnStackedBarWidgetForm.vue3.js","sources":["../../../../src/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue?vue&type=template&id=56e6c6a2&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-stacked-bar-form\">\n\t\t<div class=\"cn-stacked-bar-form__row2\">\n\t\t\t<CnRegisterSchemaSelect\n\t\t\t\t:register=\"source.register\"\n\t\t\t\t:schema=\"source.schema\"\n\t\t\t\t@update:register=\"updateSource('register', $event)\"\n\t\t\t\t@update:schema=\"updateSource('schema', $event)\" />\n\t\t</div>\n\n\t\t<CnFieldPicker\n\t\t\t:value=\"groupBy\"\n\t\t\t:label=\"t('nextcloud-vue', 'Field to group by')\"\n\t\t\t:options=\"availableFields\"\n\t\t\tplaceholder=\"status\"\n\t\t\t@update=\"updateField('groupBy', $event)\" />\n\n\t\t<CnFilterRowsEditor :value=\"filterRows\" :fields=\"availableFields\" @input=\"onFilterRows\" />\n\n\t\t<NcTextField\n\t\t\t:modelValue=\"orderText\"\n\t\t\t:label=\"t('nextcloud-vue', 'Order of the segments (values, comma separated)')\"\n\t\t\tplaceholder=\"received, in_progress, decision\"\n\t\t\t@update:modelValue=\"updateField('orderText', $event)\" />\n\n\t\t<NcTextField\n\t\t\t:modelValue=\"emptyText\"\n\t\t\t:label=\"t('nextcloud-vue', 'Text when there is nothing to count')\"\n\t\t\t@update:modelValue=\"updateField('emptyText', $event)\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcTextField } from '@nextcloud/vue'\nimport CnFieldPicker from '../CnFieldPicker/CnFieldPicker.vue'\nimport CnFilterRowsEditor from '../CnFilterRowsEditor/CnFilterRowsEditor.vue'\nimport CnRegisterSchemaSelect from '../CnRegisterSchemaSelect/CnRegisterSchemaSelect.vue'\nimport { fetchSchemaProperties } from '../../utils/fetchSchemaProperties.js'\nimport { filterToRows, rowsToFilter } from '../CnFilterRowsEditor/filterRows.js'\n\n/**\n * CnStackedBarWidgetForm is the settings form of a `stacked-bar` widget. It\n * edits the source (register, schema, filter), the field the records are\n * grouped by, the order of the segments and the empty text. Static `segments`\n * and the `labels` map are written in the manifest and pass through\n * untouched. Emits `update:content` on every change; `validate()` asks for a\n * register, a schema and a group field unless the widget has static segments.\n */\nexport default {\n\tname: 'CnStackedBarWidgetForm',\n\n\tcomponents: { NcTextField, CnFieldPicker, CnFilterRowsEditor, CnRegisterSchemaSelect },\n\n\tprops: {\n\t\t/**\n\t\t * The placement being edited (pre-fills from `editingWidget.content`), or null.\n\t\t *\n\t\t * @type {{content: object}|null}\n\t\t */\n\t\teditingWidget: { type: Object, default: null },\n\t\t/**\n\t\t * Initial content values when not editing (registry defaults).\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tvalue: { type: Object, default: () => ({}) },\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Emitted with the assembled content blob on every field change.\n\t\t *\n\t\t * @event update:content\n\t\t * @type {object}\n\t\t */\n\t\t'update:content',\n\t],\n\n\tdata() {\n\t\tconst initial = this.editingWidget?.content || this.value || {}\n\t\tconst src = initial.source || {}\n\t\treturn {\n\t\t\tinitial,\n\t\t\tsource: { register: src.register ?? '', schema: src.schema ?? '' },\n\t\t\tgroupBy: src.groupBy ?? '',\n\t\t\tfilterRows: filterToRows(src.filter || {}),\n\t\t\torderText: Array.isArray(initial.order) ? initial.order.join(', ') : '',\n\t\t\temptyText: initial.emptyText ?? '',\n\t\t\tavailableFields: [],\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The assembled content blob. Keys the form does not edit (static\n\t\t * segments, labels) are carried over from the initial content.\n\t\t *\n\t\t * @return {object}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\tassembledContent() {\n\t\t\treturn {\n\t\t\t\t...this.initial,\n\t\t\t\tsource: {\n\t\t\t\t\tregister: this.source.register,\n\t\t\t\t\tschema: this.source.schema,\n\t\t\t\t\tgroupBy: this.groupBy,\n\t\t\t\t\tfilter: rowsToFilter(this.filterRows),\n\t\t\t\t},\n\n\t\t\t\torder: this.orderText.split(',').map((key) => key.trim()).filter((key) => key !== ''),\n\t\t\t\temptyText: this.emptyText,\n\t\t\t}\n\t\t},\n\t},\n\n\twatch: {\n\t\t'source.register': 'loadFields',\n\t\t'source.schema': 'loadFields',\n\t},\n\n\tmounted() {\n\t\tthis.loadFields()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Load the schema's field names for the picker.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\tasync loadFields() {\n\t\t\tthis.availableFields = await fetchSchemaProperties(this.source.register, this.source.schema)\n\t\t},\n\n\t\t/**\n\t\t * Set one field and emit the assembled content.\n\t\t *\n\t\t * @param {string} field The data key to write.\n\t\t * @param {string} value The new value.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\tupdateField(field, value) {\n\t\t\tthis[field] = value\n\t\t\tthis.$emit('update:content', this.assembledContent)\n\t\t},\n\n\t\t/**\n\t\t * Set the register or the schema and emit.\n\t\t *\n\t\t * @param {'register'|'schema'} field The source key to write.\n\t\t * @param {string} value The chosen slug.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\tupdateSource(field, value) {\n\t\t\tthis.source[field] = value\n\t\t\tthis.$emit('update:content', this.assembledContent)\n\t\t},\n\n\t\t/**\n\t\t * Take the filter editor's rows and emit.\n\t\t *\n\t\t * @param {Array<{key: string, op: string, value: string}>} rows The rows.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\tonFilterRows(rows) {\n\t\t\tthis.filterRows = rows\n\t\t\tthis.$emit('update:content', this.assembledContent)\n\t\t},\n\n\t\t/**\n\t\t * Validate the form; an empty array means valid.\n\t\t *\n\t\t * @return {string[]} The validation errors.\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\tvalidate() {\n\t\t\tconst hasStatic = Array.isArray(this.initial.segments) && this.initial.segments.length > 0\n\t\t\tif (hasStatic && !this.source.register && !this.source.schema) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst errors = []\n\t\t\tif (!this.source.register || !this.source.schema) {\n\t\t\t\terrors.push(t('nextcloud-vue', 'A register and schema are required'))\n\t\t\t}\n\t\t\tif (!this.groupBy) {\n\t\t\t\terrors.push(t('nextcloud-vue', 'A field to group by is required'))\n\t\t\t}\n\t\t\treturn errors\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-stacked-bar-form {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-stacked-bar-form__row2 {\n\tdisplay: flex;\n\tgap: 12px;\n\talign-items: flex-end;\n}\n\n.cn-stacked-bar-form__row2 > * {\n\tflex: 1;\n\tmin-width: 0;\n}\n</style>\n"],"names":["_createVNode"],"mappings":";;AAWM,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,qBAAqB,GAAA;AAC1B,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,2BAA2B,GAAA;;;;;;;;;uBAMhC,iBAAA,EAAA;MALLA,WAI+C,CAAA,iCAAA,EAAA;;QAF7C,MAAM,EAAE,KAAE,CAAA,MAAA,CAAA,MAAA;QACV,mBAAa,EAAA,MAAA,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAA,MAAA,KAAA,QAAA,CAAA,YAAA,CAAA,UAAA,EAAA,MAAA,CAAA,CAAA,CAAA;QACb,iBAAa,EAAA,MAAA,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAA,MAAA,KAAE,QAAY,CAAA,YAAA,CAAA,QAAA,EAAW,MAAM,CAAA,CAAA,CAAA;;;IAG9CA,WAKsB,CAAA,wBAAA,EAAA;;AAHrB,MAAA,KAAK,EAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,mBAAA,CAAA;;AAET,MAAA,WAAU,EAAA,QAAA;MACT,QAAM,EAAA,MAAA,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAA,MAAA,KAAE,QAAW,CAAA,WAAA,CAAA,SAAA,EAAE,MAAA,CAAA,CAAA,CAAA;;IAEtBA,WAA4C,CAAA,6BAAA,EAAA;AAAxB,MAAA,KAAK,EAAE,KAAU,CAAA,UAAA;AAAG,MAAA,MAAI,EAAA,KAAA,CAAA,eAAA;AAAA,MAAA,OAAA,EAAA,QAAA,CAAA,YAAA;;eAMW,CAAA,sBAAA,EAAA;AAHtD,MAAA,UAAS,EAAA,KAAA,CAAA,SAAA;AACT,MAAA,KAAK,EAAE,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,iDAAA,CAAA;AACT,MAAA,WAAW,EAAC,iCAAiC;MAC5C,qBAAiB,EAAA,MAAA,CAAA,CAAA,CAAA,KAAA,MAAA,CAAA,CAAA,CAAA,GAAA,MAAA,KAAE,QAAW,CAAA,WAAA,CAAA,WAAA,EAAc,MAAM,CAAA,CAAA,CAAA;;;;;;;;;;;;"}
@@ -41,6 +41,28 @@ var script = defineComponent({
41
41
  type: Boolean,
42
42
  default: false,
43
43
  },
44
+
45
+ /**
46
+ * A count shown after the title, for example the number of documents
47
+ * behind the tab. `null` (the default) shows none; `0` is shown.
48
+ *
49
+ * @type {number|string|null}
50
+ */
51
+ count: {
52
+ type: [Number, String],
53
+ default: null,
54
+ },
55
+
56
+ /**
57
+ * List this tab under the strip's "More" menu instead of in the strip.
58
+ * Picking it there selects it, and while it is the selected tab it
59
+ * shows in the strip. Give an overflow tab a `title`: the menu needs
60
+ * plain text, and a `#title` slot is markup.
61
+ */
62
+ overflow: {
63
+ type: Boolean,
64
+ default: false,
65
+ },
44
66
  },
45
67
 
46
68
  emits: [
@@ -77,6 +99,19 @@ var script = defineComponent({
77
99
  get disabled() {
78
100
  return props.disabled
79
101
  },
102
+
103
+ get count() {
104
+ return props.count
105
+ },
106
+
107
+ get overflow() {
108
+ return props.overflow
109
+ },
110
+
111
+ // Plain text for the "More" menu, where a `#title` slot cannot go.
112
+ get label() {
113
+ return props.title
114
+ },
80
115
  };
81
116
 
82
117
  onMounted(() => tabsApi?.register(entry));
@@ -1 +1 @@
1
- {"version":3,"file":"CnTab.vue2.js","sources":["../../../../src/components/CnTabs/CnTab.vue"],"sourcesContent":["<template>\n\t<div\n\t\t:id=\"panelId\"\n\t\tclass=\"cn-tab\"\n\t\trole=\"tabpanel\"\n\t\t:aria-labelledby=\"tabId\"\n\t\t:tabindex=\"visible ? 0 : -1\"\n\t\t:hidden=\"visible ? null : true\"\n\t\t:style=\"visible ? null : { display: 'none' }\">\n\t\t<!-- @slot The panel body. -->\n\t\t<slot v-if=\"rendered\" />\n\t</div>\n</template>\n\n<script>\n/**\n * CnTab — one panel inside a `CnTabs` strip.\n *\n * ```vue\n * <CnTab title=\"Documents\">…</CnTab>\n *\n * <CnTab :active=\"selected === i\" @click=\"selected = i\">\n * <template #title>\n * {{ label }} <CnIcon name=\"close\" @click.stop=\"close(i)\" />\n * </template>\n * …\n * </CnTab>\n * ```\n *\n * ## The panel stays in the DOM\n *\n * An inactive panel is hidden with `hidden` + `display: none`, not removed with\n * `v-if`. This matches bootstrap-vue's `<BTab>`, and it is load-bearing: panels\n * that fetch on `mounted()` would refire that request on every tab switch if\n * they were destroyed and recreated. Use `v-if` inside the panel yourself if\n * you specifically want teardown.\n *\n * `lazy` changes only the FIRST paint: the body waits until the tab is first\n * activated, and from then on the panel behaves exactly as above. It is for\n * strips whose panels are expensive to mount, not for saving memory.\n *\n * ## Rendered outside a CnTabs parent\n *\n * The panel shows its content rather than vanishing. That is deliberate: the\n * alternative failure — an app whose `inject()` misses because the library got\n * loaded twice — would otherwise render blank with no error at all. See\n * `tabsKey.js` for why the key is `Symbol.for`.\n */\nimport { computed, defineComponent, getCurrentInstance, inject, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { CN_TABS_INJECTION_KEY } from './tabsKey.js'\n\nexport default defineComponent({\n\tname: 'CnTab',\n\n\tprops: {\n\t\t/** Plain-text tab title. Ignored when a `#title` slot is supplied. */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Select this tab. Honoured on mount and on every later change. */\n\t\tactive: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Render the nav button disabled and skip this tab in keyboard navigation. */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Hold the panel body back until this tab is first activated, then keep\n\t\t * it mounted for the rest of the strip's life.\n\t\t *\n\t\t * Off by default, because the eager panel is what makes a tab switch\n\t\t * instant and it is the behaviour every existing consumer already has.\n\t\t * Turn it on when the panels are expensive: six panels that each fetch\n\t\t * on `mounted()` fire six requests on page load, and five of those\n\t\t * answer questions nobody has asked yet.\n\t\t *\n\t\t * This is NOT `v-if`-per-switch. Once a panel has been shown it stays\n\t\t * in the DOM, so switching back to it never refetches. That is the\n\t\t * whole reason an inactive panel is hidden rather than destroyed.\n\t\t */\n\t\tlazy: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\temits: [\n\t\t/** The user activated this tab. */\n\t\t'click',\n\t],\n\n\tsetup(props, { slots, emit }) {\n\t\tconst tabsApi = inject(CN_TABS_INJECTION_KEY, null)\n\t\tconst uid = getCurrentInstance().uid\n\t\tconst tabId = `cn-tab-${uid}`\n\t\tconst panelId = `cn-tabpanel-${uid}`\n\n\t\tconst entry = {\n\t\t\tuid,\n\t\t\ttabId,\n\t\t\tpanelId,\n\t\t\t// Invoked from the PARENT's render effect, so reading `props.title`\n\t\t\t// (or calling the slot) here is what keeps a computed title reactive\n\t\t\t// in the nav strip.\n\t\t\ttitleRender: () => (slots.title ? slots.title() : props.title),\n\t\t\tonActivate: () => {\n\t\t\t\tif (props.disabled) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ttabsApi?.select(uid)\n\t\t\t\temit('click')\n\t\t\t},\n\n\t\t\tget active() {\n\t\t\t\treturn props.active\n\t\t\t},\n\n\t\t\tget disabled() {\n\t\t\t\treturn props.disabled\n\t\t\t},\n\t\t}\n\n\t\tonMounted(() => tabsApi?.register(entry))\n\t\tonBeforeUnmount(() => tabsApi?.unregister(uid))\n\n\t\twatch(() => props.active, (isActive) => {\n\t\t\tif (isActive && !props.disabled) {\n\t\t\t\ttabsApi?.select(uid)\n\t\t\t}\n\t\t})\n\n\t\tconst visible = computed(() => (tabsApi ? tabsApi.isActive(uid) : true))\n\n\t\t// Latches on first activation and never resets, so a lazy panel mounts\n\t\t// once and then behaves exactly like an eager one.\n\t\tconst shown = ref(false)\n\t\twatch(visible, (isVisible) => {\n\t\t\tif (isVisible) {\n\t\t\t\tshown.value = true\n\t\t\t}\n\t\t}, { immediate: true })\n\n\t\tconst rendered = computed(() => !props.lazy || shown.value)\n\n\t\treturn { visible, rendered, tabId, panelId }\n\t},\n})\n</script>\n"],"names":[],"mappings":";;;AAmDA,aAAe,eAAe,CAAC;CAC9B,IAAI,EAAE,OAAO;;AAEb,CAAA,KAAK,EAAE;;AAEN,EAAA,KAAK,EAAE;GACN,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;;AAGD,EAAA,MAAM,EAAE;GACP,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,KAAK;GACd;;AAED;AACA,EAAA,QAAQ,EAAE;GACT,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,KAAK;GACd;;AAED;;AAEC;AACA;;;;;AAKA;AACA;;;;;EAKD,IAAI,EAAE;GACL,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,KAAK;GACd;EACD;;AAED,CAAA,KAAK,EAAE;;AAEN,EAAA,OAAO;EACP;;CAED,KAAK,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,IAAK,EAAC,EAAE;EAC7B,MAAM,UAAU,MAAM,CAAC,qBAAqB,EAAE,IAAI,EAAA;AAClD,EAAA,MAAM,GAAI,GAAE,kBAAkB,EAAE,CAAC,IAAE;EACnC,MAAM,KAAI,GAAI,CAAC,OAAO,EAAE,GAAG,CAAC,EAAA;AAC5B,EAAA,MAAM,OAAQ,GAAE,CAAC,YAAY,EAAE,GAAG,CAAC,EAAA;;EAEnC,MAAM,KAAI,GAAI;AACb,GAAA,GAAG;GACH,KAAK;AACL,GAAA,OAAO;;;AAGP;GACA,WAAW,EAAE,OAAO,KAAK,CAAC,QAAQ,KAAK,CAAC,KAAK,KAAK,KAAK,CAAC,KAAK,CAAC;AAC9D,GAAA,UAAU,EAAE,MAAM;AACjB,IAAA,IAAI,KAAK,CAAC,QAAQ,EAAE;KACnB,MAAK;AACN,KAAA;AACA,IAAA,OAAO,EAAE,MAAM,CAAC,GAAG,EAAA;IACnB,IAAI,CAAC,OAAO,EAAA;IACZ;;GAED,IAAI,MAAM,GAAG;AACZ,IAAA,OAAO,KAAK,CAAC,MAAK;IAClB;;GAED,IAAI,QAAQ,GAAG;AACd,IAAA,OAAO,KAAK,CAAC,QAAO;IACpB;AACF,IAAA;;AAEA,EAAA,SAAS,CAAC,MAAM,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,EAAA;AACxC,EAAA,eAAe,CAAC,MAAM,OAAO,EAAE,UAAU,CAAC,GAAG,CAAC,EAAA;;AAE9C,EAAA,KAAK,CAAC,MAAM,KAAK,CAAC,MAAM,EAAE,CAAC,QAAQ,KAAK;GACvC,IAAI,QAAS,IAAG,CAAC,KAAK,CAAC,QAAQ,EAAE;AAChC,IAAA,OAAO,EAAE,MAAM,CAAC,GAAG,EAAA;AACpB,IAAA;GACA,EAAA;;AAED,EAAA,MAAM,OAAQ,GAAE,QAAQ,CAAC,OAAO,OAAQ,GAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAE,GAAE,IAAI,CAAC,EAAA;;;AAGvE;AACA,EAAA,MAAM,KAAI,GAAI,GAAG,CAAC,KAAK,EAAA;EACvB,KAAK,CAAC,OAAO,EAAE,CAAC,SAAS,KAAK;GAC7B,IAAI,SAAS,EAAE;AACd,IAAA,KAAK,CAAC,KAAI,GAAI,KAAG;AAClB,IAAA;AACD,GAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAA;;EAEtB,MAAM,WAAW,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,IAAG,IAAK,KAAK,CAAC,KAAK,EAAA;;AAE1D,EAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAQ,EAAA;EAC3C;AACF,CAAC;;;;"}
1
+ {"version":3,"file":"CnTab.vue2.js","sources":["../../../../src/components/CnTabs/CnTab.vue"],"sourcesContent":["<template>\n\t<div\n\t\t:id=\"panelId\"\n\t\tclass=\"cn-tab\"\n\t\trole=\"tabpanel\"\n\t\t:aria-labelledby=\"tabId\"\n\t\t:tabindex=\"visible ? 0 : -1\"\n\t\t:hidden=\"visible ? null : true\"\n\t\t:style=\"visible ? null : { display: 'none' }\">\n\t\t<!-- @slot The panel body. -->\n\t\t<slot v-if=\"rendered\" />\n\t</div>\n</template>\n\n<script>\n/**\n * CnTab — one panel inside a `CnTabs` strip.\n *\n * ```vue\n * <CnTab title=\"Documents\">…</CnTab>\n *\n * <CnTab :active=\"selected === i\" @click=\"selected = i\">\n * <template #title>\n * {{ label }} <CnIcon name=\"close\" @click.stop=\"close(i)\" />\n * </template>\n * …\n * </CnTab>\n * ```\n *\n * ## The panel stays in the DOM\n *\n * An inactive panel is hidden with `hidden` + `display: none`, not removed with\n * `v-if`. This matches bootstrap-vue's `<BTab>`, and it is load-bearing: panels\n * that fetch on `mounted()` would refire that request on every tab switch if\n * they were destroyed and recreated. Use `v-if` inside the panel yourself if\n * you specifically want teardown.\n *\n * `lazy` changes only the FIRST paint: the body waits until the tab is first\n * activated, and from then on the panel behaves exactly as above. It is for\n * strips whose panels are expensive to mount, not for saving memory.\n *\n * ## Rendered outside a CnTabs parent\n *\n * The panel shows its content rather than vanishing. That is deliberate: the\n * alternative failure — an app whose `inject()` misses because the library got\n * loaded twice — would otherwise render blank with no error at all. See\n * `tabsKey.js` for why the key is `Symbol.for`.\n */\nimport { computed, defineComponent, getCurrentInstance, inject, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { CN_TABS_INJECTION_KEY } from './tabsKey.js'\n\nexport default defineComponent({\n\tname: 'CnTab',\n\n\tprops: {\n\t\t/** Plain-text tab title. Ignored when a `#title` slot is supplied. */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Select this tab. Honoured on mount and on every later change. */\n\t\tactive: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Render the nav button disabled and skip this tab in keyboard navigation. */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Hold the panel body back until this tab is first activated, then keep\n\t\t * it mounted for the rest of the strip's life.\n\t\t *\n\t\t * Off by default, because the eager panel is what makes a tab switch\n\t\t * instant and it is the behaviour every existing consumer already has.\n\t\t * Turn it on when the panels are expensive: six panels that each fetch\n\t\t * on `mounted()` fire six requests on page load, and five of those\n\t\t * answer questions nobody has asked yet.\n\t\t *\n\t\t * This is NOT `v-if`-per-switch. Once a panel has been shown it stays\n\t\t * in the DOM, so switching back to it never refetches. That is the\n\t\t * whole reason an inactive panel is hidden rather than destroyed.\n\t\t */\n\t\tlazy: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * A count shown after the title, for example the number of documents\n\t\t * behind the tab. `null` (the default) shows none; `0` is shown.\n\t\t *\n\t\t * @type {number|string|null}\n\t\t */\n\t\tcount: {\n\t\t\ttype: [Number, String],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * List this tab under the strip's \"More\" menu instead of in the strip.\n\t\t * Picking it there selects it, and while it is the selected tab it\n\t\t * shows in the strip. Give an overflow tab a `title`: the menu needs\n\t\t * plain text, and a `#title` slot is markup.\n\t\t */\n\t\toverflow: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\temits: [\n\t\t/** The user activated this tab. */\n\t\t'click',\n\t],\n\n\tsetup(props, { slots, emit }) {\n\t\tconst tabsApi = inject(CN_TABS_INJECTION_KEY, null)\n\t\tconst uid = getCurrentInstance().uid\n\t\tconst tabId = `cn-tab-${uid}`\n\t\tconst panelId = `cn-tabpanel-${uid}`\n\n\t\tconst entry = {\n\t\t\tuid,\n\t\t\ttabId,\n\t\t\tpanelId,\n\t\t\t// Invoked from the PARENT's render effect, so reading `props.title`\n\t\t\t// (or calling the slot) here is what keeps a computed title reactive\n\t\t\t// in the nav strip.\n\t\t\ttitleRender: () => (slots.title ? slots.title() : props.title),\n\t\t\tonActivate: () => {\n\t\t\t\tif (props.disabled) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ttabsApi?.select(uid)\n\t\t\t\temit('click')\n\t\t\t},\n\n\t\t\tget active() {\n\t\t\t\treturn props.active\n\t\t\t},\n\n\t\t\tget disabled() {\n\t\t\t\treturn props.disabled\n\t\t\t},\n\n\t\t\tget count() {\n\t\t\t\treturn props.count\n\t\t\t},\n\n\t\t\tget overflow() {\n\t\t\t\treturn props.overflow\n\t\t\t},\n\n\t\t\t// Plain text for the \"More\" menu, where a `#title` slot cannot go.\n\t\t\tget label() {\n\t\t\t\treturn props.title\n\t\t\t},\n\t\t}\n\n\t\tonMounted(() => tabsApi?.register(entry))\n\t\tonBeforeUnmount(() => tabsApi?.unregister(uid))\n\n\t\twatch(() => props.active, (isActive) => {\n\t\t\tif (isActive && !props.disabled) {\n\t\t\t\ttabsApi?.select(uid)\n\t\t\t}\n\t\t})\n\n\t\tconst visible = computed(() => (tabsApi ? tabsApi.isActive(uid) : true))\n\n\t\t// Latches on first activation and never resets, so a lazy panel mounts\n\t\t// once and then behaves exactly like an eager one.\n\t\tconst shown = ref(false)\n\t\twatch(visible, (isVisible) => {\n\t\t\tif (isVisible) {\n\t\t\t\tshown.value = true\n\t\t\t}\n\t\t}, { immediate: true })\n\n\t\tconst rendered = computed(() => !props.lazy || shown.value)\n\n\t\treturn { visible, rendered, tabId, panelId }\n\t},\n})\n</script>\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnTab.vue3.js","sources":["../../../../src/components/CnTabs/CnTab.vue?vue&type=template&id=5d643b98&lang.js"],"sourcesContent":["<template>\n\t<div\n\t\t:id=\"panelId\"\n\t\tclass=\"cn-tab\"\n\t\trole=\"tabpanel\"\n\t\t:aria-labelledby=\"tabId\"\n\t\t:tabindex=\"visible ? 0 : -1\"\n\t\t:hidden=\"visible ? null : true\"\n\t\t:style=\"visible ? null : { display: 'none' }\">\n\t\t<!-- @slot The panel body. -->\n\t\t<slot v-if=\"rendered\" />\n\t</div>\n</template>\n\n<script>\n/**\n * CnTab — one panel inside a `CnTabs` strip.\n *\n * ```vue\n * <CnTab title=\"Documents\">…</CnTab>\n *\n * <CnTab :active=\"selected === i\" @click=\"selected = i\">\n * <template #title>\n * {{ label }} <CnIcon name=\"close\" @click.stop=\"close(i)\" />\n * </template>\n * …\n * </CnTab>\n * ```\n *\n * ## The panel stays in the DOM\n *\n * An inactive panel is hidden with `hidden` + `display: none`, not removed with\n * `v-if`. This matches bootstrap-vue's `<BTab>`, and it is load-bearing: panels\n * that fetch on `mounted()` would refire that request on every tab switch if\n * they were destroyed and recreated. Use `v-if` inside the panel yourself if\n * you specifically want teardown.\n *\n * `lazy` changes only the FIRST paint: the body waits until the tab is first\n * activated, and from then on the panel behaves exactly as above. It is for\n * strips whose panels are expensive to mount, not for saving memory.\n *\n * ## Rendered outside a CnTabs parent\n *\n * The panel shows its content rather than vanishing. That is deliberate: the\n * alternative failure — an app whose `inject()` misses because the library got\n * loaded twice — would otherwise render blank with no error at all. See\n * `tabsKey.js` for why the key is `Symbol.for`.\n */\nimport { computed, defineComponent, getCurrentInstance, inject, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { CN_TABS_INJECTION_KEY } from './tabsKey.js'\n\nexport default defineComponent({\n\tname: 'CnTab',\n\n\tprops: {\n\t\t/** Plain-text tab title. Ignored when a `#title` slot is supplied. */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Select this tab. Honoured on mount and on every later change. */\n\t\tactive: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Render the nav button disabled and skip this tab in keyboard navigation. */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Hold the panel body back until this tab is first activated, then keep\n\t\t * it mounted for the rest of the strip's life.\n\t\t *\n\t\t * Off by default, because the eager panel is what makes a tab switch\n\t\t * instant and it is the behaviour every existing consumer already has.\n\t\t * Turn it on when the panels are expensive: six panels that each fetch\n\t\t * on `mounted()` fire six requests on page load, and five of those\n\t\t * answer questions nobody has asked yet.\n\t\t *\n\t\t * This is NOT `v-if`-per-switch. Once a panel has been shown it stays\n\t\t * in the DOM, so switching back to it never refetches. That is the\n\t\t * whole reason an inactive panel is hidden rather than destroyed.\n\t\t */\n\t\tlazy: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\temits: [\n\t\t/** The user activated this tab. */\n\t\t'click',\n\t],\n\n\tsetup(props, { slots, emit }) {\n\t\tconst tabsApi = inject(CN_TABS_INJECTION_KEY, null)\n\t\tconst uid = getCurrentInstance().uid\n\t\tconst tabId = `cn-tab-${uid}`\n\t\tconst panelId = `cn-tabpanel-${uid}`\n\n\t\tconst entry = {\n\t\t\tuid,\n\t\t\ttabId,\n\t\t\tpanelId,\n\t\t\t// Invoked from the PARENT's render effect, so reading `props.title`\n\t\t\t// (or calling the slot) here is what keeps a computed title reactive\n\t\t\t// in the nav strip.\n\t\t\ttitleRender: () => (slots.title ? slots.title() : props.title),\n\t\t\tonActivate: () => {\n\t\t\t\tif (props.disabled) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ttabsApi?.select(uid)\n\t\t\t\temit('click')\n\t\t\t},\n\n\t\t\tget active() {\n\t\t\t\treturn props.active\n\t\t\t},\n\n\t\t\tget disabled() {\n\t\t\t\treturn props.disabled\n\t\t\t},\n\t\t}\n\n\t\tonMounted(() => tabsApi?.register(entry))\n\t\tonBeforeUnmount(() => tabsApi?.unregister(uid))\n\n\t\twatch(() => props.active, (isActive) => {\n\t\t\tif (isActive && !props.disabled) {\n\t\t\t\ttabsApi?.select(uid)\n\t\t\t}\n\t\t})\n\n\t\tconst visible = computed(() => (tabsApi ? tabsApi.isActive(uid) : true))\n\n\t\t// Latches on first activation and never resets, so a lazy panel mounts\n\t\t// once and then behaves exactly like an eager one.\n\t\tconst shown = ref(false)\n\t\twatch(visible, (isVisible) => {\n\t\t\tif (isVisible) {\n\t\t\t\tshown.value = true\n\t\t\t}\n\t\t}, { immediate: true })\n\n\t\tconst rendered = computed(() => !props.lazy || shown.value)\n\n\t\treturn { visible, rendered, tabId, panelId }\n\t},\n})\n</script>\n"],"names":["_createElementBlock","_renderSlot"],"mappings":";;;;;uBACCA,kBAUK,CAAA,KAAA,EAAA;AATH,IAAA,EAAE,EAAE,IAAO,CAAA,OAAA;AACZ,IAAA,KAAK,EAAC,QAAO;AACb,IAAA,IAAI,EAAC,UAAS;AACb,IAAA,iBAAe,EAAE,IAAK,CAAA,KAAA;AACtB,IAAA,QAAQ,EAAE;AACV,IAAA,MAAM,EAAE,IAAM,CAAA,OAAA,GAAA,IAAA,GAAA,IAAA;AACd,IAAA,KAAK,iBAAE,IAAQ,CAAA,OAAA,GAAA,IAAA,GAAA,EAAA,OAAA,EAAA,MAAA,EAAA,CAAA;;KAEJ,IAAQ,CAAA,QAAA;QAApBC,UAAuB,CAAA,IAAA,CAAA,MAAA,EAAA,SAAA,EAAA,EAAA,EAAA,SAAA,EAAA,SAAA,EAAA,CAAA,CAAA;;;;;;;"}
1
+ {"version":3,"file":"CnTab.vue3.js","sources":["../../../../src/components/CnTabs/CnTab.vue?vue&type=template&id=5d643b98&lang.js"],"sourcesContent":["<template>\n\t<div\n\t\t:id=\"panelId\"\n\t\tclass=\"cn-tab\"\n\t\trole=\"tabpanel\"\n\t\t:aria-labelledby=\"tabId\"\n\t\t:tabindex=\"visible ? 0 : -1\"\n\t\t:hidden=\"visible ? null : true\"\n\t\t:style=\"visible ? null : { display: 'none' }\">\n\t\t<!-- @slot The panel body. -->\n\t\t<slot v-if=\"rendered\" />\n\t</div>\n</template>\n\n<script>\n/**\n * CnTab — one panel inside a `CnTabs` strip.\n *\n * ```vue\n * <CnTab title=\"Documents\">…</CnTab>\n *\n * <CnTab :active=\"selected === i\" @click=\"selected = i\">\n * <template #title>\n * {{ label }} <CnIcon name=\"close\" @click.stop=\"close(i)\" />\n * </template>\n * …\n * </CnTab>\n * ```\n *\n * ## The panel stays in the DOM\n *\n * An inactive panel is hidden with `hidden` + `display: none`, not removed with\n * `v-if`. This matches bootstrap-vue's `<BTab>`, and it is load-bearing: panels\n * that fetch on `mounted()` would refire that request on every tab switch if\n * they were destroyed and recreated. Use `v-if` inside the panel yourself if\n * you specifically want teardown.\n *\n * `lazy` changes only the FIRST paint: the body waits until the tab is first\n * activated, and from then on the panel behaves exactly as above. It is for\n * strips whose panels are expensive to mount, not for saving memory.\n *\n * ## Rendered outside a CnTabs parent\n *\n * The panel shows its content rather than vanishing. That is deliberate: the\n * alternative failure — an app whose `inject()` misses because the library got\n * loaded twice — would otherwise render blank with no error at all. See\n * `tabsKey.js` for why the key is `Symbol.for`.\n */\nimport { computed, defineComponent, getCurrentInstance, inject, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { CN_TABS_INJECTION_KEY } from './tabsKey.js'\n\nexport default defineComponent({\n\tname: 'CnTab',\n\n\tprops: {\n\t\t/** Plain-text tab title. Ignored when a `#title` slot is supplied. */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Select this tab. Honoured on mount and on every later change. */\n\t\tactive: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Render the nav button disabled and skip this tab in keyboard navigation. */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Hold the panel body back until this tab is first activated, then keep\n\t\t * it mounted for the rest of the strip's life.\n\t\t *\n\t\t * Off by default, because the eager panel is what makes a tab switch\n\t\t * instant and it is the behaviour every existing consumer already has.\n\t\t * Turn it on when the panels are expensive: six panels that each fetch\n\t\t * on `mounted()` fire six requests on page load, and five of those\n\t\t * answer questions nobody has asked yet.\n\t\t *\n\t\t * This is NOT `v-if`-per-switch. Once a panel has been shown it stays\n\t\t * in the DOM, so switching back to it never refetches. That is the\n\t\t * whole reason an inactive panel is hidden rather than destroyed.\n\t\t */\n\t\tlazy: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * A count shown after the title, for example the number of documents\n\t\t * behind the tab. `null` (the default) shows none; `0` is shown.\n\t\t *\n\t\t * @type {number|string|null}\n\t\t */\n\t\tcount: {\n\t\t\ttype: [Number, String],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * List this tab under the strip's \"More\" menu instead of in the strip.\n\t\t * Picking it there selects it, and while it is the selected tab it\n\t\t * shows in the strip. Give an overflow tab a `title`: the menu needs\n\t\t * plain text, and a `#title` slot is markup.\n\t\t */\n\t\toverflow: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\temits: [\n\t\t/** The user activated this tab. */\n\t\t'click',\n\t],\n\n\tsetup(props, { slots, emit }) {\n\t\tconst tabsApi = inject(CN_TABS_INJECTION_KEY, null)\n\t\tconst uid = getCurrentInstance().uid\n\t\tconst tabId = `cn-tab-${uid}`\n\t\tconst panelId = `cn-tabpanel-${uid}`\n\n\t\tconst entry = {\n\t\t\tuid,\n\t\t\ttabId,\n\t\t\tpanelId,\n\t\t\t// Invoked from the PARENT's render effect, so reading `props.title`\n\t\t\t// (or calling the slot) here is what keeps a computed title reactive\n\t\t\t// in the nav strip.\n\t\t\ttitleRender: () => (slots.title ? slots.title() : props.title),\n\t\t\tonActivate: () => {\n\t\t\t\tif (props.disabled) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ttabsApi?.select(uid)\n\t\t\t\temit('click')\n\t\t\t},\n\n\t\t\tget active() {\n\t\t\t\treturn props.active\n\t\t\t},\n\n\t\t\tget disabled() {\n\t\t\t\treturn props.disabled\n\t\t\t},\n\n\t\t\tget count() {\n\t\t\t\treturn props.count\n\t\t\t},\n\n\t\t\tget overflow() {\n\t\t\t\treturn props.overflow\n\t\t\t},\n\n\t\t\t// Plain text for the \"More\" menu, where a `#title` slot cannot go.\n\t\t\tget label() {\n\t\t\t\treturn props.title\n\t\t\t},\n\t\t}\n\n\t\tonMounted(() => tabsApi?.register(entry))\n\t\tonBeforeUnmount(() => tabsApi?.unregister(uid))\n\n\t\twatch(() => props.active, (isActive) => {\n\t\t\tif (isActive && !props.disabled) {\n\t\t\t\ttabsApi?.select(uid)\n\t\t\t}\n\t\t})\n\n\t\tconst visible = computed(() => (tabsApi ? tabsApi.isActive(uid) : true))\n\n\t\t// Latches on first activation and never resets, so a lazy panel mounts\n\t\t// once and then behaves exactly like an eager one.\n\t\tconst shown = ref(false)\n\t\twatch(visible, (isVisible) => {\n\t\t\tif (isVisible) {\n\t\t\t\tshown.value = true\n\t\t\t}\n\t\t}, { immediate: true })\n\n\t\tconst rendered = computed(() => !props.lazy || shown.value)\n\n\t\treturn { visible, rendered, tabId, panelId }\n\t},\n})\n</script>\n"],"names":["_createElementBlock","_renderSlot"],"mappings":";;;;;uBACCA,kBAUK,CAAA,KAAA,EAAA;AATH,IAAA,EAAE,EAAE,IAAO,CAAA,OAAA;AACZ,IAAA,KAAK,EAAC,QAAO;AACb,IAAA,IAAI,EAAC,UAAS;AACb,IAAA,iBAAe,EAAE,IAAK,CAAA,KAAA;AACtB,IAAA,QAAQ,EAAE;AACV,IAAA,MAAM,EAAE,IAAM,CAAA,OAAA,GAAA,IAAA,GAAA,IAAA;AACd,IAAA,KAAK,iBAAE,IAAQ,CAAA,OAAA,GAAA,IAAA,GAAA,EAAA,OAAA,EAAA,MAAA,EAAA,CAAA;;KAEJ,IAAQ,CAAA,QAAA;QAApBC,UAAuB,CAAA,IAAA,CAAA,MAAA,EAAA,SAAA,EAAA,EAAA,EAAA,SAAA,EAAA,SAAA,EAAA,CAAA,CAAA;;;;;;;"}
@@ -1,4 +1,7 @@
1
- import { defineComponent, reactive, ref, computed, watch, onMounted, onUpdated, onBeforeUnmount, provide } from 'vue';
1
+ import { translate } from '@nextcloud/l10n';
2
+ import { NcActionButton, NcActions } from '@nextcloud/vue';
3
+ import { defineComponent, reactive, ref, computed, watch, onMounted, onUpdated, onBeforeUnmount, provide, nextTick } from 'vue';
4
+ import ChevronDown from 'vue-material-design-icons/ChevronDown.vue';
2
5
  import ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue';
3
6
  import ChevronRight from 'vue-material-design-icons/ChevronRight.vue';
4
7
  import { CN_TABS_INJECTION_KEY } from './tabsKey.js';
@@ -7,8 +10,11 @@ var script = defineComponent({
7
10
  name: 'CnTabs',
8
11
 
9
12
  components: {
13
+ ChevronDown,
10
14
  ChevronLeft,
11
15
  ChevronRight,
16
+ NcActionButton,
17
+ NcActions,
12
18
  },
13
19
 
14
20
  props: {
@@ -30,6 +36,20 @@ var script = defineComponent({
30
36
  default: false,
31
37
  },
32
38
 
39
+ /**
40
+ * How the strip is drawn: `line` (the default: tabs joined to the
41
+ * panel below) or `segmented` (a compact pill switch, for a view
42
+ * switch such as "My work / My team"). Roles and keyboard behaviour
43
+ * are the same in both.
44
+ *
45
+ * @type {'line'|'segmented'}
46
+ */
47
+ variant: {
48
+ type: String,
49
+ default: 'line',
50
+ validator: (v) => ['line', 'segmented'].includes(v),
51
+ },
52
+
33
53
  /**
34
54
  * Accessible name for the tab strip, applied to the `role="tablist"`
35
55
  * element. Screen readers announce it when focus enters the strip.
@@ -38,6 +58,12 @@ var script = defineComponent({
38
58
  type: String,
39
59
  default: '',
40
60
  },
61
+
62
+ /** Name of the menu that lists the `overflow` tabs. */
63
+ moreLabel: {
64
+ type: String,
65
+ default: () => translate('nextcloud-vue', 'More'),
66
+ },
41
67
  },
42
68
 
43
69
  emits: [
@@ -50,6 +76,10 @@ var script = defineComponent({
50
76
  const tabs = reactive([]);
51
77
  const activeUid = ref(null);
52
78
  const navButtons = ref([]);
79
+ const nav = ref(null);
80
+ // Whether the selection was chosen (a tab declared `active`, or the
81
+ // user picked one) rather than defaulted to the first tab registered.
82
+ let selectionChosen = false;
53
83
 
54
84
  /**
55
85
  * Register a child tab. The first child to register wins the initial
@@ -65,8 +95,23 @@ var script = defineComponent({
65
95
  if (tab.disabled) {
66
96
  return
67
97
  }
68
- if (activeUid.value === null || tab.active) {
98
+ if (tab.active) {
99
+ activeUid.value = tab.uid;
100
+ selectionChosen = true;
101
+ return
102
+ }
103
+ if (activeUid.value === null) {
69
104
  activeUid.value = tab.uid;
105
+ return
106
+ }
107
+ // A tab under "More" only held the default selection because it
108
+ // registered first. The first tab that is actually in the strip
109
+ // takes it over, unless somebody chose.
110
+ if (!selectionChosen && !tab.overflow) {
111
+ const current = tabs.find((candidate) => candidate.uid === activeUid.value);
112
+ if (current && current.overflow) {
113
+ activeUid.value = tab.uid;
114
+ }
70
115
  }
71
116
  }
72
117
 
@@ -98,6 +143,7 @@ var script = defineComponent({
98
143
  * @return {void}
99
144
  */
100
145
  function select(uid) {
146
+ selectionChosen = true;
101
147
  if (activeUid.value === uid) {
102
148
  return
103
149
  }
@@ -118,6 +164,67 @@ var script = defineComponent({
118
164
 
119
165
  const activeIndex = computed(() => tabs.findIndex((tab) => tab.uid === activeUid.value));
120
166
 
167
+ /**
168
+ * The tabs drawn in the strip: every tab that is not `overflow`, plus
169
+ * an overflow tab for as long as it is the selected one.
170
+ *
171
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
172
+ */
173
+ const visibleTabs = computed(() => tabs.filter((tab) => !tab.overflow || tab.uid === activeUid.value));
174
+
175
+ /**
176
+ * The tabs listed under "More": overflow tabs that are not selected.
177
+ *
178
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
179
+ */
180
+ const overflowTabs = computed(() => tabs.filter((tab) => tab.overflow && tab.uid !== activeUid.value));
181
+
182
+ /**
183
+ * Whether a tab declares a count. `0` is a count; null and '' are not.
184
+ *
185
+ * @param {object} tab Child descriptor.
186
+ *
187
+ * @return {boolean} True when the count renders.
188
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
189
+ */
190
+ function hasCount(tab) {
191
+ return tab.count !== null && tab.count !== undefined && tab.count !== ''
192
+ }
193
+
194
+ /**
195
+ * A "More" entry's text: the tab's title, and its count when it has one.
196
+ *
197
+ * @param {object} tab Child descriptor.
198
+ *
199
+ * @return {string} The menu label.
200
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
201
+ */
202
+ function overflowLabel(tab) {
203
+ return hasCount(tab) ? `${tab.label} (${tab.count})` : tab.label
204
+ }
205
+
206
+ /**
207
+ * Select a tab picked from "More" and move focus to it in the strip,
208
+ * so a keyboard user lands on the tab they chose rather than on a menu
209
+ * button that no longer lists it.
210
+ *
211
+ * @param {object} tab Child descriptor.
212
+ *
213
+ * @return {void}
214
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow
215
+ */
216
+ function onOverflowPick(tab) {
217
+ tab.onActivate();
218
+ nextTick(() => {
219
+ const button = nav.value?.querySelector(`#${tab.tabId}`);
220
+ if (button && typeof button.focus === 'function') {
221
+ button.focus();
222
+ }
223
+ });
224
+ }
225
+
226
+ const moreAriaLabel = computed(() => translate('nextcloud-vue', 'More tabs'));
227
+
121
228
  /**
122
229
  * Move the selection and DOM focus to a tab by index, skipping disabled
123
230
  * ones. Focus has to move with the selection: in the WAI-ARIA tabs
@@ -129,14 +236,16 @@ var script = defineComponent({
129
236
  * @return {void}
130
237
  */
131
238
  function focusTab(index) {
132
- const enabled = tabs.filter((tab) => !tab.disabled);
239
+ // The strip's own tabs only: the arrow keys walk what is drawn, and
240
+ // the "More" menu has its own keyboard handling.
241
+ const enabled = visibleTabs.value.filter((tab) => !tab.disabled);
133
242
  if (enabled.length === 0) {
134
243
  return
135
244
  }
136
245
  const wrapped = ((index % enabled.length) + enabled.length) % enabled.length;
137
246
  const target = enabled[wrapped];
247
+ const position = visibleTabs.value.indexOf(target);
138
248
  select(target.uid);
139
- const position = tabs.indexOf(target);
140
249
  const button = navButtons.value?.[position];
141
250
  if (button && typeof button.focus === 'function') {
142
251
  button.focus();
@@ -151,7 +260,7 @@ var script = defineComponent({
151
260
  * @return {void}
152
261
  */
153
262
  function onNavKeydown(event) {
154
- const enabled = tabs.filter((tab) => !tab.disabled);
263
+ const enabled = visibleTabs.value.filter((tab) => !tab.disabled);
155
264
  const current = enabled.findIndex((tab) => tab.uid === activeUid.value);
156
265
  let next = null;
157
266
 
@@ -172,8 +281,6 @@ var script = defineComponent({
172
281
  focusTab(next);
173
282
  }
174
283
 
175
- const nav = ref(null);
176
-
177
284
  /**
178
285
  * Scroll the strip so the selected tab is wholly in view.
179
286
  *
@@ -268,6 +375,12 @@ var script = defineComponent({
268
375
 
269
376
  return {
270
377
  tabs,
378
+ visibleTabs,
379
+ overflowTabs,
380
+ hasCount,
381
+ overflowLabel,
382
+ onOverflowPick,
383
+ moreAriaLabel,
271
384
  isActive,
272
385
  activeIndex,
273
386
  nav,
@@ -1 +1 @@
1
- {"version":3,"file":"CnTabs.vue2.js","sources":["../../../../src/components/CnTabs/CnTabs.vue"],"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":[],"mappings":"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"}
1
+ {"version":3,"file":"CnTabs.vue2.js","sources":["../../../../src/components/CnTabs/CnTabs.vue"],"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":["t"],"mappings":"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"}