@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,319 @@
1
+ <!--
2
+ - SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ - SPDX-License-Identifier: EUPL-1.2
4
+ -->
5
+
6
+ <template>
7
+ <section
8
+ v-if="normalisedItems.length > 0"
9
+ class="cn-next-step-card"
10
+ data-testid="cn-next-step-card"
11
+ :aria-labelledby="title ? titleId : null"
12
+ :aria-label="title ? null : fallbackLabel">
13
+ <div class="cn-next-step-card__main">
14
+ <component
15
+ :is="titleTag"
16
+ v-if="title"
17
+ :id="titleId"
18
+ class="cn-next-step-card__title">
19
+ {{ title }}
20
+ </component>
21
+ <ul class="cn-next-step-card__list">
22
+ <li
23
+ v-for="(item, index) in normalisedItems"
24
+ :key="`${index}-${item.label}`"
25
+ class="cn-next-step-card__item"
26
+ :class="{
27
+ 'cn-next-step-card__item--done': item.done,
28
+ 'cn-next-step-card__item--current': index === currentIndex,
29
+ }"
30
+ data-testid="cn-next-step-item">
31
+ <span class="cn-next-step-card__marker" aria-hidden="true">
32
+ <Check v-if="item.done" :size="14" />
33
+ </span>
34
+ <span class="cn-next-step-card__label">{{ item.label }}</span>
35
+ <span v-if="item.hint" class="cn-next-step-card__hint">{{ item.hint }}</span>
36
+ <!-- The state in words. The marker is a shape and a colour, and
37
+ neither reaches a screen reader or a reader who cannot tell
38
+ the two colours apart. -->
39
+ <span class="cn-next-step-card__state">{{ item.done ? doneLabel : todoLabel }}</span>
40
+ </li>
41
+ </ul>
42
+ </div>
43
+ <div v-if="actionLabel || $slots.action || after" class="cn-next-step-card__side">
44
+ <!-- @slot action Replace the primary button, for a host that renders its own control. -->
45
+ <slot name="action">
46
+ <NcButton
47
+ v-if="actionLabel"
48
+ :id="actionId || null"
49
+ variant="primary"
50
+ :disabled="actionDisabled"
51
+ data-testid="cn-next-step-action"
52
+ @click="$emit('action')">
53
+ {{ actionLabel }}
54
+ </NcButton>
55
+ </slot>
56
+ <span v-if="after" class="cn-next-step-card__after">{{ after }}</span>
57
+ </div>
58
+ </section>
59
+ </template>
60
+
61
+ <script>
62
+ import { translate as t } from '@nextcloud/l10n'
63
+ import { NcButton } from '@nextcloud/vue'
64
+ import Check from 'vue-material-design-icons/Check.vue'
65
+
66
+ let nextStepCardUid = 0
67
+
68
+ /**
69
+ * CnNextStepCard: what this record needs now, next to the button that does it.
70
+ *
71
+ * A short checklist for the current step and one primary action. It answers
72
+ * "what do I do next?" on a record whose menu would otherwise make the handler
73
+ * guess. Renders nothing when there is no checklist, so a step that needs no
74
+ * explaining shows no empty card.
75
+ *
76
+ * ```vue
77
+ * <CnNextStepCard
78
+ * title="What now? Step 2: handling"
79
+ * :items="[
80
+ * { label: 'Receipt confirmed', done: true },
81
+ * { label: 'Review the open documents', hint: '2 of 5 done' },
82
+ * { label: 'Draft the decision' },
83
+ * ]"
84
+ * action-label="Continue reviewing"
85
+ * after="Then: step 3, decision"
86
+ * @action="onContinue" />
87
+ * ```
88
+ *
89
+ * CnDetailPage renders this card from its `nextStep` config, and the
90
+ * `next-step` detail widget type renders it inside a grid.
91
+ */
92
+ export default {
93
+ name: 'CnNextStepCard',
94
+
95
+ components: {
96
+ Check,
97
+ NcButton,
98
+ },
99
+
100
+ props: {
101
+ /** Heading of the card, for example "What now? Step 2: handling". */
102
+ title: {
103
+ type: String,
104
+ default: '',
105
+ },
106
+
107
+ /** Element the heading renders as. Pick the level that fits the page outline. */
108
+ titleTag: {
109
+ type: String,
110
+ default: 'h3',
111
+ },
112
+
113
+ /**
114
+ * The checklist: `{ label, done?, hint? }` per item. The first item
115
+ * that is not done is emphasised as the one to pick up now.
116
+ */
117
+ items: {
118
+ type: Array,
119
+ default: () => [],
120
+ },
121
+
122
+ /** Label of the primary button. Empty renders no button. */
123
+ actionLabel: {
124
+ type: String,
125
+ default: '',
126
+ },
127
+
128
+ /** DOM id for the primary button, so a skip link can land on it. */
129
+ actionId: {
130
+ type: String,
131
+ default: '',
132
+ },
133
+
134
+ /** Disable the primary button, for example while its request is in flight. */
135
+ actionDisabled: {
136
+ type: Boolean,
137
+ default: false,
138
+ },
139
+
140
+ /** One line under the button saying what follows, for example "Then: step 3, decision". */
141
+ after: {
142
+ type: String,
143
+ default: '',
144
+ },
145
+
146
+ /** Text state of a done item, read by screen readers. */
147
+ doneLabel: {
148
+ type: String,
149
+ default: () => t('nextcloud-vue', 'Done'),
150
+ },
151
+
152
+ /** Text state of an open item, read by screen readers. */
153
+ todoLabel: {
154
+ type: String,
155
+ default: () => t('nextcloud-vue', 'To do'),
156
+ },
157
+ },
158
+
159
+ emits: [
160
+ /** The primary button was pressed. */
161
+ 'action',
162
+ ],
163
+
164
+ data() {
165
+ nextStepCardUid += 1
166
+ return {
167
+ titleId: `cn-next-step-title-${nextStepCardUid}`,
168
+ }
169
+ },
170
+
171
+ computed: {
172
+ /**
173
+ * The items that can render: an object with a label.
174
+ *
175
+ * @return {Array<{label: string, done: boolean, hint: string}>} The checklist.
176
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card
177
+ */
178
+ normalisedItems() {
179
+ return (Array.isArray(this.items) ? this.items : [])
180
+ .filter((item) => item && typeof item.label === 'string' && item.label !== '')
181
+ .map((item) => ({
182
+ label: item.label,
183
+ done: item.done === true,
184
+ hint: typeof item.hint === 'string' ? item.hint : '',
185
+ }))
186
+ },
187
+
188
+ /**
189
+ * Index of the first open item, the one to pick up now.
190
+ *
191
+ * @return {number} The index, or -1 when everything is done.
192
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card
193
+ */
194
+ currentIndex() {
195
+ return this.normalisedItems.findIndex((item) => !item.done)
196
+ },
197
+
198
+ /**
199
+ * Accessible name for a card without a title.
200
+ *
201
+ * @return {string} The label.
202
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card
203
+ */
204
+ fallbackLabel() {
205
+ return t('nextcloud-vue', 'Next step')
206
+ },
207
+ },
208
+ }
209
+ </script>
210
+
211
+ <style scoped>
212
+ .cn-next-step-card {
213
+ align-items: center;
214
+ background: var(--color-main-background);
215
+ border: 1px solid var(--color-border);
216
+ border-radius: var(--border-radius-large);
217
+ /* The edge says "this is the thing to act on". An inset shadow, so it
218
+ follows the card's rounded corners instead of squaring them off. */
219
+ box-shadow: inset 4px 0 0 var(--color-primary-element);
220
+ display: flex;
221
+ flex-wrap: wrap;
222
+ gap: calc(4 * var(--default-grid-baseline)) calc(8 * var(--default-grid-baseline));
223
+ padding: calc(5 * var(--default-grid-baseline)) calc(6 * var(--default-grid-baseline));
224
+ padding-inline-start: calc(7 * var(--default-grid-baseline));
225
+ }
226
+
227
+ .cn-next-step-card__main {
228
+ display: flex;
229
+ flex: 1 1 420px;
230
+ flex-direction: column;
231
+ gap: calc(2.5 * var(--default-grid-baseline));
232
+ min-width: 0;
233
+ }
234
+
235
+ .cn-next-step-card__title {
236
+ color: var(--color-main-text);
237
+ font-size: 0.85em;
238
+ font-weight: 700;
239
+ letter-spacing: 0.06em;
240
+ margin: 0;
241
+ text-transform: uppercase;
242
+ }
243
+
244
+ .cn-next-step-card__list {
245
+ display: grid;
246
+ gap: calc(2 * var(--default-grid-baseline));
247
+ list-style: none;
248
+ margin: 0;
249
+ padding: 0;
250
+ }
251
+
252
+ .cn-next-step-card__item {
253
+ align-items: center;
254
+ color: var(--color-text-maxcontrast);
255
+ display: flex;
256
+ flex-wrap: wrap;
257
+ gap: calc(2.5 * var(--default-grid-baseline));
258
+ }
259
+
260
+ .cn-next-step-card__item--current {
261
+ color: var(--color-main-text);
262
+ }
263
+
264
+ .cn-next-step-card__item--current .cn-next-step-card__label {
265
+ font-weight: 700;
266
+ }
267
+
268
+ .cn-next-step-card__marker {
269
+ align-items: center;
270
+ border: 2px solid var(--color-border-maxcontrast);
271
+ border-radius: 50%;
272
+ box-sizing: border-box;
273
+ display: inline-flex;
274
+ flex: none;
275
+ height: 20px;
276
+ justify-content: center;
277
+ width: 20px;
278
+ }
279
+
280
+ .cn-next-step-card__item--current .cn-next-step-card__marker {
281
+ border-color: var(--color-primary-element);
282
+ }
283
+
284
+ .cn-next-step-card__item--done .cn-next-step-card__marker {
285
+ background: var(--color-primary-element);
286
+ border-color: var(--color-primary-element);
287
+ color: var(--color-primary-element-text);
288
+ }
289
+
290
+ .cn-next-step-card__hint {
291
+ color: var(--color-text-maxcontrast);
292
+ font-weight: 400;
293
+ }
294
+
295
+ /* Read, not seen. */
296
+ .cn-next-step-card__state {
297
+ border: 0;
298
+ clip-path: inset(50%);
299
+ height: 1px;
300
+ margin: -1px;
301
+ overflow: hidden;
302
+ padding: 0;
303
+ position: absolute;
304
+ white-space: nowrap;
305
+ width: 1px;
306
+ }
307
+
308
+ .cn-next-step-card__side {
309
+ align-items: flex-start;
310
+ display: flex;
311
+ flex-direction: column;
312
+ gap: calc(2 * var(--default-grid-baseline));
313
+ }
314
+
315
+ .cn-next-step-card__after {
316
+ color: var(--color-text-maxcontrast);
317
+ font-size: 0.9em;
318
+ }
319
+ </style>
@@ -0,0 +1,59 @@
1
+ <!--
2
+ - SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ - SPDX-License-Identifier: EUPL-1.2
4
+ -->
5
+
6
+ <template>
7
+ <CnNextStepCard
8
+ v-if="step"
9
+ :title="step.title"
10
+ :items="step.items"
11
+ :after="step.after" />
12
+ </template>
13
+
14
+ <script>
15
+ import CnNextStepCard from './CnNextStepCard.vue'
16
+ import { resolveNextStep } from '../../utils/detailActionModel.js'
17
+
18
+ /**
19
+ * CnNextStepWidget: the `next-step` detail widget type.
20
+ *
21
+ * Renders CnNextStepCard for the bound record's stage from
22
+ * `content: { field?, stages: { <stage>: { title?, checklist, after? } } }`.
23
+ * The checklist only: the page's own primary button stays where the page
24
+ * puts it. Resolved by its registry key, not exported.
25
+ */
26
+ export default {
27
+ name: 'CnNextStepWidget',
28
+
29
+ components: {
30
+ CnNextStepCard,
31
+ },
32
+
33
+ props: {
34
+ /** The widget's content: `{ field?, title?, stages }`. */
35
+ content: {
36
+ type: Object,
37
+ default: () => ({}),
38
+ },
39
+
40
+ /** The bound record. */
41
+ objectData: {
42
+ type: Object,
43
+ default: null,
44
+ },
45
+ },
46
+
47
+ computed: {
48
+ /**
49
+ * The card for the record's stage.
50
+ *
51
+ * @return {object|null} The resolved card, or null when the stage declares none.
52
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-next-step-card
53
+ */
54
+ step() {
55
+ return resolveNextStep(this.content, this.objectData)
56
+ },
57
+ },
58
+ }
59
+ </script>
@@ -0,0 +1,4 @@
1
+ import CnNextStepCard from './CnNextStepCard.vue'
2
+
3
+ export default CnNextStepCard
4
+ export { CnNextStepCard }
@@ -456,6 +456,13 @@ export default {
456
456
  * identity when used standalone (no CnAppRoot ancestor).
457
457
  */
458
458
  cnTranslate: { default: () => (key) => key },
459
+ /**
460
+ * Formatter registry, provided by CnAppRoot (`cnFormatters`: the
461
+ * library's built-ins plus the app's `formatters`). A field override
462
+ * `{ formatter: '<id>' }` renders the value through the same function a
463
+ * table column with that `formatter` uses. Empty when used standalone.
464
+ */
465
+ cnFormatters: { default: () => ({}) },
459
466
  /**
460
467
  * The host surface's panel-action channel, when this widget is rendered
461
468
  * in a tab panel. Null everywhere else, which is every other surface.
@@ -641,6 +648,11 @@ export default {
641
648
  * - `editable` (boolean) — Override editability (default: based on schema readOnly)
642
649
  * - `label` (string) — Override the display label
643
650
  * - `widget` (string) — Override the widget type for editing
651
+ * - `formatter` (string) — Display the value through the formatter
652
+ * registered under this id on CnAppRoot (`cnFormatters`), called as
653
+ * `(value, object, property)` like a table cell's formatter. An id
654
+ * nobody registered, or a formatter that throws, keeps the default
655
+ * rendering. Editing still writes the stored value.
644
656
  *
645
657
  * Accepts an Array too: an empty overrides map (`{}`) round-trips through
646
658
  * PHP/JSON as an empty Array (`[]`), so a persisted manifest can deliver
@@ -917,12 +929,46 @@ export default {
917
929
  }
918
930
  },
919
931
 
932
+ /**
933
+ * Display values of the fields whose override names a registered
934
+ * formatter, keyed by field key. A field is left out when its formatter
935
+ * id is not registered or the formatter throws, so it falls back to the
936
+ * default rendering.
937
+ *
938
+ * @return {Record<string, string>}
939
+ */
940
+ formatterDisplayValues() {
941
+ const values = {}
942
+ for (const field of this.resolvedFields) {
943
+ const fn = field.formatter && this.cnFormatters && this.cnFormatters[field.formatter]
944
+ if (typeof fn !== 'function') {
945
+ continue
946
+ }
947
+ const raw = field.key in this.dirtyFields
948
+ ? this.dirtyFields[field.key]
949
+ : (this.objectData || {})[field.key]
950
+ const prop = (this.schema.properties && this.schema.properties[field.key]) || {}
951
+ try {
952
+ const formatted = fn(raw, this.objectData || {}, prop)
953
+ values[field.key] = (formatted === null || formatted === undefined || formatted === '') ? '—' : String(formatted)
954
+ } catch (e) {
955
+ // eslint-disable-next-line no-console
956
+ console.warn(`[CnObjectDataWidget] formatter "${field.formatter}" threw; falling back`, e)
957
+ }
958
+ }
959
+ return values
960
+ },
961
+
920
962
  /**
921
963
  * Formatted display values for each field.
922
964
  */
923
965
  displayValues() {
924
966
  const values = {}
925
967
  for (const field of this.resolvedFields) {
968
+ if (field.key in this.formatterDisplayValues) {
969
+ values[field.key] = this.formatterDisplayValues[field.key]
970
+ continue
971
+ }
926
972
  // Show pending edit value if dirty
927
973
  const raw = field.key in this.dirtyFields
928
974
  ? this.dirtyFields[field.key]
@@ -1191,6 +1237,9 @@ export default {
1191
1237
  * @return {'object-array'|'scalar-array'|'object'|'scalar'} The value kind.
1192
1238
  */
1193
1239
  fieldValueKind(field) {
1240
+ if (field.key in this.formatterDisplayValues) {
1241
+ return 'scalar'
1242
+ }
1194
1243
  const prop = ((this.schema && this.schema.properties) || {})[field.key]
1195
1244
  if (this.isRelationField(prop)) {
1196
1245
  return 'scalar'
@@ -268,6 +268,9 @@ export default {
268
268
  gridColumn: Number.isFinite(o.gridColumn) ? o.gridColumn : 1,
269
269
  widget: o.widget || 'auto',
270
270
  editable: o.editable !== false,
271
+ // Not edited here, but carried through, so saving the
272
+ // cog editor does not drop a manifest-authored formatter.
273
+ formatter: typeof o.formatter === 'string' ? o.formatter : '',
271
274
  }
272
275
  })
273
276
  },
@@ -386,6 +389,9 @@ export default {
386
389
  if (row.editable === false) {
387
390
  o.editable = false
388
391
  }
392
+ if (row.formatter) {
393
+ o.formatter = row.formatter
394
+ }
389
395
  if (Object.keys(o).length) {
390
396
  out[row.key] = o
391
397
  }
@@ -50,11 +50,27 @@
50
50
  <template #item="{ element: card }">
51
51
  <div
52
52
  class="cn-object-kanban__card"
53
- :class="{ 'cn-object-kanban__card--pending': isPending(card) }">
53
+ :class="{
54
+ 'cn-object-kanban__card--pending': isPending(card),
55
+ 'cn-object-kanban__card--overdue': dueState(card) === 'overdue',
56
+ 'cn-object-kanban__card--soon': dueState(card) === 'soon',
57
+ }">
54
58
  <!-- @slot card Fully replace the default card rendering. -->
55
59
  <!-- @binding {object} object The card's object. -->
56
60
  <!-- @binding {object} column The column the card currently sits in. -->
57
- <slot name="card" :object="card" :column="column">
61
+ <!-- @binding {string|null} dueState The card's due state under `dueRule`: `overdue`, `soon`, `ok`, or null without a rule. -->
62
+ <slot
63
+ name="card"
64
+ :object="card"
65
+ :column="column"
66
+ :dueState="dueState(card)">
67
+ <span
68
+ v-if="dueLabel(card)"
69
+ class="cn-object-kanban__due"
70
+ :class="`cn-object-kanban__due--${dueState(card)}`"
71
+ data-testid="cn-kanban-due">
72
+ {{ dueLabel(card) }}
73
+ </span>
58
74
  <div class="cn-object-kanban__card-title" @click="onCardClick(card)">
59
75
  {{ cardTitle(card) }}
60
76
  </div>
@@ -92,6 +108,7 @@ import { translate as t } from '@nextcloud/l10n'
92
108
  import { NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'
93
109
  import draggable from 'vuedraggable'
94
110
  import ViewColumn from 'vue-material-design-icons/ViewColumn.vue'
111
+ import { dueStateForRow } from '../../utils/dueRule.js'
95
112
  import { CnCellRenderer } from '../CnCellRenderer/index.js'
96
113
 
97
114
  /**
@@ -225,6 +242,22 @@ export default {
225
242
  type: String,
226
243
  default: 'id',
227
244
  },
245
+
246
+ /**
247
+ * Marks late cards: `{ field, soonDays? }`. `field` is the dot-path to
248
+ * a card's due date. A date before today gets an error edge and the
249
+ * label "Overdue"; a date within `soonDays` (default 3) gets "Due
250
+ * soon" in the warning colour. `null` (the default) marks nothing.
251
+ *
252
+ * The same rule shape the table's date cell uses, so a list and its
253
+ * board agree on what late means.
254
+ *
255
+ * @type {{field: string, soonDays?: number}|null}
256
+ */
257
+ dueRule: {
258
+ type: Object,
259
+ default: null,
260
+ },
228
261
  },
229
262
 
230
263
  emits: ['card-click', 'load-more', 'move', 'move-rejected'],
@@ -240,6 +273,8 @@ export default {
240
273
  // objectId -> { fromValue, toValue, card, originIndex }
241
274
  pendingMoves: {},
242
275
  dragOriginValue: null,
276
+ // The day late marking measures from.
277
+ today: new Date(),
243
278
  }
244
279
  },
245
280
 
@@ -294,6 +329,33 @@ export default {
294
329
  methods: {
295
330
  t,
296
331
 
332
+ /**
333
+ * A card's due state under `dueRule`.
334
+ *
335
+ * @param {object} card The card's row.
336
+ * @return {'overdue'|'soon'|'ok'|null} The state, or null when no rule applies.
337
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-late-marking-on-board-cards
338
+ */
339
+ dueState(card) {
340
+ return dueStateForRow(card, this.dueRule, this.today)
341
+ },
342
+
343
+ /**
344
+ * The words a late card carries. Colour alone would not reach a reader
345
+ * who cannot see it, or cannot tell red from amber.
346
+ *
347
+ * @param {object} card The card's row.
348
+ * @return {string} "Overdue", "Due soon", or '' for a card that is fine.
349
+ * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-late-marking-on-board-cards
350
+ */
351
+ dueLabel(card) {
352
+ const state = this.dueState(card)
353
+ if (state === 'overdue') {
354
+ return t('nextcloud-vue', 'Overdue')
355
+ }
356
+ return state === 'soon' ? t('nextcloud-vue', 'Due soon') : ''
357
+ },
358
+
297
359
  /**
298
360
  * Rebuild `localColumns` from either the `columns` prop (backend-paginated
299
361
  * mode) or `objects` + `groupByField` (locally-derived mode).
@@ -703,6 +765,28 @@ export default {
703
765
  opacity: 0.6;
704
766
  }
705
767
 
768
+ /* A late card. The label carries the meaning; the edge helps a sighted
769
+ reader find it. An inset shadow, so the card keeps its size. */
770
+ .cn-object-kanban__card--overdue {
771
+ border-color: var(--color-error);
772
+ box-shadow: inset 3px 0 0 var(--color-error);
773
+ }
774
+
775
+ .cn-object-kanban__due {
776
+ display: block;
777
+ font-size: 0.85em;
778
+ font-weight: 700;
779
+ }
780
+
781
+ .cn-object-kanban__due--overdue {
782
+ color: var(--color-error-text, var(--color-error));
783
+ }
784
+
785
+ .cn-object-kanban__due--soon {
786
+ color: var(--color-warning-text, var(--color-main-text));
787
+ font-weight: 600;
788
+ }
789
+
706
790
  .cn-object-kanban__card-title {
707
791
  font-weight: 500;
708
792
  cursor: pointer;
@@ -57,6 +57,7 @@ export const defaultPageTypes = {
57
57
  map: defineAsyncComponent(() => import('../CnMapPage/CnMapPage.vue').then((m) => m.default)),
58
58
  roadmap: defineAsyncComponent(() => import('../CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue').then((m) => m.default)),
59
59
  reports: defineAsyncComponent(() => import('../CnReportsPage/CnReportsPage.vue').then((m) => m.default)),
60
+ links: defineAsyncComponent(() => import('../CnLinkCardsPage/CnLinkCardsPage.vue').then((m) => m.default)),
60
61
  store: defineAsyncComponent(() => import('../CnStorePage/CnStorePage.vue').then((m) => m.default)),
61
62
  search: defineAsyncComponent(() => import('../CnSearchPage/CnSearchPage.vue').then((m) => m.default)),
62
63
  wiki: defineAsyncComponent(() => import('../CnWikiPage/CnWikiPage.vue').then((m) => m.default)),
@@ -45,6 +45,8 @@ In `manifest.json` (the usual entry-point — CnIndexPage mounts the bar for you
45
45
  | `filter` | Object | yes | Filter map merged into the fetch when this tab is active. Same value syntax as `config.filter`: literals pass through; `"@route.<name>"` / `":<name>"` resolve from `$route.params`. |
46
46
  | `default` | Boolean | no | Pre-selected on mount; first tab with `default:true` (else index 0) is active |
47
47
  | `icon` | String | no | Optional MDI icon name shown next to the label |
48
+ | `showCount` | Boolean | no | On a CnIndexPage: show how many records this tab matches. Tabs that filter the same single field share one grouped request. |
49
+ | `count` | Number | no | A count you supply yourself, shown after the label. |
48
50
 
49
51
  ## Props
50
52
 
@@ -57,6 +59,7 @@ In `manifest.json` (the usual entry-point — CnIndexPage mounts the bar for you
57
59
  - `selectLabel` (String, default `'Filter'`) — accessible label / placeholder for the dropdown control.
58
60
  - `placeholder` (String) — placeholder text for the dropdown (falls back to `selectLabel`).
59
61
  - `maxVisible` (Number, default `0`) — chips mode only: how many pills render inline before the rest move behind one more chip, a `⋯` pill that opens a small panel of the hidden lenses (also as chips). `0` renders every tab. The visible set is the first `maxVisible` entries of `tabs`, so their declared order decides what stays in the strip; an active hidden tab moves its label and fill onto that chip. Manifest: `config.quickFilterMaxVisible`.
62
+ - `counts` (Object or Array, default `null`): how many records each tab matches, keyed by tab index. A tab with a number shows it after its label. CnIndexPage fills this for tabs that set `showCount`.
60
63
 
61
64
  ### Events
62
65