@conduction/nextcloud-vue 2.83.0 → 2.85.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  2. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +131 -3
  3. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  4. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +54 -18
  5. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  6. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
  7. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +1 -1
  8. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
  9. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
  10. package/dist/esm/components/CnBoardView/CnBoardView.vue.js +1 -1
  11. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +232 -4
  12. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  13. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +95 -51
  14. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  15. package/dist/esm/components/CnBodySections/CnBodySections.vue.js +1 -1
  16. package/dist/esm/components/CnBodySections/CnBodySections.vue2.js +22 -1
  17. package/dist/esm/components/CnBodySections/CnBodySections.vue2.js.map +1 -1
  18. package/dist/esm/components/CnBodySections/CnBodySections.vue3.js.map +1 -1
  19. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue.js +1 -1
  20. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js +47 -4
  21. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js.map +1 -1
  22. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js.map +1 -1
  23. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue.js +1 -1
  24. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +239 -3
  25. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
  26. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js +5 -5
  27. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
  28. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  29. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +50 -0
  30. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  31. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +22 -12
  32. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  33. package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue.js +1 -1
  34. package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue2.js +1 -1
  35. package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue2.js.map +1 -1
  36. package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue3.js.map +1 -1
  37. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +226 -8
  38. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  39. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  40. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  41. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +3 -1
  42. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  43. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  44. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  45. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +18 -5
  46. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  47. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  48. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js +1 -1
  49. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +38 -1
  50. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
  51. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +45 -5
  52. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
  53. package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue.js +1 -1
  54. package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue2.js +1 -1
  55. package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue2.js.map +1 -1
  56. package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue3.js.map +1 -1
  57. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +548 -77
  58. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  59. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +191 -82
  60. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  61. package/dist/esm/components/CnIndexPage/useSelfFetchList.js +11 -2
  62. package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
  63. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js +29 -6
  64. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js.map +1 -1
  65. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js.map +1 -1
  66. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
  67. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +126 -2
  68. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  69. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +24 -2
  70. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  71. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  72. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +106 -3
  73. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  74. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  75. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue.js +1 -1
  76. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js +67 -5
  77. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js.map +1 -1
  78. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js +181 -124
  79. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js.map +1 -1
  80. package/dist/esm/components/CnReportsPage/CnReportsPage.vue.js +1 -1
  81. package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js +135 -2
  82. package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js.map +1 -1
  83. package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js +95 -43
  84. package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js.map +1 -1
  85. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue.js +1 -1
  86. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js +5 -1
  87. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js.map +1 -1
  88. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js.map +1 -1
  89. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue.js +1 -1
  90. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +72 -5
  91. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
  92. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js +33 -8
  93. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
  94. package/dist/esm/components/CnStatWidget/CnStatWidget.vue.js +1 -1
  95. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +135 -11
  96. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  97. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +6 -2
  98. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  99. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue.js +1 -1
  100. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +17 -2
  101. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
  102. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +1 -1
  103. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
  104. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  105. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +39 -6
  106. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  107. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  108. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js +1 -1
  109. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +140 -7
  110. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -1
  111. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -1
  112. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
  113. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js +37 -9
  114. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
  115. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +6 -5
  116. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
  117. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue.js +1 -1
  118. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +16 -0
  119. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
  120. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
  121. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js +1 -1
  122. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +2 -1
  123. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -1
  124. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -1
  125. package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue2.js +53 -1
  126. package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue2.js.map +1 -1
  127. package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue3.js +3 -2
  128. package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue3.js.map +1 -1
  129. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
  130. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +135 -0
  131. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  132. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  133. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
  134. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +19 -0
  135. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
  136. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +37 -19
  137. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
  138. package/dist/esm/composables/useListView.js +21 -0
  139. package/dist/esm/composables/useListView.js.map +1 -1
  140. package/dist/esm/dialogs/CnTransitionInputDialog.vue.js +1 -1
  141. package/dist/esm/dialogs/CnTransitionInputDialog.vue2.js +68 -6
  142. package/dist/esm/dialogs/CnTransitionInputDialog.vue2.js.map +1 -1
  143. package/dist/esm/dialogs/CnTransitionInputDialog.vue3.js +133 -105
  144. package/dist/esm/dialogs/CnTransitionInputDialog.vue3.js.map +1 -1
  145. package/dist/esm/index.js +1 -0
  146. package/dist/esm/index.js.map +1 -1
  147. package/dist/esm/l10n/en.json.js +38 -1
  148. package/dist/esm/l10n/en.json.js.map +1 -1
  149. package/dist/esm/l10n/nl.json.js +78 -1
  150. package/dist/esm/l10n/nl.json.js.map +1 -1
  151. package/dist/esm/library-style.js +1 -1
  152. package/dist/esm/nextcloud-vue.css +1030 -533
  153. package/dist/esm/store/useObjectStore.js +16 -2
  154. package/dist/esm/store/useObjectStore.js.map +1 -1
  155. package/dist/esm/utils/boardColumns.js +8 -1
  156. package/dist/esm/utils/boardColumns.js.map +1 -1
  157. package/dist/esm/utils/boardReferenceColumns.js +278 -0
  158. package/dist/esm/utils/boardReferenceColumns.js.map +1 -0
  159. package/dist/esm/utils/buildManifest.js +41 -4
  160. package/dist/esm/utils/buildManifest.js.map +1 -1
  161. package/dist/esm/utils/detailWidgetVisibility.js +149 -0
  162. package/dist/esm/utils/detailWidgetVisibility.js.map +1 -0
  163. package/dist/esm/utils/formatMetric.js +39 -3
  164. package/dist/esm/utils/formatMetric.js.map +1 -1
  165. package/dist/esm/utils/gridStack.js +40 -1
  166. package/dist/esm/utils/gridStack.js.map +1 -1
  167. package/dist/esm/utils/humaniseKey.js +41 -0
  168. package/dist/esm/utils/humaniseKey.js.map +1 -0
  169. package/dist/esm/utils/notificationRuleLabel.js +6 -23
  170. package/dist/esm/utils/notificationRuleLabel.js.map +1 -1
  171. package/dist/esm/utils/resolveManifestTokens.js +1 -0
  172. package/dist/esm/utils/resolveManifestTokens.js.map +1 -1
  173. package/dist/esm/utils/sentinelTokens.js +9 -0
  174. package/dist/esm/utils/sentinelTokens.js.map +1 -1
  175. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  176. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  177. package/dist/esm/utils/widgetDispatch.js +8 -2
  178. package/dist/esm/utils/widgetDispatch.js.map +1 -1
  179. package/dist/nextcloud-vue.cjs.js +17159 -13422
  180. package/dist/nextcloud-vue.cjs.js.map +2 -2
  181. package/dist/nextcloud-vue.css +1030 -533
  182. package/l10n/en.json +38 -1
  183. package/l10n/nl.json +78 -1
  184. package/package.json +1 -1
  185. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +160 -8
  186. package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +1 -1
  187. package/src/components/CnBoardView/CnBoardView.vue +262 -6
  188. package/src/components/CnBodySections/CnBodySections.vue +22 -1
  189. package/src/components/CnCountdownWidget/CnCountdownWidget.vue +47 -4
  190. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +244 -8
  191. package/src/components/CnDashboardPage/CnDashboardPage.vue +62 -2
  192. package/src/components/CnDeltaWidget/CnDeltaWidget.vue +1 -1
  193. package/src/components/CnDetailPage/CnDetailPage.md +3 -1
  194. package/src/components/CnDetailPage/CnDetailPage.vue +226 -8
  195. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +3 -1
  196. package/src/components/CnFactsWidget/CnFactsWidget.vue +18 -5
  197. package/src/components/CnFieldListWidget/CnFieldListWidget.vue +63 -1
  198. package/src/components/CnGaugeWidget/CnGaugeWidget.vue +1 -1
  199. package/src/components/CnIndexPage/CnIndexPage.vue +586 -25
  200. package/src/components/CnIndexPage/useSelfFetchList.js +10 -1
  201. package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +29 -6
  202. package/src/components/CnObjectDataWidget/CnObjectDataWidget.md +1 -1
  203. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +154 -5
  204. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +106 -3
  205. package/src/components/CnRegisterMapping/CnRegisterMapping.md +5 -0
  206. package/src/components/CnRegisterMapping/CnRegisterMapping.vue +231 -96
  207. package/src/components/CnReportsPage/CnReportsPage.vue +179 -1
  208. package/src/components/CnSettingsPage/CnSettingsPage.vue +5 -1
  209. package/src/components/CnStagesWidget/CnStagesWidget.vue +103 -6
  210. package/src/components/CnStatWidget/CnStatWidget.vue +137 -12
  211. package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +34 -3
  212. package/src/components/CnTabsWidget/CnTabsWidget.vue +39 -6
  213. package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +140 -7
  214. package/src/components/CnVersionInfoCard/CnVersionInfoCard.md +2 -1
  215. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +42 -13
  216. package/src/components/CnWalkthrough/CnWalkthrough.vue +16 -0
  217. package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +2 -1
  218. package/src/components/CnWidgetCardGrid/CnWidgetCardGrid.vue +55 -2
  219. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +135 -0
  220. package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +10 -0
  221. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +44 -1
  222. package/src/composables/useListView.js +21 -0
  223. package/src/css/board.css +65 -4
  224. package/src/css/dialog.css +4 -2
  225. package/src/css/form-field.css +14 -0
  226. package/src/css/form-widget.css +11 -0
  227. package/src/css/index-page.css +49 -0
  228. package/src/css/kpi-card.css +13 -0
  229. package/src/css/look-board-reports.css +58 -0
  230. package/src/css/look-board.css +108 -7
  231. package/src/dialogs/CnTransitionInputDialog.vue +87 -7
  232. package/src/index.js +2 -0
  233. package/src/schemas/app-manifest-v2.schema.json +137 -6
  234. package/src/store/useObjectStore.js +16 -2
  235. package/src/utils/boardColumns.js +8 -1
  236. package/src/utils/boardReferenceColumns.js +274 -0
  237. package/src/utils/buildManifest.js +40 -3
  238. package/src/utils/detailWidgetVisibility.js +145 -0
  239. package/src/utils/formatMetric.js +38 -2
  240. package/src/utils/gridStack.js +39 -0
  241. package/src/utils/humaniseKey.js +38 -0
  242. package/src/utils/notificationRuleLabel.js +5 -23
  243. package/src/utils/resolveManifestTokens.js +1 -0
  244. package/src/utils/sentinelTokens.js +9 -0
  245. package/src/utils/validateManifestV2.compiled.js +3 -3
  246. package/src/utils/widgetDispatch.js +8 -2
