@conduction/nextcloud-vue 2.59.0 → 2.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js +14 -1
  2. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
  4. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +80 -1
  5. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  6. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +46 -4
  7. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  8. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +232 -10
  9. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  10. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +79 -25
  11. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  12. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue2.js +42 -3
  13. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue2.js.map +1 -1
  14. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue3.js +39 -6
  15. package/dist/esm/components/CnBannerWidgetForm/CnBannerWidgetForm.vue3.js.map +1 -1
  16. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +46 -0
  17. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  18. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +11 -4
  19. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  20. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue.js +10 -0
  21. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue.js.map +1 -0
  22. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js +39 -0
  23. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js.map +1 -0
  24. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js +16 -0
  25. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js.map +1 -0
  26. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +117 -1
  27. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  28. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +147 -89
  29. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  30. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +10 -0
  31. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js.map +1 -0
  32. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js +259 -0
  33. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -0
  34. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +131 -0
  35. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -0
  36. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +10 -0
  37. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js.map +1 -0
  38. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +116 -0
  39. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -0
  40. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +24 -0
  41. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -0
  42. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +10 -2
  43. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  44. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  45. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +503 -28
  46. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  47. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +303 -144
  48. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  49. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js +10 -0
  50. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue.js.map +1 -0
  51. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js +237 -0
  52. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue2.js.map +1 -0
  53. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js +120 -0
  54. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewList.vue3.js.map +1 -0
  55. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js +10 -0
  56. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue.js.map +1 -0
  57. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +77 -0
  58. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -0
  59. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js +27 -0
  60. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -0
  61. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +171 -12
  62. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
  63. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +15 -7
  64. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
  65. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js +37 -3
  66. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue2.js.map +1 -1
  67. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js +44 -16
  68. package/dist/esm/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue3.js.map +1 -1
  69. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +132 -3
  70. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  71. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +6 -3
  72. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  73. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue.js +10 -0
  74. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue.js.map +1 -0
  75. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js +316 -0
  76. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js.map +1 -0
  77. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js +105 -0
  78. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js.map +1 -0
  79. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js +10 -0
  80. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue.js.map +1 -0
  81. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js +150 -0
  82. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js.map +1 -0
  83. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js +106 -0
  84. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js.map +1 -0
  85. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js +9 -0
  86. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue.js.map +1 -0
  87. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +47 -0
  88. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -0
  89. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js +17 -0
  90. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue3.js.map +1 -0
  91. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +49 -0
  92. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  93. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  94. package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js +6 -0
  95. package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue2.js.map +1 -1
  96. package/dist/esm/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue3.js.map +1 -1
  97. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js +46 -0
  98. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue2.js.map +1 -1
  99. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js +15 -3
  100. package/dist/esm/components/CnObjectKanban/CnObjectKanban.vue3.js.map +1 -1
  101. package/dist/esm/components/CnPageRenderer/pageTypes.js +1 -0
  102. package/dist/esm/components/CnPageRenderer/pageTypes.js.map +1 -1
  103. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +37 -1
  104. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
  105. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +29 -13
  106. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
  107. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js +50 -3
  108. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue2.js.map +1 -1
  109. package/dist/esm/components/CnSavedViewsControl/CnSavedViewsControl.vue3.js.map +1 -1
  110. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js +10 -0
  111. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js.map +1 -0
  112. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +186 -0
  113. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -0
  114. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js +46 -0
  115. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -0
  116. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js +11 -0
  117. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js.map +1 -0
  118. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +298 -0
  119. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -0
  120. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +77 -0
  121. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -0
  122. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js +10 -0
  123. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue.js.map +1 -0
  124. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js +169 -0
  125. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue2.js.map +1 -0
  126. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js +48 -0
  127. package/dist/esm/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue3.js.map +1 -0
  128. package/dist/esm/components/CnTabs/CnTab.vue2.js +35 -0
  129. package/dist/esm/components/CnTabs/CnTab.vue2.js.map +1 -1
  130. package/dist/esm/components/CnTabs/CnTab.vue3.js.map +1 -1
  131. package/dist/esm/components/CnTabs/CnTabs.vue2.js +120 -7
  132. package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
  133. package/dist/esm/components/CnTabs/CnTabs.vue3.js +57 -5
  134. package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
  135. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +59 -4
  136. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  137. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +6 -2
  138. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  139. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js +10 -0
  140. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js.map +1 -0
  141. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +484 -0
  142. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -0
  143. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js +115 -0
  144. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -0
  145. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js +10 -0
  146. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue.js.map +1 -0
  147. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js +189 -0
  148. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue2.js.map +1 -0
  149. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js +74 -0
  150. package/dist/esm/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue3.js.map +1 -0
  151. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +82 -8
  152. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  153. package/dist/esm/index.js +16 -0
  154. package/dist/esm/index.js.map +1 -1
  155. package/dist/esm/l10n/en.json.js +55 -1
  156. package/dist/esm/l10n/en.json.js.map +1 -1
  157. package/dist/esm/l10n/nl.json.js +55 -1
  158. package/dist/esm/l10n/nl.json.js.map +1 -1
  159. package/dist/esm/nextcloud-vue.css +1036 -19
  160. package/dist/esm/utils/dateVariant.js +124 -0
  161. package/dist/esm/utils/dateVariant.js.map +1 -0
  162. package/dist/esm/utils/detailActionModel.js +260 -0
  163. package/dist/esm/utils/detailActionModel.js.map +1 -0
  164. package/dist/esm/utils/dueRule.js +84 -0
  165. package/dist/esm/utils/dueRule.js.map +1 -0
  166. package/dist/esm/utils/fetchAggregate.js +37 -1
  167. package/dist/esm/utils/fetchAggregate.js.map +1 -1
  168. package/dist/esm/utils/fetchFilterCounts.js +186 -0
  169. package/dist/esm/utils/fetchFilterCounts.js.map +1 -0
  170. package/dist/esm/utils/headers.js +28 -2
  171. package/dist/esm/utils/headers.js.map +1 -1
  172. package/dist/esm/utils/libraryWidgetKeys.js +5 -0
  173. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  174. package/dist/esm/utils/readPath.js +30 -0
  175. package/dist/esm/utils/readPath.js.map +1 -0
  176. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  177. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  178. package/dist/nextcloud-vue.cjs.js +16019 -9528
  179. package/dist/nextcloud-vue.cjs.js.map +2 -2
  180. package/dist/nextcloud-vue.css +1036 -19
  181. package/l10n/en.json +55 -1
  182. package/l10n/nl.json +55 -1
  183. package/package.json +1 -1
  184. package/scripts/.jsdoc-baselines.json +4 -0
  185. package/src/components/CnActionsMenu/CnActionsMenu.vue +14 -1
  186. package/src/components/CnAppNav/CnAppNav.md +26 -0
  187. package/src/components/CnAppNav/CnAppNav.vue +145 -2
  188. package/src/components/CnBannerWidget/CnBannerWidget.vue +373 -11
  189. package/src/components/CnBannerWidgetForm/CnBannerWidgetForm.vue +65 -3
  190. package/src/components/CnBoardView/CnBoardView.vue +77 -0
  191. package/src/components/CnBrandStripe/CnBrandStripe.md +22 -0
  192. package/src/components/CnBrandStripe/CnBrandStripe.vue +100 -0
  193. package/src/components/CnBrandStripe/index.js +4 -0
  194. package/src/components/CnCellRenderer/CnCellRenderer.vue +207 -1
  195. package/src/components/CnConversationThread/CnConversationThread.md +51 -0
  196. package/src/components/CnConversationThread/CnConversationThread.vue +457 -0
  197. package/src/components/CnConversationThread/CnConversationWidget.vue +144 -0
  198. package/src/components/CnConversationThread/index.js +4 -0
  199. package/src/components/CnDashboardPage/CnDashboardPage.vue +10 -2
  200. package/src/components/CnDetailPage/CnDetailPage.md +16 -0
  201. package/src/components/CnDetailPage/CnDetailPage.vue +687 -49
  202. package/src/components/CnDocumentReviewList/CnDocumentReviewList.md +55 -0
  203. package/src/components/CnDocumentReviewList/CnDocumentReviewList.vue +384 -0
  204. package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +108 -0
  205. package/src/components/CnDocumentReviewList/index.js +4 -0
  206. package/src/components/CnHeaderWidget/CnHeaderWidget.vue +191 -14
  207. package/src/components/CnHeaderWidgetForm/CnHeaderWidgetForm.vue +53 -3
  208. package/src/components/CnIndexPage/CnIndexPage.vue +135 -3
  209. package/src/components/CnLinkCardsPage/CnLinkCardsPage.vue +484 -0
  210. package/src/components/CnLinkCardsPage/index.js +4 -0
  211. package/src/components/CnNextStepCard/CnNextStepCard.md +40 -0
  212. package/src/components/CnNextStepCard/CnNextStepCard.vue +319 -0
  213. package/src/components/CnNextStepCard/CnNextStepWidget.vue +59 -0
  214. package/src/components/CnNextStepCard/index.js +4 -0
  215. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +49 -0
  216. package/src/components/CnObjectDataWidgetForm/CnObjectDataWidgetForm.vue +6 -0
  217. package/src/components/CnObjectKanban/CnObjectKanban.vue +86 -2
  218. package/src/components/CnPageRenderer/pageTypes.js +1 -0
  219. package/src/components/CnQuickFilterBar/CnQuickFilterBar.md +3 -0
  220. package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +60 -1
  221. package/src/components/CnSavedViewsControl/CnSavedViewsControl.vue +50 -3
  222. package/src/components/CnSegmentedControl/CnSegmentedControl.md +46 -0
  223. package/src/components/CnSegmentedControl/CnSegmentedControl.vue +291 -0
  224. package/src/components/CnSegmentedControl/index.js +4 -0
  225. package/src/components/CnStackedBarWidget/CnStackedBarWidget.md +15 -0
  226. package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +414 -0
  227. package/src/components/CnStackedBarWidget/index.js +14 -0
  228. package/src/components/CnStackedBarWidgetForm/CnStackedBarWidgetForm.vue +224 -0
  229. package/src/components/CnStackedBarWidgetForm/index.js +4 -0
  230. package/src/components/CnTabs/CnTab.vue +35 -0
  231. package/src/components/CnTabs/CnTabs.vue +244 -10
  232. package/src/components/CnTabsWidget/CnTabsWidget.vue +63 -4
  233. package/src/components/CnWeekStripWidget/CnWeekStripWidget.md +31 -0
  234. package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +668 -0
  235. package/src/components/CnWeekStripWidget/index.js +14 -0
  236. package/src/components/CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue +265 -0
  237. package/src/components/CnWeekStripWidgetForm/index.js +4 -0
  238. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +74 -0
  239. package/src/components/index.js +12 -0
  240. package/src/css/detail-page.css +57 -2
  241. package/src/index.js +16 -0
  242. package/src/schemas/app-manifest-v2.schema.json +433 -3
  243. package/src/schemas/app-manifest.schema.json +2 -1
  244. package/src/types/manifest.d.ts +1 -1
  245. package/src/utils/dateVariant.js +120 -0
  246. package/src/utils/detailActionModel.js +256 -0
  247. package/src/utils/dueRule.js +94 -0
  248. package/src/utils/fetchAggregate.js +36 -0
  249. package/src/utils/fetchFilterCounts.js +182 -0
  250. package/src/utils/headers.js +28 -2
  251. package/src/utils/index.js +1 -0
  252. package/src/utils/libraryWidgetKeys.js +5 -0
  253. package/src/utils/readPath.js +27 -0
  254. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"CnSavedViewsControl.vue3.js","sources":["../../../../src/components/CnSavedViewsControl/CnSavedViewsControl.vue?vue&type=template&id=9dd8cb32&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<NcActions\n\t\t:forceMenu=\"true\"\n\t\t:forceName=\"true\"\n\t\t:menuName=\"menuLabel\"\n\t\tdata-testid=\"cn-saved-views-control\"\n\t\t:aria-label=\"menuLabel\">\n\t\t<template #icon>\n\t\t\t<BookmarkOutline :size=\"20\" />\n\t\t</template>\n\n\t\t<NcActionCaption :name=\"t('nextcloud-vue', 'Saved views')\" />\n\n\t\t<!-- Loading state -->\n\t\t<NcActionCaption\n\t\t\tv-if=\"loading\"\n\t\t\tdata-testid=\"cn-saved-views-loading\"\n\t\t\t:name=\"t('nextcloud-vue', 'Loading…')\" />\n\n\t\t<!-- Empty state -->\n\t\t<NcActionCaption\n\t\t\tv-else-if=\"views.length === 0\"\n\t\t\tdata-testid=\"cn-saved-views-empty\"\n\t\t\t:name=\"t('nextcloud-vue', 'No saved views yet')\" />\n\n\t\t<!-- The labels already in use, offered before anybody types a new one. -->\n\t\t<template v-if=\"!loading && offeredLabels.length > 0\">\n\t\t\t<NcActionButton\n\t\t\t\tv-for=\"label in offeredLabels\"\n\t\t\t\t:key=\"`label-${label}`\"\n\t\t\t\tdata-testid=\"cn-saved-views-label\"\n\t\t\t\t:data-label=\"label\"\n\t\t\t\t:modelValue=\"labelFilter === label\"\n\t\t\t\ttype=\"checkbox\"\n\t\t\t\t:aria-label=\"labelFilterLabel(label)\"\n\t\t\t\t@click=\"onLabelFilter(label)\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<TagOutline :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ label }}\n\t\t\t</NcActionButton>\n\t\t\t<NcActionSeparator />\n\t\t</template>\n\n\t\t<!--\n\t\t\tA label that matches nothing says so. Chained to the ROWS rather\n\t\t\tthan to the label list: written as an else-if of the labels block\n\t\t\tit only ever rendered on a page with no labels at all, which is\n\t\t\tthe one case it cannot be about.\n\t\t-->\n\t\t<NcActionCaption\n\t\t\tv-if=\"!loading && views.length > 0 && rows.length === 0\"\n\t\t\tdata-testid=\"cn-saved-views-none-for-label\"\n\t\t\t:name=\"t('nextcloud-vue', 'No views with this label')\" />\n\n\t\t<!-- One row per view, in tree order: the name applies it, a trailing\n\t\t icon button deletes it. NcActions only recognises NcAction* vnodes\n\t\t as menu items and silently drops anything else, so a row with more\n\t\t than one control has to be an NcActionButtonGroup. -->\n\t\t<template v-if=\"!loading\">\n\t\t\t<NcActionButtonGroup\n\t\t\t\tv-for=\"row in rows\"\n\t\t\t\t:key=\"`view-${row.view.id || row.view.slug}`\"\n\t\t\t\tclass=\"cn-saved-view-row\">\n\t\t\t\t<NcActionButton\n\t\t\t\t\tdata-testid=\"cn-saved-views-item\"\n\t\t\t\t\t:data-view-id=\"row.view.id || row.view.slug\"\n\t\t\t\t\t:data-depth=\"row.depth\"\n\t\t\t\t\t:data-group=\"row.group\"\n\t\t\t\t\t:aria-label=\"rowLabel(row)\"\n\t\t\t\t\t@click=\"onApply(row.view)\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<EyeOutline :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ rowName(row) }}\n\t\t\t\t</NcActionButton>\n\t\t\t\t<NcActionButton\n\t\t\t\t\tv-if=\"isOwn(row.view) && row.group !== SEEDED_GROUP\"\n\t\t\t\t\t:key=\"`delete-${row.view.id || row.view.slug}`\"\n\t\t\t\t\tdata-testid=\"cn-saved-views-delete\"\n\t\t\t\t\t:data-view-id=\"row.view.id || row.view.slug\"\n\t\t\t\t\t:aria-label=\"deleteLabel(row.view)\"\n\t\t\t\t\t@click=\"onDeleteRequest(row.view)\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<TrashCanOutline :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcActionButton>\n\t\t\t</NcActionButtonGroup>\n\t\t</template>\n\n\t\t<NcActionSeparator />\n\n\t\t<NcActionButton\n\t\t\tdata-testid=\"cn-saved-views-save\"\n\t\t\t:aria-label=\"t('nextcloud-vue', 'Save current view…')\"\n\t\t\t@click=\"onSaveRequest\">\n\t\t\t<template #icon>\n\t\t\t\t<ContentSaveOutline :size=\"20\" />\n\t\t\t</template>\n\t\t\t{{ t('nextcloud-vue', 'Save current view…') }}\n\t\t</NcActionButton>\n\t</NcActions>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionButtonGroup, NcActionCaption, NcActions, NcActionSeparator } from '@nextcloud/vue'\nimport BookmarkOutline from 'vue-material-design-icons/BookmarkOutline.vue'\nimport ContentSaveOutline from 'vue-material-design-icons/ContentSaveOutline.vue'\nimport EyeOutline from 'vue-material-design-icons/EyeOutline.vue'\nimport TagOutline from 'vue-material-design-icons/TagOutline.vue'\nimport TrashCanOutline from 'vue-material-design-icons/TrashCanOutline.vue'\nimport { buildViewTree, labelsInUse, VIEW_GROUPS } from '../../utils/buildViewTree.js'\nimport { isOwnView } from '../../utils/savedViewHelpers.js'\n\n/**\n * CnSavedViewsControl — toolbar dropdown listing OpenRegister saved-search\n * views for the current index page (saved-views-ui).\n *\n * Purely presentational: the parent (CnIndexPage) owns fetching via\n * `useSavedViewsApi()` and all mutations; this control only lists the\n * given `views` and emits intents:\n *\n * - `@apply(view)` — a view entry was clicked; parent writes the view's\n * stored filters/search/sort into the route query.\n * - `@save-request()` — \"Save current view…\" clicked; parent opens\n * CnSaveViewDialog.\n * - `@delete-request(view)` — a view's trailing delete icon clicked; parent\n * opens a confirm dialog. Only rendered for views the current user owns\n * (`view.owner === currentUserId`) — OpenRegister refuses foreign\n * deletes server-side anyway (owner-scoped 404).\n *\n * @event {object} apply — Apply the clicked view. Payload: the View API object.\n * @event {void} save-request — Open the save-current-view dialog.\n * @event {object} delete-request — Confirm-delete the clicked view. Payload: the View API object.\n */\nexport default {\n\tname: 'CnSavedViewsControl',\n\n\tcomponents: {\n\t\tNcActions,\n\t\tNcActionButton,\n\t\tNcActionButtonGroup,\n\t\tNcActionCaption,\n\t\tNcActionSeparator,\n\t\tBookmarkOutline,\n\t\tContentSaveOutline,\n\t\tEyeOutline,\n\t\tTagOutline,\n\t\tTrashCanOutline,\n\t},\n\n\tprops: {\n\t\t/** Views to list (View API objects from `GET /apps/openregister/api/views`). */\n\t\tviews: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** True while the parent is fetching the view list. */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** The signed-in NC user id — gates the per-view delete affordance. */\n\t\tcurrentUserId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * How deep the tree indents before it flattens. Mirrors\n\t\t * `savedViewTree.maxDepth` in the manifest. Flattening is about\n\t\t * indentation only: a view past the bound still renders.\n\t\t */\n\t\tmaxDepth: {\n\t\t\ttype: Number,\n\t\t\tdefault: 3,\n\t\t},\n\t},\n\n\temits: ['apply', 'delete-request', 'save-request'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/** The label currently filtering the list, or '' for all of them. */\n\t\t\tlabelFilter: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {string} The dropdown trigger label. */\n\t\tmenuLabel() {\n\t\t\treturn t('nextcloud-vue', 'Views')\n\t\t},\n\n\t\t/** @return {string} The seeded group name, for the template. */\n\t\tSEEDED_GROUP() {\n\t\t\treturn VIEW_GROUPS.SEEDED\n\t\t},\n\n\t\t/**\n\t\t * The views in tree order, seeded first, filtered by the active label.\n\t\t *\n\t\t * @return {Array<object>} Rows of `{ view, depth, group, orphaned }`.\n\t\t */\n\t\trows() {\n\t\t\treturn buildViewTree({\n\t\t\t\tviews: this.views,\n\t\t\t\tlabelFilter: this.labelFilter,\n\t\t\t\tmaxDepth: this.maxDepth,\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The labels already in use.\n\t\t *\n\t\t * Read from the UNFILTERED list on purpose: reading it from `rows`\n\t\t * would remove every other label from the menu the moment one was\n\t\t * chosen, and a filter you cannot change without clearing it first is\n\t\t * a filter people stop using.\n\t\t *\n\t\t * @return {Array<string>} The labels.\n\t\t */\n\t\tofferedLabels() {\n\t\t\treturn labelsInUse(this.views)\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * The name a row shows, indented to its depth.\n\t\t *\n\t\t * Indented with figure spaces rather than CSS padding because the row\n\t\t * is an `NcActionButton` whose label is read out as text: a screen\n\t\t * reader gets the same shape a sighted reader does, and no stylesheet\n\t\t * has to know about the tree.\n\t\t *\n\t\t * @param {object} row The row from buildViewTree().\n\t\t * @return {string} The name.\n\t\t */\n\t\trowName(row) {\n\t\t\treturn `${'\\u2007'.repeat(row.depth * 2)}${row.view.name}`\n\t\t},\n\n\t\t/**\n\t\t * What a row is called to somebody who cannot see the indentation.\n\t\t *\n\t\t * Says the level in words, and says when a view's parent is one this\n\t\t * reader cannot see. A child that silently sits at the root looks\n\t\t * exactly like a view that never had a parent.\n\t\t *\n\t\t * @param {object} row The row from buildViewTree().\n\t\t * @return {string} The accessible label.\n\t\t */\n\t\trowLabel(row) {\n\t\t\tconst name = row.depth === 0\n\t\t\t\t? row.view.name\n\t\t\t\t: t('nextcloud-vue', '{name}, level {level}', { name: row.view.name, level: row.depth + 1 })\n\n\t\t\tif (row.orphaned !== true) {\n\t\t\t\treturn name\n\t\t\t}\n\n\t\t\treturn t('nextcloud-vue', '{name}. Parts of this view come from one you cannot see.', { name })\n\t\t},\n\n\t\t/**\n\t\t * The accessible label of a label filter entry.\n\t\t *\n\t\t * @param {string} label The label.\n\t\t * @return {string} The label.\n\t\t */\n\t\tlabelFilterLabel(label) {\n\t\t\treturn this.labelFilter === label\n\t\t\t\t? t('nextcloud-vue', 'Stop filtering on \"{label}\"', { label })\n\t\t\t\t: t('nextcloud-vue', 'Show only views labelled \"{label}\"', { label })\n\t\t},\n\n\t\t/**\n\t\t * Turn a label filter on, or off when it is already on.\n\t\t *\n\t\t * Clicking the active label clears it, so the way out is the way in\n\t\t * rather than a second control somebody has to find.\n\t\t *\n\t\t * @param {string} label The label.\n\t\t */\n\t\tonLabelFilter(label) {\n\t\t\tthis.labelFilter = this.labelFilter === label ? '' : label\n\t\t},\n\n\t\t/**\n\t\t * Whether the delete entry renders for a view.\n\t\t *\n\t\t * @param {object} view The View API object.\n\t\t * @return {boolean} True when the current user owns the view.\n\t\t */\n\t\tisOwn(view) {\n\t\t\treturn isOwnView(view, this.currentUserId)\n\t\t},\n\n\t\t/**\n\t\t * Accessible label for a view's delete entry.\n\t\t *\n\t\t * @param {object} view The View API object.\n\t\t * @return {string} The label.\n\t\t */\n\t\tdeleteLabel(view) {\n\t\t\treturn t('nextcloud-vue', 'Delete \"{name}\"', { name: view.name })\n\t\t},\n\n\t\t/**\n\t\t * View-entry click: hand the view to the parent to apply.\n\t\t *\n\t\t * @param {object} view The clicked View API object.\n\t\t */\n\t\tonApply(view) {\n\t\t\t/**\n\t\t\t * @event apply A view entry was clicked; apply its stored state.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('apply', view)\n\t\t},\n\n\t\t/**\n\t\t * \"Save current view…\" click: ask the parent to open the save dialog.\n\t\t */\n\t\tonSaveRequest() {\n\t\t\t/**\n\t\t\t * @event save-request \"Save current view…\" clicked; open the save dialog.\n\t\t\t */\n\t\t\tthis.$emit('save-request')\n\t\t},\n\n\t\t/**\n\t\t * Delete-entry click: hand the view to the parent to confirm-delete.\n\t\t *\n\t\t * @param {object} view The View API object to delete.\n\t\t */\n\t\tonDeleteRequest(view) {\n\t\t\t/**\n\t\t\t * @event delete-request A view's delete entry was clicked; confirm and delete.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('delete-request', view)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/* NcActionButtonGroup gives every child `flex: 1 1` (equal width) by\n default; override so the name button grows and the delete icon stays\n compact. :deep() is required — these li's belong to NcActionButtonGroup's\n own scope, not this component's. */\n.cn-saved-view-row :deep(.nc-button-group-content) {\n\tgap: 0;\n}\n\n/* Anchored on the FIRST child, not the last: a row carries one or two buttons\n depending on ownership, so \"the last one\" is the name button on a row that\n has only that. */\n.cn-saved-view-row :deep(.nc-button-group-content > li) {\n\tflex: 0 0 auto;\n}\n\n.cn-saved-view-row :deep(.nc-button-group-content > li:first-child) {\n\tflex: 1 1 auto;\n}\n\n/* NcActionButtonGroup also centers .action-button content (fine for an\n icon-only toolbar button, wrong for a row with a name) — restore the\n normal left-aligned NcActionButton look for the name button. */\n.cn-saved-view-row :deep(.nc-button-group-content > li:first-child .action-button) {\n\tjustify-content: flex-start;\n}\n</style>\n"],"names":["_createBlock","_createElementBlock","_Fragment","_renderList","_createVNode","_createTextVNode","_toDisplayString"],"mappings":"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"}
