@conduction/nextcloud-vue 2.64.0 → 2.66.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 (84) hide show
  1. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +99 -3
  2. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +23 -7
  4. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  5. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js +13 -0
  6. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js.map +1 -1
  7. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js +2 -1
  8. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js.map +1 -1
  9. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +125 -4
  10. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  11. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +439 -381
  12. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  13. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +9 -3
  14. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -1
  15. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +1 -1
  16. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -1
  17. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +142 -26
  18. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  19. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +195 -124
  20. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  21. package/dist/esm/components/CnFkResolveCell/CnFkResolveCell.vue2.js +3 -1
  22. package/dist/esm/components/CnFkResolveCell/CnFkResolveCell.vue2.js.map +1 -1
  23. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +144 -4
  24. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  25. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  26. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +84 -11
  27. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
  28. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +3 -2
  29. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
  30. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +11 -0
  31. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
  32. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js +2 -1
  33. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
  34. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
  35. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
  36. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +22 -3
  37. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  38. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +10 -3
  39. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  40. package/dist/esm/l10n/en.json.js +3 -0
  41. package/dist/esm/l10n/en.json.js.map +1 -1
  42. package/dist/esm/l10n/nl.json.js +3 -0
  43. package/dist/esm/l10n/nl.json.js.map +1 -1
  44. package/dist/esm/mixins/pageViews.js +400 -0
  45. package/dist/esm/mixins/pageViews.js.map +1 -0
  46. package/dist/esm/nextcloud-vue.css +182 -0
  47. package/dist/esm/store/useObjectStore.js +118 -33
  48. package/dist/esm/store/useObjectStore.js.map +1 -1
  49. package/dist/esm/utils/actionsDispatcher.js +25 -2
  50. package/dist/esm/utils/actionsDispatcher.js.map +1 -1
  51. package/dist/esm/utils/columnFilters.js +27 -3
  52. package/dist/esm/utils/columnFilters.js.map +1 -1
  53. package/dist/esm/utils/schema.js +40 -2
  54. package/dist/esm/utils/schema.js.map +1 -1
  55. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  56. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  57. package/dist/nextcloud-vue.cjs.js +2001 -684
  58. package/dist/nextcloud-vue.cjs.js.map +2 -2
  59. package/dist/nextcloud-vue.css +182 -0
  60. package/l10n/en.json +3 -0
  61. package/l10n/nl.json +3 -0
  62. package/package.json +1 -1
  63. package/src/components/CnAppNav/CnAppNav.vue +148 -7
  64. package/src/components/CnBreadcrumbs/CnBreadcrumbs.vue +14 -0
  65. package/src/components/CnDashboardPage/CnDashboardPage.vue +194 -10
  66. package/src/components/CnDataTable/CnColumnFilterPopover.vue +11 -5
  67. package/src/components/CnDetailPage/CnDetailPage.md +5 -1
  68. package/src/components/CnDetailPage/CnDetailPage.vue +208 -11
  69. package/src/components/CnFkResolveCell/CnFkResolveCell.vue +3 -1
  70. package/src/components/CnFormDialog/CnFormDialog.vue +144 -4
  71. package/src/components/CnHeaderWidget/CnHeaderWidget.vue +100 -13
  72. package/src/components/CnSegmentedControl/CnSegmentedControl.md +1 -1
  73. package/src/components/CnSegmentedControl/CnSegmentedControl.vue +12 -0
  74. package/src/components/CnStagesWidget/CnStagesWidget.vue +9 -0
  75. package/src/components/CnStatWidget/CnStatWidget.vue +33 -5
  76. package/src/css/detail-page.css +27 -0
  77. package/src/css/kpi-card.css +47 -0
  78. package/src/mixins/pageViews.js +396 -0
  79. package/src/schemas/app-manifest-v2.schema.json +51 -2
  80. package/src/store/useObjectStore.js +118 -33
  81. package/src/utils/actionsDispatcher.js +25 -2
  82. package/src/utils/columnFilters.js +29 -2
  83. package/src/utils/schema.js +40 -2
  84. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -9,6 +9,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
9
9
  ? (openBlock(), createBlock(_component_NcBreadcrumbs, {
10
10
  key: 0,
11
11
  "aria-label": $props.ariaLabel,
12
+ rootIcon: $props.rootText ? '' : undefined,
12
13
  "data-testid": "cn-breadcrumbs"
13
14
  }, {
14
15
  default: withCtx(() => [
@@ -37,7 +38,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
37
38
  }), 128 /* KEYED_FRAGMENT */))
38
39
  ]),
