@conduction/nextcloud-vue 2.104.0 → 2.106.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 (224) hide show
  1. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  2. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -1
  4. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  5. package/dist/esm/components/CnCardGrid/CnCardGrid.vue.js +1 -1
  6. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +18 -0
  7. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
  8. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js +3 -1
  9. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
  10. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
  11. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +18 -0
  12. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  13. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +1 -1
  14. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  15. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  16. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +93 -1
  17. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  18. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  19. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  20. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +6 -3
  21. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  22. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +3 -3
  23. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  24. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +112 -5
  25. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  26. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  27. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue.js +9 -0
  28. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue.js.map +1 -0
  29. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js +24 -0
  30. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js.map +1 -0
  31. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js +49 -0
  32. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js.map +1 -0
  33. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  34. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +36 -18
  35. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  36. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +17 -38
  37. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  38. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  39. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  40. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  41. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue.js +1 -1
  42. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +1 -1
  43. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
  44. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
  45. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js +1 -1
  46. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js +2 -0
  47. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js.map +1 -1
  48. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js +32 -29
  49. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js.map +1 -1
  50. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  51. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +53 -2
  52. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  53. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +119 -111
  54. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  55. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue.js +1 -1
  56. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +62 -1
  57. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
  58. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
  59. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue.js +1 -1
  60. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js +73 -4
  61. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js.map +1 -1
  62. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js +74 -27
  63. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js.map +1 -1
  64. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +208 -46
  65. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  66. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +121 -98
  67. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  68. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +1 -1
  69. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +24 -0
  70. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -1
  71. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +20 -4
  72. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -1
  73. package/dist/esm/components/CnIndexPage/useSelfFetchList.js +25 -6
  74. package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
  75. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +112 -1
  76. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
  77. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +100 -44
  78. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
  79. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue.js +1 -1
  80. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js +67 -4
  81. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js.map +1 -1
  82. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js +1 -1
  83. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js.map +1 -1
  84. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue.js +9 -0
  85. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue.js.map +1 -0
  86. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue2.js +88 -0
  87. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue2.js.map +1 -0
  88. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue3.js +39 -0
  89. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue3.js.map +1 -0
  90. package/dist/esm/components/CnObjectCard/CnObjectCard.vue.js +1 -1
  91. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +295 -12
  92. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
  93. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +267 -144
  94. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
  95. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
  96. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +4 -0
  97. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  98. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +3 -1
  99. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  100. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  101. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +93 -1
  102. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  103. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +322 -246
  104. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  105. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue.js +1 -1
  106. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js +122 -7
  107. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js.map +1 -1
  108. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js +189 -93
  109. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js.map +1 -1
  110. package/dist/esm/components/CnSettingsField/CnSettingsField.vue.js +1 -1
  111. package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js.map +1 -1
  112. package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js +3 -3
  113. package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js.map +1 -1
  114. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
  115. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  116. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +16 -4
  117. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  118. package/dist/esm/components/CnTabs/CnTabs.vue.js +1 -1
  119. package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
  120. package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
  121. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  122. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +42 -0
  123. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  124. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +38 -19
  125. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  126. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue.js +9 -0
  127. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue.js.map +1 -0
  128. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue2.js +258 -0
  129. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue2.js.map +1 -0
  130. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue3.js +69 -0
  131. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue3.js.map +1 -0
  132. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
  133. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js +11 -0
  134. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
  135. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +4 -4
  136. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
  137. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +16 -14
  138. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
  139. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +12 -0
  140. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  141. package/dist/esm/index.js +2 -0
  142. package/dist/esm/index.js.map +1 -1
  143. package/dist/esm/l10n/en.json.js +24 -1
  144. package/dist/esm/l10n/en.json.js.map +1 -1
  145. package/dist/esm/l10n/nl.json.js +24 -1
  146. package/dist/esm/l10n/nl.json.js.map +1 -1
  147. package/dist/esm/library-style.js +1 -1
  148. package/dist/esm/nextcloud-vue.css +1369 -748
  149. package/dist/esm/utils/dueText.js +18 -3
  150. package/dist/esm/utils/dueText.js.map +1 -1
  151. package/dist/esm/utils/libraryWidgetKeys.js +2 -0
  152. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  153. package/dist/esm/utils/nextcloudApps.js +45 -0
  154. package/dist/esm/utils/nextcloudApps.js.map +1 -0
  155. package/dist/esm/utils/scopeListLayout.js +9 -2
  156. package/dist/esm/utils/scopeListLayout.js.map +1 -1
  157. package/dist/esm/utils/sortOrder.js +11 -1
  158. package/dist/esm/utils/sortOrder.js.map +1 -1
  159. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  160. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  161. package/dist/esm/utils/visibleWhen.js +12 -4
  162. package/dist/esm/utils/visibleWhen.js.map +1 -1
  163. package/dist/nextcloud-vue.cjs.js +8965 -6497
  164. package/dist/nextcloud-vue.cjs.js.map +2 -2
  165. package/dist/nextcloud-vue.css +1369 -748
  166. package/l10n/en.json +24 -1
  167. package/l10n/nl.json +24 -1
  168. package/package.json +1 -1
  169. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -1
  170. package/src/components/CnCardGrid/CnCardGrid.md +2 -0
  171. package/src/components/CnCardGrid/CnCardGrid.vue +20 -0
  172. package/src/components/CnCellRenderer/CnCellRenderer.vue +19 -1
  173. package/src/components/CnChartWidget/CnChartWidget.md +2 -0
  174. package/src/components/CnChartWidget/CnChartWidget.vue +93 -1
  175. package/src/components/CnDashboardPage/CnDashboardPage.vue +9 -6
  176. package/src/components/CnDetailPage/CnDetailPage.md +3 -2
  177. package/src/components/CnDetailPage/CnDetailPage.vue +112 -5
  178. package/src/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue +60 -0
  179. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +36 -29
  180. package/src/components/CnFactsWidget/CnFactsWidget.vue +4 -0
  181. package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -1
  182. package/src/components/CnFeaturesBoard/CnFeaturesBoard.vue +4 -1
  183. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +70 -4
  184. package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +62 -1
  185. package/src/components/CnFlowsPage/CnFlowBoard.vue +132 -8
  186. package/src/components/CnIndexPage/CnIndexPage.md +8 -5
  187. package/src/components/CnIndexPage/CnIndexPage.vue +236 -54
  188. package/src/components/CnIndexPage/CnIndexPageWidgets.vue +36 -1
  189. package/src/components/CnIndexPage/useSelfFetchList.js +25 -6
  190. package/src/components/CnIndexSidebar/CnIndexSidebar.md +2 -0
  191. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +148 -1
  192. package/src/components/CnNcWidgetWidget/CnNcWidgetWidget.vue +68 -5
  193. package/src/components/CnNoticeWidget/CnNoticeWidget.vue +153 -0
  194. package/src/components/CnNoticeWidget/index.js +12 -0
  195. package/src/components/CnObjectCard/CnObjectCard.md +1 -0
  196. package/src/components/CnObjectCard/CnObjectCard.vue +354 -13
  197. package/src/components/CnObjectSidebar/CnFilesTab.vue +6 -0
  198. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +190 -4
  199. package/src/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue +209 -9
  200. package/src/components/CnSettingsField/CnSettingsField.vue +4 -1
  201. package/src/components/CnSetupWizard/CnSetupWizard.vue +9 -1
  202. package/src/components/CnTabs/CnTabs.vue +11 -0
  203. package/src/components/CnTabsWidget/CnTabsWidget.vue +91 -18
  204. package/src/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.md +11 -0
  205. package/src/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue +382 -0
  206. package/src/components/CnTeamWorkloadWidget/index.js +14 -0
  207. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +15 -4
  208. package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
  209. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +12 -0
  210. package/src/components/index.js +2 -0
  211. package/src/css/dialog.css +20 -2
  212. package/src/css/index-page.css +14 -2
  213. package/src/css/look-board-detail.css +125 -8
  214. package/src/css/look-board-index.css +100 -4
  215. package/src/css/look-board.css +122 -14
  216. package/src/index.js +2 -1
  217. package/src/schemas/app-manifest-v2.schema.json +110 -8
  218. package/src/utils/dueText.js +18 -3
  219. package/src/utils/libraryWidgetKeys.js +2 -0
  220. package/src/utils/nextcloudApps.js +50 -0
  221. package/src/utils/scopeListLayout.js +9 -2
  222. package/src/utils/sortOrder.js +10 -0
  223. package/src/utils/validateManifestV2.compiled.js +3 -3
  224. package/src/utils/visibleWhen.js +12 -4
