@conduction/nextcloud-vue 2.56.0-beta.30 → 2.56.0-beta.32

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 (290) hide show
  1. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  2. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +3 -3
  3. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  4. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +80 -4
  5. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  6. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +95 -91
  7. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  8. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +140 -10
  9. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  10. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +52 -16
  11. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  12. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +17 -3
  13. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  14. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +63 -52
  15. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  16. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js +10 -0
  17. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue.js.map +1 -0
  18. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js +252 -0
  19. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue2.js.map +1 -0
  20. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js +140 -0
  21. package/dist/esm/components/CnChipsFieldWidget/CnChipsFieldWidget.vue3.js.map +1 -0
  22. package/dist/esm/components/CnChipsFieldWidget/index.js +26 -0
  23. package/dist/esm/components/CnChipsFieldWidget/index.js.map +1 -0
  24. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js +10 -0
  25. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue.js.map +1 -0
  26. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js +354 -0
  27. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue2.js.map +1 -0
  28. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js +172 -0
  29. package/dist/esm/components/CnChoicePickerWidget/CnChoicePickerWidget.vue3.js.map +1 -0
  30. package/dist/esm/components/CnChoicePickerWidget/index.js +27 -0
  31. package/dist/esm/components/CnChoicePickerWidget/index.js.map +1 -0
  32. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +1 -1
  33. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
  34. package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
  35. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +2 -2
  36. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
  37. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
  38. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +65 -5
  39. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  40. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -1
  41. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  42. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +19 -11
  43. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  44. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +3 -2
  45. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  46. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +72 -0
  47. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  48. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +21 -4
  49. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  50. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js +1 -1
  51. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue2.js.map +1 -1
  52. package/dist/esm/components/CnDocumentReviewList/CnDocumentReviewWidget.vue3.js.map +1 -1
  53. package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js +7 -7
  54. package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue2.js.map +1 -1
  55. package/dist/esm/components/CnFacetSidebar/CnFacetSidebar.vue3.js.map +1 -1
  56. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +131 -0
  57. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  58. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  59. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +80 -5
  60. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
  61. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +282 -69
  62. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
  63. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +302 -4
  64. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  65. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +417 -244
  66. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  67. package/dist/esm/components/CnFilesBrowser/filesBrowser.js +101 -1
  68. package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
  69. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +141 -1
  70. package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
  71. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +60 -0
  72. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
  73. package/dist/esm/components/CnFormPage/CnFormPage.vue3.js +74 -49
  74. package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
  75. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +29 -2
  76. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  77. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +48 -4
  78. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  79. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +7 -7
  80. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
  81. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
  82. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js +1 -1
  83. package/dist/esm/components/CnNextStepCard/CnNextStepWidget.vue2.js.map +1 -1
  84. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +65 -2
  85. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  86. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +6 -2
  87. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  88. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +63 -1
  89. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  90. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +8 -1
  91. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  92. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +14 -0
  93. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
  94. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js +5 -1
  95. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
  96. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js +10 -0
  97. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue.js.map +1 -0
  98. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js +174 -0
  99. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue2.js.map +1 -0
  100. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js +75 -0
  101. package/dist/esm/components/CnRadioCardsWidget/CnRadioCardsWidget.vue3.js.map +1 -0
  102. package/dist/esm/components/CnRadioCardsWidget/index.js +28 -0
  103. package/dist/esm/components/CnRadioCardsWidget/index.js.map +1 -0
  104. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +30 -6
  105. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  106. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +34 -25
  107. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  108. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js +10 -0
  109. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue.js.map +1 -0
  110. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js +222 -0
  111. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue2.js.map +1 -0
  112. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js +64 -0
  113. package/dist/esm/components/CnSavedViewsWidget/CnSavedViewsWidget.vue3.js.map +1 -0
  114. package/dist/esm/components/CnSavedViewsWidget/index.js +30 -0
  115. package/dist/esm/components/CnSavedViewsWidget/index.js.map +1 -0
  116. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
  117. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
  118. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js +10 -0
  119. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue.js.map +1 -0
  120. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js +347 -0
  121. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue2.js.map +1 -0
  122. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js +184 -0
  123. package/dist/esm/components/CnSettingsFormWidget/CnSettingsFormWidget.vue3.js.map +1 -0
  124. package/dist/esm/components/CnSettingsFormWidget/index.js +27 -0
  125. package/dist/esm/components/CnSettingsFormWidget/index.js.map +1 -0
  126. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +87 -6
  127. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  128. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  129. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +84 -1
  130. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
  131. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +85 -41
  132. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
  133. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js +10 -0
  134. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue.js.map +1 -0
  135. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js +159 -0
  136. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue2.js.map +1 -0
  137. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js +60 -0
  138. package/dist/esm/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue3.js.map +1 -0
  139. package/dist/esm/components/CnSwitchRowsWidget/index.js +26 -0
  140. package/dist/esm/components/CnSwitchRowsWidget/index.js.map +1 -0
  141. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +47 -1
  142. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  143. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +16 -14
  144. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  145. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +80 -1
  146. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
  147. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +75 -32
  148. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
  149. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js +10 -0
  150. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js.map +1 -0
  151. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +269 -0
  152. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -0
  153. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js +106 -0
  154. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -0
  155. package/dist/esm/components/CnToggleTableWidget/index.js +27 -0
  156. package/dist/esm/components/CnToggleTableWidget/index.js.map +1 -0
  157. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +7 -0
  158. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  159. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +7 -2
  160. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  161. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  162. package/dist/esm/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
  163. package/dist/esm/components/CnWidgetObjectTable/dashboardRegistration.js.map +1 -1
  164. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js +197 -6
  165. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue2.js.map +1 -1
  166. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js +96 -50
  167. package/dist/esm/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue3.js.map +1 -1
  168. package/dist/esm/composables/useListView.js +4 -4
  169. package/dist/esm/composables/useListView.js.map +1 -1
  170. package/dist/esm/index.js +17 -1
  171. package/dist/esm/index.js.map +1 -1
  172. package/dist/esm/l10n/en.json.js +9 -1
  173. package/dist/esm/l10n/en.json.js.map +1 -1
  174. package/dist/esm/l10n/nl.json.js +55 -1
  175. package/dist/esm/l10n/nl.json.js.map +1 -1
  176. package/dist/esm/nextcloud-vue.css +1041 -12
  177. package/dist/esm/utils/contentHeaderLink.js +51 -0
  178. package/dist/esm/utils/contentHeaderLink.js.map +1 -0
  179. package/dist/esm/utils/facets.js +75 -3
  180. package/dist/esm/utils/facets.js.map +1 -1
  181. package/dist/esm/utils/fetchFilterCounts.js +40 -5
  182. package/dist/esm/utils/fetchFilterCounts.js.map +1 -1
  183. package/dist/esm/utils/fillTemplate.js +7 -1
  184. package/dist/esm/utils/fillTemplate.js.map +1 -1
  185. package/dist/esm/utils/initials.js +32 -0
  186. package/dist/esm/utils/initials.js.map +1 -0
  187. package/dist/esm/utils/libraryWidgetKeys.js +7 -0
  188. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  189. package/dist/esm/utils/objectWrite.js +59 -0
  190. package/dist/esm/utils/objectWrite.js.map +1 -0
  191. package/dist/esm/utils/pageControls.js +81 -0
  192. package/dist/esm/utils/pageControls.js.map +1 -0
  193. package/dist/esm/utils/personalLenses.js +40 -3
  194. package/dist/esm/utils/personalLenses.js.map +1 -1
  195. package/dist/esm/utils/resolveFilterTokens.js +1 -1
  196. package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
  197. package/dist/esm/utils/resolveManifestTokens.js +2 -0
  198. package/dist/esm/utils/resolveManifestTokens.js.map +1 -1
  199. package/dist/esm/utils/sentinelTokens.js +19 -1
  200. package/dist/esm/utils/sentinelTokens.js.map +1 -1
  201. package/dist/esm/utils/userGroupsPeek.js +91 -0
  202. package/dist/esm/utils/userGroupsPeek.js.map +1 -0
  203. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  204. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  205. package/dist/esm/utils/visibleWhen.js +57 -5
  206. package/dist/esm/utils/visibleWhen.js.map +1 -1
  207. package/dist/esm/utils/widgetDispatch.js +1 -1
  208. package/dist/esm/utils/widgetDispatch.js.map +1 -1
  209. package/dist/esm/utils/widgetVisibility.js +4 -57
  210. package/dist/esm/utils/widgetVisibility.js.map +1 -1
  211. package/dist/nextcloud-vue.cjs.js +13589 -7833
  212. package/dist/nextcloud-vue.cjs.js.map +2 -2
  213. package/dist/nextcloud-vue.css +1041 -12
  214. package/l10n/en.json +9 -1
  215. package/l10n/nl.json +55 -1
  216. package/package.json +2 -1
  217. package/scripts/check-docs-mdx.mjs +108 -0
  218. package/src/components/CnActionsBar/CnActionsBar.vue +6 -1
  219. package/src/components/CnAppRoot/CnAppRoot.md +4 -2
  220. package/src/components/CnAppRoot/CnAppRoot.vue +151 -59
  221. package/src/components/CnBannerWidget/CnBannerWidget.vue +205 -20
  222. package/src/components/CnCellRenderer/CnCellRenderer.md +2 -0
  223. package/src/components/CnCellRenderer/CnCellRenderer.vue +47 -4
  224. package/src/components/CnChipsFieldWidget/CnChipsFieldWidget.vue +433 -0
  225. package/src/components/CnChipsFieldWidget/index.js +25 -0
  226. package/src/components/CnChoicePickerWidget/CnChoicePickerWidget.vue +556 -0
  227. package/src/components/CnChoicePickerWidget/index.js +26 -0
  228. package/src/components/CnConversationThread/CnConversationWidget.vue +1 -1
  229. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +2 -2
  230. package/src/components/CnDashboardPage/CnDashboardPage.vue +78 -5
  231. package/src/components/CnDataTable/CnDataTable.vue +21 -12
  232. package/src/components/CnDetailPage/CnDetailPage.vue +89 -3
  233. package/src/components/CnDocumentReviewList/CnDocumentReviewWidget.vue +1 -1
  234. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +7 -7
  235. package/src/components/CnFactsWidget/CnFactsWidget.vue +142 -2
  236. package/src/components/CnFieldListWidget/CnFieldListWidget.vue +268 -6
  237. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +588 -117
  238. package/src/components/CnFilesBrowser/filesBrowser.js +100 -0
  239. package/src/components/CnFilesBrowser/filesBrowserColumns.js +140 -0
  240. package/src/components/CnFormPage/CnFormPage.vue +81 -0
  241. package/src/components/CnIndexPage/CnIndexPage.vue +56 -4
  242. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +7 -7
  243. package/src/components/CnNextStepCard/CnNextStepWidget.vue +1 -1
  244. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +69 -2
  245. package/src/components/CnObjectSidebar/CnFilesTab.vue +70 -1
  246. package/src/components/CnObjectSidebar/CnNotesTab.vue +16 -0
  247. package/src/components/CnRadioCardsWidget/CnRadioCardsWidget.vue +313 -0
  248. package/src/components/CnRadioCardsWidget/index.js +27 -0
  249. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +55 -6
  250. package/src/components/CnSavedViewsWidget/CnSavedViewsWidget.vue +296 -0
  251. package/src/components/CnSavedViewsWidget/index.js +29 -0
  252. package/src/components/CnSegmentedControl/CnSegmentedControl.vue +13 -0
  253. package/src/components/CnSettingsFormWidget/CnSettingsFormWidget.vue +555 -0
  254. package/src/components/CnSettingsFormWidget/index.js +26 -0
  255. package/src/components/CnStatWidget/CnStatWidget.vue +87 -6
  256. package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +180 -3
  257. package/src/components/CnSwitchRowsWidget/CnSwitchRowsWidget.vue +226 -0
  258. package/src/components/CnSwitchRowsWidget/index.js +25 -0
  259. package/src/components/CnTabsWidget/CnTabsWidget.vue +82 -3
  260. package/src/components/CnTasksWidget/CnTasksWidget.vue +175 -15
  261. package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +386 -0
  262. package/src/components/CnToggleTableWidget/index.js +26 -0
  263. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +7 -0
  264. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +7 -2
  265. package/src/components/CnWidgetObjectTable/dashboardRegistration.js +5 -0
  266. package/src/components/CnWorkspaceFilterWidget/CnWorkspaceFilterWidget.vue +309 -11
  267. package/src/components/index.js +15 -0
  268. package/src/composables/useListView.js +4 -4
  269. package/src/css/look-board-detail.css +36 -2
  270. package/src/css/look-board-index.css +39 -8
  271. package/src/css/look-board.css +72 -0
  272. package/src/index.js +15 -0
  273. package/src/schemas/app-manifest-v2.schema.json +93 -9
  274. package/src/utils/contentHeaderLink.js +48 -0
  275. package/src/utils/facets.js +74 -2
  276. package/src/utils/fetchFilterCounts.js +39 -4
  277. package/src/utils/fillTemplate.js +7 -1
  278. package/src/utils/initials.js +29 -0
  279. package/src/utils/libraryWidgetKeys.js +7 -0
  280. package/src/utils/objectWrite.js +55 -0
  281. package/src/utils/pageControls.js +78 -0
  282. package/src/utils/personalLenses.js +39 -2
  283. package/src/utils/resolveFilterTokens.js +3 -1
  284. package/src/utils/resolveManifestTokens.js +2 -0
  285. package/src/utils/sentinelTokens.js +19 -1
  286. package/src/utils/userGroupsPeek.js +87 -0
  287. package/src/utils/validateManifestV2.compiled.js +3 -3
  288. package/src/utils/visibleWhen.js +56 -4
  289. package/src/utils/widgetDispatch.js +1 -1
  290. package/src/utils/widgetVisibility.js +7 -56