39
40
  _: 1 /* STABLE */
40
- }, 8 /* PROPS */, ["aria-label"]))
41
+ }, 8 /* PROPS */, ["aria-label", "rootIcon"]))
41
42
  : createCommentVNode("v-if", true)
42
43
  }
43
44
 
@@ -1 +1 @@
1
- {"version":3,"file":"CnBreadcrumbs.vue3.js","sources":["../../../../src/components/CnBreadcrumbs/CnBreadcrumbs.vue?vue&type=template&id=296fde9b&lang.js"],"sourcesContent":["<!--\n CnBreadcrumbs — declarative breadcrumb trail wrapping NcBreadcrumbs.\n\n Renders a `crumbs` array as NcBreadcrumb entries: every crumb but the\n last navigates via `to` (router target) or `href` (plain anchor); the\n LAST crumb is the current location — rendered without a link target and\n carrying aria-current=\"page\" so assistive tech announces the position\n in the trail. Crumb icons resolve by PascalCase MDI name through the\n shared CnIcon registry, so a home crumb is `{ icon: 'Home', to: … }`\n without the consumer importing an icon component.\n\n NcBreadcrumbs supplies the nav landmark, the chevron separators and the\n responsive collapse of middle crumbs into an overflow menu; this wrapper\n only owns the array-in API and the last-crumb semantics, keeping fleet\n apps from hand-rolling the same v-for.\n-->\n<template>\n\t<NcBreadcrumbs\n\t\tv-if=\"crumbs.length > 0\"\n\t\t:aria-label=\"ariaLabel\"\n\t\tdata-testid=\"cn-breadcrumbs\">\n\t\t<!-- `name` falls back to '' because NcBreadcrumb declares it a REQUIRED\n\t\t String and warns per render on the label-less icon-only crumb. The\n\t\t empty string never shows: NcBreadcrumb prints `name` only for\n\t\t crumbs without an icon slot. -->\n\t\t<NcBreadcrumb\n\t\t\tv-for=\"(crumb, index) in crumbs\"\n\t\t\t:key=\"`${index}-${crumb.label ?? crumb.icon}`\"\n\t\t\t:name=\"crumb.label ?? ''\"\n\t\t\t:to=\"isCurrent(index) ? undefined : crumb.to\"\n\t\t\t:href=\"isCurrent(index) ? undefined : crumb.href\"\n\t\t\t:aria-current=\"isCurrent(index) ? 'page' : undefined\"\n\t\t\t:data-testid=\"`cn-breadcrumbs-crumb-${index}`\">\n\t\t\t<template v-if=\"crumb.icon\" #icon>\n\t\t\t\t<CnIcon :name=\"crumb.icon\" :size=\"20\" />\n\t\t\t</template>\n\t\t</NcBreadcrumb>\n\t</NcBreadcrumbs>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcBreadcrumb, NcBreadcrumbs } from '@nextcloud/vue'\nimport { CnIcon } from '../CnIcon/index.js'\n\n/**\n * CnBreadcrumbs — declarative breadcrumb trail wrapping NcBreadcrumbs.\n *\n * Feed it the whole trail as one `crumbs` array — first entry is the root\n * (typically a Home crumb), last entry is the CURRENT location. Every crumb\n * but the last links via its `to` (router) or `href` (anchor); the last one\n * is rendered unlinked with `aria-current=\"page\"`, per the WAI-ARIA\n * breadcrumb pattern.\n *\n * ```vue\n * <CnBreadcrumbs\n * :crumbs=\"[\n * { icon: 'Home', to: { name: 'SecretList' } },\n * { label: 'Team vault', to: { name: 'SecretListFolder', params: { folderId: '7' } } },\n * { label: 'Production' },\n * ]\" />\n * ```\n */\nexport default {\n\tname: 'CnBreadcrumbs',\n\n\tcomponents: {\n\t\tNcBreadcrumbs,\n\t\tNcBreadcrumb,\n\t\tCnIcon,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The trail, root first, current location LAST. Each crumb:\n\t\t * `{ label, to?, href?, icon? }` — `label` is the visible (already\n\t\t * translated) text, `to` a vue-router target, `href` a plain URL\n\t\t * (use one or the other; both are ignored on the last crumb), and\n\t\t * `icon` a PascalCase MDI name resolved via the CnIcon registry\n\t\t * (register it with `registerIcons()` at boot).\n\t\t *\n\t\t * @type {Array<{label: string, to?: (object|string), href?: string, icon?: string}>}\n\t\t */\n\t\tcrumbs: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t\tvalidator: (value) => value.every((crumb) => crumb && typeof crumb === 'object'\n\t\t\t\t&& (typeof crumb.label === 'string' || typeof crumb.icon === 'string')),\n\t\t},\n\n\t\t/**\n\t\t * Accessible name of the breadcrumb nav landmark. Defaults to the\n\t\t * lib's translation of \"Breadcrumbs\"; override per surface when a\n\t\t * page carries more than one trail.\n\t\t */\n\t\tariaLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Breadcrumbs'),\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether the crumb at `index` is the trail's last entry — the\n\t\t * current location, which renders unlinked with aria-current.\n\t\t *\n\t\t * @param {number} index Crumb position in `crumbs`.\n\t\t * @return {boolean}\n\t\t */\n\t\tisCurrent(index) {\n\t\t\treturn index === this.crumbs.length - 1\n\t\t},\n\t},\n}\n</script>\n"],"names":["_createBlock"],"mappings":";;;;;;;AAkCQ,EAAA,OAAA,CAAA,MAAA,CAAA,MAAM,CAAC,MAAM,GAAA,CAAA;oBADlBA;;AAEA,QAAA,YAAU,EAAA,MAAA,CAAA,SAAA;AACX,QAAA,aAAW,EAAC,gBAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"CnBreadcrumbs.vue3.js","sources":["../../../../src/components/CnBreadcrumbs/CnBreadcrumbs.vue?vue&type=template&id=296fde9b&lang.js"],"sourcesContent":["<!--\n CnBreadcrumbs — declarative breadcrumb trail wrapping NcBreadcrumbs.\n\n Renders a `crumbs` array as NcBreadcrumb entries: every crumb but the\n last navigates via `to` (router target) or `href` (plain anchor); the\n LAST crumb is the current location — rendered without a link target and\n carrying aria-current=\"page\" so assistive tech announces the position\n in the trail. Crumb icons resolve by PascalCase MDI name through the\n shared CnIcon registry, so a home crumb is `{ icon: 'Home', to: … }`\n without the consumer importing an icon component.\n\n NcBreadcrumbs supplies the nav landmark, the chevron separators and the\n responsive collapse of middle crumbs into an overflow menu; this wrapper\n only owns the array-in API and the last-crumb semantics, keeping fleet\n apps from hand-rolling the same v-for.\n-->\n<template>\n\t<NcBreadcrumbs\n\t\tv-if=\"crumbs.length > 0\"\n\t\t:aria-label=\"ariaLabel\"\n\t\t:rootIcon=\"rootText ? '' : undefined\"\n\t\tdata-testid=\"cn-breadcrumbs\">\n\t\t<!-- `name` falls back to '' because NcBreadcrumb declares it a REQUIRED\n\t\t String and warns per render on the label-less icon-only crumb. The\n\t\t empty string never shows: NcBreadcrumb prints `name` only for\n\t\t crumbs without an icon slot. -->\n\t\t<NcBreadcrumb\n\t\t\tv-for=\"(crumb, index) in crumbs\"\n\t\t\t:key=\"`${index}-${crumb.label ?? crumb.icon}`\"\n\t\t\t:name=\"crumb.label ?? ''\"\n\t\t\t:to=\"isCurrent(index) ? undefined : crumb.to\"\n\t\t\t:href=\"isCurrent(index) ? undefined : crumb.href\"\n\t\t\t:aria-current=\"isCurrent(index) ? 'page' : undefined\"\n\t\t\t:data-testid=\"`cn-breadcrumbs-crumb-${index}`\">\n\t\t\t<template v-if=\"crumb.icon\" #icon>\n\t\t\t\t<CnIcon :name=\"crumb.icon\" :size=\"20\" />\n\t\t\t</template>\n\t\t</NcBreadcrumb>\n\t</NcBreadcrumbs>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcBreadcrumb, NcBreadcrumbs } from '@nextcloud/vue'\nimport { CnIcon } from '../CnIcon/index.js'\n\n/**\n * CnBreadcrumbs — declarative breadcrumb trail wrapping NcBreadcrumbs.\n *\n * Feed it the whole trail as one `crumbs` array — first entry is the root\n * (typically a Home crumb), last entry is the CURRENT location. Every crumb\n * but the last links via its `to` (router) or `href` (anchor); the last one\n * is rendered unlinked with `aria-current=\"page\"`, per the WAI-ARIA\n * breadcrumb pattern.\n *\n * ```vue\n * <CnBreadcrumbs\n * :crumbs=\"[\n * { icon: 'Home', to: { name: 'SecretList' } },\n * { label: 'Team vault', to: { name: 'SecretListFolder', params: { folderId: '7' } } },\n * { label: 'Production' },\n * ]\" />\n * ```\n */\nexport default {\n\tname: 'CnBreadcrumbs',\n\n\tcomponents: {\n\t\tNcBreadcrumbs,\n\t\tNcBreadcrumb,\n\t\tCnIcon,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The trail, root first, current location LAST. Each crumb:\n\t\t * `{ label, to?, href?, icon? }` — `label` is the visible (already\n\t\t * translated) text, `to` a vue-router target, `href` a plain URL\n\t\t * (use one or the other; both are ignored on the last crumb), and\n\t\t * `icon` a PascalCase MDI name resolved via the CnIcon registry\n\t\t * (register it with `registerIcons()` at boot).\n\t\t *\n\t\t * @type {Array<{label: string, to?: (object|string), href?: string, icon?: string}>}\n\t\t */\n\t\tcrumbs: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t\tvalidator: (value) => value.every((crumb) => crumb && typeof crumb === 'object'\n\t\t\t\t&& (typeof crumb.label === 'string' || typeof crumb.icon === 'string')),\n\t\t},\n\n\t\t/**\n\t\t * Accessible name of the breadcrumb nav landmark. Defaults to the\n\t\t * lib's translation of \"Breadcrumbs\"; override per surface when a\n\t\t * page carries more than one trail.\n\t\t */\n\t\tariaLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Breadcrumbs'),\n\t\t},\n\n\t\t/**\n\t\t * Draw the FIRST crumb's label as text. NcBreadcrumbs gives the root\n\t\t * crumb a home icon and prints its `label` only as the accessible\n\t\t * name, so \"All cases\" read as a house. Off by default, which keeps\n\t\t * the home icon. A first crumb with its own `icon` keeps that icon.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-declared-breadcrumb-label-shows-as-text\n\t\t */\n\t\trootText: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether the crumb at `index` is the trail's last entry — the\n\t\t * current location, which renders unlinked with aria-current.\n\t\t *\n\t\t * @param {number} index Crumb position in `crumbs`.\n\t\t * @return {boolean}\n\t\t */\n\t\tisCurrent(index) {\n\t\t\treturn index === this.crumbs.length - 1\n\t\t},\n\t},\n}\n</script>\n"],"names":[],"mappings":";;;;;;;AAkCQ,EAAA,OAAA,CAAA,MAAA,CAAA,MAAM,CAAC,MAAM,GAAA,CAAA;;;AACjB,QAAA,YAAS,EAAE,MAAS,CAAA,SAAA;QACrB,QAAQ,EAAE,MAAA,CAAA,QAAA,GAAA,EAAA,GAAA,SAAA;AACX,QAAA,aAAW,EAAC,gBAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -13,6 +13,7 @@ import __sfc_main$b from '../CnBuildiqEditButton/CnBuildiqEditButton.vue.js';
13
13
  import __sfc_main$4 from '../CnChartWidget/CnChartWidget.vue.js';
