@conduction/nextcloud-vue 2.56.0-beta.12 → 2.56.0-beta.14

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 (119) hide show
  1. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +19 -0
  2. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +10 -3
  4. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  5. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +101 -5
  6. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  7. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +23 -7
  8. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  9. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js +13 -0
  10. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js.map +1 -1
  11. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js +2 -1
  12. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js.map +1 -1
  13. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +125 -4
  14. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  15. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +439 -381
  16. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  17. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +9 -3
  18. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -1
  19. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +1 -1
  20. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -1
  21. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +4 -4
  22. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  23. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  24. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +142 -26
  25. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  26. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +195 -124
  27. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  28. package/dist/esm/components/CnFkResolveCell/CnFkResolveCell.vue2.js +3 -1
  29. package/dist/esm/components/CnFkResolveCell/CnFkResolveCell.vue2.js.map +1 -1
  30. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +144 -4
  31. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  32. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  33. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +84 -11
  34. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
  35. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +3 -2
  36. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
  37. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +31 -1
  38. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  39. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +335 -313
  40. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  41. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +11 -0
  42. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
  43. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js +2 -1
  44. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
  45. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +5 -11
  46. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  47. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  48. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
  49. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
  50. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +22 -3
  51. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  52. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +10 -3
  53. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  54. package/dist/esm/composables/useDependencyCheck.js +44 -1
  55. package/dist/esm/composables/useDependencyCheck.js.map +1 -1
  56. package/dist/esm/composables/useListView.js +43 -1
  57. package/dist/esm/composables/useListView.js.map +1 -1
  58. package/dist/esm/composables/useSetupStatus.js +26 -3
  59. package/dist/esm/composables/useSetupStatus.js.map +1 -1
  60. package/dist/esm/l10n/en.json.js +7 -1
  61. package/dist/esm/l10n/en.json.js.map +1 -1
  62. package/dist/esm/l10n/nl.json.js +7 -1
  63. package/dist/esm/l10n/nl.json.js.map +1 -1
  64. package/dist/esm/mixins/pageViews.js +400 -0
  65. package/dist/esm/mixins/pageViews.js.map +1 -0
  66. package/dist/esm/nextcloud-vue.css +202 -2
  67. package/dist/esm/store/plugins/liveUpdates.js +19 -9
  68. package/dist/esm/store/plugins/liveUpdates.js.map +1 -1
  69. package/dist/esm/store/useObjectStore.js +138 -42
  70. package/dist/esm/store/useObjectStore.js.map +1 -1
  71. package/dist/esm/utils/actionsDispatcher.js +25 -2
  72. package/dist/esm/utils/actionsDispatcher.js.map +1 -1
  73. package/dist/esm/utils/columnFilters.js +27 -3
  74. package/dist/esm/utils/columnFilters.js.map +1 -1
  75. package/dist/esm/utils/schema.js +40 -2
  76. package/dist/esm/utils/schema.js.map +1 -1
  77. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  78. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  79. package/dist/nextcloud-vue.cjs.js +2857 -1335
  80. package/dist/nextcloud-vue.cjs.js.map +2 -2
  81. package/dist/nextcloud-vue.css +202 -2
  82. package/l10n/en.json +7 -1
  83. package/l10n/nl.json +7 -1
  84. package/package.json +1 -1
  85. package/src/components/CnActionsBar/CnActionsBar.md +2 -1
  86. package/src/components/CnActionsBar/CnActionsBar.vue +25 -1
  87. package/src/components/CnAppNav/CnAppNav.vue +150 -9
  88. package/src/components/CnBreadcrumbs/CnBreadcrumbs.vue +14 -0
  89. package/src/components/CnDashboardPage/CnDashboardPage.vue +194 -10
  90. package/src/components/CnDataTable/CnColumnFilterPopover.vue +11 -5
  91. package/src/components/CnDataTable/CnDataTable.md +1 -1
  92. package/src/components/CnDataTable/CnDataTable.vue +4 -4
  93. package/src/components/CnDetailPage/CnDetailPage.md +5 -1
  94. package/src/components/CnDetailPage/CnDetailPage.vue +208 -11
  95. package/src/components/CnFkResolveCell/CnFkResolveCell.vue +3 -1
  96. package/src/components/CnFormDialog/CnFormDialog.vue +144 -4
  97. package/src/components/CnHeaderWidget/CnHeaderWidget.vue +100 -13
  98. package/src/components/CnIndexPage/CnIndexPage.md +2 -1
  99. package/src/components/CnIndexPage/CnIndexPage.vue +47 -1
  100. package/src/components/CnSegmentedControl/CnSegmentedControl.md +1 -1
  101. package/src/components/CnSegmentedControl/CnSegmentedControl.vue +12 -0
  102. package/src/components/CnSetupWizard/CnSetupWizard.vue +5 -11
  103. package/src/components/CnStagesWidget/CnStagesWidget.vue +9 -0
  104. package/src/components/CnStatWidget/CnStatWidget.vue +33 -5
  105. package/src/composables/useDependencyCheck.js +43 -0
  106. package/src/composables/useListView.js +43 -1
  107. package/src/composables/useSetupStatus.js +26 -3
  108. package/src/css/actions-bar.css +20 -2
  109. package/src/css/detail-page.css +27 -0
  110. package/src/css/kpi-card.css +47 -0
  111. package/src/mixins/pageViews.js +396 -0
  112. package/src/schemas/app-manifest-v2.schema.json +53 -4
  113. package/src/schemas/app-manifest.schema.json +1 -1
  114. package/src/store/plugins/liveUpdates.js +19 -9
  115. package/src/store/useObjectStore.js +137 -41
  116. package/src/utils/actionsDispatcher.js +25 -2
  117. package/src/utils/columnFilters.js +29 -2
  118. package/src/utils/schema.js +40 -2
  119. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -13,25 +13,28 @@ import InformationOutline from 'vue-material-design-icons/InformationOutline.vue