1
+ {"version":3,"file":"CnSavedViewsControl.vue3.js","sources":["../../../../src/components/CnSavedViewsControl/CnSavedViewsControl.vue?vue&type=template&id=9dd8cb32&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<NcActions\n\t\t:forceMenu=\"true\"\n\t\t:forceName=\"true\"\n\t\t:menuName=\"menuLabel\"\n\t\tdata-testid=\"cn-saved-views-control\"\n\t\t:aria-label=\"menuLabel\">\n\t\t<template #icon>\n\t\t\t<BookmarkOutline :size=\"20\" />\n\t\t</template>\n\n\t\t<NcActionCaption :name=\"t('nextcloud-vue', 'Saved views')\" />\n\n\t\t<!-- Loading state -->\n\t\t<NcActionCaption\n\t\t\tv-if=\"loading\"\n\t\t\tdata-testid=\"cn-saved-views-loading\"\n\t\t\t:name=\"t('nextcloud-vue', 'Loading…')\" />\n\n\t\t<!-- Empty state -->\n\t\t<NcActionCaption\n\t\t\tv-else-if=\"views.length === 0\"\n\t\t\tdata-testid=\"cn-saved-views-empty\"\n\t\t\t:name=\"t('nextcloud-vue', 'No saved views yet')\" />\n\n\t\t<!-- The labels already in use, offered before anybody types a new one. -->\n\t\t<template v-if=\"!loading && offeredLabels.length > 0\">\n\t\t\t<NcActionButton\n\t\t\t\tv-for=\"label in offeredLabels\"\n\t\t\t\t:key=\"`label-${label}`\"\n\t\t\t\tdata-testid=\"cn-saved-views-label\"\n\t\t\t\t:data-label=\"label\"\n\t\t\t\t:modelValue=\"labelFilter === label\"\n\t\t\t\ttype=\"checkbox\"\n\t\t\t\t:aria-label=\"labelFilterLabel(label)\"\n\t\t\t\t@click=\"onLabelFilter(label)\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<TagOutline :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ label }}\n\t\t\t</NcActionButton>\n\t\t\t<NcActionSeparator />\n\t\t</template>\n\n\t\t<!--\n\t\t\tA label that matches nothing says so. Chained to the ROWS rather\n\t\t\tthan to the label list: written as an else-if of the labels block\n\t\t\tit only ever rendered on a page with no labels at all, which is\n\t\t\tthe one case it cannot be about.\n\t\t-->\n\t\t<NcActionCaption\n\t\t\tv-if=\"!loading && views.length > 0 && rows.length === 0\"\n\t\t\tdata-testid=\"cn-saved-views-none-for-label\"\n\t\t\t:name=\"t('nextcloud-vue', 'No views with this label')\" />\n\n\t\t<!-- One row per view, in tree order: the name applies it, a trailing\n\t\t icon button deletes it. NcActions only recognises NcAction* vnodes\n\t\t as menu items and silently drops anything else, so a row with more\n\t\t than one control has to be an NcActionButtonGroup. -->\n\t\t<template v-if=\"!loading\">\n\t\t\t<NcActionButtonGroup\n\t\t\t\tv-for=\"row in rows\"\n\t\t\t\t:key=\"`view-${row.view.id || row.view.slug}`\"\n\t\t\t\tclass=\"cn-saved-view-row\">\n\t\t\t\t<NcActionButton\n\t\t\t\t\tdata-testid=\"cn-saved-views-item\"\n\t\t\t\t\t:data-view-id=\"row.view.id || row.view.slug\"\n\t\t\t\t\t:data-depth=\"row.depth\"\n\t\t\t\t\t:data-group=\"row.group\"\n\t\t\t\t\t:aria-label=\"rowLabel(row)\"\n\t\t\t\t\t@click=\"onApply(row.view)\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<EyeOutline :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ rowName(row) }}\n\t\t\t\t</NcActionButton>\n\t\t\t\t<NcActionButton\n\t\t\t\t\tv-if=\"isOwn(row.view) && row.group !== SEEDED_GROUP\"\n\t\t\t\t\t:key=\"`delete-${row.view.id || row.view.slug}`\"\n\t\t\t\t\tdata-testid=\"cn-saved-views-delete\"\n\t\t\t\t\t:data-view-id=\"row.view.id || row.view.slug\"\n\t\t\t\t\t:aria-label=\"deleteLabel(row.view)\"\n\t\t\t\t\t@click=\"onDeleteRequest(row.view)\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<TrashCanOutline :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcActionButton>\n\t\t\t</NcActionButtonGroup>\n\t\t</template>\n\n\t\t<NcActionSeparator />\n\n\t\t<NcActionButton\n\t\t\tdata-testid=\"cn-saved-views-save\"\n\t\t\t:aria-label=\"t('nextcloud-vue', 'Save current view…')\"\n\t\t\t@click=\"onSaveRequest\">\n\t\t\t<template #icon>\n\t\t\t\t<ContentSaveOutline :size=\"20\" />\n\t\t\t</template>\n\t\t\t{{ t('nextcloud-vue', 'Save current view…') }}\n\t\t</NcActionButton>\n\t</NcActions>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActionButtonGroup, NcActionCaption, NcActions, NcActionSeparator } from '@nextcloud/vue'\nimport BookmarkOutline from 'vue-material-design-icons/BookmarkOutline.vue'\nimport ContentSaveOutline from 'vue-material-design-icons/ContentSaveOutline.vue'\nimport EyeOutline from 'vue-material-design-icons/EyeOutline.vue'\nimport TagOutline from 'vue-material-design-icons/TagOutline.vue'\nimport TrashCanOutline from 'vue-material-design-icons/TrashCanOutline.vue'\nimport { buildViewTree, labelsInUse, VIEW_GROUPS } from '../../utils/buildViewTree.js'\nimport { isOwnView } from '../../utils/savedViewHelpers.js'\n\n/**\n * CnSavedViewsControl — toolbar dropdown listing OpenRegister saved-search\n * views for the current index page (saved-views-ui).\n *\n * Purely presentational: the parent (CnIndexPage) owns fetching via\n * `useSavedViewsApi()` and all mutations; this control only lists the\n * given `views` and emits intents:\n *\n * - `@apply(view)` — a view entry was clicked; parent writes the view's\n * stored filters/search/sort into the route query.\n * - `@save-request()` — \"Save current view…\" clicked; parent opens\n * CnSaveViewDialog.\n * - `@delete-request(view)` — a view's trailing delete icon clicked; parent\n * opens a confirm dialog. Only rendered for views the current user owns\n * (`view.owner === currentUserId`) — OpenRegister refuses foreign\n * deletes server-side anyway (owner-scoped 404).\n *\n * @event {object} apply — Apply the clicked view. Payload: the View API object.\n * @event {void} save-request — Open the save-current-view dialog.\n * @event {object} delete-request — Confirm-delete the clicked view. Payload: the View API object.\n */\nexport default {\n\tname: 'CnSavedViewsControl',\n\n\tcomponents: {\n\t\tNcActions,\n\t\tNcActionButton,\n\t\tNcActionButtonGroup,\n\t\tNcActionCaption,\n\t\tNcActionSeparator,\n\t\tBookmarkOutline,\n\t\tContentSaveOutline,\n\t\tEyeOutline,\n\t\tTagOutline,\n\t\tTrashCanOutline,\n\t},\n\n\tprops: {\n\t\t/** Views to list (View API objects from `GET /apps/openregister/api/views`). */\n\t\tviews: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** True while the parent is fetching the view list. */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** The signed-in NC user id — gates the per-view delete affordance. */\n\t\tcurrentUserId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * How deep the tree indents before it flattens. Mirrors\n\t\t * `savedViewTree.maxDepth` in the manifest. Flattening is about\n\t\t * indentation only: a view past the bound still renders.\n\t\t */\n\t\tmaxDepth: {\n\t\t\ttype: Number,\n\t\t\tdefault: 3,\n\t\t},\n\n\t\t/**\n\t\t * How many records each view matches, keyed by view id (or slug). A\n\t\t * view with a number here shows it after its name; a view can also\n\t\t * carry its own `count`. `null` (the default) shows no counts.\n\t\t *\n\t\t * @type {{[viewId: string]: number}|null}\n\t\t */\n\t\tcounts: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['apply', 'delete-request', 'save-request'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/** The label currently filtering the list, or '' for all of them. */\n\t\t\tlabelFilter: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {string} The dropdown trigger label. */\n\t\tmenuLabel() {\n\t\t\treturn t('nextcloud-vue', 'Views')\n\t\t},\n\n\t\t/** @return {string} The seeded group name, for the template. */\n\t\tSEEDED_GROUP() {\n\t\t\treturn VIEW_GROUPS.SEEDED\n\t\t},\n\n\t\t/**\n\t\t * The views in tree order, seeded first, filtered by the active label.\n\t\t *\n\t\t * @return {Array<object>} Rows of `{ view, depth, group, orphaned }`.\n\t\t */\n\t\trows() {\n\t\t\treturn buildViewTree({\n\t\t\t\tviews: this.views,\n\t\t\t\tlabelFilter: this.labelFilter,\n\t\t\t\tmaxDepth: this.maxDepth,\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The labels already in use.\n\t\t *\n\t\t * Read from the UNFILTERED list on purpose: reading it from `rows`\n\t\t * would remove every other label from the menu the moment one was\n\t\t * chosen, and a filter you cannot change without clearing it first is\n\t\t * a filter people stop using.\n\t\t *\n\t\t * @return {Array<string>} The labels.\n\t\t */\n\t\tofferedLabels() {\n\t\t\treturn labelsInUse(this.views)\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * The name a row shows, indented to its depth.\n\t\t *\n\t\t * Indented with figure spaces rather than CSS padding because the row\n\t\t * is an `NcActionButton` whose label is read out as text: a screen\n\t\t * reader gets the same shape a sighted reader does, and no stylesheet\n\t\t * has to know about the tree.\n\t\t *\n\t\t * @param {object} row The row from buildViewTree().\n\t\t * @return {string} The name.\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n\t\t */\n\t\trowName(row) {\n\t\t\treturn `${'\\u2007'.repeat(row.depth * 2)}${this.nameWithCount(row.view)}`\n\t\t},\n\n\t\t/**\n\t\t * The number of records a view matches, or null when none is known:\n\t\t * `counts[id]` (or `counts[slug]`) first, else the view's own `count`.\n\t\t *\n\t\t * @param {object} view The saved view.\n\t\t * @return {number|null}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n\t\t */\n\t\tcountOf(view) {\n\t\t\tconst counts = this.counts || {}\n\t\t\tlet value = counts[view.id]\n\t\t\tif (value === undefined || value === null) {\n\t\t\t\tvalue = counts[view.slug]\n\t\t\t}\n\t\t\tif (value === undefined || value === null) {\n\t\t\t\tvalue = view.count\n\t\t\t}\n\t\t\treturn (typeof value === 'number' && Number.isFinite(value)) ? value : null\n\t\t},\n\n\t\t/**\n\t\t * A view's name, followed by its count when one is known.\n\t\t *\n\t\t * @param {object} view The saved view.\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n\t\t */\n\t\tnameWithCount(view) {\n\t\t\tconst count = this.countOf(view)\n\t\t\treturn count === null ? view.name : `${view.name} (${count})`\n\t\t},\n\n\t\t/**\n\t\t * What a row is called to somebody who cannot see the indentation.\n\t\t *\n\t\t * Says the level in words, and says when a view's parent is one this\n\t\t * reader cannot see. A child that silently sits at the root looks\n\t\t * exactly like a view that never had a parent.\n\t\t *\n\t\t * @param {object} row The row from buildViewTree().\n\t\t * @return {string} The accessible label.\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n\t\t */\n\t\trowLabel(row) {\n\t\t\tconst viewName = this.nameWithCount(row.view)\n\t\t\tconst name = row.depth === 0\n\t\t\t\t? viewName\n\t\t\t\t: t('nextcloud-vue', '{name}, level {level}', { name: viewName, level: row.depth + 1 })\n\n\t\t\tif (row.orphaned !== true) {\n\t\t\t\treturn name\n\t\t\t}\n\n\t\t\treturn t('nextcloud-vue', '{name}. Parts of this view come from one you cannot see.', { name })\n\t\t},\n\n\t\t/**\n\t\t * The accessible label of a label filter entry.\n\t\t *\n\t\t * @param {string} label The label.\n\t\t * @return {string} The label.\n\t\t */\n\t\tlabelFilterLabel(label) {\n\t\t\treturn this.labelFilter === label\n\t\t\t\t? t('nextcloud-vue', 'Stop filtering on \"{label}\"', { label })\n\t\t\t\t: t('nextcloud-vue', 'Show only views labelled \"{label}\"', { label })\n\t\t},\n\n\t\t/**\n\t\t * Turn a label filter on, or off when it is already on.\n\t\t *\n\t\t * Clicking the active label clears it, so the way out is the way in\n\t\t * rather than a second control somebody has to find.\n\t\t *\n\t\t * @param {string} label The label.\n\t\t */\n\t\tonLabelFilter(label) {\n\t\t\tthis.labelFilter = this.labelFilter === label ? '' : label\n\t\t},\n\n\t\t/**\n\t\t * Whether the delete entry renders for a view.\n\t\t *\n\t\t * @param {object} view The View API object.\n\t\t * @return {boolean} True when the current user owns the view.\n\t\t */\n\t\tisOwn(view) {\n\t\t\treturn isOwnView(view, this.currentUserId)\n\t\t},\n\n\t\t/**\n\t\t * Accessible label for a view's delete entry.\n\t\t *\n\t\t * @param {object} view The View API object.\n\t\t * @return {string} The label.\n\t\t */\n\t\tdeleteLabel(view) {\n\t\t\treturn t('nextcloud-vue', 'Delete \"{name}\"', { name: view.name })\n\t\t},\n\n\t\t/**\n\t\t * View-entry click: hand the view to the parent to apply.\n\t\t *\n\t\t * @param {object} view The clicked View API object.\n\t\t */\n\t\tonApply(view) {\n\t\t\t/**\n\t\t\t * @event apply A view entry was clicked; apply its stored state.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('apply', view)\n\t\t},\n\n\t\t/**\n\t\t * \"Save current view…\" click: ask the parent to open the save dialog.\n\t\t */\n\t\tonSaveRequest() {\n\t\t\t/**\n\t\t\t * @event save-request \"Save current view…\" clicked; open the save dialog.\n\t\t\t */\n\t\t\tthis.$emit('save-request')\n\t\t},\n\n\t\t/**\n\t\t * Delete-entry click: hand the view to the parent to confirm-delete.\n\t\t *\n\t\t * @param {object} view The View API object to delete.\n\t\t */\n\t\tonDeleteRequest(view) {\n\t\t\t/**\n\t\t\t * @event delete-request A view's delete entry was clicked; confirm and delete.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('delete-request', view)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/* NcActionButtonGroup gives every child `flex: 1 1` (equal width) by\n default; override so the name button grows and the delete icon stays\n compact. :deep() is required — these li's belong to NcActionButtonGroup's\n own scope, not this component's. */\n.cn-saved-view-row :deep(.nc-button-group-content) {\n\tgap: 0;\n}\n\n/* Anchored on the FIRST child, not the last: a row carries one or two buttons\n depending on ownership, so \"the last one\" is the name button on a row that\n has only that. */\n.cn-saved-view-row :deep(.nc-button-group-content > li) {\n\tflex: 0 0 auto;\n}\n\n.cn-saved-view-row :deep(.nc-button-group-content > li:first-child) {\n\tflex: 1 1 auto;\n}\n\n/* NcActionButtonGroup also centers .action-button content (fine for an\n icon-only toolbar button, wrong for a row with a name) — restore the\n normal left-aligned NcActionButton look for the name button. */\n.cn-saved-view-row :deep(.nc-button-group-content > li:first-child .action-button) {\n\tjustify-content: flex-start;\n}\n</style>\n"],"names":["_createBlock","_createElementBlock","_Fragment","_renderList","_createVNode","_createTextVNode","_toDisplayString"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import script from './CnSegmentedControl.vue2.js';
2
+ import { render } from './CnSegmentedControl.vue3.js';
3
+
4
+ script.render = render;
5
+ script.__scopeId = "data-v-7b344b4f";
6
+ script.__file = "src/components/CnSegmentedControl/CnSegmentedControl.vue";
7
+ const __sfc_main = script;
8
+
9
+ export { __sfc_main as default };
10
+ //# sourceMappingURL=CnSegmentedControl.vue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CnSegmentedControl.vue.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}
@@ -0,0 +1,186 @@
1
+ /**
2
+ * CnSegmentedControl is a switch between a few views of the same thing, such
3
+ * as "My work / My team". One option is always the chosen one.
4
+ *
5
+ * It is a radio group: screen readers announce the group and which option is
6
+ * checked, the arrow keys move the choice, and Tab enters and leaves the group
7
+ * in one step. Give it a name with `aria-label` (or `aria-labelledby`).
8
+ *
9
+ * ```vue
10
+ * <CnSegmentedControl
11
+ * v-model="view"
12
+ * aria-label="View"
13
+ * :options="[
14
+ * { value: 'mine', label: 'My work' },
15
+ * { value: 'team', label: 'My team' },
16
+ * ]" />
17
+ * ```
18
+ *
19
+ * To switch between panels of content, use `CnTabs` with `variant="segmented"`
20
+ * instead: that keeps the tab and panel semantics and looks the same.
21
+ */
22
+ var script = {
23
+ name: 'CnSegmentedControl',
24
+
25
+ props: {
26
+ /**
27
+ * The options. Each is `{ value, label, disabled?, count? }`, or a
28
+ * plain string that is both the value and the label. `count` shows a
29
+ * number after the label.
30
+ *
31
+ * @type {Array<string|{value: (string|number|boolean), label: string, disabled?: boolean, count?: number}>}
32
+ */
33
+ options: {
34
+ type: Array,
35
+ required: true,
36
+ },
37
+
38
+ /**
39
+ * The value of the chosen option (v-model).
40
+ *
41
+ * @type {string|number|boolean|null}
42
+ */
43
+ modelValue: {
44
+ type: [Boolean, String, Number],
45
+ default: null,
46
+ },
47
+
48
+ /** Accessible name of the group, read out when focus enters it. */
49
+ ariaLabel: {
50
+ type: String,
51
+ default: '',
52
+ },
53
+
54
+ /** Id of an element that names the group, as an alternative to `ariaLabel`. */
55
+ ariaLabelledby: {
56
+ type: String,
57
+ default: '',
58
+ },
59
+
60
+ /** Stretch the control to the full width, with options of equal width. */
61
+ stretch: {
62
+ type: Boolean,
63
+ default: false,
64
+ },
65
+ },
66
+
67
+ emits: [
68
+ /**
69
+ * Emitted with the value of the newly chosen option.
70
+ *
71
+ * @event update:modelValue
72
+ * @type {string|number|boolean}
73
+ */
74
+ 'update:modelValue',
75
+ ],
76
+
77
+ computed: {
78
+ /**
79
+ * The options in one shape.
80
+ *
81
+ * @return {Array<{key: string, value: (string|number|boolean), label: string, disabled: boolean, count: (number|null)}>}
82
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control
83
+ */
84
+ normalizedOptions() {
85
+ return (Array.isArray(this.options) ? this.options : [])
86
+ .filter((option) => option !== null && option !== undefined)
87
+ .map((option) => {
88
+ if (typeof option !== 'object') {
89
+ return { key: String(option), value: option, label: String(option), disabled: false, count: null }
90
+ }
91
+ return {
92
+ key: String(option.value),
93
+ value: option.value,
94
+ label: String(option.label ?? option.value),
95
+ disabled: option.disabled === true,
96
+ count: Number.isFinite(option.count) ? option.count : null,
97
+ }
98
+ })
99
+ },
100
+
101
+ /**
102
+ * The option Tab lands on: the chosen one, else the first enabled one.
103
+ *
104
+ * @return {number} Its index, or -1 when no option can take focus.
105
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control
106
+ */
107
+ focusableIndex() {
108
+ const checked = this.normalizedOptions.findIndex((option) => this.isChecked(option) && !option.disabled);
109
+ if (checked !== -1) {
110
+ return checked
111
+ }
112
+ return this.normalizedOptions.findIndex((option) => !option.disabled)
113
+ },
114
+ },
115
+
116
+ methods: {
117
+ /**
118
+ * Whether an option is the chosen one.
119
+ *
120
+ * @param {{value: (string|number|boolean)}} option The option.
121
+ * @return {boolean}
122
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control
123
+ */
124
+ isChecked(option) {
125
+ return this.modelValue !== null && String(option.value) === String(this.modelValue)
126
+ },
127
+
128
+ /**
129
+ * Choose an option.
130
+ *
131
+ * @param {{value: (string|number|boolean), disabled: boolean}} option The option.
132
+ * @return {void}
133
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control
134
+ */
135
+ select(option) {
136
+ if (option.disabled || this.isChecked(option)) {
137
+ return
138
+ }
139
+ this.$emit('update:modelValue', option.value);
140
+ },
141
+
142
+ /**
143
+ * Arrow keys, Home and End move the choice, as in a native radio
144
+ * group. Disabled options are passed over and the ends wrap around.
145
+ *
146
+ * @param {KeyboardEvent} event The key press.
147
+ * @return {void}
148
+ * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control
149
+ */
150
+ onKeydown(event) {
151
+ const enabled = this.normalizedOptions
152
+ .map((option, index) => ({ option, index }))
153
+ .filter((entry) => !entry.option.disabled);
154
+ if (enabled.length === 0) {
155
+ return
156
+ }
157
+ const buttons = Array.from(this.$el.querySelectorAll('[role="radio"]'));
158
+ const focused = buttons.indexOf(document.activeElement);
159
+ const from = focused !== -1 ? focused : this.focusableIndex;
160
+ let position = enabled.findIndex((entry) => entry.index === from);
161
+ if (position === -1) {
162
+ position = 0;
163
+ }
164
+ if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {
165
+ position = (position + 1) % enabled.length;
166
+ } else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {
167
+ position = (position - 1 + enabled.length) % enabled.length;
168
+ } else if (event.key === 'Home') {
169
+ position = 0;
170
+ } else if (event.key === 'End') {
171
+ position = enabled.length - 1;
172
+ } else {
173
+ return
174
+ }
175
+ event.preventDefault();
176
+ const target = enabled[position];
177
+ this.select(target.option);
178
+ if (buttons[target.index] && typeof buttons[target.index].focus === 'function') {
179
+ buttons[target.index].focus();
180
+ }
181
+ },
182
+ },
183
+ };
184
+
185
+ export { script as default };
186
+ //# sourceMappingURL=CnSegmentedControl.vue2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CnSegmentedControl.vue2.js","sources":["../../../../src/components/CnSegmentedControl/CnSegmentedControl.vue"],"sourcesContent":["<!--\n SPDX-FileCopyrightText: 2026 Conduction B.V.\n SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div\n\t\tclass=\"cn-segmented-control\"\n\t\t:class=\"{ 'cn-segmented-control--stretch': stretch }\"\n\t\trole=\"radiogroup\"\n\t\t:aria-label=\"ariaLabel || null\"\n\t\t:aria-labelledby=\"ariaLabelledby || null\"\n\t\t@keydown=\"onKeydown\">\n\t\t<button\n\t\t\tv-for=\"(option, index) in normalizedOptions\"\n\t\t\t:key=\"option.key\"\n\t\t\ttype=\"button\"\n\t\t\trole=\"radio\"\n\t\t\tclass=\"cn-segmented-control__option\"\n\t\t\t:class=\"{ 'cn-segmented-control__option--checked': isChecked(option) }\"\n\t\t\t:aria-checked=\"isChecked(option) ? 'true' : 'false'\"\n\t\t\t:tabindex=\"index === focusableIndex ? 0 : -1\"\n\t\t\t:disabled=\"option.disabled || null\"\n\t\t\t:data-value=\"option.key\"\n\t\t\t@click=\"select(option)\">\n\t\t\t<!-- @slot option Replaces the content of one option. -->\n\t\t\t<!-- @binding {object} option The option: `{ value, label, disabled, count }`. -->\n\t\t\t<!-- @binding {boolean} checked Whether this option is the chosen one. -->\n\t\t\t<slot name=\"option\" :option=\"option\" :checked=\"isChecked(option)\">\n\t\t\t\t<span class=\"cn-segmented-control__label\">{{ option.label }}</span>\n\t\t\t\t<!-- The leading space is not drawn in this flex row; it keeps a\n\t\t\t\t screen reader from reading label and count as one word. -->\n\t\t\t\t<span v-if=\"option.count !== null\" class=\"cn-segmented-control__count\">{{ ' ' + option.count }}</span>\n\t\t\t</slot>\n\t\t</button>\n\t</div>\n</template>\n\n<script>\n/**\n * CnSegmentedControl is a switch between a few views of the same thing, such\n * as \"My work / My team\". One option is always the chosen one.\n *\n * It is a radio group: screen readers announce the group and which option is\n * checked, the arrow keys move the choice, and Tab enters and leaves the group\n * in one step. Give it a name with `aria-label` (or `aria-labelledby`).\n *\n * ```vue\n * <CnSegmentedControl\n * v-model=\"view\"\n * aria-label=\"View\"\n * :options=\"[\n * { value: 'mine', label: 'My work' },\n * { value: 'team', label: 'My team' },\n * ]\" />\n * ```\n *\n * To switch between panels of content, use `CnTabs` with `variant=\"segmented\"`\n * instead: that keeps the tab and panel semantics and looks the same.\n */\nexport default {\n\tname: 'CnSegmentedControl',\n\n\tprops: {\n\t\t/**\n\t\t * The options. Each is `{ value, label, disabled?, count? }`, or a\n\t\t * plain string that is both the value and the label. `count` shows a\n\t\t * number after the label.\n\t\t *\n\t\t * @type {Array<string|{value: (string|number|boolean), label: string, disabled?: boolean, count?: number}>}\n\t\t */\n\t\toptions: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/**\n\t\t * The value of the chosen option (v-model).\n\t\t *\n\t\t * @type {string|number|boolean|null}\n\t\t */\n\t\tmodelValue: {\n\t\t\ttype: [Boolean, String, Number],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Accessible name of the group, read out when focus enters it. */\n\t\tariaLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Id of an element that names the group, as an alternative to `ariaLabel`. */\n\t\tariaLabelledby: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Stretch the control to the full width, with options of equal width. */\n\t\tstretch: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Emitted with the value of the newly chosen option.\n\t\t *\n\t\t * @event update:modelValue\n\t\t * @type {string|number|boolean}\n\t\t */\n\t\t'update:modelValue',\n\t],\n\n\tcomputed: {\n\t\t/**\n\t\t * The options in one shape.\n\t\t *\n\t\t * @return {Array<{key: string, value: (string|number|boolean), label: string, disabled: boolean, count: (number|null)}>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control\n\t\t */\n\t\tnormalizedOptions() {\n\t\t\treturn (Array.isArray(this.options) ? this.options : [])\n\t\t\t\t.filter((option) => option !== null && option !== undefined)\n\t\t\t\t.map((option) => {\n\t\t\t\t\tif (typeof option !== 'object') {\n\t\t\t\t\t\treturn { key: String(option), value: option, label: String(option), disabled: false, count: null }\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkey: String(option.value),\n\t\t\t\t\t\tvalue: option.value,\n\t\t\t\t\t\tlabel: String(option.label ?? option.value),\n\t\t\t\t\t\tdisabled: option.disabled === true,\n\t\t\t\t\t\tcount: Number.isFinite(option.count) ? option.count : null,\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The option Tab lands on: the chosen one, else the first enabled one.\n\t\t *\n\t\t * @return {number} Its index, or -1 when no option can take focus.\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control\n\t\t */\n\t\tfocusableIndex() {\n\t\t\tconst checked = this.normalizedOptions.findIndex((option) => this.isChecked(option) && !option.disabled)\n\t\t\tif (checked !== -1) {\n\t\t\t\treturn checked\n\t\t\t}\n\t\t\treturn this.normalizedOptions.findIndex((option) => !option.disabled)\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether an option is the chosen one.\n\t\t *\n\t\t * @param {{value: (string|number|boolean)}} option The option.\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control\n\t\t */\n\t\tisChecked(option) {\n\t\t\treturn this.modelValue !== null && String(option.value) === String(this.modelValue)\n\t\t},\n\n\t\t/**\n\t\t * Choose an option.\n\t\t *\n\t\t * @param {{value: (string|number|boolean), disabled: boolean}} option The option.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control\n\t\t */\n\t\tselect(option) {\n\t\t\tif (option.disabled || this.isChecked(option)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('update:modelValue', option.value)\n\t\t},\n\n\t\t/**\n\t\t * Arrow keys, Home and End move the choice, as in a native radio\n\t\t * group. Disabled options are passed over and the ends wrap around.\n\t\t *\n\t\t * @param {KeyboardEvent} event The key press.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control\n\t\t */\n\t\tonKeydown(event) {\n\t\t\tconst enabled = this.normalizedOptions\n\t\t\t\t.map((option, index) => ({ option, index }))\n\t\t\t\t.filter((entry) => !entry.option.disabled)\n\t\t\tif (enabled.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst buttons = Array.from(this.$el.querySelectorAll('[role=\"radio\"]'))\n\t\t\tconst focused = buttons.indexOf(document.activeElement)\n\t\t\tconst from = focused !== -1 ? focused : this.focusableIndex\n\t\t\tlet position = enabled.findIndex((entry) => entry.index === from)\n\t\t\tif (position === -1) {\n\t\t\t\tposition = 0\n\t\t\t}\n\t\t\tif (event.key === 'ArrowRight' || event.key === 'ArrowDown') {\n\t\t\t\tposition = (position + 1) % enabled.length\n\t\t\t} else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {\n\t\t\t\tposition = (position - 1 + enabled.length) % enabled.length\n\t\t\t} else if (event.key === 'Home') {\n\t\t\t\tposition = 0\n\t\t\t} else if (event.key === 'End') {\n\t\t\t\tposition = enabled.length - 1\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tconst target = enabled[position]\n\t\t\tthis.select(target.option)\n\t\t\tif (buttons[target.index] && typeof buttons[target.index].focus === 'function') {\n\t\t\t\tbuttons[target.index].focus()\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-segmented-control {\n\tdisplay: inline-flex;\n\tgap: 4px;\n\tmax-width: 100%;\n\tpadding: 4px;\n\tborder-radius: var(--border-radius-container, var(--border-radius-large, 10px));\n\tbackground-color: var(--color-background-dark);\n\toverflow-x: auto;\n}\n\n.cn-segmented-control--stretch {\n\tdisplay: flex;\n}\n\n.cn-segmented-control--stretch .cn-segmented-control__option {\n\tflex: 1 1 0;\n}\n\n/* The parent class outscores Nextcloud's server rule that gives every plain\n `button` a margin and a border. */\n.cn-segmented-control .cn-segmented-control__option {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\tgap: 8px;\n\tbox-sizing: border-box;\n\tmin-height: 36px;\n\tmargin: 0;\n\tpadding: 0 16px;\n\tborder: 1px solid transparent;\n\tborder-radius: var(--border-radius-element, var(--border-radius-large, 8px));\n\tbackground-color: transparent;\n\t/* Full-contrast label on every option: the choice is carried by the\n\t raised surface, not by dimming the others. */\n\tcolor: var(--color-main-text);\n\tfont: inherit;\n\tfont-weight: 600;\n\twhite-space: nowrap;\n\tcursor: pointer;\n}\n\n.cn-segmented-control .cn-segmented-control__option:hover {\n\tbackground-color: var(--color-background-hover);\n}\n\n.cn-segmented-control .cn-segmented-control__option:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 2px;\n}\n\n/* The border survives forced-colours mode, which drops background and shadow. */\n.cn-segmented-control .cn-segmented-control__option--checked,\n.cn-segmented-control .cn-segmented-control__option--checked:hover {\n\tborder-color: var(--color-border-dark);\n\tbackground-color: var(--color-main-background);\n\tbox-shadow: 0 1px 2px var(--color-box-shadow);\n}\n\n.cn-segmented-control .cn-segmented-control__option[disabled] {\n\tcursor: default;\n\topacity: 0.5;\n}\n\n.cn-segmented-control__count {\n\tfont-weight: 400;\n}\n</style>\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import { openBlock, createElementBlock, normalizeClass, Fragment, renderList, renderSlot, createElementVNode, toDisplayString, createCommentVNode } from 'vue';
2
+
3
+ const _hoisted_1 = ["aria-label", "aria-labelledby"];
4
+ const _hoisted_2 = ["aria-checked", "tabindex", "disabled", "data-value", "onClick"];
5
+ const _hoisted_3 = { class: "cn-segmented-control__label" };
6
+ const _hoisted_4 = {
7
+ key: 0,
8
+ class: "cn-segmented-control__count"
9
+ };
10
+
11
+ function render(_ctx, _cache, $props, $setup, $data, $options) {
12
+ return (openBlock(), createElementBlock("div", {
13
+ class: normalizeClass(["cn-segmented-control", { 'cn-segmented-control--stretch': $props.stretch }]),
14
+ role: "radiogroup",
15
+ "aria-label": $props.ariaLabel || null,
16
+ "aria-labelledby": $props.ariaLabelledby || null,
17
+ onKeydown: _cache[0] || (_cache[0] = (...args) => ($options.onKeydown && $options.onKeydown(...args)))
18
+ }, [
19
+ (openBlock(true), createElementBlock(Fragment, null, renderList($options.normalizedOptions, (option, index) => {
20
+ return (openBlock(), createElementBlock("button", {
21
+ key: option.key,
22
+ type: "button",
23
+ role: "radio",
24
+ class: normalizeClass(["cn-segmented-control__option", { 'cn-segmented-control__option--checked': $options.isChecked(option) }]),
25
+ "aria-checked": $options.isChecked(option) ? 'true' : 'false',
26
+ tabindex: index === $options.focusableIndex ? 0 : -1,
27
+ disabled: option.disabled || null,
28
+ "data-value": option.key,
29
+ onClick: $event => ($options.select(option))
30
+ }, [
31
+ renderSlot(_ctx.$slots, "option", {
32
+ option: option,
33
+ checked: $options.isChecked(option)
34
+ }, () => [
35
+ createElementVNode("span", _hoisted_3, toDisplayString(option.label), 1 /* TEXT */),
36
+ (option.count !== null)
37
+ ? (openBlock(), createElementBlock("span", _hoisted_4, toDisplayString(' ' + option.count), 1 /* TEXT */))
38
+ : createCommentVNode("v-if", true)
39
+ ])
40
+ ], 10 /* CLASS, PROPS */, _hoisted_2))
41
+ }), 128 /* KEYED_FRAGMENT */))
42
+ ], 42 /* CLASS, PROPS, NEED_HYDRATION */, _hoisted_1))
43
+ }
44
+
45
+ export { render };
46
+ //# sourceMappingURL=CnSegmentedControl.vue3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CnSegmentedControl.vue3.js","sources":["../../../../src/components/CnSegmentedControl/CnSegmentedControl.vue?vue&type=template&id=7b344b4f&lang.js"],"sourcesContent":["<!--\n SPDX-FileCopyrightText: 2026 Conduction B.V.\n SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div\n\t\tclass=\"cn-segmented-control\"\n\t\t:class=\"{ 'cn-segmented-control--stretch': stretch }\"\n\t\trole=\"radiogroup\"\n\t\t:aria-label=\"ariaLabel || null\"\n\t\t:aria-labelledby=\"ariaLabelledby || null\"\n\t\t@keydown=\"onKeydown\">\n\t\t<button\n\t\t\tv-for=\"(option, index) in normalizedOptions\"\n\t\t\t:key=\"option.key\"\n\t\t\ttype=\"button\"\n\t\t\trole=\"radio\"\n\t\t\tclass=\"cn-segmented-control__option\"\n\t\t\t:class=\"{ 'cn-segmented-control__option--checked': isChecked(option) }\"\n\t\t\t:aria-checked=\"isChecked(option) ? 'true' : 'false'\"\n\t\t\t:tabindex=\"index === focusableIndex ? 0 : -1\"\n\t\t\t:disabled=\"option.disabled || null\"\n\t\t\t:data-value=\"option.key\"\n\t\t\t@click=\"select(option)\">\n\t\t\t<!-- @slot option Replaces the content of one option. -->\n\t\t\t<!-- @binding {object} option The option: `{ value, label, disabled, count }`. -->\n\t\t\t<!-- @binding {boolean} checked Whether this option is the chosen one. -->\n\t\t\t<slot name=\"option\" :option=\"option\" :checked=\"isChecked(option)\">\n\t\t\t\t<span class=\"cn-segmented-control__label\">{{ option.label }}</span>\n\t\t\t\t<!-- The leading space is not drawn in this flex row; it keeps a\n\t\t\t\t screen reader from reading label and count as one word. -->\n\t\t\t\t<span v-if=\"option.count !== null\" class=\"cn-segmented-control__count\">{{ ' ' + option.count }}</span>\n\t\t\t</slot>\n\t\t</button>\n\t</div>\n</template>\n\n<script>\n/**\n * CnSegmentedControl is a switch between a few views of the same thing, such\n * as \"My work / My team\". One option is always the chosen one.\n *\n * It is a radio group: screen readers announce the group and which option is\n * checked, the arrow keys move the choice, and Tab enters and leaves the group\n * in one step. Give it a name with `aria-label` (or `aria-labelledby`).\n *\n * ```vue\n * <CnSegmentedControl\n * v-model=\"view\"\n * aria-label=\"View\"\n * :options=\"[\n * { value: 'mine', label: 'My work' },\n * { value: 'team', label: 'My team' },\n * ]\" />\n * ```\n *\n * To switch between panels of content, use `CnTabs` with `variant=\"segmented\"`\n * instead: that keeps the tab and panel semantics and looks the same.\n */\nexport default {\n\tname: 'CnSegmentedControl',\n\n\tprops: {\n\t\t/**\n\t\t * The options. Each is `{ value, label, disabled?, count? }`, or a\n\t\t * plain string that is both the value and the label. `count` shows a\n\t\t * number after the label.\n\t\t *\n\t\t * @type {Array<string|{value: (string|number|boolean), label: string, disabled?: boolean, count?: number}>}\n\t\t */\n\t\toptions: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/**\n\t\t * The value of the chosen option (v-model).\n\t\t *\n\t\t * @type {string|number|boolean|null}\n\t\t */\n\t\tmodelValue: {\n\t\t\ttype: [Boolean, String, Number],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Accessible name of the group, read out when focus enters it. */\n\t\tariaLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Id of an element that names the group, as an alternative to `ariaLabel`. */\n\t\tariaLabelledby: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Stretch the control to the full width, with options of equal width. */\n\t\tstretch: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Emitted with the value of the newly chosen option.\n\t\t *\n\t\t * @event update:modelValue\n\t\t * @type {string|number|boolean}\n\t\t */\n\t\t'update:modelValue',\n\t],\n\n\tcomputed: {\n\t\t/**\n\t\t * The options in one shape.\n\t\t *\n\t\t * @return {Array<{key: string, value: (string|number|boolean), label: string, disabled: boolean, count: (number|null)}>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control\n\t\t */\n\t\tnormalizedOptions() {\n\t\t\treturn (Array.isArray(this.options) ? this.options : [])\n\t\t\t\t.filter((option) => option !== null && option !== undefined)\n\t\t\t\t.map((option) => {\n\t\t\t\t\tif (typeof option !== 'object') {\n\t\t\t\t\t\treturn { key: String(option), value: option, label: String(option), disabled: false, count: null }\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkey: String(option.value),\n\t\t\t\t\t\tvalue: option.value,\n\t\t\t\t\t\tlabel: String(option.label ?? option.value),\n\t\t\t\t\t\tdisabled: option.disabled === true,\n\t\t\t\t\t\tcount: Number.isFinite(option.count) ? option.count : null,\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The option Tab lands on: the chosen one, else the first enabled one.\n\t\t *\n\t\t * @return {number} Its index, or -1 when no option can take focus.\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control\n\t\t */\n\t\tfocusableIndex() {\n\t\t\tconst checked = this.normalizedOptions.findIndex((option) => this.isChecked(option) && !option.disabled)\n\t\t\tif (checked !== -1) {\n\t\t\t\treturn checked\n\t\t\t}\n\t\t\treturn this.normalizedOptions.findIndex((option) => !option.disabled)\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether an option is the chosen one.\n\t\t *\n\t\t * @param {{value: (string|number|boolean)}} option The option.\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control\n\t\t */\n\t\tisChecked(option) {\n\t\t\treturn this.modelValue !== null && String(option.value) === String(this.modelValue)\n\t\t},\n\n\t\t/**\n\t\t * Choose an option.\n\t\t *\n\t\t * @param {{value: (string|number|boolean), disabled: boolean}} option The option.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control\n\t\t */\n\t\tselect(option) {\n\t\t\tif (option.disabled || this.isChecked(option)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('update:modelValue', option.value)\n\t\t},\n\n\t\t/**\n\t\t * Arrow keys, Home and End move the choice, as in a native radio\n\t\t * group. Disabled options are passed over and the ends wrap around.\n\t\t *\n\t\t * @param {KeyboardEvent} event The key press.\n\t\t * @return {void}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-segmented-control\n\t\t */\n\t\tonKeydown(event) {\n\t\t\tconst enabled = this.normalizedOptions\n\t\t\t\t.map((option, index) => ({ option, index }))\n\t\t\t\t.filter((entry) => !entry.option.disabled)\n\t\t\tif (enabled.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst buttons = Array.from(this.$el.querySelectorAll('[role=\"radio\"]'))\n\t\t\tconst focused = buttons.indexOf(document.activeElement)\n\t\t\tconst from = focused !== -1 ? focused : this.focusableIndex\n\t\t\tlet position = enabled.findIndex((entry) => entry.index === from)\n\t\t\tif (position === -1) {\n\t\t\t\tposition = 0\n\t\t\t}\n\t\t\tif (event.key === 'ArrowRight' || event.key === 'ArrowDown') {\n\t\t\t\tposition = (position + 1) % enabled.length\n\t\t\t} else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {\n\t\t\t\tposition = (position - 1 + enabled.length) % enabled.length\n\t\t\t} else if (event.key === 'Home') {\n\t\t\t\tposition = 0\n\t\t\t} else if (event.key === 'End') {\n\t\t\t\tposition = enabled.length - 1\n\t\t\t} else {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tconst target = enabled[position]\n\t\t\tthis.select(target.option)\n\t\t\tif (buttons[target.index] && typeof buttons[target.index].focus === 'function') {\n\t\t\t\tbuttons[target.index].focus()\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-segmented-control {\n\tdisplay: inline-flex;\n\tgap: 4px;\n\tmax-width: 100%;\n\tpadding: 4px;\n\tborder-radius: var(--border-radius-container, var(--border-radius-large, 10px));\n\tbackground-color: var(--color-background-dark);\n\toverflow-x: auto;\n}\n\n.cn-segmented-control--stretch {\n\tdisplay: flex;\n}\n\n.cn-segmented-control--stretch .cn-segmented-control__option {\n\tflex: 1 1 0;\n}\n\n/* The parent class outscores Nextcloud's server rule that gives every plain\n `button` a margin and a border. */\n.cn-segmented-control .cn-segmented-control__option {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\tgap: 8px;\n\tbox-sizing: border-box;\n\tmin-height: 36px;\n\tmargin: 0;\n\tpadding: 0 16px;\n\tborder: 1px solid transparent;\n\tborder-radius: var(--border-radius-element, var(--border-radius-large, 8px));\n\tbackground-color: transparent;\n\t/* Full-contrast label on every option: the choice is carried by the\n\t raised surface, not by dimming the others. */\n\tcolor: var(--color-main-text);\n\tfont: inherit;\n\tfont-weight: 600;\n\twhite-space: nowrap;\n\tcursor: pointer;\n}\n\n.cn-segmented-control .cn-segmented-control__option:hover {\n\tbackground-color: var(--color-background-hover);\n}\n\n.cn-segmented-control .cn-segmented-control__option:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 2px;\n}\n\n/* The border survives forced-colours mode, which drops background and shadow. */\n.cn-segmented-control .cn-segmented-control__option--checked,\n.cn-segmented-control .cn-segmented-control__option--checked:hover {\n\tborder-color: var(--color-border-dark);\n\tbackground-color: var(--color-main-background);\n\tbox-shadow: 0 1px 2px var(--color-box-shadow);\n}\n\n.cn-segmented-control .cn-segmented-control__option[disabled] {\n\tcursor: default;\n\topacity: 0.5;\n}\n\n.cn-segmented-control__count {\n\tfont-weight: 400;\n}\n</style>\n"],"names":["_normalizeClass","_renderList","_renderSlot","_createElementVNode","_toDisplayString"],"mappings":";;;;AAgCS,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,6BAAA,GAAA;;;;;;;;IAtBP,KAAK,EAAAA,cAAA,CAAA,CAAC,sBAAsB,EAAA,EAAA,+BAAA,EACR,MAAA,CAAA,OAAA,EAAA,CAAA,CAAA;AACpB,IAAA,IAAI,EAAC,YAAC;AACL,IAAA,YAAU,EAAE,MAAS,CAAA,SAAA,IAAA,IAAA;AACrB,IAAA,iBAAe,EAAC,MAAA,CAAA,cAAA,IAAA,IAAA;AAChB,IAAA,SAAO,0CAAE,QAAG,CAAA,SAAA,IAAA,QAAA,CAAA,SAAA,CAAA,GAAA,IAAA,CAAA,CAAA,CAAA;;iDAEc,EAAA,IAAA,EAAAC,UAAA,CAAA,QAAA,CAAA,iBAAS,EAA3B,CAAA,MAAM,EAAE,KAAK,KAAA;;QACpB,GAAG,EAAE,MAAM,CAAC,GAAG;AAChB,QAAA,IAAI,EAAC,QAAQ;AACb,QAAA,IAAI,EAAC,OAAO;AACZ,QAAA,KAAK,EAACD,cAAA,CAAA,CAAA,8BAA4B,EACX,EAAA,uCAAA,EAAA,QAAA,CAAA,SAAA,CAAA,MAAA,CAAA,EAAA,CAAA,CAAA;QACtB,cAAY,EAAE,QAAQ,CAAA,SAAA,CAAA,MAAA,CAAA,GAAA,MAAA,GAAA,OAAA;QACtB,QAAQ,EAAE,KAAK,KAAK,QAAc,CAAA,cAAA,GAAA,CAAA,GAAA,CAAA,CAAA;QAClC,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAA,IAAA;QACzB,YAAU,EAAG,MAAK,CAAC,GAAE;QACrB,OAAK,EAAA,MAAA,KAAE,QAAM,CAAA,MAAA,CAAC,MAAM,CAAA,CAAA;;QAIpBE;AAAoB,UAAA,MAAM,QAAO;UAAI,OAAO,EAAE,QAAS,CAAA,SAAA,CAAC,MAAM,CAAA;WAA9D;AACA,UAAAC,kBAAA,CAAK,MAAL,EAAA,UAAK,EAAAC,eAAA,CAAA,MAAA,CAAA,KAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;;;;;;"}
@@ -0,0 +1,11 @@
1
+ import script from './CnStackedBarWidget.vue2.js';
2
+ export { rampColor } from './CnStackedBarWidget.vue2.js';
3
+ import { render } from './CnStackedBarWidget.vue3.js';
4
+
5
+ script.render = render;
6
+ script.__scopeId = "data-v-c0bac1a2";
7
+ script.__file = "src/components/CnStackedBarWidget/CnStackedBarWidget.vue";
8
+ const __sfc_main = script;
9
+
10
+ export { __sfc_main as default };
11
+ //# sourceMappingURL=CnStackedBarWidget.vue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CnStackedBarWidget.vue.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;"}