@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
@@ -0,0 +1,556 @@
1
+ <!--
2
+ - SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ - SPDX-License-Identifier: EUPL-1.2
4
+ -->
5
+
6
+ <template>
7
+ <div class="cn-choice-picker" data-testid="cn-choice-picker">
8
+ <p v-if="loading" class="cn-choice-picker__note" data-testid="cn-choice-picker-loading">
9
+ {{ tr('Loading') }}
10
+ </p>
11
+ <p v-else-if="options.length === 0" class="cn-choice-picker__note" data-testid="cn-choice-picker-empty">
12
+ {{ emptyLabel }}
13
+ </p>
14
+ <fieldset v-else class="cn-choice-picker__cards">
15
+ <legend class="cn-choice-picker__legend">
16
+ {{ legend }}
17
+ </legend>
18
+ <label
19
+ v-for="option in options"
20
+ :key="option.key"
21
+ class="cn-choice-picker__card"
22
+ :class="{
23
+ 'cn-choice-picker__card--selected': chosen === option.key,
24
+ 'cn-choice-picker__card--refused': option.refusal !== '',
25
+ }"
26
+ :data-testid="`cn-choice-picker-option-${option.key}`">
27
+ <input
28
+ class="cn-choice-picker__input"
29
+ type="radio"
30
+ :name="groupName"
31
+ :value="option.key"
32
+ :checked="chosen === option.key"
33
+ :disabled="option.refusal !== '' || saving"
34
+ @change="choose(option)">
35
+ <span class="cn-choice-picker__swatches" aria-hidden="true">
36
+ <span
37
+ v-for="(colour, index) in option.swatches"
38
+ :key="index"
39
+ class="cn-choice-picker__swatch"
40
+ :style="{ background: colour }" />
41
+ </span>
42
+ <span class="cn-choice-picker__text">
43
+ <strong class="cn-choice-picker__title">{{ option.title }}</strong>
44
+ <span v-if="option.refusal" class="cn-choice-picker__refusal" :data-testid="`cn-choice-picker-refusal-${option.key}`">
45
+ {{ option.refusal }}
46
+ </span>
47
+ </span>
48
+ <CnStatusBadge
49
+ v-if="option.pill"
50
+ :label="option.pill.label"
51
+ :variant="option.pill.variant"
52
+ size="small" />
53
+ </label>
54
+ </fieldset>
55
+
56
+ <NcNoteCard v-if="selected && selected.warning.length > 0" type="warning" data-testid="cn-choice-picker-warning">
57
+ <p v-if="warningIntro" class="cn-choice-picker__warning-intro">
58
+ {{ warningIntro }}
59
+ </p>
60
+ <ul class="cn-choice-picker__warning-list">
61
+ <li v-for="(line, index) in selected.warning" :key="index">
62
+ {{ line }}
63
+ </li>
64
+ </ul>
65
+ </NcNoteCard>
66
+
67
+ <NcNoteCard v-if="notice" :type="noticeType" data-testid="cn-choice-picker-notice">
68
+ {{ notice }}
69
+ </NcNoteCard>
70
+
71
+ <div v-if="options.length > 0 && !readOnly" class="cn-choice-picker__actions">
72
+ <NcButton
73
+ variant="primary"
74
+ :disabled="saving || !canSave"
75
+ data-testid="cn-choice-picker-save"
76
+ @click="save(false)">
77
+ {{ saveLabel }}
78
+ </NcButton>
79
+ <NcButton
80
+ v-if="selected && selected.warning.length > 0"
81
+ variant="secondary"
82
+ :disabled="saving"
83
+ data-testid="cn-choice-picker-confirm"
84
+ @click="save(true)">
85
+ {{ confirmLabel }}
86
+ </NcButton>
87
+ </div>
88
+ <p v-if="hint" class="cn-choice-picker__hint">
89
+ {{ hint }}
90
+ </p>
91
+ </div>
92
+ </template>
93
+
94
+ <script>
95
+ import { NcButton, NcNoteCard } from '@nextcloud/vue'
96
+ import CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'
97
+ import { isBadgeVariant } from '../../utils/badgeVariants.js'
98
+ import { prefixUrl } from '../../utils/headers.js'
99
+ import { objectUrlOf } from '../../utils/objectWrite.js'
100
+ import { readPath } from '../../utils/readPath.js'
101
+ import { serverErrorMessage } from '../../utils/serverErrorMessage.js'
102
+
103
+ let uid = 0
104
+
105
+ /**
106
+ * CnChoicePickerWidget — the `choice-picker` widget: choice cards read from a
107
+ * source (or a static list), each with a radio, colour swatches read from the
108
+ * item, a title and a state pill (a verdict such as "Leesbaar" / "Slecht
109
+ * leesbaar"), bound to one field of the CURRENT object.
110
+ *
111
+ * Save is a button. A card that carries a warning (a list of lines read from
112
+ * the item) shows them in a warning band under the cards and adds a second
113
+ * button, the confirm action, so a risky choice is saved only on purpose: the
114
+ * request tells the server which one was pressed (`@confirmed`). A card the
115
+ * source marks as refused is listed with its reason and cannot be chosen.
116
+ *
117
+ * @spec openspec/changes/abstract-choice-picker/specs/choice-picker/spec.md
118
+ */
119
+ export default {
120
+ name: 'CnChoicePickerWidget',
121
+
122
+ components: { CnStatusBadge, NcButton, NcNoteCard },
123
+
124
+ inject: {
125
+ cnTranslate: { default: () => (key) => key },
126
+ },
127
+
128
+ props: {
129
+ /**
130
+ * The widget configuration.
131
+ *
132
+ * - `field` (required) — the top-level field of the object holding the chosen key.
133
+ * - `source` — `{ url, path?, key, label, swatches?, state?, refusal?, warning? }` (tokens `@objectId`, `@object.<field>` in the url), or `items` — the same item shape as a static list.
134
+ * - `swatches` — path to an array of colours (CSS colour strings).
135
+ * - `state` — `{ path, map?, variantMap?, empty?, emptyVariant? }`: the pill; `map` turns the value into text, `variantMap` into a badge variant, `empty` is the text when the value is missing.
136
+ * - `refusal` — path to a string: the card is listed with it and cannot be chosen.
137
+ * - `warning` — `{ list, item? }`: `list` is a path to an array; `item` a template with `{path}` placeholders for object items (strings are used as they are).
138
+ * - `legend`, `warningIntro`, `saveLabel`, `confirmLabel`, `emptyText`, `hint`, `readOnly`.
139
+ * - `save` — `{ method?, url?, payload? }`: a custom request (tokens `@value`, `@confirmed`, `@objectId`, `@object.<field>`); without it the field is PATCHed on the object.
140
+ *
141
+ * @type {object}
142
+ */
143
+ content: {
144
+ type: Object,
145
+ default: () => ({}),
146
+ },
147
+
148
+ /** The current detail-page object. */
149
+ objectData: {
150
+ type: Object,
151
+ default: null,
152
+ },
153
+
154
+ /** The current object id. */
155
+ objectId: {
156
+ type: [String, Number],
157
+ default: '',
158
+ },
159
+
160
+ /** OpenRegister register slug of the surface. */
161
+ register: {
162
+ type: [String, Object],
163
+ default: '',
164
+ },
165
+
166
+ /** OpenRegister schema slug of the surface. */
167
+ schema: {
168
+ type: [String, Object],
169
+ default: '',
170
+ },
171
+ },
172
+
173
+ emits: ['saved'],
174
+
175
+ data() {
176
+ uid += 1
177
+ return {
178
+ groupName: `cn-choice-picker-${uid}`,
179
+ sourced: [],
180
+ loading: false,
181
+ saved: '',
182
+ chosen: '',
183
+ saving: false,
184
+ notice: '',
185
+ noticeType: 'success',
186
+ }
187
+ },
188
+
189
+ computed: {
190
+ spec() {
191
+ return this.content.source || {}
192
+ },
193
+
194
+ /** The cards, from the static items then the source. */
195
+ options() {
196
+ const items = [...(Array.isArray(this.content.items) ? this.content.items : []), ...this.sourced]
197
+ return items
198
+ .filter((item) => item && readPath(item, this.spec.key || 'key') !== undefined)
199
+ .map((item) => {
200
+ const key = String(readPath(item, this.spec.key || 'key'))
201
+ const refusal = this.spec.refusal ? readPath(item, this.spec.refusal) : ''
202
+ return {
203
+ key,
204
+ title: String(readPath(item, this.spec.label || 'label') ?? key),
205
+ swatches: this.listOf(item, this.spec.swatches).filter((colour) => typeof colour === 'string'),
206
+ pill: this.pillOf(item),
207
+ refusal: typeof refusal === 'string' ? refusal : '',
208
+ warning: this.warningOf(item),
209
+ }
210
+ })
211
+ },
212
+
213
+ selected() {
214
+ return this.options.find((option) => option.key === this.chosen) || null
215
+ },
216
+
217
+ canSave() {
218
+ return this.chosen !== '' && this.chosen !== this.saved && (this.selected ? this.selected.refusal === '' : false)
219
+ },
220
+
221
+ legend() {
222
+ return this.tr(this.content.legend || 'Choose one')
223
+ },
224
+
225
+ warningIntro() {
226
+ return this.tr(this.content.warningIntro || '')
227
+ },
228
+
229
+ saveLabel() {
230
+ return this.tr(this.content.saveLabel || 'Save')
231
+ },
232
+
233
+ confirmLabel() {
234
+ return this.tr(this.content.confirmLabel || 'Use it anyway')
235
+ },
236
+
237
+ emptyLabel() {
238
+ return this.tr(this.content.emptyText || 'Nothing to choose from')
239
+ },
240
+
241
+ hint() {
242
+ return this.tr(this.content.hint || '')
243
+ },
244
+
245
+ readOnly() {
246
+ return this.content.readOnly === true
247
+ },
248
+
249
+ objectUrl() {
250
+ return objectUrlOf(this.register, this.schema, this.objectId || (this.objectData && this.objectData.id))
251
+ },
252
+ },
253
+
254
+ watch: {
255
+ objectData() {
256
+ this.adopt()
257
+ this.loadSource()
258
+ },
259
+ },
260
+
261
+ created() {
262
+ this.adopt()
263
+ this.loadSource()
264
+ },
265
+
266
+ methods: {
267
+ tr(text) {
268
+ if (typeof text !== 'string' || text === '') {
269
+ return ''
270
+ }
271
+ return typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text
272
+ },
273
+
274
+ /** Read the current choice off the object. */
275
+ adopt() {
276
+ const stored = readPath(this.objectData || {}, this.content.field)
277
+ this.saved = stored === undefined || stored === null ? '' : String(stored)
278
+ this.chosen = this.saved
279
+ },
280
+
281
+ /**
282
+ * A list off an item.
283
+ *
284
+ * @param {object} item The item.
285
+ * @param {string} path The path, or undefined.
286
+ * @return {Array}
287
+ */
288
+ listOf(item, path) {
289
+ const list = path ? readPath(item, path) : []
290
+ return Array.isArray(list) ? list : []
291
+ },
292
+
293
+ /**
294
+ * The state pill of an item.
295
+ *
296
+ * @param {object} item The item.
297
+ * @return {{label: string, variant: string}|null}
298
+ */
299
+ pillOf(item) {
300
+ const state = this.spec.state
301
+ if (!state || !state.path) {
302
+ return null
303
+ }
304
+ const raw = readPath(item, state.path)
305
+ const empty = raw === undefined || raw === null || raw === ''
306
+ if (empty) {
307
+ return state.empty ? { label: this.tr(state.empty), variant: isBadgeVariant(state.emptyVariant) ? state.emptyVariant : 'default' } : null
308
+ }
309
+ const label = state.map && Object.hasOwn(state.map, String(raw)) ? this.tr(state.map[String(raw)]) : String(raw)
310
+ const variant = state.variantMap && state.variantMap[String(raw)]
311
+ return { label, variant: isBadgeVariant(variant) ? variant : 'default' }
312
+ },
313
+
314
+ /**
315
+ * The warning lines of an item.
316
+ *
317
+ * @param {object} item The item.
318
+ * @return {string[]}
319
+ */
320
+ warningOf(item) {
321
+ const spec = this.spec.warning
322
+ if (!spec || !spec.list) {
323
+ return []
324
+ }
325
+ return this.listOf(item, spec.list)
326
+ .map((entry) => {
327
+ if (typeof entry === 'string') {
328
+ return entry
329
+ }
330
+ if (!entry || typeof entry !== 'object' || !spec.item) {
331
+ return ''
332
+ }
333
+ return this.tr(spec.item).replace(/\{([\w.]+)\}/g, (m, path) => String(readPath(entry, path) ?? ''))
334
+ })
335
+ .filter((line) => line !== '')
336
+ },
337
+
338
+ /**
339
+ * Resolve the tokens in a url or payload.
340
+ *
341
+ * @param {unknown} value The value.
342
+ * @param {object} vars `{ value, confirmed }`.
343
+ * @return {unknown}
344
+ */
345
+ resolve(value, vars) {
346
+ if (Array.isArray(value)) {
347
+ return value.map((v) => this.resolve(v, vars))
348
+ }
349
+ if (value && typeof value === 'object') {
350
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, this.resolve(v, vars)]))
351
+ }
352
+ if (typeof value !== 'string') {
353
+ return value
354
+ }
355
+ const object = this.objectData || {}
356
+ if (value === '@value') {
357
+ return vars.value
358
+ }
359
+ if (value === '@confirmed') {
360
+ return vars.confirmed
361
+ }
362
+ const whole = value.match(/^@object\.([\w.@]+)$/)
363
+ if (whole) {
364
+ return readPath(object, whole[1])
365
+ }
366
+ return value
367
+ .replace(/@objectId/g, () => encodeURIComponent(String(this.objectId || object.id || '')))
368
+ .replace(/@object\.([\w.@]+)/g, (m, path) => encodeURIComponent(String(readPath(object, path) ?? '')))
369
+ .replace(/@value/g, () => encodeURIComponent(String(vars.value)))
370
+ },
371
+
372
+ /** Read the cards of `source`. */
373
+ async loadSource() {
374
+ if (!this.spec.url) {
375
+ return
376
+ }
377
+ this.loading = true
378
+ try {
379
+ const { default: axios } = await import('@nextcloud/axios')
380
+ const { data } = await axios.get(prefixUrl(String(this.resolve(this.spec.url, { value: '', confirmed: false }))))
381
+ const list = this.spec.path ? readPath(data, this.spec.path) : data
382
+ this.sourced = Array.isArray(list) ? list : []
383
+ } catch (e) {
384
+ this.noticeType = 'error'
385
+ this.notice = serverErrorMessage(e)
386
+ } finally {
387
+ this.loading = false
388
+ }
389
+ },
390
+
391
+ /**
392
+ * Choose a card (nothing is written until Save).
393
+ *
394
+ * @param {object} option The card.
395
+ */
396
+ choose(option) {
397
+ this.chosen = option.key
398
+ this.notice = ''
399
+ },
400
+
401
+ /**
402
+ * Save the chosen card: the configured request, else a PATCH of the field.
403
+ *
404
+ * @param {boolean} confirmed Whether the confirm action was pressed.
405
+ * @return {Promise<void>}
406
+ * @spec openspec/changes/abstract-choice-picker/specs/choice-picker/spec.md#requirement-a-risky-choice-is-saved-only-on-purpose
407
+ */
408
+ async save(confirmed) {
409
+ if (!this.selected) {
410
+ return
411
+ }
412
+ const request = this.content.save
413
+ if (!request && !this.objectUrl) {
414
+ this.noticeType = 'error'
415
+ this.notice = this.tr('This choice cannot be saved here.')
416
+ return
417
+ }
418
+ this.saving = true
419
+ this.notice = ''
420
+ try {
421
+ const { default: axios } = await import('@nextcloud/axios')
422
+ const vars = { value: this.selected.key, confirmed }
423
+ if (request && request.url) {
424
+ const method = String(request.method || 'POST').toLowerCase()
425
+ await axios[method](prefixUrl(String(this.resolve(request.url, vars))), this.resolve(request.payload || {}, vars))
426
+ } else {
427
+ await axios.patch(prefixUrl(this.objectUrl), { [this.content.field]: this.selected.key })
428
+ }
429
+ this.saved = this.selected.key
430
+ this.noticeType = 'success'
431
+ this.notice = this.tr(this.content.savedText || 'Saved.')
432
+ /**
433
+ * @event saved The choice was written.
434
+ * @type {{field: string, value: string, confirmed: boolean}}
435
+ */
436
+ this.$emit('saved', { field: this.content.field, value: this.saved, confirmed })
437
+ } catch (e) {
438
+ this.noticeType = 'error'
439
+ this.notice = serverErrorMessage(e)
440
+ } finally {
441
+ this.saving = false
442
+ }
443
+ },
444
+ },
445
+ }
446
+ </script>
447
+
448
+ <style scoped>
449
+ .cn-choice-picker {
450
+ display: flex;
451
+ flex-direction: column;
452
+ gap: 12px;
453
+ min-width: 0;
454
+ }
455
+
456
+ .cn-choice-picker__cards {
457
+ display: flex;
458
+ flex-wrap: wrap;
459
+ gap: 10px;
460
+ margin: 0;
461
+ padding: 0;
462
+ border: 0;
463
+ }
464
+
465
+ .cn-choice-picker__legend {
466
+ position: absolute;
467
+ width: 1px;
468
+ height: 1px;
469
+ overflow: hidden;
470
+ clip-path: inset(50%);
471
+ }
472
+
473
+ .cn-choice-picker__card {
474
+ position: relative;
475
+ display: flex;
476
+ flex: 1 1 220px;
477
+ align-items: center;
478
+ gap: 12px;
479
+ padding: 10px 12px;
480
+ border: 1px solid var(--cn-board-hairline, var(--color-border));
481
+ border-radius: 10px;
482
+ background: var(--color-main-background);
483
+ cursor: pointer;
484
+ }
485
+
486
+ .cn-choice-picker__card--selected {
487
+ padding: 9px 11px;
488
+ border: 2px solid var(--color-primary-element);
489
+ }
490
+
491
+ .cn-choice-picker__card--refused {
492
+ cursor: not-allowed;
493
+ opacity: 0.7;
494
+ }
495
+
496
+ .cn-choice-picker__card:focus-within {
497
+ outline: 2px solid var(--color-primary-element);
498
+ outline-offset: 2px;
499
+ }
500
+
501
+ .cn-choice-picker__input {
502
+ flex: 0 0 auto;
503
+ width: 18px;
504
+ height: 18px;
505
+ margin: 0;
506
+ accent-color: var(--color-primary-element);
507
+ }
508
+
509
+ .cn-choice-picker__swatches {
510
+ display: flex;
511
+ gap: 4px;
512
+ }
513
+
514
+ .cn-choice-picker__swatch {
515
+ width: 18px;
516
+ height: 18px;
517
+ box-sizing: border-box;
518
+ border: 1px solid var(--color-border);
519
+ border-radius: 4px;
520
+ }
521
+
522
+ .cn-choice-picker__text {
523
+ display: flex;
524
+ flex: 1 1 auto;
525
+ flex-direction: column;
526
+ gap: 2px;
527
+ min-width: 0;
528
+ }
529
+
530
+ .cn-choice-picker__title {
531
+ font-size: 15px;
532
+ }
533
+
534
+ .cn-choice-picker__refusal,
535
+ .cn-choice-picker__hint,
536
+ .cn-choice-picker__note {
537
+ margin: 0;
538
+ font-size: 13px;
539
+ color: var(--color-text-maxcontrast);
540
+ }
541
+
542
+ .cn-choice-picker__warning-intro {
543
+ margin: 0 0 4px;
544
+ }
545
+
546
+ .cn-choice-picker__warning-list {
547
+ margin: 0;
548
+ padding-inline-start: 18px;
549
+ }
550
+
551
+ .cn-choice-picker__actions {
552
+ display: flex;
553
+ flex-wrap: wrap;
554
+ gap: 8px;
555
+ }
556
+ </style>
@@ -0,0 +1,26 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * CnChoicePickerWidget — self-registers the `choice-picker` widget type (choice
6
+ * cards with colour swatches and a state pill, and a confirm-with-warning save)
7
+ * into the shared dashboardWidgetRegistry at module load.
8
+ */
9
+
10
+ import CnChoicePickerWidget from './CnChoicePickerWidget.vue'
11
+ import { registerDashboardWidget } from '../CnWidgetGrid/dashboardWidgetRegistry.js'
12
+
13
+ registerDashboardWidget('choice-picker', {
14
+ renderer: CnChoicePickerWidget,
15
+ form: null,
16
+ defaultContent: {
17
+ field: '',
18
+ items: [],
19
+ },
20
+ displayName: 'Choice picker',
21
+ icon: 'Palette',
22
+ surfaces: ['detail-page'],
23
+ })
24
+
25
+ export { CnChoicePickerWidget }
26
+ export default CnChoicePickerWidget
@@ -29,7 +29,7 @@ import { readPath } from '../../utils/readPath.js'
29
29
  * `content: { field, endpoint?, authorField?, timeField?, textField?, sideField?, usValue? }`.
30
30
  * `@objectId` in `endpoint` is replaced by the record's id. A reply the
31
31
  * endpoint accepts is shown at once, until the record is read again.
32
- * Resolved by its registry key, not exported.
32
+ * Resolved by its registry key, and exported for an app that renders it itself.
33
33
  */
34
34
  export default {
35
35
  name: 'CnConversationWidget',
@@ -424,14 +424,14 @@ export default {
424
424
  * anything whose height is whatever it turned out to have.
425
425
  *
426
426
  * @param {object} item the layout item.
427
- * @return {string|undefined} `'true'`, a row minimum as a string, or undefined.
427
+ * @return {string|undefined} `'true'`, a row maximum as a string, or undefined.
428
428
  */
429
429
  sizeToContentAttr(item) {
430
430
  const v = item && item.sizeToContent
431
431
  if (v === true) {
432
432
  return 'true'
433
433
  }
434
- // A number is GridStack's "size to content, but never below N rows".
434
+ // A number is GridStack's "size to content, but never taller than N rows" (a maximum).
435
435
  if (Number.isFinite(v) && v > 0) {
436
436
  return String(v)
437
437
  }