13
13
  import Pencil from 'vue-material-design-icons/Pencil.vue';
14
14
  import Plus from 'vue-material-design-icons/Plus.vue';
15
15
  import Refresh from 'vue-material-design-icons/Refresh.vue';
16
- import __sfc_main$h from '../../dialogs/CnRelationLinkModal.vue.js';
17
- import __sfc_main$g from '../../dialogs/CnWidgetStyleEditorModal.vue.js';
16
+ import ViewDashboardOutline from 'vue-material-design-icons/ViewDashboardOutline.vue';
17
+ import __sfc_main$i from '../../dialogs/CnRelationLinkModal.vue.js';
18
+ import __sfc_main$h from '../../dialogs/CnWidgetStyleEditorModal.vue.js';
18
19
  import __sfc_main$1 from '../CnActionsMenu/CnActionsMenu.vue.js';
19
- import __sfc_main$e from '../CnBodySections/CnBodySections.vue.js';
20
+ import __sfc_main$f from '../CnBodySections/CnBodySections.vue.js';
20
21
  import __sfc_main$2 from '../CnBuildiqEditButton/CnBuildiqEditButton.vue.js';
21
22
  import __sfc_main$7 from '../CnDashboardGrid/CnDashboardGrid.vue.js';
22
23
  import __sfc_main$5 from '../CnDetailWidgetHost/CnDetailWidgetHost.vue.js';
23
24
  import __sfc_main$6 from '../CnFormDialog/CnFormDialog.vue.js';
24
- import __sfc_main$8 from '../CnLifecycleActions/CnLifecycleActions.vue.js';
25
+ import __sfc_main$9 from '../CnLifecycleActions/CnLifecycleActions.vue.js';
25
26
  import __sfc_main$3 from '../CnLockedBanner/CnLockedBanner.vue.js';
26
27
  import __sfc_main$4 from '../CnNextStepCard/CnNextStepCard.vue.js';