@@ -555,3 +555,75 @@
555
555
  .cn-look-board .cn-cell-renderer__age:not(.cn-look-nextcloud *) {
556
556
  font-weight: 600;
557
557
  }
558
+
559
+ /* ---- Record list on a dashboard card (PtDashboard) ---------------------- */
560
+ /* The dashboard's card body is flush, so the rows carry their own inset: the
561
+ board's card pads 24px at the sides and each row 14px, a soft hairline
562
+ between rows, the title 15px weight 600 and the meta 13px muted. A row is a
563
+ link, but only the hover says so: no underline on any text run, however the
564
+ host styles its links (`!important`, because a host rule such as
565
+ `#content a` outranks any class). The pill and the date sit at the right. */
566
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list {
567
+ padding: 0 24px 10px;
568
+ }
569
+
570
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list--rows .cn-record-list__item + .cn-record-list__item {
571
+ border-top: 1px solid var(--color-border-dark, var(--color-border));
572
+ }
573
+
574
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row {
575
+ padding: 14px 0;
576
+ flex-wrap: wrap;
577
+ gap: 8px 16px;
578
+ }
579
+
580
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row,
581
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row:hover,
582
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row:focus-visible,
583
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row * {
584
+ color: inherit;
585
+ text-decoration: none !important;
586
+ }
587
+
588
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__title {
589
+ color: var(--color-main-text);
590
+ font-size: 15px;
591
+ font-weight: 600;
592
+ }
593
+
594
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__meta {
595
+ color: var(--color-text-maxcontrast);
596
+ font-size: 13px;
597
+ }
598
+
599
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__trailing {
600
+ color: var(--color-text-maxcontrast);
601
+ font-size: 13px;
602
+ font-weight: 600;
603
+ }
604
+
605
+ /* Hover: the whole row takes the hover ground, 8px beyond the text so the
606
+ text does not move. */
607
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row--link {
608
+ margin-inline: -8px;
609
+ padding-inline: 8px;
610
+ border-radius: 8px;
611
+ }
612
+
613
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row--link:hover,
614
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row--link:focus-visible {
615
+ background: var(--color-background-hover);
616
+ }
617
+
618
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__row--link:hover .cn-record-list__title {
619
+ text-decoration: none !important;
620
+ }
621
+
622
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__aside {
623
+ margin-inline-start: auto;
624
+ }
625
+
626
+ /* The board draws no chevron after the pill and date. */
627
+ .cn-look-board .cn-dashboard-page:not(.cn-look-nextcloud *) .cn-record-list__chevron {
628
+ display: none;
629
+ }
package/src/index.js CHANGED
@@ -43,6 +43,8 @@ export {
43
43
  CnAppNav,
44
44
  CnAppRoot,
45
45
  CnAuditTrailCard,
46
+ CnAuditTrailWidget,
47
+ CnBannerWidget,
46
48
  CnBoardView,
47
49
  CnBodySections,
48
50
  CnBreadcrumbs,
@@ -58,7 +60,9 @@ export {
58
60
  CnChartWidgetForm,
59
61
  CnChatPage,
60
62
  CnChildRecordsField,
63
+ CnChipsFieldWidget,
61
64
  CnChoiceCards,
65
+ CnChoicePickerWidget,
62
66
  CnColorPicker,
63
67
  CnCommandPalette,
64
68
  CnConfigurationCard,
@@ -70,6 +74,7 @@ export {
70
74
  CnContainerWidgetForm,
71
75
  CnContextMenu,
72
76
  CnConversationThread,
77
+ CnConversationWidget,
73
78
  CnCopyDialog,
74
79
  CnCountdownWidget,
75
80
  CnCountdownWidgetForm,
@@ -97,6 +102,7 @@ export {
97
102
  CnDividerWidget,
98
103
  CnDividerWidgetForm,
99
104
  CnDocumentReviewList,
105
+ CnDocumentReviewWidget,
100
106
  CnDurationField,
101
107
  CnEditActionsModal,
102
108
  CnEditDataModal,
@@ -164,11 +170,13 @@ export {
164
170
  CnIntegrationTab,
165
171
  CnIntegrationWidget,
166
172
  CnIntegrationWidgetGrid,
173
+ CnInteractionFormWidget,
167
174
  CnItemCard,
168
175
  CnJourney,
169
176
  CnJourneyDialog,
170
177
  CnJourneyReviewList,
171
178
  CnJsonViewer,
179
+ CnKbSearchWidget,
172
180
  CnKpiGrid,
173
181
  CnLabelWidget,
174
182
  CnLabelWidgetForm,
@@ -200,6 +208,7 @@ export {
200
208
  CnNcWidgetGridPicker,
201
209
  CnNcWidgetWidget,
202
210
  CnNextStepCard,
211
+ CnNextStepWidget,
203
212
  CnNoteBody,
204
213
  CnNoteCard,
205
214
  CnNoteComposer,
@@ -238,6 +247,7 @@ export {
238
247
  CnQuickFilterBar,
239
248
  CnQuicklinksWidget,
240
249
  CnQuicklinksWidgetForm,
250
+ CnRadioCardsWidget,
241
251
  CnRecordListWidget,
242
252
  CnReferencePreview,
243
253
  CnRegisterMapping,
@@ -256,6 +266,7 @@ export {
256
266
  CnSavedViewsControl,
257
267
  CnSavedViewShareDialog,
258
268
  CnSavedViewShareFields,
269
+ CnSavedViewsWidget,
259
270
  CnSavedViewWidget,
260
271
  CnSavedViewWidgetForm,
261
272
  CnSaveViewDialog,
@@ -263,6 +274,7 @@ export {
263
274
  CnSearchPage,
264
275
  CnSectionsWidget,
265
276
  CnSettingsCard,
277
+ CnSettingsFormWidget,
266
278
  CnSettingsPage,
267
279
  CnSettingsSection,
268
280
  CnSetupWizard,
@@ -282,6 +294,7 @@ export {
282
294
  CnSubObjectsField,
283
295
  CnSummaryAggregates,
284
296
  CnSupportDialog,
297
+ CnSwitchRowsWidget,
285
298
  CnTab,
286
299
  CnTabbedFormDialog,
287
300
  CnTableWidget,
@@ -302,6 +315,7 @@ export {
302
315
  CnTimelineStages,
303
316
  CnTimelineView,
304
317
  CnTimelineWidget,
318
+ CnToggleTableWidget,
305
319
  CnTranslatedBadge,
306
320
  CnTreeView,
307
321
  CnUnreadMarker,
@@ -324,6 +338,7 @@ export {
324
338
  CnWidgetVisibilityRulesModal,
325
339
  CnWidgetWrapper,
326
340
  CnWizardDialog,
341
+ CnWorkspaceFilterWidget,
327
342
  DEFAULT_DATE_RANGE_PRESETS,
328
343
  defaultPageTypes,
329
344
  fetchWebdavFolderTree,
@@ -3,7 +3,7 @@
3
3
  "$id": "https://raw.githubusercontent.com/ConductionNL/nextcloud-vue/main/src/schemas/app-manifest-v2.schema.json",
4
4
  "title": "Conduction App Manifest v2",
5
5
  "description": "v2 schema for the JSON-driven page and navigation manifest consumed by @conduction/nextcloud-vue. Introduces a uniform widgets[] array on every page type with a per-slot grid coordinate system, a typed actions[] discriminator, and the required $schema field for version detection. v1 manifests continue to validate against app-manifest.schema.json (unchanged).",
6
- "version": "2.85.0",
6
+ "version": "2.89.0",
7
7
  "type": "object",
8
8
  "required": ["$schema", "version"],
9
9
  "allOf": [
@@ -803,6 +803,16 @@
803
803
  "pattern": "^@answer\\.[A-Za-z][A-Za-z0-9_]*$",
804
804
  "description": "form-live-values: another answer in the same form (@answer.<field>), read by a form field's assign rule and resolved client-side by CnFormPage / CnFormDialog. Mirrors SENTINEL_TOKEN_PATTERNS.answer."
805
805
  },
806
+ "sentinelRowToken": {
807
+ "type": "string",
808
+ "pattern": "^@row\\.[A-Za-z][A-Za-z0-9_.@]*$",
809
+ "description": "Row context: a field of the row a row action runs on (@row.<field>, dot path allowed), read by the field-list widget's rowActions request url and payload (payload values resolve at any depth). Mirrors SENTINEL_TOKEN_PATTERNS.row."
810
+ },
811
+ "sentinelSubjectToken": {
812
+ "type": "string",
813
+ "pattern": "^@subject\\.[A-Za-z][A-Za-z0-9_.]*$",
814
+ "description": "Subject context: a field of the record an attention card names (the first row of its ordered `visibleWhen` source query), read by the banner widget's action route params and query (@subject.<field>, dot path allowed). Mirrors SENTINEL_TOKEN_PATTERNS.subject."
815
+ },
806
816
  "sentinelTokenAny": {
807
817
  "$comment": "Union of every canonical context plus the deprecated-during-window overlay. Single source the shared resolver + gate import.",
808
818
  "anyOf": [
@@ -814,6 +824,8 @@
814
824
  { "$ref": "#/$defs/sentinelDeclarativeToken" },
815
825
  { "$ref": "#/$defs/sentinelVisibleWhenToken" },
816
826
  { "$ref": "#/$defs/sentinelAnswerToken" },
827
+ { "$ref": "#/$defs/sentinelRowToken" },
828
+ { "$ref": "#/$defs/sentinelSubjectToken" },
817
829
  { "$ref": "#/$defs/sentinelDeprecatedToken" }
818
830
  ]
819
831
  },
@@ -1691,7 +1703,7 @@
1691
1703
  "sizeToContent": {
1692
1704
  "type": ["boolean", "integer"],
1693
1705
  "minimum": 1,
1694
- "description": "Opt-in: hand this placement's height to whatever it renders, so a widget that decides AT RUNTIME it has nothing to show costs no row. `true` sizes purely to content; an integer is a row minimum. It makes gridHeight advisory for this widget — right for anything whose height is whatever it turned out to be (a banner whose condition is false, a list that came back empty), wrong for anything with a deliberate size. Maps to CnDashboardGrid's per-item `sizeToContent`."
1706
+ "description": "Opt-in: hand this placement's height to whatever it renders, so a widget that decides AT RUNTIME it has nothing to show costs no row. `true` sizes purely to content; an integer sizes to content too but caps the height at that many rows (GridStack's maximum, NOT a minimum: content shorter than N rows is as short as it is, content taller is clipped at N). It makes gridHeight advisory for this widget — right for anything whose height is whatever it turned out to be (a banner whose condition is false, a list that came back empty), wrong for anything with a deliberate size. Maps to CnDashboardGrid's per-item `sizeToContent`."
1695
1707
  },
1696
1708
  "props": {
1697
1709
  "type": "object",
@@ -1898,11 +1910,16 @@
1898
1910
  "properties": {
1899
1911
  "register": { "type": "string" },
1900
1912
  "schema": { "type": "string" },
1901
- "filter": { "type": "object", "additionalProperties": true }
1913
+ "filter": { "type": "object", "additionalProperties": true },
1914
+ "order": {
1915
+ "type": "object",
1916
+ "additionalProperties": { "type": "string", "enum": ["asc", "desc"] },
1917
+ "description": "Order of the query (`{ field: \"asc\" | \"desc\" }`), so the first result is the first of an ordered list. With it an attention card names that row: `{subject.<field>}` in its title, reason and kicker and `@subject.<field>` in an action route read it (screens-start-here-banner, 2.89.0)."
1918
+ }
1902
1919
  }
1903
1920
  },
1904
1921
  "field": { "type": "string", "description": "Dot-path into the response ('@total' or omitted = collection total for a source)." },
1905
- "op": { "type": "string", "enum": ["eq", "neq", "gt", "gte", "lt", "lte"], "default": "eq", "description": "Comparison operator." },
1922
+ "op": { "type": "string", "enum": ["eq", "neq", "gt", "gte", "lt", "lte", "empty", "notEmpty"], "default": "eq", "description": "Comparison operator. `notEmpty` on `id` is the \"there is a first row\" test of a card that names one." },
1906
1923
  "value": { "description": "Literal right-hand side of the comparison." }
1907
1924
  }
1908
1925
  },
@@ -1919,6 +1936,11 @@
1919
1936
  "default": "banner",
1920
1937
  "description": "How the banner renders. `banner` (default) is the note card. `attention` is a card with a severity edge, a kicker, a title, a reason line and up to two actions."
1921
1938
  },
1939
+ "stacked": {
1940
+ "type": "boolean",
1941
+ "default": false,
1942
+ "description": "Attention card: put the actions under the text instead of beside it, with a 16px title (PqMijnWerk \"Begin hier\"; screens-start-here-banner, 2.89.0)."
1943
+ },
1922
1944
  "kicker": {
1923
1945
  "type": "string",
1924
1946
  "description": "Attention card: the small label above the title."
@@ -2184,9 +2206,27 @@
2184
2206
  },
2185
2207
  "style": {
2186
2208
  "type": "string",
2187
- "enum": ["radio", "select"],
2209
+ "enum": ["radio", "select", "chips", "toggle"],
2188
2210
  "default": "radio",
2189
- "description": "Choice style: 'radio' (default) list or 'select' dropdown."
2211
+ "description": "Choice style: 'radio' (default) list, 'select' dropdown, 'chips' (one compact segmented group with a count after each label; screens-page-lenses, 2.87.0) or 'toggle' (a switch writing `onValue` / `offValue`; screens-page-controls, 2.88.0)."
2212
+ },
2213
+ "layout": {
2214
+ "type": "string",
2215
+ "enum": ["start", "bar"],
2216
+ "description": "'start' draws the start card; 'bar' draws `controls` side by side in one row (screens-page-controls, 2.88.0)."
2217
+ },
2218
+ "controls": {
2219
+ "type": "array",
2220
+ "items": { "type": "object", "additionalProperties": true },
2221
+ "description": "With `layout: \"bar\"`: the bar's controls, each a workspace-filter content (chips, a toggle, a select) with its own `writes`. `align: \"end\"` pushes a control to the right edge (2.88.0)."
2222
+ },
2223
+ "onValue": { "description": "style 'toggle': the value written while the switch is on. Default true (2.88.0)." },
2224
+ "offValue": { "description": "style 'toggle': the value written while the switch is off. Default false (2.88.0)." },
2225
+ "align": { "type": "string", "enum": ["start", "end"], "description": "In a bar: 'end' pushes this control to the right edge (2.88.0)." },
2226
+ "urlKey": {
2227
+ "type": "string",
2228
+ "minLength": 1,
2229
+ "description": "Keep the choice in the address as `?<urlKey>=<value>` (replacing the history entry, keeping other query keys). The address wins over `default` on load, so a chip is linkable (screens-page-lenses, 2.87.0)."
2190
2230
  },
2191
2231
  "allLabel": {
2192
2232
  "type": "string",
@@ -2199,7 +2239,7 @@
2199
2239
  },
2200
2240
  "options": {
2201
2241
  "type": "array",
2202
- "description": "Static options — each { value, label, count? } (or { id, name } / bare strings, normalised).",
2242
+ "description": "Static options — each { value, label, count?, countFrom? } (or { id, name } / bare strings, normalised). `countFrom` is `{ register, schema, filter? }`, or a list of those that are added up; the option shows that list's total (one list request each; none when it fails). screens-page-lenses, 2.87.0.",
2203
2243
  "items": { "type": ["object", "string", "number"] }
2204
2244
  },
2205
2245
  "source": {
@@ -2925,15 +2965,20 @@
2925
2965
  "filter": { "type": "object", "additionalProperties": true }
2926
2966
  }
2927
2967
  },
2968
+ "workspace": {
2969
+ "type": "string",
2970
+ "minLength": 1,
2971
+ "description": "WORKSPACE mode (screens-page-lenses, 2.87.0): the key of the page workspace context to compare, read at once with no request. A page control (a `workspace-filter` with `writes`) sets the key, and a dashboard widget carrying this condition appears and disappears as it changes. Honoured on a dashboard widget or KPI-row tile only, and as a top-level condition (not inside `all` / `any`)."
2972
+ },
2928
2973
  "field": {
2929
2974
  "type": "string",
2930
2975
  "description": "Dot-path to the compared value — into the endpoint body, the source's first result, or (LOCAL mode) the caller's object context."
2931
2976
  },
2932
2977
  "op": {
2933
2978
  "type": "string",
2934
- "enum": ["eq", "neq", "gt", "gte", "lt", "lte", "empty", "notEmpty"],
2979
+ "enum": ["eq", "neq", "gt", "gte", "lt", "lte", "empty", "notEmpty", "in"],
2935
2980
  "default": "eq",
2936
- "description": "Comparison operator (default eq). eq/neq compare loosely-normalised primitives; ordering operators coerce both sides to Number. `empty` / `notEmpty` take no `value` and treat undefined, null, '' and an empty array alike — the one question eq cannot ask, because an unset field arrives in a shape that depends on the store and String(null) is the word 'null' rather than blank."
2981
+ "description": "Comparison operator (default eq). eq/neq compare loosely-normalised primitives; ordering operators coerce both sides to Number. `in` holds when the value is one of the listed `value` array (2.87.0). `empty` / `notEmpty` take no `value` and treat undefined, null, '' and an empty array alike — the one question eq cannot ask, because an unset field arrives in a shape that depends on the store and String(null) is the word 'null' rather than blank."
2937
2982
  },
2938
2983
  "value": {
2939
2984
  "description": "The right-hand side of the comparison, resolved through the shared @-token grammar so `@me` names the signed-in reader rather than a literal only the server knows. Omitted for `empty` / `notEmpty`."
@@ -2985,6 +3030,45 @@
2985
3030
  "type": "array",
2986
3031
  "items": { "type": "string" },
2987
3032
  "description": "OpenRegister `_extend[]` values forwarded on the fetch (e.g. [\"calculations\"]) so read-time virtual/derived fields are hydrated and available as columns. Each entry is passed through verbatim."
3033
+ },
3034
+ "pageControls": { "$ref": "#/$defs/pageControls" }
3035
+ }
3036
+ },
3037
+ "pageControls": {
3038
+ "type": "object",
3039
+ "additionalProperties": false,
3040
+ "description": "screens-page-controls (2.88.0): which page controls a list widget (`record-list`, `object-table`) listens to, and what they mean for it. A page control is a `workspace-filter` select or switch that writes a workspace key. `sort` picks the list's sort from the key's value; `toggle` drops filter keys while the key is on. Without it the list ignores the page's controls.",
3041
+ "properties": {
3042
+ "sort": {
3043
+ "type": "object",
3044
+ "required": ["key", "map"],
3045
+ "additionalProperties": false,
3046
+ "description": "The workspace key a sort select writes, and what each of its values sorts by. A value with no entry leaves the list's own sort.",
3047
+ "properties": {
3048
+ "key": { "type": "string", "minLength": 1 },
3049
+ "map": {
3050
+ "type": "object",
3051
+ "additionalProperties": {
3052
+ "type": "object",
3053
+ "required": ["field"],
3054
+ "additionalProperties": false,
3055
+ "properties": {
3056
+ "field": { "type": "string", "minLength": 1 },
3057
+ "dir": { "type": "string", "enum": ["asc", "desc"], "default": "asc" }
3058
+ }
3059
+ }
3060
+ }
3061
+ }
3062
+ },
3063
+ "toggle": {
3064
+ "type": "object",
3065
+ "required": ["key", "removes"],
3066
+ "additionalProperties": false,
3067
+ "description": "The workspace key a switch writes. While it is on (true, 1, \"1\", \"true\") the filter keys in `removes` are dropped, so a list that hides finished work shows it.",
3068
+ "properties": {
3069
+ "key": { "type": "string", "minLength": 1 },
3070
+ "removes": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } }
3071
+ }
2988
3072
  }
2989
3073
  }
2990
3074
  },
@@ -0,0 +1,48 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * contentHeaderLink — the text link a widget's content asks its card header
6
+ * to carry: `content.headerLink`, or the shorter `viewAllRoute` (with an
7
+ * optional `viewAllLabel`) that an inbox-style widget uses for its "View all".
8
+ *
9
+ * @module utils/contentHeaderLink
10
+ */
11
+
12
+ /**
13
+ * The header link a widget's `content` declares, in the shape
14
+ * CnWidgetWrapper's `headerLink` takes, or null when it declares none.
15
+ * The label is left as written: the wrapper translates it.
16
+ *
17
+ * `viewAllRoute` is a route name, or a location `{ name, params?, query? }`.
18
+ *
19
+ * @param {object|null|undefined} content The widget's content.
20
+ * @return {{label: string, route?: string, params?: object, query?: object, href?: string}|null}
21
+ */
22
+ export function contentHeaderLink(content) {
23
+ if (!content || typeof content !== 'object') {
24
+ return null
25
+ }
26
+ const declared = content.headerLink
27
+ if (declared && typeof declared === 'object' && typeof declared.label === 'string' && declared.label !== '') {
28
+ return declared
29
+ }
30
+ const view = content.viewAllRoute
31
+ const name = typeof view === 'string' ? view : (view && typeof view === 'object' ? view.name : '')
32
+ if (typeof name !== 'string' || name === '') {
33
+ return null
34
+ }
35
+ const link = {
36
+ label: typeof content.viewAllLabel === 'string' && content.viewAllLabel !== '' ? content.viewAllLabel : 'View all',
37
+ route: name,
38
+ }
39
+ if (view && typeof view === 'object') {
40
+ if (view.params && typeof view.params === 'object') {
41
+ link.params = view.params
42
+ }
43
+ if (view.query && typeof view.query === 'object') {
44
+ link.query = view.query
45
+ }
46
+ }
47
+ return link
48
+ }
@@ -37,14 +37,29 @@ export function normalizeFacetBucket(bucket) {
37
37
  return option
38
38
  }
39
39
 
40
+ /**
41
+ * The option id that stands for "this property holds no value".
42
+ *
43
+ * Chosen so no real bucket key can collide with it. Selecting it is sent to
44
+ * OpenRegister as `<property>_isnull=true` by {@link facetFilterParams}.
45
+ *
46
+ * @type {string}
47
+ */
48
+ export const MISSING_VALUE = '__cn_missing__'
49
+
40
50
  /**
41
51
  * Normalise the `facets` block of a collection response into sidebar format.
42
52
  *
43
53
  * Fields whose facet carries no bucket list are skipped entirely, so a caller
44
54
  * can tell an unfaceted field from one that faceted to nothing.
45
55
  *
56
+ * What OpenRegister puts beside the buckets is carried through rather than
57
+ * dropped: `missing` (how many objects in scope hold no value, sent as
58
+ * `{ results }` or `{ count }`), `type` and `title`. A missing count is only
59
+ * set when the backend sent one, for the same reason a bucket count is.
60
+ *
46
61
  * @param {object} facets The `facets` block of an API response
47
- * @return {object} `{ fieldName: { values: [{ value, count?, label? }] } }`
62
+ * @return {object} `{ fieldName: { values: [{ value, count?, label? }], missing?, type?, title? } }`
48
63
  */
49
64
  export function normalizeFacets(facets) {
50
65
  const transformed = {}
@@ -54,12 +69,69 @@ export function normalizeFacets(facets) {
54
69
  if (!buckets) {
55
70
  continue
56
71
  }
57
- transformed[key] = { values: buckets.map(normalizeFacetBucket) }
72
+ const normalized = { values: buckets.map(normalizeFacetBucket) }
73
+
74
+ const missing = facet.missing?.results ?? facet.missing?.count
75
+ if (typeof missing === 'number') {
76
+ normalized.missing = missing
77
+ }
78
+ for (const name of ['type', 'title']) {
79
+ if (facet[name] !== undefined && facet[name] !== null) {
80
+ normalized[name] = facet[name]
81
+ }
82
+ }
83
+
84
+ transformed[key] = normalized
58
85
  }
59
86
 
60
87
  return transformed
61
88
  }
62
89
 
90
+ /**
91
+ * The sidebar options for one normalised facet, with the missing-value option
92
+ * appended when some objects hold no value.
93
+ *
94
+ * @param {object|undefined} facet A normalised facet from {@link normalizeFacets}
95
+ * @param {function(number): string} missingLabel Renders the missing option's label from its count
96
+ * @return {Array<{id: unknown, label: string}>|null} The options, or null when the
97
+ * facet offers nothing, so the caller can fall back to its static options
98
+ */
99
+ export function facetOptions(facet, missingLabel) {
100
+ const options = (facet?.values || []).map((v) => ({
101
+ id: v.value,
102
+ label: facetOptionLabel(v),
103
+ }))
104
+
105
+ if (typeof facet?.missing === 'number' && facet.missing > 0) {
106
+ options.push({ id: MISSING_VALUE, label: missingLabel(facet.missing) })
107
+ }
108
+
109
+ return options.length > 0 ? options : null
110
+ }
111
+
112
+ /**
113
+ * The query parameters one facet selection sends.
114
+ *
115
+ * An ordinary selection is the property itself, one value unwrapped and
116
+ * several as a list. The missing-value option is exclusive: no object both
117
+ * holds one of the chosen values and holds none, so combining them would
118
+ * always answer an empty list. It is sent as `<property>_isnull=true`.
119
+ *
120
+ * @param {string} key The filtered property
121
+ * @param {Array} values The selected option ids
122
+ * @return {object} The parameters to merge into the request
123
+ */
124
+ export function facetFilterParams(key, values) {
125
+ if (!Array.isArray(values) || values.length === 0) {
126
+ return {}
127
+ }
128
+ if (values.includes(MISSING_VALUE)) {
129
+ return { [`${key}_isnull`]: 'true' }
130
+ }
131
+
132
+ return { [key]: values.length === 1 ? values[0] : values }
133
+ }
134
+
63
135
  /**
64
136
  * Build the visible label for one faceted filter option.
65
137
  *
@@ -9,8 +9,9 @@
9
9
  * `status: closed`, ...). Those are answered by one `/grouped` request on that
10
10
  * field. A filter that narrows on something else (two fields, an operator, a
11
11
  * list of values) cannot be read off a grouped result, so each of those costs
12
- * one `/value` count. A filter that holds a LIST of values is counted through
13
- * the list endpoint, because the aggregation endpoints cannot express it.
12
+ * one `/value` count. A filter that holds a LIST of values, or tests a field
13
+ * for no value (`isnull`), is counted through the list endpoint, because the
14
+ * aggregation endpoints cannot express it.
14
15
  * Nothing is requested for an entry that did not ask.
15
16
  *
16
17
  * @module utils/fetchFilterCounts
@@ -38,6 +39,37 @@ export function hasListValue(filter) {
38
39
  return Object.values(filter).some((value) => Array.isArray(value))
39
40
  }
40
41
 
42
+ /**
43
+ * Whether a filter tests a field for "no value" (`{ assignee: null }` or
44
+ * `{ supersededBy: { isnull: true } }`).
45
+ *
46
+ * OpenRegister's aggregation endpoints have no null operator: `isnull` is
47
+ * refused there and an empty value is dropped or matches nothing, so such a
48
+ * filter counted 0 while the list showed rows. The list endpoint does read
49
+ * `field[isnull]=true`, so a filter like this is counted through it.
50
+ *
51
+ * @param {object} filter A filter map.
52
+ * @return {boolean} True when any value is null or carries an `isnull` operator.
53
+ */
54
+ export function hasNullTest(filter) {
55
+ if (!filter || typeof filter !== 'object') {
56
+ return false
57
+ }
58
+ return Object.values(filter).some((value) => value === null
59
+ || (value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).some((op) => op.toLowerCase() === 'isnull')))
60
+ }
61
+
62
+ /**
63
+ * Whether the aggregation endpoints cannot express a filter, so it is counted
64
+ * through the list endpoint: a list of values, or a test for no value.
65
+ *
66
+ * @param {object} filter A filter map.
67
+ * @return {boolean} True when the filter needs the list endpoint.
68
+ */
69
+ export function needsListEndpoint(filter) {
70
+ return hasListValue(filter) || hasNullTest(filter)
71
+ }
72
+
41
73
  /**
42
74
  * Count the records a filter matches by asking the LIST endpoint for one
43
75
  * record and reading its `total`. The filter is sent the way the list request
@@ -59,6 +91,9 @@ export async function fetchListTotal(register, schema, filter, ctx) {
59
91
  for (const [op, operand] of Object.entries(value)) {
60
92
  params[`${key}[${op}]`] = operand
61
93
  }
94
+ } else if (value === null) {
95
+ // "No value": the list endpoint reads it as the isnull operator.
96
+ params[`${key}[isnull]`] = 'true'
62
97
  } else {
63
98
  params[key] = value
64
99
  }
@@ -125,12 +160,12 @@ export async function fetchFilterCounts({ register, schema, entries, baseFilter,
125
160
  // and `/value` returns exactly its number.
126
161
  // A list of values anywhere in a request rules the aggregation endpoints
127
162
  // out for it. A base filter with one rules them out for every entry.
128
- const baseHasList = hasListValue(base)
163
+ const baseHasList = needsListEndpoint(base)
129
164
  const byField = {}
130
165
  const single = []
131
166
  const viaList = []
132
167
  for (const entry of entries) {
133
- if (baseHasList || hasListValue(entry.filter)) {
168
+ if (baseHasList || needsListEndpoint(entry.filter)) {
134
169
  viaList.push(entry)
135
170
  continue
136
171
  }
@@ -49,6 +49,11 @@ function formatValue(value, format, now) {
49
49
  if (format === 'datetime') {
50
50
  return formatBoardDateTime(value, now)
51
51
  }
52
+ if (format === 'days') {
53
+ // Whole days from the date to now (0 for today; negative for a future date).
54
+ const date = parseDateValue(value)
55
+ return date === null ? '' : String(Math.floor((now.getTime() - date.getTime()) / 86400000))
56
+ }
52
57
  return String(value)
53
58
  }
54
59
 
@@ -57,7 +62,8 @@ function formatValue(value, format, now) {
57
62
  * `"Termijn {deadline|date}"` from an object.
58
63
  *
59
64
  * Every `{path}` token is a dot path into the object; `{path|date}` and
60
- * `{path|datetime}` format it as the boards write dates. A token without a
65
+ * `{path|datetime}` format it as the boards write dates, and `{path|days}` is the
66
+ * whole days since that date ("has waited {createdAt|days} days"). A token without a
61
67
  * value empties the WHOLE template, because "Termijn " is worse than nothing.
62
68
  * A template without tokens is returned as written (a constant label).
63
69
  *
@@ -0,0 +1,29 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * initials: the letters an avatar shows for a free name.
6
+ *
7
+ * @module utils/initials
8
+ */
9
+
10
+ /**
11
+ * The initials of a name: the first letters of its first and its last word,
12
+ * upper-cased ("Sanne de Vries" reads "SV", "Jan" reads "J").
13
+ *
14
+ * @param {unknown} name The name.
15
+ * @return {string} One or two letters, or '' when there is no name.
16
+ * @spec openspec/changes/screens-record-list-avatar/specs/record-list-avatar/spec.md#requirement-a-record-list-row-can-carry-an-avatar
17
+ */
18
+ export function initialsOf(name) {
19
+ if (typeof name !== 'string') {
20
+ return ''
21
+ }
22
+ const words = name.trim().split(/\s+/).filter((word) => word !== '')
23
+ if (words.length === 0) {
24
+ return ''
25
+ }
26
+ const first = Array.from(words[0])[0] || ''
27
+ const last = words.length > 1 ? (Array.from(words[words.length - 1])[0] || '') : ''
28
+ return (first + last).toLocaleUpperCase()
29
+ }
@@ -93,6 +93,8 @@ export const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
93
93
  'banner',
94
94
  'calendar',
95
95
  'chart',
96
+ 'chips-field',
97
+ 'choice-picker',
96
98
  'container',
97
99
  'conversation',
98
100
  'countdown',
@@ -125,21 +127,26 @@ export const DASHBOARD_CATALOG_WIDGET_KEYS = Object.freeze([
125
127
  'object-table',
126
128
  'people',
127
129
  'quicklinks',
130
+ 'radio-cards',
128
131
  'record-list',
129
132
  'related',
130
133
  'saved-view',
134
+ 'saved-views',
131
135
  'sections',
136
+ 'settings-form',
132
137
  'spend-analytics',
133
138
  'stacked-bar',
134
139
  'stages',
135
140
  'stat',
136
141
  'stats-block',
142
+ 'switch-rows',
137
143
  'table',
138
144
  'tabs',
139
145
  'tasks',
140
146
  'text',
141
147
  'tile',
142
148
  'timeline',
149
+ 'toggle-table',
143
150
  'video',
144
151
  'week-strip',
145
152
  'workspace-filter',