14
14
  import __sfc_main from '../CnDashboardGrid/CnDashboardGrid.vue.js';
15
15
  import __sfc_main$8 from '../CnDateRangePicker/CnDateRangePicker.vue.js';
16
+ import __sfc_main$e from '../CnSegmentedControl/CnSegmentedControl.vue.js';
16
17
  import __sfc_main$5 from '../CnStatsBlockWidget/CnStatsBlockWidget.vue.js';
17
18
  import __sfc_main$3 from '../CnTileWidget/CnTileWidget.vue.js';
18
19
  import __sfc_main$6 from '../CnWidgetRefItem/CnWidgetRefItem.vue.js';
@@ -20,6 +21,7 @@ import __sfc_main$2 from '../CnWidgetRenderer/CnWidgetRenderer.vue.js';
20
21
  import __sfc_main$1 from '../CnWidgetWrapper/CnWidgetWrapper.vue.js';
21
22
  import { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js';
22
23
  import { readUserPreference, writeUserPreference } from '../../composables/useUserPreferences.js';
24
+ import { pageViews } from '../../mixins/pageViews.js';
23
25
  import { mergeUserLayout, dashboardLayoutKey } from '../../store/plugins/dashboardLayouts.js';
24
26
  import { isAppInstalled } from '../../utils/appInstalled.js';
25
27
  import { readVisibleWhenValue, compareVisibleWhen } from '../../utils/visibleWhen.js';
@@ -175,8 +177,11 @@ var script = {
175
177
  CnBuildiqEditButton: __sfc_main$b,
176
178
  CnWidgetStyleEditorModal: __sfc_main$c,
177
179
  CnLeafMountHost: __sfc_main$d,
180
+ CnSegmentedControl: __sfc_main$e,
178
181
  },
179
182
 
183
+ mixins: [pageViews],
184
+
180
185
  inject: {
181
186
  /**
182
187
  * Buildiq in-app edit state (ADR-041), provided by CnAppRoot as a ref.
@@ -249,6 +254,21 @@ var script = {
249
254
  default: true,
250
255
  },
251
256
 
257
+ /**
258
+ * Whether a widget draws its overflow Actions menu when neither its
259
+ * placement nor its definition says (`showActions`). `false` (manifest
260
+ * `config.showWidgetActions: false`) drops the menu from every such
261
+ * widget, for a dashboard whose widgets carry a header link and
262
+ * nothing else. A widget that sets `showActions: true` keeps its menu.
263
+ * Card widgets have no menu either way.
264
+ *
265
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-dashboard-can-drop-the-widget-actions-menu
266
+ */
267
+ showWidgetActions: {
268
+ type: Boolean,
269
+ default: true,
270
+ },
271
+
252
272
  /**
253
273
  * Widget definitions array. Each widget defines metadata for rendering.
254
274
  *
@@ -1164,6 +1184,89 @@ var script = {
1164
1184
  return this.layout.length > 0 || this.widgetRefItems.length > 0
1165
1185
  },
1166
1186
 
1187
+ /**
1188
+ * Whether the grid sections render at all: not while loading, not
1189
+ * while the widget predicates settle, not on the empty page and not
1190
+ * on a widget-ref content page.
1191
+ *
1192
+ * @return {boolean}
1193
+ * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-page-declares-views-that-each-hold-a-widget-grid
1194
+ */
1195
+ gridSectionsShown() {
1196
+ if (this.loading || (!this.widgetConditionsSettled && !this.gridEditable)) {
1197
+ return false
1198
+ }
1199
+ if (!this.hasWidgets && !this.hasBodyWidgets && !this.hasViews) {
1200
+ return false
1201
+ }
1202
+ return this.widgetRefItems.length === 0
1203
+ },
1204
+
1205
+ /**
1206
+ * The chosen view's layout as the grid draws it: in live mode the
1207
+ * widgets that would draw nothing leave and the rest close up, as
1208
+ * for the page's own layout.
1209
+ *
1210
+ * @return {Array<object>}
1211
+ * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-view-with-nothing-to-draw-says-so
1212
+ */
1213
+ viewDisplayLayout() {
1214
+ const items = this.activeView ? this.activeView.layout : [];
1215
+ if (this.gridEditable) {
1216
+ return items
1217
+ }
1218
+ const visible = items.filter((item) => !this.isCollapsedWidget(item));
1219
+ return visible.length === items.length ? items : this.compactDisplayLayout(visible)
1220
+ },
1221
+
1222
+ /**
1223
+ * Whether the chosen view has nothing to draw.
1224
+ *
1225
+ * @return {boolean}
1226
+ * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-view-with-nothing-to-draw-says-so
1227
+ */
1228
+ viewIsEmpty() {
1229
+ return this.hasViews && this.viewDisplayLayout.length === 0
1230
+ },
1231
+
1232
+ /**
1233
+ * The grids this page draws. Without views: the page's own layout,
1234
+ * as it always was. With views: the page's own layout when it has
1235
+ * items, then the chosen view's layout in the region the switch
1236
+ * controls (left out when the view is empty; the empty state takes
1237
+ * the region then).
1238
+ *
1239
+ * @return {Array<{key: string, view: boolean, layout: Array<object>, editable: boolean, attrs: object}>}
1240
+ * @spec openspec/changes/view-switch-containers/specs/view-switch-containers/spec.md#requirement-a-page-declares-views-that-each-hold-a-widget-grid
1241
+ */
1242
+ gridSections() {
1243
+ if (!this.gridSectionsShown) {
1244
+ return []
1245
+ }
1246
+ const page = { key: 'page', view: false, layout: this.displayLayout, editable: this.gridEditable, attrs: {} };
1247
+ if (!this.hasViews) {
1248
+ return [page]
1249
+ }
1250
+ const sections = this.displayLayout.length > 0 ? [page] : [];
1251
+ if (!this.viewIsEmpty) {
1252
+ sections.push({
1253
+ key: `view-${this.activeViewId}`,
1254
+ view: true,
1255
+ layout: this.viewDisplayLayout,
1256
+ // A user arrangement covers the page's own layout only.
1257
+ editable: this.gridEditable && !this.userLayout,
1258
+ attrs: {
1259
+ id: this.viewRegionId,
1260
+ role: 'region',
1261
+ 'aria-label': this.activeView.label,
1262
+ class: 'cn-page-view-region',
1263
+ 'data-testid': 'cn-dashboard-page-view-region',
1264
+ },
1265
+ });
1266
+ }
1267
+ return sections
1268
+ },
1269
+
1167
1270
  /**
1168
1271
  * The widget settings object handed to CnWidgetStyleEditorModal,
1169
1272
  * built from the widget definition currently being configured
@@ -1294,6 +1397,14 @@ var script = {
1294
1397
  this.evaluateWidgetConditions();
1295
1398
  },
1296
1399
  },
1400
+
1401
+ /** A view's widgets carry predicates too. */
1402
+ views: {
1403
+ deep: true,
1404
+ handler() {
1405
+ this.evaluateWidgetConditions();
1406
+ },
1407
+ },
1297
1408
  },
1298
1409
 
1299
1410
  created() {
@@ -2158,7 +2269,8 @@ var script = {
2158
2269
  * @spec openspec/changes/live-check-follow-ups/specs/live-check-follow-ups/spec.md#requirement-an-attention-card-says-when-it-could-not-check
2159
2270
  */
2160
2271
  async evaluateWidgetConditions() {
2161
- const conditional = (this.widgets || []).filter((def) => def && def.id && def.type
2272
+ const defs = [...(this.widgets || []), ...this.normalizedViews.flatMap((view) => view.widgets)];
2273
+ const conditional = defs.filter((def) => def && def.id && def.type
2162
2274
  && this.widgetDisplayConfig(def).visibleWhen);
2163
2275
  // Skip when the CONDITIONAL SET is unchanged: the deep `widgets`
2164
2276
  // watch fires on any def edit, but only an added/removed/edited
@@ -2292,7 +2404,7 @@ var script = {
2292
2404
  */
2293
2405
  getWidgetDef(widgetId) {
2294
2406
  const list = Array.isArray(this.widgets) ? this.widgets : [];
2295
- return list.find((w) => w && w.id === widgetId) || null
2407
+ return list.find((w) => w && w.id === widgetId) || this.findViewWidget(widgetId)
2296
2408
  },
2297
2409
 
2298
2410
  /**
@@ -2683,11 +2795,20 @@ var script = {
2683
2795
  * exactly that reason; there is now one rule for every family, so a
2684
2796
  * custom slot and a registered widget cannot disagree about it.
2685
2797
  *
2798
+ * A greeting header that declares `content.ground: true` sits on the
2799
+ * page ground, so it asks for no card either: one key on the widget
2800
+ * instead of a second one on its placement.
2801
+ *
2802
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-greeting-can-sit-on-the-page-ground
2686
2803
  * @param {object} item the layout placement.
2687
2804
  * @return {boolean}
2688
2805
  */
2689
2806
  widgetBorderless(item) {
2690
- return item.borderless === true
2807
+ if (item.borderless === true) {
2808
+ return true
2809
+ }
2810
+ const def = this.getWidgetDef(item.widgetId);
2811
+ return Boolean(def && def.type === 'header' && this.getWidgetContent(item).ground === true)
2691
2812
  },
2692
2813
 
2693
2814
  /**
@@ -2704,7 +2825,7 @@ var script = {
2704
2825
  const def = this.getWidgetDef(item.widgetId);
2705
2826
  const value = item.showActions !== undefined ? item.showActions : def?.showActions;
2706
2827
  if (value === undefined || value === null) {
2707
- return !this.isCardWidget(item)
2828
+ return this.showWidgetActions && !this.isCardWidget(item)
2708
2829
  }
2709
2830
  return value !== false
2710
2831
  },