27
- import __sfc_main$d from '../CnRelatedCollections/CnRelatedCollections.vue.js';
28
- import __sfc_main$c from '../CnSummaryAggregates/CnSummaryAggregates.vue.js';
29
- import __sfc_main$f from '../CnTranslatedBadge/CnTranslatedBadge.vue.js';
28
+ import __sfc_main$e from '../CnRelatedCollections/CnRelatedCollections.vue.js';
29
+ import __sfc_main$8 from '../CnSegmentedControl/CnSegmentedControl.vue.js';
30
+ import __sfc_main$d from '../CnSummaryAggregates/CnSummaryAggregates.vue.js';
31
+ import __sfc_main$g from '../CnTranslatedBadge/CnTranslatedBadge.vue.js';
30
32
  import { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js';
31
33
  import { useObjectLock } from '../../composables/useObjectLock.js';
32
34
  import { useObjectPresence } from '../../composables/useObjectPresence.js';
33
35
  import { useObjectSubscription } from '../../composables/useObjectSubscription.js';
34
36
  import { gridLayout } from '../../mixins/gridLayout.js';
37
+ import { pageViews } from '../../mixins/pageViews.js';
35
38
  import { isAppInstalled } from '../../utils/appInstalled.js';
36
39
  import { compactLayoutRows } from '../../utils/dashboardPlacement.js';
37
40
  import { defaultDetailGrid } from '../../utils/defaultDetailGrid.js';
@@ -39,10 +42,10 @@ import { stageOf, normalisePinnedAction, stageEntry, MAX_QUICK_ACTIONS, groupMen
39
42
  import { cnGridCellStyle, hasGridRow } from '../../utils/grid.js';
40
43
  import { slotRenders } from '../../utils/slotContent.js';
41
44
  import { isDataWidgetDef, isRelatedWidgetDef, isGeoWidgetDef, isContentOnlyWidgetDef, isCardWidgetDef, widgetContentOf, resolveRegistryRenderer, widgetTitleOf } from '../../utils/widgetDispatch.js';
42
- import __sfc_main$9 from '../CnActionButtons/CnActionButtons.vue.js';
43
- import __sfc_main$a from '../CnBreadcrumbs/CnBreadcrumbs.vue.js';
45
+ import __sfc_main$a from '../CnActionButtons/CnActionButtons.vue.js';
46
+ import __sfc_main$b from '../CnBreadcrumbs/CnBreadcrumbs.vue.js';
44
47
  import __sfc_main from '../CnIcon/CnIcon.vue.js';
45
- import __sfc_main$b from '../CnStatusBadge/CnStatusBadge.vue.js';
48
+ import __sfc_main$c from '../CnStatusBadge/CnStatusBadge.vue.js';
46
49
  import { getWidgetTypeEntry } from '../CnWidgetGrid/dashboardWidgetRegistry.js';
47
50
  import '../CnWidgetGrid/registerDashboardWidgets.js';
48
51
  import { useObjectStore } from '../../store/useObjectStore.js';
@@ -186,22 +189,24 @@ var script = {
186
189
  CnDetailWidgetHost: __sfc_main$5,
187
190
  CnFormDialog: __sfc_main$6,
188
191
  CnDashboardGrid: __sfc_main$7,
189
- CnLifecycleActions: __sfc_main$8,
190
- CnActionButtons: __sfc_main$9,
191
- CnBreadcrumbs: __sfc_main$a,
192
- CnStatusBadge: __sfc_main$b,
193
- CnSummaryAggregates: __sfc_main$c,
194
- CnRelatedCollections: __sfc_main$d,
195
- CnBodySections: __sfc_main$e,
196
- CnTranslatedBadge: __sfc_main$f,
197
- CnWidgetStyleEditorModal: __sfc_main$g,
198
- CnRelationLinkModal: __sfc_main$h,
192
+ CnSegmentedControl: __sfc_main$8,
193
+ ViewDashboardOutline,
194
+ CnLifecycleActions: __sfc_main$9,
195
+ CnActionButtons: __sfc_main$a,
196
+ CnBreadcrumbs: __sfc_main$b,
197
+ CnStatusBadge: __sfc_main$c,
198
+ CnSummaryAggregates: __sfc_main$d,
199
+ CnRelatedCollections: __sfc_main$e,
200
+ CnBodySections: __sfc_main$f,
201
+ CnTranslatedBadge: __sfc_main$g,
202
+ CnWidgetStyleEditorModal: __sfc_main$h,
203
+ CnRelationLinkModal: __sfc_main$i,
199
204
  Cog,
200
205
  Plus,
201
206
  Pencil,
202
207
  },
203
208
 
204
- mixins: [gridLayout],
209
+ mixins: [gridLayout, pageViews],
205
210
 
206
211
  inject: {
207
212
  /**
@@ -1112,11 +1117,14 @@ var script = {
1112
1117
  * A breadcrumb line above the header (manifest `config.breadcrumb`):
1113
1118
  * `{ label, route?, params?, href? }` names the list the record
1114
1119
  * belongs to; the record's display name follows as the current
1115
- * crumb. `label` goes through the host translate function. Null (the
1116
- * default) draws no trail.
1120
+ * crumb. `label` goes through the host translate function and shows
1121
+ * as text; `icon` (an MDI name, e.g. `Home`) draws that icon instead,
1122
+ * with the label as its accessible name. Null (the default) draws no
1123
+ * trail.
1117
1124
  *
1118
1125
  * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-detail-page-drops-its-eyebrow-and-shows-a-breadcrumb
1119
- * @type {{label: string, route?: string, params?: object, href?: string}|null}
1126
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-declared-breadcrumb-label-shows-as-text
1127
+ * @type {{label: string, route?: string, params?: object, href?: string, icon?: string}|null}
1120
1128
  */
1121
1129
  breadcrumb: {
1122
1130
  type: Object,
@@ -1138,6 +1146,33 @@ var script = {
1138
1146
  default: () => [],
1139
1147
  },
1140
1148
 
1149
+ /**
1150
+ * Draw the header as a card (manifest `config.headerCard: true`): the
1151
+ * pills, the title and the actions on the page's surface colour inside
1152
+ * a bordered, rounded box, as the board's case header. Off by default,
1153
+ * which keeps the header on the page ground.
1154
+ *
1155
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-detail-header-can-be-a-card-that-holds-a-widget
1156
+ */
1157
+ headerCard: {
1158
+ type: Boolean,
1159
+ default: false,
1160
+ },
1161
+
1162
+ /**
1163
+ * The id of a widget declared in `widgets` to render inside the header,
1164
+ * on its own row under the title and the actions (manifest
1165
+ * `config.headerWidget`), e.g. the stages bars. It renders without a
1166
+ * card of its own and leaves the body grid. Empty (the default), or an
1167
+ * id that names no widget, renders nothing extra.
1168
+ *
1169
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-detail-header-can-be-a-card-that-holds-a-widget
1170
+ */
1171
+ headerWidget: {
1172
+ type: String,
1173
+ default: '',
1174
+ },
1175
+
1141
1176
  /**
1142
1177
  * Whether the current user administers this instance. Shows the
1143
1178
  * `adminOnly` header actions. `null` (the default) reads it from
@@ -1677,6 +1712,21 @@ var script = {
1677
1712
  .filter(Boolean)
1678
1713
  },
1679
1714
 
1715
+ /**
1716
+ * The widget the header holds (`headerWidget`): the definition in
1717
+ * `widgets` with that id, or null without the key or a match.
1718
+ *
1719
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-detail-header-can-be-a-card-that-holds-a-widget
1720
+ * @return {object|null} The widget definition, or null.
1721
+ */
1722
+ headerWidgetDef() {
1723
+ if (!this.headerWidget) {
1724
+ return null
1725
+ }
1726
+ const declared = Array.isArray(this.widgets) ? this.widgets : [];
1727
+ return declared.find((widget) => widget && widget.id === this.headerWidget) || null
1728
+ },
1729
+
1680
1730
  /**
1681
1731
  * The side column's widget definitions. A string names a widget
1682
1732
  * declared in `widgets`; an object is a definition of its own.
@@ -2249,10 +2299,12 @@ var script = {
2249
2299
  /**
2250
2300
  * The breadcrumb trail: the declared crumb (a router target from
2251
2301
  * `route` + `params`, or an `href`), then the record's display name
2252
- * as the current crumb. Empty without a `breadcrumb` label.
2302
+ * as the current crumb. Empty without a `breadcrumb` label. The first
2303
+ * crumb carries `icon` only when the manifest declares one.
2253
2304
  *
2254
2305
  * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-detail-page-drops-its-eyebrow-and-shows-a-breadcrumb
2255
- * @return {Array<{label: string, to?: object, href?: string}>}
2306
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-declared-breadcrumb-label-shows-as-text
2307
+ * @return {Array<{label: string, to?: object, href?: string, icon?: string}>}
2256
2308
  */
2257
2309
  breadcrumbCrumbs() {
2258
2310
  const crumb = this.breadcrumb;
@@ -2260,6 +2312,9 @@ var script = {
2260
2312
  return []
2261
2313
  }
2262
2314
  const first = { label: this.effectiveTranslate(crumb.label) };
2315
+ if (typeof crumb.icon === 'string' && crumb.icon !== '') {
2316
+ first.icon = crumb.icon;
2317
+ }
2263
2318
  if (crumb.route) {
2264
2319
  first.to = { name: crumb.route, ...(crumb.params ? { params: crumb.params } : {}) };
2265
2320
  } else if (crumb.href) {
@@ -2371,6 +2426,7 @@ var script = {
2371
2426
  && this.currentObject
2372
2427
  && !this.hasDefaultSlotContent
2373
2428
  && !this.hasGridLayout
2429
+ && !this.hasViews
2374
2430
  },
2375
2431
 
2376
2432
  /**
@@ -2450,6 +2506,55 @@ var script = {
2450
2506
  return this.bodyGridLayout.length > 0
2451
2507
  },
2452
2508
 
2509
+ /**
2510
+ * The chosen view's layout as the grid draws it: items that draw
2511
+ * nothing leave and their rows close, as for the body grid.
2512
+ *
2513
+ * @return {Array<object>}
2514
+ * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-view-with-nothing-to-draw-says-so
2515
+ */
2516
+ viewDisplayLayout() {
2517
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- reading the flag IS the effect, as in bodyGridLayout
2518
+ this.editingBody;
2519
+ return this.activeView ? this.closeDroppedRows(this.activeView.layout) : []
2520
+ },
2521
+
2522
+ /**
2523
+ * Whether the chosen view has nothing to draw.
2524
+ *
2525
+ * @return {boolean}
2526
+ * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-view-with-nothing-to-draw-says-so
2527
+ */
2528
+ viewIsEmpty() {
2529
+ return this.hasViews && this.viewDisplayLayout.length === 0
2530
+ },
2531
+
2532
+ /**
2533
+ * The grids the body draws: the body grid when it has items, then the
2534
+ * chosen view's grid in the region the view switch controls (left out
2535
+ * when the view is empty; the empty state takes the region then).
2536
+ *
2537
+ * @return {Array<{key: string, view: boolean, layout: Array<object>, attrs: object}>}
2538
+ * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-page-declares-views-that-each-hold-a-widget-grid
2539
+ */
2540
+ bodyGridSections() {
2541
+ const sections = this.hasBodyGrid ? [{ key: 'body', view: false, layout: this.bodyGridLayout, attrs: {} }] : [];
2542
+ if (this.hasViews && !this.viewIsEmpty) {
2543
+ sections.push({
2544
+ key: `view-${this.activeViewId}`,
2545
+ view: true,
2546
+ layout: this.viewDisplayLayout,
2547
+ attrs: {
2548
+ id: this.viewRegionId,
2549
+ role: 'region',
2550
+ 'aria-label': this.activeView.label,
2551
+ 'data-testid': 'cn-detail-page-view-region',
2552
+ },
2553
+ });
2554
+ }
2555
+ return sections
2556
+ },
2557
+
2453
2558
  /**
2454
2559
  * Whether the sidebar is rendered externally (via objectSidebarState inject)
2455
2560
  * rather than inline. When external, CnDetailPage only manages state —
@@ -3671,6 +3776,8 @@ var script = {
3671
3776
  */
3672
3777
  findWidget(item) {
3673
3778
  return this.bodyGridWidgets.find((w) => w.id === item.widgetId)
3779
+ || this.findViewWidget(item.widgetId)
3780
+ || undefined
3674
3781
  },
3675
3782
 
3676
3783
  /**
@@ -3719,6 +3826,10 @@ var script = {
3719
3826
  * it — CnPageRenderer's split pane forwards no slots, which is exactly
3720
3827
  * that fault and wants fixing, not concealing.
3721
3828
  *
3829
+ * The widget the header holds (`headerWidget`) draws nothing here
3830
+ * either: it is already drawn in the header.
3831
+ *
3832
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-detail-header-can-be-a-card-that-holds-a-widget
3722
3833
  * @param {object} item A layout item.
3723
3834
  * @return {boolean} False only when the item provably draws nothing.
3724
3835
  */
@@ -3729,6 +3840,11 @@ var script = {
3729
3840
  if (this.editingBody) {
3730
3841
  return true
3731
3842
  }
3843
+ // The widget the header holds (`headerWidget`) is drawn there, so
3844
+ // its grid cell would be the same widget twice.
3845
+ if (this.headerWidgetDef !== null && item.widgetId === this.headerWidgetDef.id) {
3846
+ return false
3847
+ }
3732
3848
  const widget = this.findWidget(item);
3733
3849
  if (!widget) {
3734
3850
  return true