@@ -26,6 +26,15 @@ function resolveInitialQuickFilterIndex(quickFilters) {
26
26
  * @param {Array<object>} filterMaps Already route-resolved filter maps.
27
27
  * @return {object} The merged filter (scalar when a key has one value, array when many).
28
28
  */
29
+ /**
30
+ * Rows one board fetch asks for. A board shows every matching row at once,
31
+ * so it loads far more than a table page (OpenRegister serves up to 1000);
32
+ * past this the board offers "Load more".
33
+ *
34
+ * @type {number}
35
+ */
36
+ export const BOARD_PAGE_SIZE = 500
37
+
29
38
  /**
30
39
  * The sort a quick filter sets while it is active (`sort: { key, order }`, or a
31
40
  * list of those for a multi-column order), as sort keys, or null when it sets
@@ -236,7 +245,7 @@ export function useSelfFetchList(props, instance, inject, extras = {}) {
236
245
  defaultSortKeys: initialSortKeys,
237
246
  defaultActiveFilters: initialActiveFilters,
238
247
  defaultSearchTerm: initialSearchTerm,
239
- defaultPageSize: (props.pagination && props.pagination.limit) || undefined,
248
+ defaultPageSize: props.viewMode === 'board' ? BOARD_PAGE_SIZE : ((props.pagination && props.pagination.limit) || undefined),
240
249
  defaultVisibleColumns: configuredColumnKeys.length ? configuredColumnKeys : null,
241
250
  // `pages[].config.extend` → OpenRegister's repeated `_extend[]`. A
242
251
  // getter so a reactive change re-scopes the next fetch, matching how
@@ -183,7 +183,7 @@ export default {
183
183
  /** Channel options `{ value, label }`. */
184
184
  channelOptions() {
185
185
  return Array.isArray(this.content.channels) && this.content.channels.length > 0
186
- ? this.content.channels
186
+ ? this.content.channels.map((channel) => ({ ...channel, label: this.copy(channel && channel.label) }))
187
187
  : [
188
188
  { value: 'telefoon', label: t('nextcloud-vue', 'Phone') },
189
189
  { value: 'email', label: t('nextcloud-vue', 'Email') },
@@ -204,10 +204,11 @@ export default {
204
204
  const prop = properties[this.content.outcomeField || 'outcome']
205
205
  if (prop && Array.isArray(prop.enum) && prop.enum.length > 0) {
206
206
  const labels = prop['x-enum-labels'] || prop.enumLabels || {}
207
- const tr = typeof this.cnTranslate === 'function' ? this.cnTranslate : (k) => k
208
- return prop.enum.map((value) => ({ value, label: tr(labels[value] || String(value)) }))
207
+ return prop.enum.map((value) => ({ value, label: this.copy(labels[value] || String(value)) }))
209
208
  }
210
- return Array.isArray(this.content.outcomes) ? this.content.outcomes : []
209
+ return Array.isArray(this.content.outcomes)
210
+ ? this.content.outcomes.map((outcome) => ({ ...outcome, label: this.copy(outcome && outcome.label) }))
211
+ : []
211
212
  },
212
213
 
213
214
  /**
@@ -280,8 +281,7 @@ export default {
280
281
  // `content.submitLabel` names what the form creates, e.g. "Save contact moment".
281
282
  registerLabel() {
282
283
  if (this.content.submitLabel) {
283
- const tr = typeof this.cnTranslate === 'function' ? this.cnTranslate : (k) => k
284
- return tr(this.content.submitLabel)
284
+ return this.copy(this.content.submitLabel)
285
285
  }
286
286
  return t('nextcloud-vue', 'Register')
287
287
  },
@@ -359,6 +359,29 @@ export default {
359
359
  },
360
360
 
361
361
  /** The default channel value (first configured channel, else `telefoon`). */
362
+ /**
363
+ * A label the manifest supplies (a channel, an outcome, the submit
364
+ * label), translated: the app's own translation first, since the words
365
+ * are the app's, then the library's catalogue, then the text as
366
+ * written. Before, a channel label was shown as written and the submit
367
+ * label only reached the app's translate, so pipelinq showed "Phone"
368
+ * and "Save contact moment" in English to a Dutch reader.
369
+ *
370
+ * @spec openspec/changes/interaction-form-fits-and-reads-dutch/specs/interaction-form-widget/spec.md#requirement-the-interaction-form-fits-its-card-and-reads-in-the-readers-language
371
+ * @param {string} key The label as the manifest writes it.
372
+ * @return {string} The translated label.
373
+ */
374
+ copy(key) {
375
+ if (typeof key !== 'string' || key === '') {
376
+ return key
377
+ }
378
+ const host = typeof this.cnTranslate === 'function' ? this.cnTranslate(key) : key
379
+ if (typeof host === 'string' && host !== '' && host !== key) {
380
+ return host
381
+ }
382
+ return t('nextcloud-vue', key)
383
+ },
384
+
362
385
  firstChannel() {
363
386
  const ch = (this.content && this.content.channels) || []
364
387
  return (Array.isArray(ch) && ch.length > 0 && ch[0].value) || 'telefoon'
@@ -121,7 +121,7 @@ export default {
121
121
  | `icon` | Object\|Function | `null` | MDI icon component shown in the card header |
122
122
  | `objectType` | String | `''` | Registered object type slug used by `objectStore.saveObject()` |
123
123
  | `store` | Object | `null` | Explicit Pinia objectStore instance; auto-detected via Pinia when omitted |
124
- | `overrides` | Object | `{}` | Per-property config: `{ key: { order, gridColumn, gridRow, hidden, editable, label, widget } }` |
124
+ | `overrides` | Object | `{}` | Per-property config: `{ key: { order, gridColumn, gridRow, hidden, editable, label, widget, visibleWhen } }`. `visibleWhen` shows the field only when the shared visibility predicate holds for this object, in its local mode: `{ field, op, value }` (ops `eq`, `neq`, `in`, `empty`, ...), or `all` / `any` of those. It reads unsaved changes too. A rule that needs a request (`endpoint`, `source`) is ignored. Read grid only: the Edit form still offers the field. |
125
125
  | `columns` | Number | `3` | Number of CSS grid columns in the widget |
126
126
  | `collapsedFields` (`collapsed-fields`) | Number | `null` | Number of fields shown while collapsed, with a "Show all N fields" button for the rest. `null` fits the cell: the whole rows that fit stay, and the button only appears when the cell overflows |
127
127
  | `hideEmpty` | Boolean | `false` | Hide fields with no value instead of rendering an em dash. For discriminated supertypes (one `ticket` schema holding request/complaint/contactmoment), so the grid is type-aware without the schema enumerating which fields belong to which variant. Read grid only — the field being edited, a field with unsaved changes, and the Edit form stay visible; `false`/`0` are values and are never hidden. |
@@ -339,16 +339,30 @@
339
339
  {{ stringifyCell(v) }}
340
340
  </span>
341
341
  </div>
342
- <!-- Single plain OBJECT → key: value definition list. -->
342
+ <!-- Single plain OBJECT → key: value definition list; a
343
+ nested plain object (e.g. a per-language map) gets
344
+ its own list one level down. -->
343
345
  <dl
344
346
  v-else-if="fieldValueKind(field) === 'object'"
345
347
  class="cn-object-data-widget__deflist">
346
348
  <template v-for="(pair, pi) in objectEntries(rawOf(field))" :key="pi">
347
349
  <dt>
348
- {{ pair[0] }}
350
+ {{ nestedLabel(field, [pair[0]]) }}
349
351
  </dt>
350
352
  <dd>
351
- {{ stringifyCell(pair[1]) }}
353
+ <dl
354
+ v-if="isPlainObject(pair[1])"
355
+ class="cn-object-data-widget__deflist cn-object-data-widget__deflist--nested">
356
+ <template v-for="(sub, si) in objectEntries(pair[1])" :key="si">
357
+ <dt>
358
+ {{ nestedLabel(field, [pair[0], sub[0]]) }}
359
+ </dt>
360
+ <dd>
361
+ <span class="cn-object-data-widget__deflist-text">{{ stringifyNested(sub[1]) }}</span>
362
+ </dd>
363
+ </template>
364
+ </dl>
365
+ <span v-else class="cn-object-data-widget__deflist-text">{{ stringifyNested(pair[1]) }}</span>
352
366
  </dd>
353
367
  </template>
354
368
  </dl>
@@ -413,15 +427,20 @@ import CnFormDialog from '../CnFormDialog/CnFormDialog.vue'
413
427
  import { normalizeLook } from '../../composables/useLook.js'
414
428
  import { useObjectStore } from '../../store/index.js'
415
429
  import { groupDisplayName, loadGroupDisplayName, searchNextcloudGroups } from '../../utils/groupAutocomplete.js'
430
+ import { humaniseKey } from '../../utils/humaniseKey.js'
416
431
  import { PANEL_ACTION_SINK } from '../../utils/panelActions.js'
417
432
  import { resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
418
433
  import { fieldsFromSchema, formatValue } from '../../utils/schema.js'
419
434
  import { schemaRefSlug } from '../../utils/schemaRefSlug.js'
420
435
  import { resolveNextcloudUser, searchNextcloudUsers } from '../../utils/userAutocomplete.js'
436
+ import { evaluateVisibleWhenLocal, isLocallyDecidableVisibleWhen } from '../../utils/visibleWhen.js'
421
437
  import { CnIcon } from '../CnIcon/index.js'
422
438
  import { CnObjectMetadataModal } from '../CnObjectMetadataModal/index.js'
423
439
  import { CnWidgetWrapper } from '../CnWidgetWrapper/index.js'
424
440
 
441
+ // Widgets whose value the inline text fallback would corrupt.
442
+ const STRUCTURED_WIDGETS = ['json', 'code', 'file', 'sub-objects', 'child-records']
443
+
425
444
  /**
426
445
  * CnObjectDataWidget — Schema-driven editable data grid widget.
427
446
  *
@@ -939,13 +958,18 @@ export default {
939
958
  gridRow: (this.resolvedOverrides[field.key] && this.resolvedOverrides[field.key].gridRow) || 1,
940
959
  }))
941
960
 
961
+ // Per-field condition: `overrides[key].visibleWhen` shows a field only
962
+ // for the objects it belongs to, such as a person's preferred name on
963
+ // a client that can also be an organisation.
964
+ const shown = withSpans.filter((field) => this.isVisibleByCondition(field))
965
+
942
966
  if (!this.hideEmpty) {
943
- return withSpans
967
+ return shown
944
968
  }
945
969
 
946
970
  // Keep a field the user is currently editing or has pending changes for,
947
971
  // otherwise it would vanish mid-edit the moment its value is cleared.
948
- return withSpans.filter((field) => field.key === this.editingField
972
+ return shown.filter((field) => field.key === this.editingField
949
973
  || field.key in this.dirtyFields
950
974
  || !this.isEmptyValue((this.objectData || {})[field.key]))
951
975
  },
@@ -1357,6 +1381,59 @@ export default {
1357
1381
  return (raw && typeof raw === 'object') ? Object.entries(raw) : []
1358
1382
  },
1359
1383
 
1384
+ /**
1385
+ * Whether a value is a plain object (not an array, not null).
1386
+ *
1387
+ * @param {unknown} v The value.
1388
+ * @return {boolean} True for a plain object.
1389
+ */
1390
+ isPlainObject(v) {
1391
+ return v !== null && typeof v === 'object' && !Array.isArray(v)
1392
+ },
1393
+
1394
+ /**
1395
+ * Label for a key inside an object value: the sub-schema's `title`
1396
+ * (translated) when the schema describes it, else the key, made
1397
+ * readable when it is identifier-shaped (`shortDescription` → "Short
1398
+ * description"; `nl` stays `nl`).
1399
+ *
1400
+ * @param {object} field Resolved field definition.
1401
+ * @param {string[]} path Keys from the field's value down to this entry.
1402
+ * @return {string} The label.
1403
+ * @spec openspec/changes/data-widget-nested-objects/specs/data-widget-nested-objects/spec.md#requirement-a-nested-object-reads-as-a-list-per-key
1404
+ */
1405
+ nestedLabel(field, path) {
1406
+ let prop = ((this.schema && this.schema.properties) || {})[field.key]
1407
+ for (const key of path) {
1408
+ if (!this.isPlainObject(prop)) {
1409
+ prop = null
1410
+ break
1411
+ }
1412
+ prop = (prop.properties && prop.properties[key])
1413
+ || (this.isPlainObject(prop.additionalProperties) ? prop.additionalProperties : null)
1414
+ }
1415
+ if (prop && typeof prop.title === 'string' && prop.title) {
1416
+ return this.cnTranslate(prop.title)
1417
+ }
1418
+ const key = String(path[path.length - 1])
1419
+ return /[A-Z_-]/.test(key) ? humaniseKey(key) : key
1420
+ },
1421
+
1422
+ /**
1423
+ * Stringify a value inside a definition list: a list of scalars reads
1424
+ * as a comma-separated line, anything else as `stringifyCell`.
1425
+ *
1426
+ * @param {unknown} v The value.
1427
+ * @return {string} The display string.
1428
+ * @spec openspec/changes/data-widget-nested-objects/specs/data-widget-nested-objects/spec.md#requirement-a-nested-object-reads-as-a-list-per-key
1429
+ */
1430
+ stringifyNested(v) {
1431
+ if (Array.isArray(v) && v.length && v.every((item) => item === null || typeof item !== 'object')) {
1432
+ return v.map((item) => this.stringifyCell(item)).join(', ')
1433
+ }
1434
+ return this.stringifyCell(v)
1435
+ },
1436
+
1360
1437
  /**
1361
1438
  * Stringify a scalar cell value; a nested object/array collapses to
1362
1439
  * compact JSON (never "[object Object]").
@@ -1684,15 +1761,53 @@ export default {
1684
1761
  }))
1685
1762
  },
1686
1763
 
1764
+ /**
1765
+ * Whether a field's value cannot be edited inline: a structured widget
1766
+ * (json, code, file, sub-objects, child-records), or a plain-object /
1767
+ * list-of-objects value that is not a relation. Scalar widgets without
1768
+ * a dedicated editor (duration, language, …) keep the text fallback.
1769
+ *
1770
+ * @param {object} field Resolved field definition.
1771
+ * @return {boolean} True when only the form dialog can edit it.
1772
+ * @spec openspec/changes/data-widget-nested-objects/specs/data-widget-nested-objects/spec.md#requirement-a-structured-value-is-never-edited-inline
1773
+ */
1774
+ hasNoInlineEditor(field) {
1775
+ if (STRUCTURED_WIDGETS.includes(field.widget)) {
1776
+ return true
1777
+ }
1778
+ const prop = ((this.schema && this.schema.properties) || {})[field.key]
1779
+ if (this.isRelationField(prop)) {
1780
+ return false
1781
+ }
1782
+ // A list of objects by schema, also while empty (it resolves to `tags`).
1783
+ if (prop && prop.type === 'array' && prop.items && prop.items.type === 'object') {
1784
+ return true
1785
+ }
1786
+ // Classify the raw value itself: fieldValueKind reports a formatter
1787
+ // field as 'scalar' whatever it holds.
1788
+ const raw = this.rawOf(field)
1789
+ if (Array.isArray(raw)) {
1790
+ return raw.some((v) => this.isPlainObject(v))
1791
+ }
1792
+ return this.isPlainObject(raw)
1793
+ },
1794
+
1687
1795
  /**
1688
1796
  * Check if a field is editable.
1689
1797
  *
1690
1798
  * @param {object} field - Resolved field definition from resolvedFields
1799
+ * @spec openspec/changes/data-widget-nested-objects/specs/data-widget-nested-objects/spec.md#requirement-a-structured-value-is-never-edited-inline
1691
1800
  */
1692
1801
  isEditable(field) {
1693
1802
  if (!this.editable) {
1694
1803
  return false
1695
1804
  }
1805
+ // No inline editor exists for structured values: the text fallback
1806
+ // would commit "[object Object]". Those edit through the form dialog,
1807
+ // unless the consumer supplies its own `field-<key>` editor slot.
1808
+ if (!this.$slots['field-' + field.key] && this.hasNoInlineEditor(field)) {
1809
+ return false
1810
+ }
1696
1811
  // Per-field override takes priority
1697
1812
  const override = this.resolvedOverrides[field.key]
1698
1813
  if (override && typeof override.editable === 'boolean') {
@@ -1709,6 +1824,29 @@ export default {
1709
1824
  return !field.readOnly
1710
1825
  },
1711
1826
 
1827
+ /**
1828
+ * Evaluate a field's `visibleWhen` override against the object.
1829
+ *
1830
+ * The rule is the shared visibility predicate in its LOCAL mode
1831
+ * (`{ field, op, value }`, `all` / `any`, `appInstalled`), evaluated by
1832
+ * `evaluateVisibleWhenLocal` against the object with its unsaved
1833
+ * changes, so a field appears or goes the moment the field it depends
1834
+ * on changes. A rule that needs a request (`endpoint`, `source`) cannot
1835
+ * be decided here and is ignored: the field shows, as it did before
1836
+ * the rule existed.
1837
+ *
1838
+ * @param {object} field - Resolved field definition.
1839
+ * @return {boolean} True when the field is shown.
1840
+ */
1841
+ isVisibleByCondition(field) {
1842
+ const override = this.resolvedOverrides[field.key]
1843
+ const rule = override ? override.visibleWhen : undefined
1844
+ if (rule === undefined || rule === null || !isLocallyDecidableVisibleWhen(rule)) {
1845
+ return true
1846
+ }
1847
+ return evaluateVisibleWhenLocal(rule, { ...(this.objectData || {}), ...this.dirtyFields })
1848
+ },
1849
+
1712
1850
  /**
1713
1851
  * Evaluate a field's conditional read-only rule against the object data.
1714
1852
  *
@@ -2410,6 +2548,17 @@ export default {
2410
2548
  overflow-wrap: anywhere;
2411
2549
  }
2412
2550
 
2551
+ /* A long description keeps its own paragraphs. Set on an inline span with no
2552
+ surrounding template whitespace, so only the value's own line breaks show. */
2553
+ .cn-object-data-widget__deflist-text {
2554
+ white-space: pre-wrap;
2555
+ }
2556
+
2557
+ .cn-object-data-widget__deflist--nested {
2558
+ padding-inline-start: 8px;
2559
+ border-inline-start: 2px solid var(--color-border);
2560
+ }
2561
+
2413
2562
  /* Responsive: collapse to single column on narrow widths */
2414
2563
  @media (max-width: 600px) {
2415
2564
  .cn-object-data-widget__grid {
@@ -141,9 +141,11 @@
141
141
  import { subscribe, unsubscribe } from '@nextcloud/event-bus'
142
142
  import { translate as t } from '@nextcloud/l10n'
143
143
  import { NcLoadingIcon } from '@nextcloud/vue'
144
+ import { inject, ref } from 'vue'
144
145
  import ChevronRight from 'vue-material-design-icons/ChevronRight.vue'
145
146
  import CnIcon from '../CnIcon/CnIcon.vue'
146
147
  import CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'
148
+ import { useEndpointSource } from '../../composables/useEndpointSource.js'
147
149
  import { actionLink, dispatchAction, interpolateActionTarget } from '../../utils/actionsDispatcher.js'
148
150
  import { isBadgeVariant } from '../../utils/badgeVariants.js'
149
151
  import { ageInDays } from '../../utils/boardDate.js'
@@ -180,7 +182,8 @@ function unwrap(value) {
180
182
  * objects filtered on a reference to this record. The source keys are those of
181
183
  * `object-list` (`register`, `schema`, `filter` with the `@objectId` /
182
184
  * `@object.<field>` / `@workspace.<key>` tokens, `sort`, `limit`, `extend`,
183
- * `rowRoute`, `pageControls`); `avatar` puts a circle with the initials of a name
185
+ * `rowRoute`, `pageControls`), or `endpointSource` (an app endpoint whose
186
+ * payload, after `responsePath`, is the array of rows, as on the object table); `avatar` puts a circle with the initials of a name
184
187
  * template before the title; what differs is the drawing: `presentation` is `rows`, `feed`
185
188
  * or `inline`, and each line is a template (`"{identifier} · {caseTypeName}"`,
186
189
  * `"Termijn {deadline|date}"`).
@@ -212,7 +215,11 @@ export default {
212
215
  /**
213
216
  * The list config: `{ register, schema, filter?, sort?, limit?, extend?,
214
217
  * presentation?, title?, meta?, text?, label?, pill?, trailing?, icon?,
215
- * rowRoute?, rowIdField?, headerLink?, headerNote?, emptyText?, errorText? }`.
218
+ * rowRoute?, rowIdField?, headerLink?, headerNote?, emptyText?, errorText?,
219
+ * valueLabels?, endpointSource? }`. `endpointSource` (`{ url, method?,
220
+ * params?, responsePath? }`) reads the rows from an app endpoint instead
221
+ * of `register` / `schema`. `valueLabels` maps a field's stored values to the labels
222
+ * the templates show.
216
223
  *
217
224
  * @type {object}
218
225
  */
@@ -236,6 +243,28 @@ export default {
236
243
 
237
244
  emits: ['header'],
238
245
 
246
+ setup(props) {
247
+ const objectCtxRaw = inject('cnObjectContext', null)
248
+ const workspaceRaw = inject('cnWorkspaceContext', null)
249
+ const appConfigRaw = inject('cnAppConfig', ref({}))
250
+ const { data, loading, error } = useEndpointSource(
251
+ () => props.content?.endpointSource || null,
252
+ {
253
+ ctx: () => {
254
+ const injected = unwrap(objectCtxRaw) || {}
255
+ return {
256
+ ...injected,
257
+ objectId: props.objectId ?? injected.objectId ?? props.objectData?.id ?? null,
258
+ object: props.objectData || injected.object || null,
259
+ workspace: unwrap(workspaceRaw) || {},
260
+ config: unwrapAppConfig(appConfigRaw) || {},
261
+ }
262
+ },
263
+ },
264
+ )
265
+ return { epData: data, epLoading: loading, epError: error }
266
+ },
267
+
239
268
  data() {
240
269
  return {
241
270
  rows: [],
@@ -292,6 +321,22 @@ export default {
292
321
  return { config: unwrapAppConfig(this.cnAppConfig) }
293
322
  },
294
323
 
324
+ /**
325
+ * Whether the rows come from `endpointSource` rather than OpenRegister.
326
+ *
327
+ * @return {boolean}
328
+ * @spec openspec/changes/endpoint-source-card-grid-record-list/specs/dashboard-page/spec.md#requirement-a-record-list-and-a-card-grid-can-read-an-endpoint
329
+ */
330
+ endpointActive() {
331
+ const source = this.content?.endpointSource
332
+ return Boolean(source && typeof source === 'object' && source.url)
333
+ },
334
+
335
+ /** @return {string} The endpoint read's state, to copy into the list when it changes. */
336
+ endpointKey() {
337
+ return this.endpointActive ? JSON.stringify([this.epData, this.epLoading, this.epError]) : ''
338
+ },
339
+
295
340
  /** @return {number} The fetch cap. */
296
341
  limit() {
297
342
  const value = Number(this.content?.limit)
@@ -396,6 +441,10 @@ export default {
396
441
  requestKey() {
397
442
  this.fetchRows()
398
443
  },
444
+
445
+ endpointKey() {
446
+ this.fetchRows()
447
+ },
399
448
  },
400
449
 
401
450
  mounted() {
@@ -441,7 +490,40 @@ export default {
441
490
  * @return {string} The text, '' when a token is empty.
442
491
  */
443
492
  fill(template, row) {
444
- return typeof template === 'string' && template !== '' ? fillTemplate(this.translate(template), row, new Date(), this.templateContext) : ''
493
+ return typeof template === 'string' && template !== '' ? fillTemplate(this.translate(template), this.labelledRow(row), new Date(), this.templateContext) : ''
494
+ },
495
+
496
+ /**
497
+ * The row as the templates read it: a field named in `valueLabels`
498
+ * (`{ channel: { phone: "Phone", telefoon: "Phone" } }`) shows the
499
+ * translated label of its stored value, so "{channel}" reads "Telefoon"
500
+ * for both spellings the data holds. A value without a label stays as
501
+ * stored. The pill, the badges and the sort read the stored value.
502
+ *
503
+ * @param {object} row The row.
504
+ * @return {object} The row, or a shallow copy with labelled fields.
505
+ * @spec openspec/changes/screens-record-list-value-labels/specs/record-list-value-labels/spec.md#requirement-a-record-list-template-can-show-a-label-for-a-stored-value
506
+ */
507
+ labelledRow(row) {
508
+ const map = this.content?.valueLabels
509
+ if (!row || typeof row !== 'object' || !map || typeof map !== 'object') {
510
+ return row
511
+ }
512
+ let out = row
513
+ for (const [field, labels] of Object.entries(map)) {
514
+ const raw = row[field]
515
+ if (!labels || typeof labels !== 'object' || (typeof raw !== 'string' && typeof raw !== 'number')) {
516
+ continue
517
+ }
518
+ const label = labels[String(raw)]
519
+ if (typeof label === 'string' && label !== '') {
520
+ if (out === row) {
521
+ out = { ...row }
522
+ }
523
+ out[field] = this.translate(label)
524
+ }
525
+ }
526
+ return out
445
527
  },
446
528
 
447
529
  /**
@@ -819,6 +901,23 @@ export default {
819
901
  return { rows, total: typeof head.total === 'number' ? head.total : rows.length }
820
902
  },
821
903
 
904
+ /**
905
+ * Copy the endpoint read into the list: its payload must be an array of
906
+ * rows (anything else is an empty list), and a failed read is an error.
907
+ *
908
+ * @spec openspec/changes/endpoint-source-card-grid-record-list/specs/dashboard-page/spec.md#requirement-a-record-list-and-a-card-grid-can-read-an-endpoint
909
+ */
910
+ takeEndpointRows() {
911
+ this.rows = Array.isArray(this.epData) ? this.epData : []
912
+ this.total = this.rows.length
913
+ this.loading = this.epLoading === true
914
+ this.error = Boolean(this.epError)
915
+ this.lenses = {}
916
+ if (!this.loading) {
917
+ this.emitHeader()
918
+ }
919
+ },
920
+
822
921
  /**
823
922
  * Fetch the list. Sends nothing while a required token is unresolved
824
923
  * or the source is incomplete.
@@ -827,6 +926,10 @@ export default {
827
926
  */
828
927
  async fetchRows() {
829
928
  const c = this.content || {}
929
+ if (this.endpointActive) {
930
+ this.takeEndpointRows()
931
+ return
932
+ }
830
933
  if (!c.register || !c.schema || this.waiting) {
831
934
  this.rows = []
832
935
  this.total = 0
@@ -111,6 +111,10 @@ export default {
111
111
  </script>
112
112
  ```
113
113
 
114
+ ## Several groups
115
+
116
+ With more than one group each group starts closed: its heading is a button that shows the group name and how many of its types are configured ("2/8 configured"), and opens the register select and type list on a click or Enter. A page with several registers is a short card until the administrator opens the group they need.
117
+
114
118
  ## Additional props
115
119
 
116
120
  | Prop | Type | Default | Description |
@@ -122,5 +126,6 @@ export default {
122
126
  | `reimporting` | Boolean | `false` | Disables the reimport button and shows a spinner while reimport is in progress |
123
127
  | `saveButtonText` | String | `'Save configuration'` | Label for the save button |
124
128
  | `reimportButtonText` | String | `'Re-import configuration'` | Label for the reimport button |
129
+ | `collapseGroups` | Boolean | `null` | Draw each group as a heading button that opens and closes it, with its configured count shown while closed; the groups start closed. `null` does this when there is more than one group, `true` or `false` forces it |
125
130
  | `autoMatch` | Boolean | `true` | Automatically match schema titles to type slugs when a register is selected |
126
131
  | `labels` | Object | *(see below)* | UI label overrides for all user-visible strings (register, schema, notConfigured, noSchemas, selectRegister, selectSchema, allConfigured, partiallyConfigured) |