@@ -9,7 +9,7 @@ import { withPersonalLenses } from '../../utils/personalLenses.js'
9
9
  import { parseSortKeysFromQuery, resolveFilterMap, resolveQueryFilters } from '../../utils/routeFilters.js'
10
10
  import { orderToSortKeys } from '../../utils/sortOrder.js'
11
11
 
12
- function resolveInitialQuickFilterIndex(quickFilters) {
12
+ export function resolveInitialQuickFilterIndex(quickFilters) {
13
13
  const tabs = Array.isArray(quickFilters) ? quickFilters : null
14
14
  if (!tabs || tabs.length === 0) {
15
15
  return null
@@ -171,7 +171,8 @@ export function useSelfFetchList(props, instance, inject, extras = {}) {
171
171
  const objCtx = unwrapCtx(objectCtxRaw)
172
172
  const base = (objCtx && typeof objCtx === 'object') ? { ...objCtx } : {}
173
173
  base.workspace = unwrapCtx(workspaceCtxRaw) || {}
174
- base.config = unwrapCtx(appConfigRaw) || {}
174
+ // The page's own `appConfig` prop wins over a config an ancestor provides.
175
+ base.config = { ...(unwrapCtx(appConfigRaw) || {}), ...(props.appConfig || {}) }
175
176
  return base
176
177
  }
177
178
 
@@ -183,7 +184,7 @@ export function useSelfFetchList(props, instance, inject, extras = {}) {
183
184
  // never again. Stringified (like CnDeltaWidget's `sourceKey`) so the
184
185
  // watcher fires on real content changes only, not object identity.
185
186
  const workspaceSignature = computed(() => JSON.stringify(unwrapCtx(workspaceCtxRaw) || {}))
186
- const appConfigSignature = computed(() => JSON.stringify(unwrapCtx(appConfigRaw) || {}))
187
+ const appConfigSignature = computed(() => JSON.stringify({ ...(unwrapCtx(appConfigRaw) || {}), ...(props.appConfig || {}) }))
187
188
 
188
189
  // Pass register/schema in their positional id slots (not as a {register, schema} object as
189
190
  // second arg) — that previously made fetch URLs go to `/api/objects/undefined/[object Object]`.
@@ -227,6 +228,22 @@ export function useSelfFetchList(props, instance, inject, extras = {}) {
227
228
  ? props.sortKeys
228
229
  : (props.sortKey ? [{ key: props.sortKey, order: props.sortOrder || 'asc' }] : defaultOrderKeys)
229
230
  // The lens that is active on mount may set its own order.
231
+ /**
232
+ * The tabs the strip shows, so the fetch filters by the chip that is
233
+ * lit: the page's `effectiveQuickFilters` (declared, generated from
234
+ * `quickFiltersFrom`, or a named source's), else the prop while the
235
+ * page's computeds do not exist yet (setup).
236
+ *
237
+ * @return {Array<object>|null} The tabs, or null when there are none.
238
+ */
239
+ function shownQuickFilters() {
240
+ const shown = instance && instance.proxy ? instance.proxy.effectiveQuickFilters : undefined
241
+ if (shown !== undefined) {
242
+ return shown
243
+ }
244
+ return withPersonalLenses(props.quickFilters, props.personalLenses)
245
+ }
246
+
230
247
  const initialTabs = withPersonalLenses(props.quickFilters, props.personalLenses)
231
248
  const initialTabSort = (!isMultiQuickFilter && Array.isArray(initialTabs))
232
249
  ? quickFilterSortKeys(initialTabs[activeQuickFilterIndex.value])
@@ -274,7 +291,7 @@ export function useSelfFetchList(props, instance, inject, extras = {}) {
274
291
  // The visible calendar month, only while the page is in calendar mode.
275
292
  const month = (instance && instance.proxy && instance.proxy.calendarRangeFilter) || {}
276
293
  const scope = { ...(widen ? { ...scopeBase, _content_search: 'true' } : scopeBase), ...month }
277
- const lensed = withPersonalLenses(props.quickFilters, props.personalLenses)
294
+ const lensed = shownQuickFilters()
278
295
  const tabs = Array.isArray(lensed) ? lensed : null
279
296
  if (!tabs) {
280
297
  return { ...queryFilters, ...scope, ...base }
@@ -289,7 +306,9 @@ export function useSelfFetchList(props, instance, inject, extras = {}) {
289
306
 
290
307
  // Single mode: the active tab's filter spread last so it wins
291
308
  // over a colliding props.filter entry.
292
- const activeIdx = activeQuickFilterIndex.value
309
+ // Tabs that arrived after setup (`quickFiltersFrom`) have no index
310
+ // yet: the default tab is the one in force, as the strip shows.
311
+ const activeIdx = activeQuickFilterIndex.value ?? resolveInitialQuickFilterIndex(tabs)
293
312
  const tabFilter = (activeIdx !== null && activeIdx !== undefined) ? tabs[activeIdx]?.filter : null
294
313
  return { ...queryFilters, ...scope, ...base, ...resolveFilterMap(tabFilter, params, ctx) }
295
314
  },
@@ -309,7 +328,7 @@ export function useSelfFetchList(props, instance, inject, extras = {}) {
309
328
  // for a lens without goes back to the page's own order. A column the
310
329
  // person sorts by afterwards still wins until the next switch.
311
330
  if (!isMultiQuickFilter && before && now[0] !== before[0]) {
312
- const tabs = withPersonalLenses(props.quickFilters, props.personalLenses) || []
331
+ const tabs = shownQuickFilters() || []
313
332
  const keys = quickFilterSortKeys(tabs[now[0]]) || (quickFilterSortKeys(tabs[before[0]]) ? pageSortKeys : null)
314
333
  if (keys && list.sortKeys) {
315
334
  list.sortKeys.value = keys
@@ -197,6 +197,8 @@ With admin-only filter control:
197
197
  | `visibleColumns` | Array | `null` | Currently visible column keys; `null` means all visible |
198
198
  | `personalColumns` | Boolean | `true` | Show the Order and pin list and Reset columns in the Columns tab |
199
199
  | `pinnedCount` | Number | `0` | How many of the first visible columns are pinned to the start of the table |
200
+ | `sortOptions` | Array | `[]` | The orders the Sort section in the Search tab offers: `[{ value, label, keys }]`, `keys` a `[{ key, order }]` list read as its ascending form. Empty draws no Sort section. Choosing a field or a direction emits `sort-change` with the new `[{ key, order }]`. |
201
+ | `currentSort` | Array | `[]` | The list's current order, `[{ key, order }]`: the Sort section shows its field, its direction and "Current order: ...". |
200
202
  | `facetData` | Object | `{}` | Live facet data from the API: `{ fieldName: { values: [{ value, count }] } }` |
201
203
  | `columnGroups` | Array | `[]` | Extra column groups beyond schema properties and built-in Metadata. Each: `{ id, label, columns: [{ key, label }], expanded? }` |
202
204
  | `showMetadata` | Boolean | `true` | Whether to include the built-in Metadata column group |
@@ -37,6 +37,42 @@
37
37
  @update:modelValue="$emit('search', $event)" />
38
38
  </div>
39
39
 
40
+ <!-- Sort (list, card and map views; the table sorts by its headers):
41
+ the field, the direction and the order the list has now. -->
42
+ <div v-if="sortOptions.length > 0" class="cn-index-sidebar__section cn-index-sidebar__sort" data-testid="cn-index-sidebar-sort">
43
+ <h3>{{ t('nextcloud-vue', 'Sort') }}</h3>
44
+ <NcSelect
45
+ :modelValue="sortSelected"
46
+ :options="sortOptions"
47
+ label="label"
48
+ :clearable="false"
49
+ :inputLabel="t('nextcloud-vue', 'Sort by')"
50
+ @update:modelValue="onSortField" />
51
+ <div class="cn-index-sidebar__sort-direction" role="radiogroup" :aria-label="t('nextcloud-vue', 'Direction')">
52
+ <NcCheckboxRadioSwitch
53
+ type="radio"
54
+ name="cn-index-sidebar-sort-direction"
55
+ value="asc"
56
+ :modelValue="sortDirection"
57
+ :disabled="!sortSelected"
58
+ @update:modelValue="onSortDirection">
59
+ {{ t('nextcloud-vue', 'Ascending') }}
60
+ </NcCheckboxRadioSwitch>
61
+ <NcCheckboxRadioSwitch
62
+ type="radio"
63
+ name="cn-index-sidebar-sort-direction"
64
+ value="desc"
65
+ :modelValue="sortDirection"
66
+ :disabled="!sortSelected"
67
+ @update:modelValue="onSortDirection">
68
+ {{ t('nextcloud-vue', 'Descending') }}
69
+ </NcCheckboxRadioSwitch>
70
+ </div>
71
+ <p class="cn-index-sidebar__sort-current" data-testid="cn-index-sidebar-sort-current">
72
+ {{ currentSortText }}
73
+ </p>
74
+ </div>
75
+
40
76
  <div v-if="hasActiveFilters" class="cn-index-sidebar__section">
41
77
  <NcButton variant="tertiary" @click="onClearAll">
42
78
  <template #icon>
@@ -273,6 +309,7 @@ import { METADATA_COLUMNS } from '../../constants/metadata.js'
273
309
  import { facetOptions } from '../../utils/facets.js'
274
310
  import { labelLang } from '../../utils/manifestTranslate.js'
275
311
  import { columnsFromSchema, filtersFromSchema } from '../../utils/schema.js'
312
+ import { reverseSortKeys, sameSortKeys } from '../../utils/sortOrder.js'
276
313
  import { CnDateRangePicker, DEFAULT_DATE_RANGE_PRESETS } from '../CnDateRangePicker/index.js'
277
314
  import { CnIcon } from '../CnIcon/index.js'
278
315
 
@@ -366,6 +403,22 @@ export default {
366
403
  default: 0,
367
404
  },
368
405
 
406
+ /**
407
+ * The orders the Sort section offers, `[{ value, label, keys }]`, each
408
+ * `keys` a `[{ key, order }]` list read as its ascending form. Empty
409
+ * draws no Sort section (the table view sorts by its headers).
410
+ */
411
+ sortOptions: {
412
+ type: Array,
413
+ default: () => [],
414
+ },
415
+
416
+ /** The list's current order, `[{ key, order }]`. */
417
+ currentSort: {
418
+ type: Array,
419
+ default: () => [],
420
+ },
421
+
369
422
  /** Current search term */
370
423
  searchValue: {
371
424
  type: String,
@@ -508,7 +561,7 @@ export default {
508
561
  },
509
562
  },
510
563
 
511
- emits: ['clear-filters', 'columns-change', 'columns-reorder', 'columns-reset', 'pin-change', 'filter-change', 'search', 'tab-change', 'update:open'],
564
+ emits: ['clear-filters', 'columns-change', 'columns-reorder', 'columns-reset', 'pin-change', 'filter-change', 'search', 'sort-change', 'tab-change', 'update:open'],
512
565
 
513
566
  data() {
514
567
  return {
@@ -535,6 +588,56 @@ export default {
535
588
  },
536
589
 
537
590
  computed: {
591
+ /**
592
+ * The Sort option the current order matches, as declared or reversed.
593
+ *
594
+ * @return {{option: object, direction: string}|null} The match, or null.
595
+ */
596
+ sortMatch() {
597
+ const current = Array.isArray(this.currentSort) ? this.currentSort : []
598
+ for (const option of this.sortOptions) {
599
+ const keys = Array.isArray(option.keys) ? option.keys : []
600
+ if (sameSortKeys(keys, current)) {
601
+ return { option, direction: 'asc' }
602
+ }
603
+ if (sameSortKeys(reverseSortKeys(keys), current)) {
604
+ return { option, direction: 'desc' }
605
+ }
606
+ }
607
+ return null
608
+ },
609
+
610
+ /** @return {object|null} The Sort option shown as chosen. */
611
+ sortSelected() {
612
+ return this.sortMatch ? this.sortMatch.option : null
613
+ },
614
+
615
+ /** @return {string} The chosen direction, `asc` or `desc`. */
616
+ sortDirection() {
617
+ return this.sortMatch ? this.sortMatch.direction : 'asc'
618
+ },
619
+
620
+ /**
621
+ * The order the list has now, in words: "Current order: Created,
622
+ * descending", or that it is not sorted.
623
+ *
624
+ * @return {string} The sentence.
625
+ */
626
+ currentSortText() {
627
+ const current = Array.isArray(this.currentSort) ? this.currentSort : []
628
+ if (current.length === 0) {
629
+ return t('nextcloud-vue', 'Not sorted')
630
+ }
631
+ const labelOf = (key) => {
632
+ const single = this.sortOptions.find((o) => Array.isArray(o.keys) && o.keys.length === 1 && o.keys[0].key === key)
633
+ return single ? single.label : key
634
+ }
635
+ const order = this.sortMatch && this.sortMatch.option.keys.length > 1
636
+ ? `${this.sortMatch.option.label}, ${this.sortDirection === 'desc' ? t('nextcloud-vue', 'descending') : t('nextcloud-vue', 'ascending')}`
637
+ : current.map((entry) => `${labelOf(entry.key)}, ${entry.order === 'desc' ? t('nextcloud-vue', 'descending') : t('nextcloud-vue', 'ascending')}`).join('; ')
638
+ return t('nextcloud-vue', 'Current order: {order}', { order })
639
+ },
640
+
538
641
  /** Resolved icon — explicit prop overrides schema.icon */
539
642
  resolvedIcon() {
540
643
  return this.icon || this.schema?.icon || ''
@@ -692,6 +795,50 @@ export default {
692
795
  },
693
796
 
694
797
  methods: {
798
+ t,
799
+
800
+ /**
801
+ * A field chosen in the Sort section: its order, in the direction
802
+ * already chosen.
803
+ *
804
+ * @param {object|null} option The chosen option.
805
+ * @return {void}
806
+ */
807
+ onSortField(option) {
808
+ const chosen = option && this.sortOptions.find((o) => o.value === option.value)
809
+ if (!chosen) {
810
+ return
811
+ }
812
+ this.emitSort(chosen, this.sortDirection)
813
+ },
814
+
815
+ /**
816
+ * A direction chosen in the Sort section, for the chosen field.
817
+ *
818
+ * @param {string} direction `asc` or `desc`.
819
+ * @return {void}
820
+ */
821
+ onSortDirection(direction) {
822
+ if (!this.sortSelected) {
823
+ return
824
+ }
825
+ this.emitSort(this.sortSelected, direction === 'desc' ? 'desc' : 'asc')
826
+ },
827
+
828
+ /**
829
+ * Emit the order an option stands for in a direction.
830
+ *
831
+ * @param {object} option The Sort option.
832
+ * @param {string} direction `asc` or `desc`.
833
+ * @return {void}
834
+ */
835
+ emitSort(option, direction) {
836
+ const keys = Array.isArray(option.keys) ? option.keys : []
837
+ /**
838
+ * @event sort-change The Sort section chose an order. Payload: `[{ key, order }]`.
839
+ */
840
+ this.$emit('sort-change', direction === 'desc' ? reverseSortKeys(keys) : keys.map((k) => ({ key: k.key, order: k.order || 'asc' })))
841
+ },
695
842
 
696
843
  /**
697
844
  * A manifest label through the injected lookup, or as written.
@@ -46,7 +46,7 @@
46
46
  {{ t('nextcloud-vue', 'This widget can only be shown on the Nextcloud dashboard itself.') }}
47
47
  </div>
48
48
 
49
- <div v-else-if="items.length === 0" class="cn-nc-widget-widget__state">
49
+ <div v-else-if="shownItems.length === 0" class="cn-nc-widget-widget__state">
50
50
  {{ t('nextcloud-vue', 'No items available') }}
51
51
  </div>
52
52
 
@@ -137,7 +137,10 @@ export default {
137
137
 
138
138
  props: {
139
139
  /**
140
- * Persisted widget content `{widgetId, displayMode, limit?, showAllUrl?, showAllLabel?, showHeader?}`.
140
+ * Persisted widget content `{widgetId, displayMode, limit?, showAllUrl?, showAllLabel?, showHeader?, excludeApps?, excludeTypes?}`.
141
+ * `excludeApps` (`["systemtags"]`) and `excludeTypes` leave out the
142
+ * items of those apps or types (an item's own `app` / `type`, else the
143
+ * app in its link or icon URL); the list then comes from the items API.
141
144
  * `showHeader: false` drops the widget's own header (icon and title);
142
145
  * it is dropped too when the hosting card draws a title row.
143
146
  * `limit` is the most items shown (the list then comes from the items
@@ -265,7 +268,38 @@ export default {
265
268
  * @return {object[]} the items.
266
269
  */
267
270
  shownItems() {
268
- return this.itemLimit > 0 ? this.items.slice(0, this.itemLimit) : this.items
271
+ const kept = this.hasExclusions ? this.items.filter((item) => !this.isExcluded(item)) : this.items
272
+ return this.itemLimit > 0 ? kept.slice(0, this.itemLimit) : kept
273
+ },
274
+
275
+ /**
276
+ * The apps and item types `content.excludeApps` / `content.excludeTypes`
277
+ * leave out, lower-cased.
278
+ *
279
+ * @spec openspec/changes/nc-widget-exclude-apps/specs/nc-widget/spec.md#requirement-a-nextcloud-widget-can-leave-out-items-of-an-app-or-type
280
+ * @return {{apps: string[], types: string[]}}
281
+ */
282
+ exclusions() {
283
+ const list = (value) => (Array.isArray(value) ? value : (typeof value === 'string' && value !== '' ? [value] : []))
284
+ .filter((entry) => typeof entry === 'string' && entry !== '')
285
+ .map((entry) => entry.toLowerCase())
286
+ return { apps: list(this.content && this.content.excludeApps), types: list(this.content && this.content.excludeTypes) }
287
+ },
288
+
289
+ /**
290
+ * How many items to ask the API for: the limit (or the default), and
291
+ * more while items are left out, so a filtered list still fills it.
292
+ *
293
+ * @return {number}
294
+ */
295
+ requestLimit() {
296
+ const base = this.itemLimit > 0 ? this.itemLimit : API_ITEM_LIMIT
297
+ return this.hasExclusions ? Math.max(base * 3, 20) : base
298
+ },
299
+
300
+ /** @return {boolean} Whether any app or type is left out. */
301
+ hasExclusions() {
302
+ return this.exclusions.apps.length > 0 || this.exclusions.types.length > 0
269
303
  },
270
304
 
271
305
  /**
@@ -317,6 +351,34 @@ export default {
317
351
  },
318
352
 
319
353
  methods: {
354
+ /**
355
+ * Whether an item belongs to an app or type the content leaves out: its
356
+ * own `app` / `type` / `objectType`, else the app in an `/apps/<app>/`
357
+ * part of its link or icons.
358
+ *
359
+ * @spec openspec/changes/nc-widget-exclude-apps/specs/nc-widget/spec.md#requirement-a-nextcloud-widget-can-leave-out-items-of-an-app-or-type
360
+ * @param {object} item A widget item.
361
+ * @return {boolean}
362
+ */
363
+ isExcluded(item) {
364
+ if (!item || typeof item !== 'object') {
365
+ return false
366
+ }
367
+ const { apps, types } = this.exclusions
368
+ const own = (value) => (typeof value === 'string' ? value.toLowerCase() : '')
369
+ const appsOf = [own(item.app)]
370
+ for (const url of [item.link, item.iconUrl, item.overlayIconUrl]) {
371
+ const match = typeof url === 'string' ? url.match(/\/apps\/([a-z0-9_-]+)\//i) : null
372
+ if (match) {
373
+ appsOf.push(match[1].toLowerCase())
374
+ }
375
+ }
376
+ if (apps.length > 0 && appsOf.some((app) => app !== '' && apps.includes(app))) {
377
+ return true
378
+ }
379
+ return types.length > 0 && [own(item.type), own(item.objectType)].some((type) => type !== '' && types.includes(type))
380
+ },
381
+
320
382
  t,
321
383
 
322
384
  /**
@@ -339,7 +401,8 @@ export default {
339
401
  // where the widget has a registered native callback.
340
402
  // A `limit` also takes the API list first: the native callback paints
341
403
  // as many items as it likes. Its fallback to native stays.
342
- if (this.displayMode !== 'horizontal' && this.itemLimit === 0) {
404
+ // Leaving items out works on the API list only, so it skips native too.
405
+ if (this.displayMode !== 'horizontal' && this.itemLimit === 0 && !this.hasExclusions) {
343
406
  const callback = this.resolveNativeCallback()
344
407
  if (typeof callback === 'function') {
345
408
  this.mountNative(callback)
@@ -424,7 +487,7 @@ export default {
424
487
  // both the flat-array (older providers) and `{items}` (v2) shapes.
425
488
  const url = generateOcsUrl('/apps/dashboard/api/v2/widget-items')
426
489
  const response = await axios.get(url, {
427
- params: { widgets: [this.widgetId], limit: this.itemLimit > 0 ? this.itemLimit : API_ITEM_LIMIT },
490
+ params: { widgets: [this.widgetId], limit: this.requestLimit },
428
491
  })
429
492
  const data = response && response.data && response.data.ocs ? response.data.ocs.data : null
430
493
  const widgetData = data ? data[this.widgetId] : null
@@ -0,0 +1,153 @@
1
+ <!--
2
+ - SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ - SPDX-License-Identifier: EUPL-1.2
4
+ -->
5
+ <template>
6
+ <section
7
+ v-if="resolvedText || resolvedTitle"
8
+ class="cn-notice-widget"
9
+ :class="`cn-notice-widget--${resolvedVariant}`"
10
+ :aria-label="resolvedTitle || null"
11
+ role="note"
12
+ data-testid="cn-notice-widget">
13
+ <CnIcon
14
+ class="cn-notice-widget__icon"
15
+ :name="resolvedIcon"
16
+ :size="18"
17
+ aria-hidden="true" />
18
+ <p class="cn-notice-widget__text">
19
+ <strong v-if="resolvedTitle" class="cn-notice-widget__title" data-testid="cn-notice-widget-title">{{ resolvedTitle }}</strong>{{ resolvedTitle && resolvedText ? ' ' : '' }}{{ resolvedText }}
20
+ </p>
21
+ </section>
22
+ </template>
23
+
24
+ <script>
25
+ import { CnIcon } from '../CnIcon/index.js'
26
+
27
+ /** The tones a notice takes, and the icon each shows by default. */
28
+ const VARIANT_ICONS = Object.freeze({
29
+ info: 'InformationOutline',
30
+ warning: 'AlertOutline',
31
+ error: 'AlertCircleOutline',
32
+ success: 'CheckCircleOutline',
33
+ })
34
+
35
+ /**
36
+ * CnNoticeWidget — a toned notice box: an icon, a bold title and a line of
37
+ * text in one rounded box, as the boards draw a warning beside a record
38
+ * (DqSociaalDomein "Bijzondere persoonsgegevens: gezondheid. Elke inzage
39
+ * wordt gelogd met het doel."). Usable in a detail page's side column, its
40
+ * body grid and on a dashboard as widget type `notice`; the surface applies
41
+ * the widget's `visibleWhen`.
42
+ *
43
+ * `content`: `{ variant?, title?, text, icon? }`. `variant` is `info`
44
+ * (default), `warning`, `error` or `success`; `title` and `text` go through
45
+ * the host's translate; `icon` names a CnIcon instead of the variant's own.
46
+ *
47
+ * ```vue
48
+ * <CnNoticeWidget :content="{ variant: 'warning', title: 'Special category data: health.', text: 'Every view is logged with its purpose.' }" />
49
+ * ```
50
+ */
51
+ export default {
52
+ name: 'CnNoticeWidget',
53
+
54
+ components: { CnIcon },
55
+
56
+ inject: {
57
+ /** The host app's translate function (CnAppRoot `cnTranslate`). */
58
+ cnTranslate: { default: () => (key) => key },
59
+ },
60
+
61
+ props: {
62
+ /**
63
+ * The notice: `{ variant?, title?, text, icon? }`.
64
+ *
65
+ * @type {{variant?: string, title?: string, text?: string, icon?: string}}
66
+ */
67
+ content: {
68
+ type: Object,
69
+ default: () => ({}),
70
+ },
71
+ },
72
+
73
+ computed: {
74
+ /** @return {string} `info`, `warning`, `error` or `success`. */
75
+ resolvedVariant() {
76
+ const variant = this.content && this.content.variant === 'danger' ? 'error' : this.content && this.content.variant
77
+ return Object.hasOwn(VARIANT_ICONS, variant) ? variant : 'info'
78
+ },
79
+
80
+ /** @return {string} The icon name. */
81
+ resolvedIcon() {
82
+ const icon = this.content && this.content.icon
83
+ return typeof icon === 'string' && icon !== '' ? icon : VARIANT_ICONS[this.resolvedVariant]
84
+ },
85
+
86
+ /** @return {string} The title through the host translate. */
87
+ resolvedTitle() {
88
+ return this.translate(this.content && this.content.title)
89
+ },
90
+
91
+ /** @return {string} The text through the host translate. */
92
+ resolvedText() {
93
+ return this.translate(this.content && this.content.text)
94
+ },
95
+ },
96
+
97
+ methods: {
98
+ /**
99
+ * @param {unknown} text A manifest string.
100
+ * @return {string} Its translation, '' for anything but a non-empty string.
101
+ */
102
+ translate(text) {
103
+ if (typeof text !== 'string' || text === '') {
104
+ return ''
105
+ }
106
+ return typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text
107
+ },
108
+ },
109
+ }
110
+ </script>
111
+
112
+ <style scoped>
113
+ .cn-notice-widget {
114
+ display: flex;
115
+ align-items: flex-start;
116
+ gap: 10px;
117
+ padding: 14px 16px;
118
+ border-radius: var(--cn-board-card-radius, 12px);
119
+ font-size: 14px;
120
+ line-height: 1.5;
121
+ background: color-mix(in srgb, var(--color-info, #0071ad) 10%, var(--color-main-background));
122
+ color: var(--color-main-text);
123
+ }
124
+
125
+ .cn-notice-widget--warning {
126
+ background: var(--color-warning-light, #fff4de);
127
+ color: var(--color-warning-text, #7a4f00);
128
+ }
129
+
130
+ .cn-notice-widget--error {
131
+ background: color-mix(in srgb, var(--color-error, #db0606) 10%, var(--color-main-background));
132
+ color: var(--color-error-text, var(--color-main-text));
133
+ }
134
+
135
+ .cn-notice-widget--success {
136
+ background: color-mix(in srgb, var(--color-success, #2d7b41) 10%, var(--color-main-background));
137
+ color: var(--color-success-text, var(--color-main-text));
138
+ }
139
+
140
+ .cn-notice-widget__icon {
141
+ flex: 0 0 auto;
142
+ margin-top: 2px;
143
+ }
144
+
145
+ .cn-notice-widget__text {
146
+ margin: 0;
147
+ overflow-wrap: anywhere;
148
+ }
149
+
150
+ .cn-notice-widget__title {
151
+ font-weight: 700;
152
+ }
153
+ </style>
@@ -0,0 +1,12 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * CnNoticeWidget — a toned notice box (icon, bold title, text) for a side
6
+ * column, a detail body or a dashboard (widget type `notice`).
7
+ */
8
+
9
+ import CnNoticeWidget from './CnNoticeWidget.vue'
10
+
11
+ export { CnNoticeWidget }
12
+ export default CnNoticeWidget
@@ -155,3 +155,4 @@ export default {
155
155
  | `leading` | Object | `null` | Leading element: `{ initials }` in a circle or `{ icon }` on a tint |
156
156
  | `footerAction` | Object | `null` | `{ label, meta?, ariaLabel? }`, one footer action; emits `footer-action` |
157
157
  | `cardFields` | Array | `null` | Property keys of the facts list, in order |
158
+ | `layout` | Object | `null` | Board look: the record layout, `{ headPill, number, title, meta, footerPill, footerDate }`; each part names a field and is left out while it is empty. A part whose field names a page column (`cardFieldColumns`) reads through that column's formatter, and a meta part through its widget, as the table cell does. A pill with `due` (`{ soonWithin }`) or `variantWhen` reads its date: overdue as error, soon (or the matching rule) as warning, labelled by `labels[variant]` or `labels.overdue` / `labels.soon` through the host's translate, else Overdue / Soon; any other date draws no pill. |