@conduction/nextcloud-vue 2.56.0-beta.11 → 2.56.0-beta.13

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 (138) hide show
  1. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +253 -10
  2. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +139 -46
  4. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  5. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +87 -0
  6. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  7. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +26 -24
  8. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  9. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +61 -16
  10. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
  11. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
  12. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js +20 -0
  13. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js.map +1 -1
  14. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js +3 -2
  15. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js.map +1 -1
  16. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js +13 -0
  17. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js.map +1 -1
  18. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js +2 -1
  19. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js.map +1 -1
  20. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +33 -2
  21. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  22. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  23. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +154 -4
  24. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  25. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +555 -480
  26. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  27. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +9 -3
  28. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -1
  29. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +1 -1
  30. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -1
  31. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +37 -1
  32. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  33. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +12 -4
  34. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  35. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +187 -19
  36. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  37. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +215 -135
  38. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  39. package/dist/esm/components/CnFkResolveCell/CnFkResolveCell.vue2.js +3 -1
  40. package/dist/esm/components/CnFkResolveCell/CnFkResolveCell.vue2.js.map +1 -1
  41. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +144 -4
  42. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  43. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  44. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +157 -7
  45. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
  46. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +23 -5
  47. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
  48. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +30 -0
  49. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  50. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +2 -2
  51. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  52. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +5 -1
  53. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  54. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -2
  55. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  56. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +11 -0
  57. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
  58. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js +2 -1
  59. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
  60. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +63 -2
  61. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
  62. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js +96 -37
  63. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
  64. package/dist/esm/components/CnStagesWidget/stagesModel.js +5 -2
  65. package/dist/esm/components/CnStagesWidget/stagesModel.js.map +1 -1
  66. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +74 -5
  67. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  68. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +15 -8
  69. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  70. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +14 -2
  71. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  72. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +2 -1
  73. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  74. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +73 -1
  75. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
  76. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +14 -2
  77. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
  78. package/dist/esm/l10n/en.json.js +5 -1
  79. package/dist/esm/l10n/en.json.js.map +1 -1
  80. package/dist/esm/l10n/nl.json.js +5 -1
  81. package/dist/esm/l10n/nl.json.js.map +1 -1
  82. package/dist/esm/mixins/pageViews.js +400 -0
  83. package/dist/esm/mixins/pageViews.js.map +1 -0
  84. package/dist/esm/nextcloud-vue.css +513 -81
  85. package/dist/esm/store/useObjectStore.js +118 -33
  86. package/dist/esm/store/useObjectStore.js.map +1 -1
  87. package/dist/esm/utils/actionsDispatcher.js +25 -2
  88. package/dist/esm/utils/actionsDispatcher.js.map +1 -1
  89. package/dist/esm/utils/columnFilters.js +27 -3
  90. package/dist/esm/utils/columnFilters.js.map +1 -1
  91. package/dist/esm/utils/schema.js +40 -2
  92. package/dist/esm/utils/schema.js.map +1 -1
  93. package/dist/esm/utils/schemaRefSlug.js +19 -3
  94. package/dist/esm/utils/schemaRefSlug.js.map +1 -1
  95. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  96. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  97. package/dist/nextcloud-vue.cjs.js +8858 -6566
  98. package/dist/nextcloud-vue.cjs.js.map +2 -2
  99. package/dist/nextcloud-vue.css +513 -81
  100. package/l10n/en.json +5 -0
  101. package/l10n/nl.json +5 -0
  102. package/package.json +1 -1
  103. package/src/components/CnAppNav/CnAppNav.vue +441 -14
  104. package/src/components/CnAppRoot/CnAppRoot.md +4 -0
  105. package/src/components/CnAppRoot/CnAppRoot.vue +94 -1
  106. package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +61 -16
  107. package/src/components/CnAuditTrailWidget/CnAuditTrailWidget.vue +22 -1
  108. package/src/components/CnBreadcrumbs/CnBreadcrumbs.vue +14 -0
  109. package/src/components/CnCellRenderer/CnCellRenderer.vue +33 -2
  110. package/src/components/CnDashboardPage/CnDashboardPage.vue +237 -12
  111. package/src/components/CnDataTable/CnColumnFilterPopover.vue +11 -5
  112. package/src/components/CnDataTable/CnDataTable.vue +45 -1
  113. package/src/components/CnDetailPage/CnDetailPage.md +8 -0
  114. package/src/components/CnDetailPage/CnDetailPage.vue +263 -7
  115. package/src/components/CnFkResolveCell/CnFkResolveCell.vue +3 -1
  116. package/src/components/CnFormDialog/CnFormDialog.vue +144 -4
  117. package/src/components/CnHeaderWidget/CnHeaderWidget.vue +206 -9
  118. package/src/components/CnIndexPage/CnIndexPage.vue +32 -2
  119. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +7 -1
  120. package/src/components/CnSegmentedControl/CnSegmentedControl.md +1 -1
  121. package/src/components/CnSegmentedControl/CnSegmentedControl.vue +12 -0
  122. package/src/components/CnStagesWidget/CnStagesWidget.vue +189 -2
  123. package/src/components/CnStagesWidget/stagesModel.js +5 -2
  124. package/src/components/CnStatWidget/CnStatWidget.vue +89 -7
  125. package/src/components/CnTabsWidget/CnTabsWidget.vue +15 -2
  126. package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +4 -0
  127. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +107 -1
  128. package/src/css/detail-page.css +37 -4
  129. package/src/css/kpi-card.css +71 -1
  130. package/src/css/table.css +18 -6
  131. package/src/mixins/pageViews.js +396 -0
  132. package/src/schemas/app-manifest-v2.schema.json +162 -3
  133. package/src/store/useObjectStore.js +118 -33
  134. package/src/utils/actionsDispatcher.js +25 -2
  135. package/src/utils/columnFilters.js +29 -2
  136. package/src/utils/schema.js +40 -2
  137. package/src/utils/schemaRefSlug.js +19 -3
  138. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"CnWidgetWrapper.vue2.js","sources":["../../../../src/components/CnWidgetWrapper/CnWidgetWrapper.vue"],"sourcesContent":["<!--\n CnWidgetWrapper — Container shell around a dashboard widget.\n\n Provides header (icon + title), scrollable content area, and optional\n footer with action buttons. Applies style configuration for borders,\n backgrounds, and padding. The built-in overflow Actions menu (Refresh /\n Documentation / Request a feature) is delegated to the shared\n CnActionsMenu so widgets and page surfaces stay in lockstep.\n-->\n<template>\n\t<div\n\t\tclass=\"cn-widget-wrapper\"\n\t\t:class=\"{\n\t\t\t'cn-widget-wrapper--borderless': noBorder,\n\t\t\t'cn-widget-wrapper--flush': noPadding,\n\t\t\t'cn-widget-wrapper--chromeless': chromeless,\n\t\t\t'cn-widget-wrapper--nc-dashboard': chrome === 'nc-dashboard',\n\t\t\t'cn-widget-wrapper--high-contrast': highContrast,\n\t\t}\"\n\t\t:data-cn-high-contrast=\"highContrast ? 'true' : undefined\"\n\t\t:style=\"wrapperStyles\">\n\t\t<!-- Header.\n\t\t Rendered when a title earns it, or when controls that are actually\n\t\t on screen need a home. Three conditions rather than one, and each of\n\t\t the three is a bug this component has already shipped: gating on the\n\t\t title alone took the Save button for an inline edit away along with\n\t\t the doubled title; reading `showActions`, which defaults to true,\n\t\t handed a header to every headerless KPI tile; and reading the\n\t\t `actions` slot's mere PRESENCE gave a chromeless panel a 59px band\n\t\t holding nothing, because `CnObjectDataWidget` provides that template\n\t\t always and fills it only while an edit is unsaved.\n\t\t `headerIsWorthIt()` carries the reasoning. -->\n\t\t<div v-if=\"headerIsWorthIt()\"\n\t\t\tclass=\"cn-widget-wrapper__header\"\n\t\t\t:class=\"{ 'cn-widget-wrapper__header--actions-only': !titleVisible }\"\n\t\t\t:style=\"[headerStyles, titleIconStyle]\">\n\t\t\t<!-- Title icon — left: rendered before the title group. Moved\n\t\t\t INSIDE header-left so the header's `space-between` cannot pull\n\t\t\t it away from the title it belongs to; header-left's `gap` is\n\t\t\t what now separates icon from title (they used to touch). -->\n\t\t\t<div v-if=\"titleVisible\" class=\"cn-widget-wrapper__header-left\">\n\t\t\t\t<div v-if=\"$slots['title-icon'] && titleIconPosition === 'left'\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__title-icon\">\n\t\t\t\t\t<slot name=\"title-icon\" />\n\t\t\t\t</div>\n\t\t\t\t<img\n\t\t\t\t\tv-if=\"iconUrl\"\n\t\t\t\t\t:src=\"iconUrl\"\n\t\t\t\t\t:alt=\"resolvedTitle\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__icon\">\n\t\t\t\t<span\n\t\t\t\t\tv-else-if=\"iconClass\"\n\t\t\t\t\t:class=\"iconClass\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__icon\" />\n\t\t\t\t<h3 :id=\"titleId\" class=\"cn-widget-wrapper__title\">\n\t\t\t\t\t{{ resolvedTitle }}\n\t\t\t\t</h3>\n\t\t\t\t<!-- @slot title-meta Rendered inside the header's left group,\n\t\t\t\t after the title and before the spacer. Use for small\n\t\t\t\t contextual chips (e.g. a date-range badge on chart\n\t\t\t\t widgets). Kept inside header-left so the chip sits next\n\t\t\t\t to the title rather than floating right next to the\n\t\t\t\t actions menu. -->\n\t\t\t\t<div v-if=\"$slots['title-meta']\" class=\"cn-widget-wrapper__title-meta\">\n\t\t\t\t\t<slot name=\"title-meta\" />\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<!-- `showActions` gates the overflow MENU, not the slot beside it.\n\t\t\t It used to gate both, so a caller who switched the menu off\n\t\t\t also lost the Save button for an inline edit, silently. -->\n\t\t\t<div v-if=\"actionsAreVisible()\" class=\"cn-widget-wrapper__actions\">\n\t\t\t\t<!-- @slot actions Custom action buttons rendered before the\n\t\t\t\t built-in overflow menu. -->\n\t\t\t\t<slot name=\"actions\" />\n\t\t\t\t<CnActionsMenu\n\t\t\t\t\tv-if=\"showActions\"\n\t\t\t\t\t:showRefresh=\"effectiveShowRefresh\"\n\t\t\t\t\t:showRequestFeature=\"effectiveShowRequestFeature\"\n\t\t\t\t\t:showReportBug=\"showReportBug\"\n\t\t\t\t\t:showDocumentation=\"showDocumentation\"\n\t\t\t\t\t:documentationUrl=\"documentationUrl\"\n\t\t\t\t\t:docsAnchor=\"docsAnchor\"\n\t\t\t\t\t:reportBugUrl=\"reportBugUrl\"\n\t\t\t\t\t:documentationLabel=\"documentationLabel\"\n\t\t\t\t\t:refreshLabel=\"refreshLabel\"\n\t\t\t\t\t:requestFeatureLabel=\"requestFeatureLabel\"\n\t\t\t\t\t:actionsMenuLabel=\"actionsMenuLabel\"\n\t\t\t\t\t:refreshing=\"refreshing\"\n\t\t\t\t\t:widgetId=\"resolvedWidgetId\"\n\t\t\t\t\t:title=\"displayTitle\"\n\t\t\t\t\t:surface=\"`widget:${resolvedWidgetId}`\"\n\t\t\t\t\t:specRef=\"specRef\"\n\t\t\t\t\trefreshChannel=\"cn:widget:refresh\"\n\t\t\t\t\ttestidBase=\"cn-widget-wrapper\"\n\t\t\t\t\t@refresh=\"onActionsRefresh\"\n\t\t\t\t\t@requestFeature=\"onActionsRequestFeature\">\n\t\t\t\t\t<!-- @slot action-items Additional NcActionButton-family\n\t\t\t\t\t items rendered inside the overflow menu, after the\n\t\t\t\t\t built-in Refresh / Documentation / Request-a-feature\n\t\t\t\t\t group. -->\n\t\t\t\t\t<template v-if=\"hasActionItemsSlot\" #action-items>\n\t\t\t\t\t\t<slot name=\"action-items\" />\n\t\t\t\t\t</template>\n\t\t\t\t</CnActionsMenu>\n\t\t\t</div>\n\t\t\t<!-- Title icon — right: rendered after actions, far right -->\n\t\t\t<div v-if=\"$slots['title-icon'] && titleIconPosition === 'right'\"\n\t\t\t\tclass=\"cn-widget-wrapper__title-icon\">\n\t\t\t\t<slot name=\"title-icon\" />\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Headerless meta (e.g. a KPI date chip on a flush card). When the\n\t\t header is hidden but a `title-meta` chip is provided, float it in the\n\t\t top-right corner over the content instead of dropping it — so a\n\t\t compact flush KPI tile can carry a date-range chip without growing a\n\t\t full header bar. -->\n\t\t<div\n\t\t\tv-if=\"!titleVisible && $slots['title-meta']\"\n\t\t\tclass=\"cn-widget-wrapper__floating-meta\">\n\t\t\t<slot name=\"title-meta\" />\n\t\t</div>\n\n\t\t<!-- Content -->\n\t\t<!--\n\t\t The content area is `overflow: auto`, so it can become a scrollable\n\t\t region. WCAG 2.1.1 (Keyboard) requires scrollable regions to be\n\t\t keyboard-focusable so keyboard-only users can scroll them, and 4.1.2\n\t\t requires an accessible name. We expose it as a labelled region and\n\t\t give it tabindex=\"0\". When the header (and its title) is hidden we\n\t\t fall back to an explicit aria-label from the widget title so the\n\t\t region is never anonymous.\n\t\t-->\n\t\t<div\n\t\t\tclass=\"cn-widget-wrapper__content\"\n\t\t\ttabindex=\"0\"\n\t\t\trole=\"region\"\n\t\t\t:aria-labelledby=\"titleVisible ? titleId : null\"\n\t\t\t:aria-label=\"titleVisible ? null : resolvedTitle\">\n\t\t\t<slot />\n\t\t</div>\n\n\t\t<!-- Footer -->\n\t\t<div v-if=\"$slots.footer || (buttons && buttons.length > 0)\" class=\"cn-widget-wrapper__footer\">\n\t\t\t<slot name=\"footer\">\n\t\t\t\t<a\n\t\t\t\t\tv-for=\"button in buttons\"\n\t\t\t\t\t:key=\"button.link\"\n\t\t\t\t\t:href=\"button.link\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__footer-link\">\n\t\t\t\t\t{{ button.text }}\n\t\t\t\t</a>\n\t\t\t</slot>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { slotRenders } from '../../utils/slotContent.js'\nimport { CnActionsMenu } from '../CnActionsMenu/index.js'\n\n/**\n * CnWidgetWrapper — Widget container with header, content, and footer.\n *\n * ```vue\n * <CnWidgetWrapper title=\"My Cases\" :icon-url=\"casesIconUrl\">\n * <MyCasesChart :data=\"chartData\" />\n * </CnWidgetWrapper>\n * ```\n *\n * With NC widget object\n * ```vue\n * <CnWidgetWrapper\n * :title=\"widget.title\"\n * :icon-url=\"widget.iconUrl\"\n * :icon-class=\"widget.iconClass\"\n * :documentation-url=\"widget.documentationUrl\"\n * :buttons=\"widget.buttons\">\n * <CnWidgetRenderer :widget=\"widget\" />\n * </CnWidgetWrapper>\n * ```\n */\n/**\n * Semantic variant → CSS colour token for the widget header icon. Tokens\n * only — the nldesign app themes by overriding the Nextcloud variables, so a\n * literal hex here would ignore government theming entirely (ADR-062 / NL\n * Design System).\n *\n * @type {Record<string, string>}\n */\nconst TITLE_ICON_VARIANT_COLORS = {\n\tprimary: 'var(--color-primary-element)',\n\tsuccess: 'var(--color-success)',\n\twarning: 'var(--color-warning)',\n\terror: 'var(--color-error)',\n\tinfo: 'var(--color-info, var(--color-primary-element))',\n\tneutral: 'var(--color-text-maxcontrast)',\n}\n\nexport default {\n\tname: 'CnWidgetWrapper',\n\n\tcomponents: {\n\t\tCnActionsMenu,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Host translate function provided by CnAppRoot as\n\t\t * `cnTranslate: this.translate` (bound to the host app's id). The\n\t\t * manifest-authored widget title is run through it so the card\n\t\t * heading follows the user's language. Defaults to an identity\n\t\t * function so an untranslated key renders as itself.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** Widget title */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Widget'),\n\t\t},\n\n\t\t/** Whether to show the header with title */\n\t\tshowTitle: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Chrome variant for the wrapper card.\n\t\t * - `'default'` — the library's own card chrome (opaque background,\n\t\t * 1px border, compact header).\n\t\t * - `'nc-dashboard'` — matches the native Nextcloud Dashboard panel\n\t\t * exactly using the same design tokens: translucent blurred\n\t\t * background (`--color-main-background-blur` + `--filter-background-blur`),\n\t\t * `--border-radius-container-large` corners, no border/shadow, a 16px\n\t\t * header with a 20px/700 title and 32px leading icon, and content\n\t\t * inset 16px on the sides + bottom. `styleConfig` overrides still\n\t\t * layer on top so a user can customise any token.\n\t\t *\n\t\t * @type {'default'|'nc-dashboard'}\n\t\t */\n\t\tchrome: {\n\t\t\ttype: String,\n\t\t\tdefault: 'default',\n\t\t\tvalidator: (v) => ['default', 'nc-dashboard'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Remove border and background — makes the wrapper transparent.\n\t\t * Useful for widgets that are self-contained cards (e.g. CnStatsBlock).\n\t\t */\n\t\tborderless: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Remove content padding — allows content to go edge-to-edge.\n\t\t * Useful for list-style widgets where items should span the full width.\n\t\t */\n\t\tflush: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Draw NO card of this widget's own: no border, no background, no\n\t\t * content padding, no title row and no header divider. For a surface\n\t\t * that already supplies the card and the heading — a tab panel being\n\t\t * the case that matters, where a nested card repeats both.\n\t\t *\n\t\t * The wrapper ELEMENT still renders. That is the contract, not an\n\t\t * oversight: `CnObjectDataWidget` measures its overflow against\n\t\t * `.cn-widget-wrapper__content` (`closest()` plus a ResizeObserver on\n\t\t * that node), `src/css/table.css` gives a table inside it its\n\t\t * edge-to-edge treatment, and `src/css/detail-page.css` and\n\t\t * `dashboard.css` size their content areas through it. Dropping the\n\t\t * node would take all of that out silently — `closest()` returning\n\t\t * null reads as \"nothing overflows\", never as an error.\n\t\t *\n\t\t * Controls in the `actions` slot are NOT chrome and keep their header:\n\t\t * the Save button that commits an inline edit lives there, and a panel\n\t\t * that hides it is silently unsaveable.\n\t\t *\n\t\t * `chromeless` is what an integration provider already asks for through\n\t\t * `CnDetailWidgetHost` (`bareWidget`), so this is the same word for the\n\t\t * same thing on every path into the wrapper.\n\t\t */\n\t\tchromeless: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Icon URL (image) */\n\t\ticonUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Icon CSS class (e.g., Nextcloud icon class) */\n\t\ticonClass: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Position of the title-icon slot in the header.\n\t\t * 'left' places it before the title; 'right' places it after the actions.\n\t\t */\n\t\ttitleIconPosition: {\n\t\t\ttype: String,\n\t\t\tdefault: 'right',\n\t\t\tvalidator: (v) => ['left', 'right'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Explicit CSS colour for the header icon. Overrides `titleIconVariant`.\n\t\t * Must be a CSS custom property or a theme token — never a literal hex\n\t\t * (NL Design System: the nldesign app re-themes by overriding the\n\t\t * Nextcloud variables, and a hardcoded colour ignores that).\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\ttitleIconColor: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Semantic colour for the header icon. Every widget's icon is\n\t\t * coloured — `primary` (the theme colour) is the default, and a\n\t\t * widget whose subject already carries a semantic meaning names it\n\t\t * here so the icon says so at a glance (a Depublished list is\n\t\t * `error`, a Concepts list `warning`, a Published list `success`).\n\t\t *\n\t\t * @type {'primary'|'success'|'warning'|'error'|'info'|'neutral'}\n\t\t */\n\t\ttitleIconVariant: {\n\t\t\ttype: String,\n\t\t\tdefault: 'primary',\n\t\t\tvalidator: (v) => ['primary', 'success', 'warning', 'error', 'info', 'neutral'].includes(v),\n\t\t},\n\n\t\t/** Footer action buttons: [{ text, link }] */\n\t\tbuttons: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Whether the header's overflow action menu (Refresh / Documentation /\n\t\t * Request-a-feature + any `#action-items`) renders. Shown by default;\n\t\t * set `false` for compact surfaces — e.g. a KPI tile whose only header\n\t\t * affordance is a date chip — to drop the menu and free header width.\n\t\t */\n\t\tshowActions: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Style configuration for the wrapper.\n\t\t *\n\t\t * @type {{ backgroundColor: string, borderStyle: string, borderWidth: number, borderColor: string, borderRadius: number, padding: { top: number, right: number, bottom: number, left: number } }}\n\t\t */\n\t\tstyleConfig: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Hide the built-in Refresh item from the overflow action menu.\n\t\t * The Refresh item is shown by default — set this when the widget\n\t\t * has no refreshable data source (e.g. a static tile). Alias for\n\t\t * the inverse `:show-refresh=\"false\"`; either form opts out.\n\t\t */\n\t\thideRefresh: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Hide the built-in Request-a-feature item from the overflow\n\t\t * action menu. Shown by default; set when the consuming app has\n\t\t * no public issue tracker to link out to. Alias for the inverse\n\t\t * `:show-request-feature=\"false\"`; either form opts out.\n\t\t */\n\t\thideRequestFeature: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Whether to show the built-in Refresh item. Tri-state:\n\t\t * - `true` / `false` — force the action on or off.\n\t\t * - `null` (the default) — **auto**: show the action only when a\n\t\t * parent has attached an `@refresh` listener (i.e. something will\n\t\t * actually handle the refresh). This keeps widgets that can't\n\t\t * refresh — e.g. a prop-driven `CnObjectDataWidget` — from showing\n\t\t * a dead button. Widgets that refresh themselves via the\n\t\t * `cn:widget:refresh` event bus (with no `@refresh` listener)\n\t\t * should set `:show-refresh=\"true\"` explicitly.\n\t\t *\n\t\t * `hideRefresh` (or `:show-refresh=\"false\"`) always wins.\n\t\t *\n\t\t * @type {boolean|null}\n\t\t */\n\t\tshowRefresh: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Inverse of `hideRequestFeature`. Defaults to `true` so the\n\t\t * action renders. Set `:show-request-feature=\"false\"` to hide it.\n\t\t * Either flag hides the action.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tshowRequestFeature: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Whether the built-in \"Report a bug\" item renders. On by default —\n\t\t * the trio Request a feature / Report a bug / Documentation is the\n\t\t * contract for every widget; this exists for the rare surface that\n\t\t * must suppress one deliberately.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tshowReportBug: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Whether the built-in \"Documentation\" item renders. On by default,\n\t\t * for the same reason as `showReportBug`. The item's target is\n\t\t * resolved by the shared menu (see `docsAnchor`), so leaving it on\n\t\t * costs the host nothing.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tshowDocumentation: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Explicit documentation link for this widget, opened in a new tab.\n\t\t * Usually unnecessary: leave it empty and set `docsAnchor` instead,\n\t\t * so the link is built from the app-wide documentation base and\n\t\t * lands on this widget's own section.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdocumentationUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional pre-translated label for the Documentation action.\n\t\t * Defaults to the lib's translation of \"Documentation\".\n\t\t */\n\t\tdocumentationLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Documentation'),\n\t\t},\n\n\t\t/**\n\t\t * Widget id for the built-in default Refresh / Request-a-feature\n\t\t * handlers (B2). Forwarded as the `surface: \"widget:<id>\"` value\n\t\t * on the Actions menu (the English headline fallback for its forge\n\t\t * deep-links) AND as the `widgetId` field on the\n\t\t * `cn:widget:refresh` event-bus payload. When unset, the wrapper\n\t\t * falls back to a slugified `displayTitle`, which works but is\n\t\t * less stable than an explicit id.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\twidgetId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * This widget's own section in the app's documentation, appended to\n\t\t * the app-wide documentation base URL (provided by CnAppRoot) to build\n\t\t * the Actions menu's Documentation deep-link. A bare slug becomes a\n\t\t * `#fragment`. Supply it per widget type — without it the item still\n\t\t * renders, but lands on the docs homepage rather than this widget.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdocsAnchor: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Explicit \"Report a bug\" target for the Actions menu. Empty (the\n\t\t * default) builds a new-issue deep-link on the app's own forge.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\treportBugUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Declares that this widget wants a high contrast treatment. The flag\n\t\t * reaches the theme as `data-cn-high-contrast` and a `--high-contrast`\n\t\t * class; the component picks no colour of its own (ADR-003). A theme\n\t\t * that says nothing about the flag renders the widget unchanged.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\thighContrast: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Optional `specRef` slug, forwarded to the Actions menu. Accepted\n\t\t * for backward compatibility with the removed in-product suggestion\n\t\t * modal; the forge issue form asks for its own context.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tspecRef: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Whether a refresh is currently in flight. When bound by the host\n\t\t * (e.g. `:refreshing=\"loading\"` around its refetch), the Refresh\n\t\t * item is disabled and shows a loading spinner for exactly as long\n\t\t * as this stays true — so the spinner reflects the real refresh time.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\trefreshing: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Optional pre-translated label for the Refresh action. Defaults\n\t\t * to the lib's translation of \"Refresh\" so callers usually don't\n\t\t * need to set this.\n\t\t */\n\t\trefreshLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Refresh'),\n\t\t},\n\n\t\t/**\n\t\t * Optional pre-translated label for the Request-a-feature action.\n\t\t * Defaults to the lib's translation of \"Request a feature\".\n\t\t */\n\t\trequestFeatureLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Request a feature'),\n\t\t},\n\n\t\t/**\n\t\t * Pre-translated aria-label / tooltip for the overflow menu\n\t\t * trigger. Defaults to \"Actions\".\n\t\t */\n\t\tactionsMenuLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Actions'),\n\t\t},\n\n\t\t/**\n\t\t * Translate function. Falls back to the injected `cnTranslate`,\n\t\t * which itself defaults to an identity function.\n\t\t *\n\t\t * @type {((key: string) => string)|null}\n\t\t */\n\t\ttranslate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['refresh', 'request-feature'],\n\n\tcomputed: {\n\t\tdisplayTitle() {\n\t\t\treturn this.title || 'Widget'\n\t\t},\n\n\t\t/**\n\t\t * The header's icon colour, published as ONE custom property on the\n\t\t * header rather than an inline `color` repeated on each icon element.\n\t\t * Everything under the header — the icon-class span, the `title-icon`\n\t\t * slot's content, an SVG inside it — inherits from that single\n\t\t * declaration, so the icon and any decoration around it cannot end up\n\t\t * different colours.\n\t\t *\n\t\t * `titleIconColor` wins when the host set one; otherwise the\n\t\t * `titleIconVariant` token does, which is why EVERY widget icon is\n\t\t * coloured and not just the ones an app remembered to configure. Every\n\t\t * value is a CSS variable, so the nldesign app's overrides re-theme\n\t\t * them for free (NL Design System).\n\t\t *\n\t\t * @return {object} A style object declaring `--cn-widget-icon-color`.\n\t\t */\n\t\ttitleIconStyle() {\n\t\t\treturn {\n\t\t\t\t'--cn-widget-icon-color': this.titleIconColor\n\t\t\t\t\t|| TITLE_ICON_VARIANT_COLORS[this.titleIconVariant]\n\t\t\t\t\t|| TITLE_ICON_VARIANT_COLORS.primary,\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Effective translate function: the explicit `translate` prop when\n\t\t * given, else the injected `cnTranslate` (identity by default).\n\t\t *\n\t\t * @return {(key: string) => string}\n\t\t */\n\t\teffectiveTranslate() {\n\t\t\treturn this.translate ?? this.cnTranslate\n\t\t},\n\n\t\t/**\n\t\t * The RENDERED widget title — `displayTitle` run through the host\n\t\t * translate function. Kept separate from `displayTitle` on purpose:\n\t\t * `resolvedWidgetId` slugifies the raw title, so identifiers (DOM\n\t\t * ids, refresh channels, action payloads) stay locale-independent\n\t\t * while only the visible/AT-facing text localises.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedTitle() {\n\t\t\treturn this.title ? this.effectiveTranslate(this.title) : this.displayTitle\n\t\t},\n\n\t\t/**\n\t\t * Stable DOM id for the header title `<h3>`, used as the\n\t\t * `aria-labelledby` target of the scrollable content region so the\n\t\t * region gets an accessible name from the widget title (WCAG 4.1.2).\n\t\t * Derived from `resolvedWidgetId` so it is stable and unique per\n\t\t * widget instance on the page.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\ttitleId() {\n\t\t\treturn `cn-widget-wrapper-title-${this.resolvedWidgetId}`\n\t\t},\n\n\t\t/**\n\t\t * Effective Refresh visibility. `hideRefresh: true` (or\n\t\t * `:show-refresh=\"false\"`) always hides it. When `showRefresh` is\n\t\t * left unset (`null`), auto-detect: show the action only when a\n\t\t * parent attached an `@refresh` listener — otherwise the refresh\n\t\t * would do nothing. `hideRefresh` remains a back-compat alias.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\teffectiveShowRefresh() {\n\t\t\tif (this.hideRefresh) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (this.showRefresh !== null) {\n\t\t\t\treturn this.showRefresh\n\t\t\t}\n\t\t\t// `$.vnode.props`, not `$attrs`: `refresh` is a declared emit, and\n\t\t\t// Vue keeps declared emits out of `$attrs`.\n\t\t\treturn Boolean(this.$.vnode.props?.onRefresh)\n\t\t},\n\n\t\t/**\n\t\t * Effective Request-a-feature visibility — same OR-of-opt-outs\n\t\t * pattern as `effectiveShowRefresh`.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\teffectiveShowRequestFeature() {\n\t\t\treturn this.showRequestFeature && !this.hideRequestFeature\n\t\t},\n\n\t\t/**\n\t\t * Stable id used in event-bus payloads and the\n\t\t * `surface: \"widget:<id>\"` field on the auto-mounted feature\n\t\t * modal. Prefers the explicit `widgetId` prop; falls back to a\n\t\t * slugified `displayTitle` so widgets without an explicit id\n\t\t * still get a usable identifier.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedWidgetId() {\n\t\t\tif (this.widgetId) {\n\t\t\t\treturn this.widgetId\n\t\t\t}\n\t\t\treturn this.displayTitle.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')\n\t\t},\n\n\t\t/**\n\t\t * Whether the caller supplied an `action-items` slot. Forwarded\n\t\t * conditionally so the shared CnActionsMenu doesn't treat an\n\t\t * always-present pass-through template as content (which would\n\t\t * keep the overflow menu visible even when every built-in is\n\t\t * opted out).\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\thasActionItemsSlot() {\n\t\t\treturn Boolean(this.$slots['action-items']) || Boolean(this.$slots && this.$slots['action-items'])\n\t\t},\n\n\t\t/**\n\t\t * Whether the caller put controls in the `actions` slot.\n\t\t *\n\t\t * Gates the header together with `showTitle`, so a surface that wants no\n\t\t * title still keeps controls that have nowhere else to go — the Save\n\t\t * button `CnObjectDataWidget` renders there being the case that matters.\n\t\t *\n\t\t * Deliberately NOT `showActions`: that prop defaults to true and governs\n\t\t * the overflow menu, so reading it here would give a header to every\n\t\t * headerless KPI tile that never had one.\n\t\t *\n\t\t * @return {boolean} true when the slot is filled.\n\t\t */\n\t\thasActionsSlot() {\n\t\t\treturn Boolean(this.$slots.actions)\n\t\t},\n\n\t\t/**\n\t\t * Whether the card outline is drawn. `chromeless` implies `borderless`\n\t\t * so a caller names the intent once instead of remembering the three\n\t\t * props it decomposes into.\n\t\t *\n\t\t * @return {boolean} true when border and background are suppressed.\n\t\t */\n\t\tnoBorder() {\n\t\t\treturn this.borderless || this.chromeless\n\t\t},\n\n\t\t/**\n\t\t * Whether the content area goes edge to edge. `chromeless` implies\n\t\t * `flush` for the same reason as `noBorder`.\n\t\t *\n\t\t * @return {boolean} true when content padding is suppressed.\n\t\t */\n\t\tnoPadding() {\n\t\t\treturn this.flush || this.chromeless\n\t\t},\n\n\t\t/**\n\t\t * Whether the title row renders. `chromeless` overrides `showTitle`,\n\t\t * which defaults to true: a surface that already names the widget must\n\t\t * not have to also remember to turn the title off.\n\t\t *\n\t\t * Read everywhere the template used to read `showTitle` directly,\n\t\t * including the two ARIA bindings on the content region — a hidden\n\t\t * title cannot be the region's `aria-labelledby` target.\n\t\t *\n\t\t * @return {boolean} true when the header's title group renders.\n\t\t */\n\t\ttitleVisible() {\n\t\t\treturn this.showTitle && !this.chromeless\n\t\t},\n\n\t\twrapperStyles() {\n\t\t\tconst styles = {}\n\n\t\t\tif (this.styleConfig.backgroundColor) {\n\t\t\t\tstyles.backgroundColor = this.styleConfig.backgroundColor\n\t\t\t}\n\n\t\t\tif (this.styleConfig.borderStyle && this.styleConfig.borderStyle !== 'none') {\n\t\t\t\tstyles.border = `${this.styleConfig.borderWidth || 1}px ${this.styleConfig.borderStyle} ${this.styleConfig.borderColor || 'var(--color-border)'}`\n\t\t\t}\n\n\t\t\tif (this.styleConfig.borderRadius !== undefined) {\n\t\t\t\tstyles.borderRadius = `${this.styleConfig.borderRadius}px`\n\t\t\t}\n\n\t\t\tif (this.styleConfig.padding) {\n\t\t\t\tconst p = this.styleConfig.padding\n\t\t\t\tstyles.padding = `${p.top || 0}px ${p.right || 0}px ${p.bottom || 0}px ${p.left || 0}px`\n\t\t\t}\n\n\t\t\treturn styles\n\t\t},\n\n\t\t/**\n\t\t * Inline styles for the header bar, derived from\n\t\t * `styleConfig.headerStyle.{backgroundColor, textColor}`. Lets a host\n\t\t * give individual widgets a custom header colour without a per-app CSS\n\t\t * workaround. Empty object when no header style is configured.\n\t\t *\n\t\t * @return {object} the header style bindings.\n\t\t */\n\t\theaderStyles() {\n\t\t\tconst hs = this.styleConfig.headerStyle\n\t\t\tif (!hs || typeof hs !== 'object') {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\tconst styles = {}\n\t\t\tif (hs.backgroundColor) {\n\t\t\t\tstyles.backgroundColor = hs.backgroundColor\n\t\t\t}\n\t\t\tif (hs.textColor) {\n\t\t\t\tstyles.color = hs.textColor\n\t\t\t}\n\t\t\treturn styles\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether the `actions` slot renders anything a reader can see.\n\t\t *\n\t\t * `CnObjectDataWidget` always PROVIDES the template and fills it only\n\t\t * while an edit is unsaved, so the slot has to be called and its vnodes\n\t\t * inspected. Reading `$slots.actions` as a boolean counts the template\n\t\t * itself as content, which is what gave a titleless tab panel a 59px\n\t\t * band holding nothing but a second Actions menu.\n\t\t *\n\t\t * A method rather than a computed on purpose. The answer changes when the\n\t\t * PARENT's state changes, and a computed reading `$slots` does not\n\t\t * re-track that; a method re-runs on every render, and a slot whose\n\t\t * content depends on parent state already forces this component to\n\t\t * re-render.\n\t\t *\n\t\t * @return {boolean} True when the slot yields visible content.\n\t\t */\n\t\tactionsSlotRenders() {\n\t\t\treturn slotRenders(this.$slots.actions)\n\t\t},\n\n\t\t/**\n\t\t * Whether the header band is worth the vertical space it costs.\n\t\t *\n\t\t * A VISIBLE title always earns it, which `chromeless` rules out. Otherwise\n\t\t * it is earned by controls that are actually on screen: content in the\n\t\t * `actions` slot, or the overflow menu when a caller both asked for it and\n\t\t * provided the slot it sits beside.\n\t\t *\n\t\t * That last clause looks redundant and is not. `showActions` defaults to\n\t\t * TRUE, so reading it alone hands a header to every headerless KPI tile\n\t\t * in the fleet, which is what the floating title-meta test caught when\n\t\t * this was first written the short way.\n\t\t *\n\t\t * @return {boolean} True when the header should render.\n\t\t */\n\t\theaderIsWorthIt() {\n\t\t\tif (this.titleVisible) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tif (this.actionsSlotRenders()) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn this.showActions && this.hasActionsSlot\n\t\t},\n\n\t\t/**\n\t\t * Whether the actions group holds anything.\n\t\t *\n\t\t * The overflow menu and the slot beside it are separate conditions now.\n\t\t * `showActions` used to gate both, so a caller who only wanted the menu\n\t\t * gone also lost the button that commits an inline edit.\n\t\t *\n\t\t * @return {boolean} True when the group would hold a visible control.\n\t\t */\n\t\tactionsAreVisible() {\n\t\t\treturn this.showActions || this.actionsSlotRenders()\n\t\t},\n\n\t\t/**\n\t\t * Re-emit the shared CnActionsMenu `@refresh` to the host, passing\n\t\t * the synthetic event through unchanged so a host listener can\n\t\t * still `preventDefault()` the built-in default (event-bus emit on\n\t\t * `cn:widget:refresh`).\n\t\t *\n\t\t * @param {{ widgetId: string, title: string }} payload Action payload.\n\t\t * @param {{ defaultPrevented: boolean, preventDefault: () => void }} ev Synthetic event.\n\t\t * @return {void}\n\t\t */\n\t\tonActionsRefresh(payload, ev) {\n\t\t\t/**\n\t\t\t * @event refresh User clicked the Refresh item in the overflow\n\t\t\t * action menu. Payload: `{ widgetId, title }`. Handlers may call\n\t\t\t * the second arg's `preventDefault()` to suppress the built-in\n\t\t\t * default (event-bus emit on `cn:widget:refresh`).\n\t\t\t * @type {{ widgetId: string, title: string }}\n\t\t\t */\n\t\t\tthis.$emit('refresh', payload, ev)\n\t\t},\n\n\t\t/**\n\t\t * Re-emit the shared CnActionsMenu `@request-feature` to the host,\n\t\t * passing the synthetic event through so a host listener can\n\t\t * `preventDefault()` the built-in default (opening the forge's\n\t\t * feature-request issue form).\n\t\t *\n\t\t * @param {{ widgetId: string, title: string }} payload Action payload.\n\t\t * @param {{ defaultPrevented: boolean, preventDefault: () => void }} ev Synthetic event.\n\t\t * @return {void}\n\t\t */\n\t\tonActionsRequestFeature(payload, ev) {\n\t\t\t/**\n\t\t\t * @event request-feature User clicked the Request a feature\n\t\t\t * item. Payload: `{ widgetId, title }`. Handlers may call the\n\t\t\t * second arg's `preventDefault()` to suppress the built-in\n\t\t\t * default (opening the forge's feature-request issue form).\n\t\t\t * @type {{ widgetId: string, title: string }}\n\t\t\t */\n\t\t\tthis.$emit('request-feature', payload, ev)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-widget-wrapper {\n\tposition: relative;\n\theight: 100%;\n\tdisplay: flex;\n\tflex-direction: column;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\t/* NC design system: every content card is rounded — same token as\n\t CnDetailCard so detail pages read as ONE card family (ADR-062). */\n\tborder-radius: var(--border-radius-large, 8px);\n\toverflow: hidden;\n}\n\n/* Headerless date chip — floated over the top-right of a flush card. */\n.cn-widget-wrapper__floating-meta {\n\tposition: absolute;\n\ttop: 8px;\n\tinset-inline-end: 10px;\n\tz-index: 2;\n}\n\n.cn-widget-wrapper__content {\n\tflex: 1;\n\toverflow: auto;\n\tmin-height: 0;\n\tpadding: 16px;\n}\n\n/* A TABLE GOES EDGE TO EDGE. ALWAYS.\n A table drawn inside 16px of padding renders a border inside a border — two\n rectangles a few pixels apart, which reads as a mistake because it is one.\n The wrapper already has a `flush` prop that zeroes this padding, but a prop\n is something an app has to remember on every table it ever adds, and the\n apps did not: the padding was visible in pipelinq's table widgets.\n So it is structural instead. `:has()` keys on the table component's own root\n class, which means a table is flush by construction and no caller can\n reintroduce the gap by forgetting a prop. */\n.cn-widget-wrapper__content:has(> .cn-table-container),\n.cn-widget-wrapper__content:has(> .cn-widget-object-table) {\n\tpadding: 0;\n}\n\n/* Keyboard focus ring for the now-focusable scrollable content region. */\n.cn-widget-wrapper__content:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: -2px;\n}\n\n.cn-widget-wrapper--borderless {\n\tborder: none;\n\tbackground: transparent;\n}\n\n.cn-widget-wrapper--borderless .cn-widget-wrapper__content {\n\tpadding: 0;\n}\n\n.cn-widget-wrapper--flush .cn-widget-wrapper__content {\n\tpadding: 0;\n}\n\n/* A form or a search list keeps its padding, the reverse of the table rule\n above. A dashboard renders every widget `flush`, and without this their\n fields touch the card's edges. Borderless and chromeless cards, and the\n Nextcloud dashboard variant, keep their own padding. */\n.cn-widget-wrapper--flush:not(.cn-widget-wrapper--borderless, .cn-widget-wrapper--nc-dashboard) > .cn-widget-wrapper__content:has(> .cn-form-widget),\n.cn-widget-wrapper--flush:not(.cn-widget-wrapper--borderless, .cn-widget-wrapper--nc-dashboard) > .cn-widget-wrapper__content:has(> .cn-kb-search-widget) {\n\tpadding: 16px;\n}\n\n/* `chromeless` — border and background are already gone through borderless,\n and the content padding through flush. What is left on the root is the\n corner radius, which still clips: with `overflow: hidden` above, a child\n that paints its own background (a table header) gets rounded corners inside\n a square panel, which is the last visible piece of a card that is supposed\n to be absent. `CnDetailCard.cn-detail-card--chromeless` zeroes the same\n thing for the same reason. */\n.cn-widget-wrapper--chromeless {\n\tborder-radius: 0;\n}\n\n/*\n * `chrome=\"nc-dashboard\"` — reproduce the native Nextcloud Dashboard panel\n * (apps/dashboard) exactly, using the same design tokens so an un-customised\n * widget is pixel-identical to a core dashboard panel. `styleConfig` inline\n * overrides (wrapperStyles / headerStyles) still win over these class rules,\n * so users can override any token. Combined selector raises specificity above\n * the base `.cn-widget-wrapper` rules it supersedes.\n */\n.cn-widget-wrapper__header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\t/* The header is `space-between`, so without an explicit gap a right-hand\n\t title-icon sits flush against whatever precedes it. The left-hand icon's\n\t spacing comes from header-left's own gap. */\n\tgap: 8px;\n\tpadding: 12px 16px;\n\tborder-bottom: 1px solid var(--color-border);\n\tflex-shrink: 0;\n}\n\n/* Actions with no title beside them. `header-left` is not rendered at all in\n this mode, and the header is `space-between`, so a single child would sit\n flush LEFT where every other surface puts its actions on the right. */\n.cn-widget-wrapper__header--actions-only {\n\tjustify-content: flex-end;\n}\n\n/* `chromeless` — the surface around this widget already drew the card, so\n nothing here may draw a second one. Border, background and content padding\n are already gone (chromeless implies borderless + flush); what is left is\n the header, which still renders when there are controls to hold. Its bottom\n rule is the card's own divider and reads as a second card edge inside the\n panel, and its 16px side inset leaves those controls hanging off the grid\n that now starts at the panel's edge. Both go.\n\n Placed AFTER the rules it overrides, which is what stylelint's\n no-descending-specificity asks for. The cascade did not need it (this\n selector is more specific either way), a reader does: an override written\n above the thing it overrides has to be read twice. */\n.cn-widget-wrapper--chromeless .cn-widget-wrapper__header {\n\tborder-bottom: none;\n\tpadding-inline: 0;\n}\n\n.cn-widget-wrapper__header-left {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmin-width: 0;\n\t/* Grow so the title hugs the (optional) title-icon on the left instead\n\t of centering between icon and actions (header is space-between). */\n\tflex: 1 1 auto;\n}\n\n.cn-widget-wrapper__icon {\n\twidth: 24px;\n\theight: 24px;\n\tflex-shrink: 0;\n\tcolor: var(--cn-widget-icon-color, var(--color-primary-element));\n}\n\n.cn-widget-wrapper__title {\n\tfont-weight: 600;\n\tfont-size: 14px;\n\tmargin: 0;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-widget-wrapper.cn-widget-wrapper--nc-dashboard {\n\tbackground: var(--color-main-background-blur, var(--color-main-background));\n\t-webkit-backdrop-filter: var(--filter-background-blur, none);\n\tbackdrop-filter: var(--filter-background-blur, none);\n\tborder: none;\n\tborder-radius: var(--border-radius-container-large, 16px);\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__header {\n\tpadding: 16px;\n\tborder-bottom: none;\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__header-left {\n\tgap: 16px;\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__icon {\n\twidth: 32px;\n\theight: 32px;\n\tbackground-size: 32px;\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__title {\n\tfont-size: 20px;\n\tfont-weight: 700;\n\tline-height: 24px;\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__content {\n\tpadding: 0 16px 16px;\n}\n\n.cn-widget-wrapper__title-meta {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 4px;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n\twhite-space: nowrap;\n\tflex-shrink: 0;\n}\n\n.cn-widget-wrapper__actions {\n\tdisplay: flex;\n\tgap: 4px;\n\tflex-shrink: 0;\n}\n\n/* Every widget's header icon is coloured — `--cn-widget-icon-color` is set on\n the header from titleIconStyle, and defaults to the theme colour if a\n wrapper is ever rendered without it. Material-design-icon components paint\n with `fill: currentColor`, so this reaches them; the :deep rule below makes\n it reach a raw <svg> in the slot too. */\n.cn-widget-wrapper__title-icon {\n\tdisplay: flex;\n\talign-items: center;\n\tflex-shrink: 0;\n\t/* COLOURED BY DEFAULT. `--cn-widget-icon-color` is set on the header from\n\t titleIconStyle; the theme colour is the fallback for a wrapper rendered\n\t without it. Before this, the prop defaulted to null and the icon inherited\n\t body text colour, which is why every widget header looked the same. */\n\tcolor: var(--cn-widget-icon-color, var(--color-primary-element));\n\n\t/* SPACE BETWEEN THE ICON AND THE TITLE.\n\t This element is a sibling of `__header-left` inside a space-between\n\t header, so it never received that element's `gap: 8px` — the icon sat\n\t flush against the title text. Matching the header-left gap rather than\n\t inventing a value keeps the two icon positions spaced identically. */\n\tmargin-inline-end: 8px;\n}\n\n/* Right-positioned title icons sit AFTER the actions, so the margin belongs on\n the other side or it pushes the icon off the header's right edge. */\n.cn-widget-wrapper__title-icon:last-child {\n\tmargin-inline-end: 0;\n\tmargin-inline-start: 8px;\n}\n\n/* Material-design-icon components paint with `fill: currentColor`, so the\n colour above reaches them; this makes it reach a raw <svg> in the slot too. */\n.cn-widget-wrapper__title-icon :deep(svg) {\n\tfill: currentcolor;\n}\n\n.cn-widget-wrapper__footer {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\tgap: 8px;\n\tpadding: 8px 16px;\n\tborder-top: 1px solid var(--color-border);\n\tflex-shrink: 0;\n}\n\n.cn-widget-wrapper__footer-link {\n\tfont-size: 13px;\n\tcolor: var(--color-primary-element);\n\ttext-decoration: none;\n}\n\n.cn-widget-wrapper__footer-link:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["CnActionsMenu","t"],"mappings":"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"}
1
+ {"version":3,"file":"CnWidgetWrapper.vue2.js","sources":["../../../../src/components/CnWidgetWrapper/CnWidgetWrapper.vue"],"sourcesContent":["<!--\n CnWidgetWrapper — Container shell around a dashboard widget.\n\n Provides header (icon + title), scrollable content area, and optional\n footer with action buttons. Applies style configuration for borders,\n backgrounds, and padding. The built-in overflow Actions menu (Refresh /\n Documentation / Request a feature) is delegated to the shared\n CnActionsMenu so widgets and page surfaces stay in lockstep.\n-->\n<template>\n\t<div\n\t\tclass=\"cn-widget-wrapper\"\n\t\t:class=\"{\n\t\t\t'cn-widget-wrapper--borderless': noBorder,\n\t\t\t'cn-widget-wrapper--flush': noPadding,\n\t\t\t'cn-widget-wrapper--chromeless': chromeless,\n\t\t\t'cn-widget-wrapper--nc-dashboard': chrome === 'nc-dashboard',\n\t\t\t'cn-widget-wrapper--high-contrast': highContrast,\n\t\t}\"\n\t\t:data-cn-high-contrast=\"highContrast ? 'true' : undefined\"\n\t\t:style=\"wrapperStyles\">\n\t\t<!-- Header.\n\t\t Rendered when a title earns it, or when controls that are actually\n\t\t on screen need a home. Three conditions rather than one, and each of\n\t\t the three is a bug this component has already shipped: gating on the\n\t\t title alone took the Save button for an inline edit away along with\n\t\t the doubled title; reading `showActions`, which defaults to true,\n\t\t handed a header to every headerless KPI tile; and reading the\n\t\t `actions` slot's mere PRESENCE gave a chromeless panel a 59px band\n\t\t holding nothing, because `CnObjectDataWidget` provides that template\n\t\t always and fills it only while an edit is unsaved.\n\t\t `headerIsWorthIt()` carries the reasoning. -->\n\t\t<div v-if=\"headerIsWorthIt()\"\n\t\t\tclass=\"cn-widget-wrapper__header\"\n\t\t\t:class=\"{ 'cn-widget-wrapper__header--actions-only': !titleVisible }\"\n\t\t\t:style=\"[headerStyles, titleIconStyle]\">\n\t\t\t<!-- Title icon — left: rendered before the title group. Moved\n\t\t\t INSIDE header-left so the header's `space-between` cannot pull\n\t\t\t it away from the title it belongs to; header-left's `gap` is\n\t\t\t what now separates icon from title (they used to touch). -->\n\t\t\t<div v-if=\"titleVisible\" class=\"cn-widget-wrapper__header-left\">\n\t\t\t\t<div v-if=\"$slots['title-icon'] && titleIconPosition === 'left'\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__title-icon\">\n\t\t\t\t\t<slot name=\"title-icon\" />\n\t\t\t\t</div>\n\t\t\t\t<img\n\t\t\t\t\tv-if=\"iconUrl\"\n\t\t\t\t\t:src=\"iconUrl\"\n\t\t\t\t\t:alt=\"resolvedTitle\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__icon\">\n\t\t\t\t<span\n\t\t\t\t\tv-else-if=\"iconClass\"\n\t\t\t\t\t:class=\"iconClass\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__icon\" />\n\t\t\t\t<h3 :id=\"titleId\" class=\"cn-widget-wrapper__title\">\n\t\t\t\t\t{{ resolvedTitle }}\n\t\t\t\t</h3>\n\t\t\t\t<!-- @slot title-meta Rendered inside the header's left group,\n\t\t\t\t after the title and before the spacer. Use for small\n\t\t\t\t contextual chips (e.g. a date-range badge on chart\n\t\t\t\t widgets). Kept inside header-left so the chip sits next\n\t\t\t\t to the title rather than floating right next to the\n\t\t\t\t actions menu. -->\n\t\t\t\t<div v-if=\"$slots['title-meta']\" class=\"cn-widget-wrapper__title-meta\">\n\t\t\t\t\t<slot name=\"title-meta\" />\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<!-- `showActions` gates the overflow MENU, not the slot beside it.\n\t\t\t It used to gate both, so a caller who switched the menu off\n\t\t\t also lost the Save button for an inline edit, silently. -->\n\t\t\t<div v-if=\"actionsAreVisible()\" class=\"cn-widget-wrapper__actions\">\n\t\t\t\t<!-- One text link in the header (`headerLink`): \"All deadlines\",\n\t\t\t\t \"To the board\". A route is a router link, an href a plain\n\t\t\t\t anchor. Sits before the slot and the overflow menu. -->\n\t\t\t\t<component\n\t\t\t\t\t:is=\"headerLinkTag\"\n\t\t\t\t\tv-if=\"headerLinkTag\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__header-link\"\n\t\t\t\t\tdata-testid=\"cn-widget-wrapper-header-link\"\n\t\t\t\t\tv-bind=\"headerLinkAttrs\">\n\t\t\t\t\t{{ headerLinkLabel }}\n\t\t\t\t</component>\n\t\t\t\t<!-- @slot actions Custom action buttons rendered before the\n\t\t\t\t built-in overflow menu. -->\n\t\t\t\t<slot name=\"actions\" />\n\t\t\t\t<CnActionsMenu\n\t\t\t\t\tv-if=\"showActions\"\n\t\t\t\t\t:showRefresh=\"effectiveShowRefresh\"\n\t\t\t\t\t:showRequestFeature=\"effectiveShowRequestFeature\"\n\t\t\t\t\t:showReportBug=\"showReportBug\"\n\t\t\t\t\t:showDocumentation=\"showDocumentation\"\n\t\t\t\t\t:documentationUrl=\"documentationUrl\"\n\t\t\t\t\t:docsAnchor=\"docsAnchor\"\n\t\t\t\t\t:reportBugUrl=\"reportBugUrl\"\n\t\t\t\t\t:documentationLabel=\"documentationLabel\"\n\t\t\t\t\t:refreshLabel=\"refreshLabel\"\n\t\t\t\t\t:requestFeatureLabel=\"requestFeatureLabel\"\n\t\t\t\t\t:actionsMenuLabel=\"actionsMenuLabel\"\n\t\t\t\t\t:refreshing=\"refreshing\"\n\t\t\t\t\t:widgetId=\"resolvedWidgetId\"\n\t\t\t\t\t:title=\"displayTitle\"\n\t\t\t\t\t:surface=\"`widget:${resolvedWidgetId}`\"\n\t\t\t\t\t:specRef=\"specRef\"\n\t\t\t\t\trefreshChannel=\"cn:widget:refresh\"\n\t\t\t\t\ttestidBase=\"cn-widget-wrapper\"\n\t\t\t\t\t@refresh=\"onActionsRefresh\"\n\t\t\t\t\t@requestFeature=\"onActionsRequestFeature\">\n\t\t\t\t\t<!-- @slot action-items Additional NcActionButton-family\n\t\t\t\t\t items rendered inside the overflow menu, after the\n\t\t\t\t\t built-in Refresh / Documentation / Request-a-feature\n\t\t\t\t\t group. -->\n\t\t\t\t\t<template v-if=\"hasActionItemsSlot\" #action-items>\n\t\t\t\t\t\t<slot name=\"action-items\" />\n\t\t\t\t\t</template>\n\t\t\t\t</CnActionsMenu>\n\t\t\t</div>\n\t\t\t<!-- Title icon — right: rendered after actions, far right -->\n\t\t\t<div v-if=\"$slots['title-icon'] && titleIconPosition === 'right'\"\n\t\t\t\tclass=\"cn-widget-wrapper__title-icon\">\n\t\t\t\t<slot name=\"title-icon\" />\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Headerless meta (e.g. a KPI date chip on a flush card). When the\n\t\t header is hidden but a `title-meta` chip is provided, float it in the\n\t\t top-right corner over the content instead of dropping it — so a\n\t\t compact flush KPI tile can carry a date-range chip without growing a\n\t\t full header bar. -->\n\t\t<div\n\t\t\tv-if=\"!titleVisible && $slots['title-meta']\"\n\t\t\tclass=\"cn-widget-wrapper__floating-meta\">\n\t\t\t<slot name=\"title-meta\" />\n\t\t</div>\n\n\t\t<!-- Content -->\n\t\t<!--\n\t\t The content area is `overflow: auto`, so it can become a scrollable\n\t\t region. WCAG 2.1.1 (Keyboard) requires scrollable regions to be\n\t\t keyboard-focusable so keyboard-only users can scroll them, and 4.1.2\n\t\t requires an accessible name. We expose it as a labelled region and\n\t\t give it tabindex=\"0\". When the header (and its title) is hidden we\n\t\t fall back to an explicit aria-label from the widget title so the\n\t\t region is never anonymous.\n\t\t-->\n\t\t<div\n\t\t\tclass=\"cn-widget-wrapper__content\"\n\t\t\ttabindex=\"0\"\n\t\t\trole=\"region\"\n\t\t\t:aria-labelledby=\"titleVisible ? titleId : null\"\n\t\t\t:aria-label=\"titleVisible ? null : resolvedTitle\">\n\t\t\t<slot />\n\t\t</div>\n\n\t\t<!-- Footer -->\n\t\t<div v-if=\"$slots.footer || (buttons && buttons.length > 0)\" class=\"cn-widget-wrapper__footer\">\n\t\t\t<slot name=\"footer\">\n\t\t\t\t<a\n\t\t\t\t\tv-for=\"button in buttons\"\n\t\t\t\t\t:key=\"button.link\"\n\t\t\t\t\t:href=\"button.link\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__footer-link\">\n\t\t\t\t\t{{ button.text }}\n\t\t\t\t</a>\n\t\t\t</slot>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { slotRenders } from '../../utils/slotContent.js'\nimport { CnActionsMenu } from '../CnActionsMenu/index.js'\n\n/**\n * CnWidgetWrapper — Widget container with header, content, and footer.\n *\n * ```vue\n * <CnWidgetWrapper title=\"My Cases\" :icon-url=\"casesIconUrl\">\n * <MyCasesChart :data=\"chartData\" />\n * </CnWidgetWrapper>\n * ```\n *\n * With NC widget object\n * ```vue\n * <CnWidgetWrapper\n * :title=\"widget.title\"\n * :icon-url=\"widget.iconUrl\"\n * :icon-class=\"widget.iconClass\"\n * :documentation-url=\"widget.documentationUrl\"\n * :buttons=\"widget.buttons\">\n * <CnWidgetRenderer :widget=\"widget\" />\n * </CnWidgetWrapper>\n * ```\n */\n/**\n * Semantic variant → CSS colour token for the widget header icon. Tokens\n * only — the nldesign app themes by overriding the Nextcloud variables, so a\n * literal hex here would ignore government theming entirely (ADR-062 / NL\n * Design System).\n *\n * @type {Record<string, string>}\n */\nconst TITLE_ICON_VARIANT_COLORS = {\n\tprimary: 'var(--color-primary-element)',\n\tsuccess: 'var(--color-success)',\n\twarning: 'var(--color-warning)',\n\terror: 'var(--color-error)',\n\tinfo: 'var(--color-info, var(--color-primary-element))',\n\tneutral: 'var(--color-text-maxcontrast)',\n}\n\nexport default {\n\tname: 'CnWidgetWrapper',\n\n\tcomponents: {\n\t\tCnActionsMenu,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Host translate function provided by CnAppRoot as\n\t\t * `cnTranslate: this.translate` (bound to the host app's id). The\n\t\t * manifest-authored widget title is run through it so the card\n\t\t * heading follows the user's language. Defaults to an identity\n\t\t * function so an untranslated key renders as itself.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** Widget title */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Widget'),\n\t\t},\n\n\t\t/** Whether to show the header with title */\n\t\tshowTitle: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Chrome variant for the wrapper card.\n\t\t * - `'default'` — the library's own card chrome (opaque background,\n\t\t * 1px border, compact header).\n\t\t * - `'nc-dashboard'` — matches the native Nextcloud Dashboard panel\n\t\t * exactly using the same design tokens: translucent blurred\n\t\t * background (`--color-main-background-blur` + `--filter-background-blur`),\n\t\t * `--border-radius-container-large` corners, no border/shadow, a 16px\n\t\t * header with a 20px/700 title and 32px leading icon, and content\n\t\t * inset 16px on the sides + bottom. `styleConfig` overrides still\n\t\t * layer on top so a user can customise any token.\n\t\t *\n\t\t * @type {'default'|'nc-dashboard'}\n\t\t */\n\t\tchrome: {\n\t\t\ttype: String,\n\t\t\tdefault: 'default',\n\t\t\tvalidator: (v) => ['default', 'nc-dashboard'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Remove border and background — makes the wrapper transparent.\n\t\t * Useful for widgets that are self-contained cards (e.g. CnStatsBlock).\n\t\t */\n\t\tborderless: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Remove content padding — allows content to go edge-to-edge.\n\t\t * Useful for list-style widgets where items should span the full width.\n\t\t */\n\t\tflush: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Draw NO card of this widget's own: no border, no background, no\n\t\t * content padding, no title row and no header divider. For a surface\n\t\t * that already supplies the card and the heading — a tab panel being\n\t\t * the case that matters, where a nested card repeats both.\n\t\t *\n\t\t * The wrapper ELEMENT still renders. That is the contract, not an\n\t\t * oversight: `CnObjectDataWidget` measures its overflow against\n\t\t * `.cn-widget-wrapper__content` (`closest()` plus a ResizeObserver on\n\t\t * that node), `src/css/table.css` gives a table inside it its\n\t\t * edge-to-edge treatment, and `src/css/detail-page.css` and\n\t\t * `dashboard.css` size their content areas through it. Dropping the\n\t\t * node would take all of that out silently — `closest()` returning\n\t\t * null reads as \"nothing overflows\", never as an error.\n\t\t *\n\t\t * Controls in the `actions` slot are NOT chrome and keep their header:\n\t\t * the Save button that commits an inline edit lives there, and a panel\n\t\t * that hides it is silently unsaveable.\n\t\t *\n\t\t * `chromeless` is what an integration provider already asks for through\n\t\t * `CnDetailWidgetHost` (`bareWidget`), so this is the same word for the\n\t\t * same thing on every path into the wrapper.\n\t\t */\n\t\tchromeless: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Icon URL (image) */\n\t\ticonUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Icon CSS class (e.g., Nextcloud icon class) */\n\t\ticonClass: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Position of the title-icon slot in the header.\n\t\t * 'left' places it before the title; 'right' places it after the actions.\n\t\t */\n\t\ttitleIconPosition: {\n\t\t\ttype: String,\n\t\t\tdefault: 'right',\n\t\t\tvalidator: (v) => ['left', 'right'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Explicit CSS colour for the header icon. Overrides `titleIconVariant`.\n\t\t * Must be a CSS custom property or a theme token — never a literal hex\n\t\t * (NL Design System: the nldesign app re-themes by overriding the\n\t\t * Nextcloud variables, and a hardcoded colour ignores that).\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\ttitleIconColor: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Semantic colour for the header icon. Every widget's icon is\n\t\t * coloured — `primary` (the theme colour) is the default, and a\n\t\t * widget whose subject already carries a semantic meaning names it\n\t\t * here so the icon says so at a glance (a Depublished list is\n\t\t * `error`, a Concepts list `warning`, a Published list `success`).\n\t\t *\n\t\t * @type {'primary'|'success'|'warning'|'error'|'info'|'neutral'}\n\t\t */\n\t\ttitleIconVariant: {\n\t\t\ttype: String,\n\t\t\tdefault: 'primary',\n\t\t\tvalidator: (v) => ['primary', 'success', 'warning', 'error', 'info', 'neutral'].includes(v),\n\t\t},\n\n\t\t/** Footer action buttons: [{ text, link }] */\n\t\tbuttons: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * One text link in the header's actions group, before the overflow\n\t\t * menu: `{ label, route?, params?, query?, href? }`. A `route` is a\n\t\t * named router link (needs a router), an `href` a plain anchor. The\n\t\t * label goes through the host translate function. Null (the\n\t\t * default) draws nothing. Manifest: `headerLink` on the widget\n\t\t * definition or its layout entry.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-widget-header-carries-a-text-link\n\t\t * @type {{label: string, route?: string, params?: object, query?: object, href?: string}|null}\n\t\t */\n\t\theaderLink: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether the header's overflow action menu (Refresh / Documentation /\n\t\t * Request-a-feature + any `#action-items`) renders. Shown by default;\n\t\t * set `false` for compact surfaces — e.g. a KPI tile whose only header\n\t\t * affordance is a date chip — to drop the menu and free header width.\n\t\t */\n\t\tshowActions: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Style configuration for the wrapper.\n\t\t *\n\t\t * @type {{ backgroundColor: string, borderStyle: string, borderWidth: number, borderColor: string, borderRadius: number, padding: { top: number, right: number, bottom: number, left: number } }}\n\t\t */\n\t\tstyleConfig: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Hide the built-in Refresh item from the overflow action menu.\n\t\t * The Refresh item is shown by default — set this when the widget\n\t\t * has no refreshable data source (e.g. a static tile). Alias for\n\t\t * the inverse `:show-refresh=\"false\"`; either form opts out.\n\t\t */\n\t\thideRefresh: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Hide the built-in Request-a-feature item from the overflow\n\t\t * action menu. Shown by default; set when the consuming app has\n\t\t * no public issue tracker to link out to. Alias for the inverse\n\t\t * `:show-request-feature=\"false\"`; either form opts out.\n\t\t */\n\t\thideRequestFeature: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Whether to show the built-in Refresh item. Tri-state:\n\t\t * - `true` / `false` — force the action on or off.\n\t\t * - `null` (the default) — **auto**: show the action only when a\n\t\t * parent has attached an `@refresh` listener (i.e. something will\n\t\t * actually handle the refresh). This keeps widgets that can't\n\t\t * refresh — e.g. a prop-driven `CnObjectDataWidget` — from showing\n\t\t * a dead button. Widgets that refresh themselves via the\n\t\t * `cn:widget:refresh` event bus (with no `@refresh` listener)\n\t\t * should set `:show-refresh=\"true\"` explicitly.\n\t\t *\n\t\t * `hideRefresh` (or `:show-refresh=\"false\"`) always wins.\n\t\t *\n\t\t * @type {boolean|null}\n\t\t */\n\t\tshowRefresh: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Inverse of `hideRequestFeature`. Defaults to `true` so the\n\t\t * action renders. Set `:show-request-feature=\"false\"` to hide it.\n\t\t * Either flag hides the action.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tshowRequestFeature: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Whether the built-in \"Report a bug\" item renders. On by default —\n\t\t * the trio Request a feature / Report a bug / Documentation is the\n\t\t * contract for every widget; this exists for the rare surface that\n\t\t * must suppress one deliberately.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tshowReportBug: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Whether the built-in \"Documentation\" item renders. On by default,\n\t\t * for the same reason as `showReportBug`. The item's target is\n\t\t * resolved by the shared menu (see `docsAnchor`), so leaving it on\n\t\t * costs the host nothing.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tshowDocumentation: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Explicit documentation link for this widget, opened in a new tab.\n\t\t * Usually unnecessary: leave it empty and set `docsAnchor` instead,\n\t\t * so the link is built from the app-wide documentation base and\n\t\t * lands on this widget's own section.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdocumentationUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional pre-translated label for the Documentation action.\n\t\t * Defaults to the lib's translation of \"Documentation\".\n\t\t */\n\t\tdocumentationLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Documentation'),\n\t\t},\n\n\t\t/**\n\t\t * Widget id for the built-in default Refresh / Request-a-feature\n\t\t * handlers (B2). Forwarded as the `surface: \"widget:<id>\"` value\n\t\t * on the Actions menu (the English headline fallback for its forge\n\t\t * deep-links) AND as the `widgetId` field on the\n\t\t * `cn:widget:refresh` event-bus payload. When unset, the wrapper\n\t\t * falls back to a slugified `displayTitle`, which works but is\n\t\t * less stable than an explicit id.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\twidgetId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * This widget's own section in the app's documentation, appended to\n\t\t * the app-wide documentation base URL (provided by CnAppRoot) to build\n\t\t * the Actions menu's Documentation deep-link. A bare slug becomes a\n\t\t * `#fragment`. Supply it per widget type — without it the item still\n\t\t * renders, but lands on the docs homepage rather than this widget.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdocsAnchor: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Explicit \"Report a bug\" target for the Actions menu. Empty (the\n\t\t * default) builds a new-issue deep-link on the app's own forge.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\treportBugUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Declares that this widget wants a high contrast treatment. The flag\n\t\t * reaches the theme as `data-cn-high-contrast` and a `--high-contrast`\n\t\t * class; the component picks no colour of its own (ADR-003). A theme\n\t\t * that says nothing about the flag renders the widget unchanged.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\thighContrast: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Optional `specRef` slug, forwarded to the Actions menu. Accepted\n\t\t * for backward compatibility with the removed in-product suggestion\n\t\t * modal; the forge issue form asks for its own context.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tspecRef: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Whether a refresh is currently in flight. When bound by the host\n\t\t * (e.g. `:refreshing=\"loading\"` around its refetch), the Refresh\n\t\t * item is disabled and shows a loading spinner for exactly as long\n\t\t * as this stays true — so the spinner reflects the real refresh time.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\trefreshing: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Optional pre-translated label for the Refresh action. Defaults\n\t\t * to the lib's translation of \"Refresh\" so callers usually don't\n\t\t * need to set this.\n\t\t */\n\t\trefreshLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Refresh'),\n\t\t},\n\n\t\t/**\n\t\t * Optional pre-translated label for the Request-a-feature action.\n\t\t * Defaults to the lib's translation of \"Request a feature\".\n\t\t */\n\t\trequestFeatureLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Request a feature'),\n\t\t},\n\n\t\t/**\n\t\t * Pre-translated aria-label / tooltip for the overflow menu\n\t\t * trigger. Defaults to \"Actions\".\n\t\t */\n\t\tactionsMenuLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Actions'),\n\t\t},\n\n\t\t/**\n\t\t * Translate function. Falls back to the injected `cnTranslate`,\n\t\t * which itself defaults to an identity function.\n\t\t *\n\t\t * @type {((key: string) => string)|null}\n\t\t */\n\t\ttranslate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['refresh', 'request-feature'],\n\n\tcomputed: {\n\t\tdisplayTitle() {\n\t\t\treturn this.title || 'Widget'\n\t\t},\n\n\t\t/**\n\t\t * The header's icon colour, published as ONE custom property on the\n\t\t * header rather than an inline `color` repeated on each icon element.\n\t\t * Everything under the header — the icon-class span, the `title-icon`\n\t\t * slot's content, an SVG inside it — inherits from that single\n\t\t * declaration, so the icon and any decoration around it cannot end up\n\t\t * different colours.\n\t\t *\n\t\t * `titleIconColor` wins when the host set one; otherwise the\n\t\t * `titleIconVariant` token does, which is why EVERY widget icon is\n\t\t * coloured and not just the ones an app remembered to configure. Every\n\t\t * value is a CSS variable, so the nldesign app's overrides re-theme\n\t\t * them for free (NL Design System).\n\t\t *\n\t\t * @return {object} A style object declaring `--cn-widget-icon-color`.\n\t\t */\n\t\ttitleIconStyle() {\n\t\t\treturn {\n\t\t\t\t'--cn-widget-icon-color': this.titleIconColor\n\t\t\t\t\t|| TITLE_ICON_VARIANT_COLORS[this.titleIconVariant]\n\t\t\t\t\t|| TITLE_ICON_VARIANT_COLORS.primary,\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Effective translate function: the explicit `translate` prop when\n\t\t * given, else the injected `cnTranslate` (identity by default).\n\t\t *\n\t\t * @return {(key: string) => string}\n\t\t */\n\t\teffectiveTranslate() {\n\t\t\treturn this.translate ?? this.cnTranslate\n\t\t},\n\n\t\t/**\n\t\t * The header link's tag: `router-link` for a route (when a router is\n\t\t * mounted), `a` for an href, '' when there is no usable link.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-widget-header-carries-a-text-link\n\t\t * @return {string}\n\t\t */\n\t\theaderLinkTag() {\n\t\t\tconst link = this.headerLink\n\t\t\tif (!link || typeof link.label !== 'string' || link.label === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (link.route && this.$router) {\n\t\t\t\treturn 'router-link'\n\t\t\t}\n\t\t\tif (typeof link.href === 'string' && link.href !== '') {\n\t\t\t\treturn 'a'\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/**\n\t\t * The header link's attributes for its tag.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-widget-header-carries-a-text-link\n\t\t * @return {object}\n\t\t */\n\t\theaderLinkAttrs() {\n\t\t\tconst link = this.headerLink || {}\n\t\t\tif (this.headerLinkTag === 'router-link') {\n\t\t\t\tconst to = { name: link.route }\n\t\t\t\tif (link.params && typeof link.params === 'object') {\n\t\t\t\t\tto.params = link.params\n\t\t\t\t}\n\t\t\t\tif (link.query && typeof link.query === 'object') {\n\t\t\t\t\tto.query = link.query\n\t\t\t\t}\n\t\t\t\treturn { to }\n\t\t\t}\n\t\t\tif (this.headerLinkTag === 'a') {\n\t\t\t\treturn { href: link.href }\n\t\t\t}\n\t\t\treturn {}\n\t\t},\n\n\t\t/**\n\t\t * The header link's text, through the host translate function.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-widget-header-carries-a-text-link\n\t\t * @return {string}\n\t\t */\n\t\theaderLinkLabel() {\n\t\t\tconst label = this.headerLink && this.headerLink.label\n\t\t\treturn label ? this.effectiveTranslate(label) : ''\n\t\t},\n\n\t\t/**\n\t\t * The RENDERED widget title — `displayTitle` run through the host\n\t\t * translate function. Kept separate from `displayTitle` on purpose:\n\t\t * `resolvedWidgetId` slugifies the raw title, so identifiers (DOM\n\t\t * ids, refresh channels, action payloads) stay locale-independent\n\t\t * while only the visible/AT-facing text localises.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedTitle() {\n\t\t\treturn this.title ? this.effectiveTranslate(this.title) : this.displayTitle\n\t\t},\n\n\t\t/**\n\t\t * Stable DOM id for the header title `<h3>`, used as the\n\t\t * `aria-labelledby` target of the scrollable content region so the\n\t\t * region gets an accessible name from the widget title (WCAG 4.1.2).\n\t\t * Derived from `resolvedWidgetId` so it is stable and unique per\n\t\t * widget instance on the page.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\ttitleId() {\n\t\t\treturn `cn-widget-wrapper-title-${this.resolvedWidgetId}`\n\t\t},\n\n\t\t/**\n\t\t * Effective Refresh visibility. `hideRefresh: true` (or\n\t\t * `:show-refresh=\"false\"`) always hides it. When `showRefresh` is\n\t\t * left unset (`null`), auto-detect: show the action only when a\n\t\t * parent attached an `@refresh` listener — otherwise the refresh\n\t\t * would do nothing. `hideRefresh` remains a back-compat alias.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\teffectiveShowRefresh() {\n\t\t\tif (this.hideRefresh) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (this.showRefresh !== null) {\n\t\t\t\treturn this.showRefresh\n\t\t\t}\n\t\t\t// `$.vnode.props`, not `$attrs`: `refresh` is a declared emit, and\n\t\t\t// Vue keeps declared emits out of `$attrs`.\n\t\t\treturn Boolean(this.$.vnode.props?.onRefresh)\n\t\t},\n\n\t\t/**\n\t\t * Effective Request-a-feature visibility — same OR-of-opt-outs\n\t\t * pattern as `effectiveShowRefresh`.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\teffectiveShowRequestFeature() {\n\t\t\treturn this.showRequestFeature && !this.hideRequestFeature\n\t\t},\n\n\t\t/**\n\t\t * Stable id used in event-bus payloads and the\n\t\t * `surface: \"widget:<id>\"` field on the auto-mounted feature\n\t\t * modal. Prefers the explicit `widgetId` prop; falls back to a\n\t\t * slugified `displayTitle` so widgets without an explicit id\n\t\t * still get a usable identifier.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedWidgetId() {\n\t\t\tif (this.widgetId) {\n\t\t\t\treturn this.widgetId\n\t\t\t}\n\t\t\treturn this.displayTitle.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')\n\t\t},\n\n\t\t/**\n\t\t * Whether the caller supplied an `action-items` slot. Forwarded\n\t\t * conditionally so the shared CnActionsMenu doesn't treat an\n\t\t * always-present pass-through template as content (which would\n\t\t * keep the overflow menu visible even when every built-in is\n\t\t * opted out).\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\thasActionItemsSlot() {\n\t\t\treturn Boolean(this.$slots['action-items']) || Boolean(this.$slots && this.$slots['action-items'])\n\t\t},\n\n\t\t/**\n\t\t * Whether the caller put controls in the `actions` slot.\n\t\t *\n\t\t * Gates the header together with `showTitle`, so a surface that wants no\n\t\t * title still keeps controls that have nowhere else to go — the Save\n\t\t * button `CnObjectDataWidget` renders there being the case that matters.\n\t\t *\n\t\t * Deliberately NOT `showActions`: that prop defaults to true and governs\n\t\t * the overflow menu, so reading it here would give a header to every\n\t\t * headerless KPI tile that never had one.\n\t\t *\n\t\t * @return {boolean} true when the slot is filled.\n\t\t */\n\t\thasActionsSlot() {\n\t\t\treturn Boolean(this.$slots.actions)\n\t\t},\n\n\t\t/**\n\t\t * Whether the card outline is drawn. `chromeless` implies `borderless`\n\t\t * so a caller names the intent once instead of remembering the three\n\t\t * props it decomposes into.\n\t\t *\n\t\t * @return {boolean} true when border and background are suppressed.\n\t\t */\n\t\tnoBorder() {\n\t\t\treturn this.borderless || this.chromeless\n\t\t},\n\n\t\t/**\n\t\t * Whether the content area goes edge to edge. `chromeless` implies\n\t\t * `flush` for the same reason as `noBorder`.\n\t\t *\n\t\t * @return {boolean} true when content padding is suppressed.\n\t\t */\n\t\tnoPadding() {\n\t\t\treturn this.flush || this.chromeless\n\t\t},\n\n\t\t/**\n\t\t * Whether the title row renders. `chromeless` overrides `showTitle`,\n\t\t * which defaults to true: a surface that already names the widget must\n\t\t * not have to also remember to turn the title off.\n\t\t *\n\t\t * Read everywhere the template used to read `showTitle` directly,\n\t\t * including the two ARIA bindings on the content region — a hidden\n\t\t * title cannot be the region's `aria-labelledby` target.\n\t\t *\n\t\t * @return {boolean} true when the header's title group renders.\n\t\t */\n\t\ttitleVisible() {\n\t\t\treturn this.showTitle && !this.chromeless\n\t\t},\n\n\t\twrapperStyles() {\n\t\t\tconst styles = {}\n\n\t\t\tif (this.styleConfig.backgroundColor) {\n\t\t\t\tstyles.backgroundColor = this.styleConfig.backgroundColor\n\t\t\t}\n\n\t\t\tif (this.styleConfig.borderStyle && this.styleConfig.borderStyle !== 'none') {\n\t\t\t\tstyles.border = `${this.styleConfig.borderWidth || 1}px ${this.styleConfig.borderStyle} ${this.styleConfig.borderColor || 'var(--color-border)'}`\n\t\t\t}\n\n\t\t\tif (this.styleConfig.borderRadius !== undefined) {\n\t\t\t\tstyles.borderRadius = `${this.styleConfig.borderRadius}px`\n\t\t\t}\n\n\t\t\tif (this.styleConfig.padding) {\n\t\t\t\tconst p = this.styleConfig.padding\n\t\t\t\tstyles.padding = `${p.top || 0}px ${p.right || 0}px ${p.bottom || 0}px ${p.left || 0}px`\n\t\t\t}\n\n\t\t\treturn styles\n\t\t},\n\n\t\t/**\n\t\t * Inline styles for the header bar, derived from\n\t\t * `styleConfig.headerStyle.{backgroundColor, textColor}`. Lets a host\n\t\t * give individual widgets a custom header colour without a per-app CSS\n\t\t * workaround. Empty object when no header style is configured.\n\t\t *\n\t\t * @return {object} the header style bindings.\n\t\t */\n\t\theaderStyles() {\n\t\t\tconst hs = this.styleConfig.headerStyle\n\t\t\tif (!hs || typeof hs !== 'object') {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\tconst styles = {}\n\t\t\tif (hs.backgroundColor) {\n\t\t\t\tstyles.backgroundColor = hs.backgroundColor\n\t\t\t}\n\t\t\tif (hs.textColor) {\n\t\t\t\tstyles.color = hs.textColor\n\t\t\t}\n\t\t\treturn styles\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether the `actions` slot renders anything a reader can see.\n\t\t *\n\t\t * `CnObjectDataWidget` always PROVIDES the template and fills it only\n\t\t * while an edit is unsaved, so the slot has to be called and its vnodes\n\t\t * inspected. Reading `$slots.actions` as a boolean counts the template\n\t\t * itself as content, which is what gave a titleless tab panel a 59px\n\t\t * band holding nothing but a second Actions menu.\n\t\t *\n\t\t * A method rather than a computed on purpose. The answer changes when the\n\t\t * PARENT's state changes, and a computed reading `$slots` does not\n\t\t * re-track that; a method re-runs on every render, and a slot whose\n\t\t * content depends on parent state already forces this component to\n\t\t * re-render.\n\t\t *\n\t\t * @return {boolean} True when the slot yields visible content.\n\t\t */\n\t\tactionsSlotRenders() {\n\t\t\treturn slotRenders(this.$slots.actions)\n\t\t},\n\n\t\t/**\n\t\t * Whether the header band is worth the vertical space it costs.\n\t\t *\n\t\t * A VISIBLE title always earns it, which `chromeless` rules out. Otherwise\n\t\t * it is earned by controls that are actually on screen: content in the\n\t\t * `actions` slot, or the overflow menu when a caller both asked for it and\n\t\t * provided the slot it sits beside.\n\t\t *\n\t\t * That last clause looks redundant and is not. `showActions` defaults to\n\t\t * TRUE, so reading it alone hands a header to every headerless KPI tile\n\t\t * in the fleet, which is what the floating title-meta test caught when\n\t\t * this was first written the short way.\n\t\t *\n\t\t * @return {boolean} True when the header should render.\n\t\t */\n\t\theaderIsWorthIt() {\n\t\t\tif (this.titleVisible) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tif (this.actionsSlotRenders()) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn this.showActions && this.hasActionsSlot\n\t\t},\n\n\t\t/**\n\t\t * Whether the actions group holds anything.\n\t\t *\n\t\t * The overflow menu and the slot beside it are separate conditions now.\n\t\t * `showActions` used to gate both, so a caller who only wanted the menu\n\t\t * gone also lost the button that commits an inline edit.\n\t\t *\n\t\t * @return {boolean} True when the group would hold a visible control.\n\t\t */\n\t\tactionsAreVisible() {\n\t\t\treturn this.showActions || this.actionsSlotRenders() || Boolean(this.headerLinkTag)\n\t\t},\n\n\t\t/**\n\t\t * Re-emit the shared CnActionsMenu `@refresh` to the host, passing\n\t\t * the synthetic event through unchanged so a host listener can\n\t\t * still `preventDefault()` the built-in default (event-bus emit on\n\t\t * `cn:widget:refresh`).\n\t\t *\n\t\t * @param {{ widgetId: string, title: string }} payload Action payload.\n\t\t * @param {{ defaultPrevented: boolean, preventDefault: () => void }} ev Synthetic event.\n\t\t * @return {void}\n\t\t */\n\t\tonActionsRefresh(payload, ev) {\n\t\t\t/**\n\t\t\t * @event refresh User clicked the Refresh item in the overflow\n\t\t\t * action menu. Payload: `{ widgetId, title }`. Handlers may call\n\t\t\t * the second arg's `preventDefault()` to suppress the built-in\n\t\t\t * default (event-bus emit on `cn:widget:refresh`).\n\t\t\t * @type {{ widgetId: string, title: string }}\n\t\t\t */\n\t\t\tthis.$emit('refresh', payload, ev)\n\t\t},\n\n\t\t/**\n\t\t * Re-emit the shared CnActionsMenu `@request-feature` to the host,\n\t\t * passing the synthetic event through so a host listener can\n\t\t * `preventDefault()` the built-in default (opening the forge's\n\t\t * feature-request issue form).\n\t\t *\n\t\t * @param {{ widgetId: string, title: string }} payload Action payload.\n\t\t * @param {{ defaultPrevented: boolean, preventDefault: () => void }} ev Synthetic event.\n\t\t * @return {void}\n\t\t */\n\t\tonActionsRequestFeature(payload, ev) {\n\t\t\t/**\n\t\t\t * @event request-feature User clicked the Request a feature\n\t\t\t * item. Payload: `{ widgetId, title }`. Handlers may call the\n\t\t\t * second arg's `preventDefault()` to suppress the built-in\n\t\t\t * default (opening the forge's feature-request issue form).\n\t\t\t * @type {{ widgetId: string, title: string }}\n\t\t\t */\n\t\t\tthis.$emit('request-feature', payload, ev)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-widget-wrapper {\n\tposition: relative;\n\theight: 100%;\n\tdisplay: flex;\n\tflex-direction: column;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\t/* NC design system: every content card is rounded — same token as\n\t CnDetailCard so detail pages read as ONE card family (ADR-062). */\n\tborder-radius: var(--border-radius-large, 8px);\n\toverflow: hidden;\n}\n\n/* Headerless date chip — floated over the top-right of a flush card. */\n.cn-widget-wrapper__floating-meta {\n\tposition: absolute;\n\ttop: 8px;\n\tinset-inline-end: 10px;\n\tz-index: 2;\n}\n\n.cn-widget-wrapper__content {\n\tflex: 1;\n\toverflow: auto;\n\tmin-height: 0;\n\tpadding: 16px;\n}\n\n/* A TABLE GOES EDGE TO EDGE. ALWAYS.\n A table drawn inside 16px of padding renders a border inside a border — two\n rectangles a few pixels apart, which reads as a mistake because it is one.\n The wrapper already has a `flush` prop that zeroes this padding, but a prop\n is something an app has to remember on every table it ever adds, and the\n apps did not: the padding was visible in pipelinq's table widgets.\n So it is structural instead. `:has()` keys on the table component's own root\n class, which means a table is flush by construction and no caller can\n reintroduce the gap by forgetting a prop. */\n.cn-widget-wrapper__content:has(> .cn-table-container),\n.cn-widget-wrapper__content:has(> .cn-widget-object-table) {\n\tpadding: 0;\n}\n\n/* Keyboard focus ring for the now-focusable scrollable content region. */\n.cn-widget-wrapper__content:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: -2px;\n}\n\n.cn-widget-wrapper--borderless {\n\tborder: none;\n\tbackground: transparent;\n}\n\n.cn-widget-wrapper--borderless .cn-widget-wrapper__content {\n\tpadding: 0;\n}\n\n.cn-widget-wrapper--flush .cn-widget-wrapper__content {\n\tpadding: 0;\n}\n\n/* A form or a search list keeps its padding, the reverse of the table rule\n above. A dashboard renders every widget `flush`, and without this their\n fields touch the card's edges. Borderless and chromeless cards, and the\n Nextcloud dashboard variant, keep their own padding. */\n.cn-widget-wrapper--flush:not(.cn-widget-wrapper--borderless, .cn-widget-wrapper--nc-dashboard) > .cn-widget-wrapper__content:has(> .cn-form-widget),\n.cn-widget-wrapper--flush:not(.cn-widget-wrapper--borderless, .cn-widget-wrapper--nc-dashboard) > .cn-widget-wrapper__content:has(> .cn-kb-search-widget) {\n\tpadding: 16px;\n}\n\n/* `chromeless` — border and background are already gone through borderless,\n and the content padding through flush. What is left on the root is the\n corner radius, which still clips: with `overflow: hidden` above, a child\n that paints its own background (a table header) gets rounded corners inside\n a square panel, which is the last visible piece of a card that is supposed\n to be absent. `CnDetailCard.cn-detail-card--chromeless` zeroes the same\n thing for the same reason. */\n.cn-widget-wrapper--chromeless {\n\tborder-radius: 0;\n}\n\n/*\n * `chrome=\"nc-dashboard\"` — reproduce the native Nextcloud Dashboard panel\n * (apps/dashboard) exactly, using the same design tokens so an un-customised\n * widget is pixel-identical to a core dashboard panel. `styleConfig` inline\n * overrides (wrapperStyles / headerStyles) still win over these class rules,\n * so users can override any token. Combined selector raises specificity above\n * the base `.cn-widget-wrapper` rules it supersedes.\n */\n.cn-widget-wrapper__header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\t/* The header is `space-between`, so without an explicit gap a right-hand\n\t title-icon sits flush against whatever precedes it. The left-hand icon's\n\t spacing comes from header-left's own gap. */\n\tgap: 8px;\n\tpadding: 12px 16px;\n\tborder-bottom: 1px solid var(--color-border);\n\tflex-shrink: 0;\n}\n\n/* Actions with no title beside them. `header-left` is not rendered at all in\n this mode, and the header is `space-between`, so a single child would sit\n flush LEFT where every other surface puts its actions on the right. */\n.cn-widget-wrapper__header--actions-only {\n\tjustify-content: flex-end;\n}\n\n/* `chromeless` — the surface around this widget already drew the card, so\n nothing here may draw a second one. Border, background and content padding\n are already gone (chromeless implies borderless + flush); what is left is\n the header, which still renders when there are controls to hold. Its bottom\n rule is the card's own divider and reads as a second card edge inside the\n panel, and its 16px side inset leaves those controls hanging off the grid\n that now starts at the panel's edge. Both go.\n\n Placed AFTER the rules it overrides, which is what stylelint's\n no-descending-specificity asks for. The cascade did not need it (this\n selector is more specific either way), a reader does: an override written\n above the thing it overrides has to be read twice. */\n.cn-widget-wrapper--chromeless .cn-widget-wrapper__header {\n\tborder-bottom: none;\n\tpadding-inline: 0;\n}\n\n.cn-widget-wrapper__header-left {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmin-width: 0;\n\t/* Grow so the title hugs the (optional) title-icon on the left instead\n\t of centering between icon and actions (header is space-between). */\n\tflex: 1 1 auto;\n}\n\n.cn-widget-wrapper__icon {\n\twidth: 24px;\n\theight: 24px;\n\tflex-shrink: 0;\n\tcolor: var(--cn-widget-icon-color, var(--color-primary-element));\n}\n\n.cn-widget-wrapper__title {\n\tfont-weight: 600;\n\tfont-size: 14px;\n\tmargin: 0;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-widget-wrapper.cn-widget-wrapper--nc-dashboard {\n\tbackground: var(--color-main-background-blur, var(--color-main-background));\n\t-webkit-backdrop-filter: var(--filter-background-blur, none);\n\tbackdrop-filter: var(--filter-background-blur, none);\n\tborder: none;\n\tborder-radius: var(--border-radius-container-large, 16px);\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__header {\n\tpadding: 16px;\n\tborder-bottom: none;\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__header-left {\n\tgap: 16px;\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__icon {\n\twidth: 32px;\n\theight: 32px;\n\tbackground-size: 32px;\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__title {\n\tfont-size: 20px;\n\tfont-weight: 700;\n\tline-height: 24px;\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__content {\n\tpadding: 0 16px 16px;\n}\n\n.cn-widget-wrapper__title-meta {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 4px;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n\twhite-space: nowrap;\n\tflex-shrink: 0;\n}\n\n.cn-widget-wrapper__actions {\n\tdisplay: flex;\n\tgap: 4px;\n\tflex-shrink: 0;\n}\n\n/* The header's text link (`headerLink`). Theme hooks: --cn-widget-header-link-size\n and --cn-widget-header-link-weight. */\n.cn-widget-wrapper__header-link {\n\talign-self: center;\n\tpadding: 0 calc(1 * var(--default-grid-baseline));\n\tcolor: var(--color-primary-element);\n\tfont-size: var(--cn-widget-header-link-size, 14px);\n\tfont-weight: var(--cn-widget-header-link-weight, 600);\n\ttext-decoration: none;\n\twhite-space: nowrap;\n}\n\n.cn-widget-wrapper__header-link:hover,\n.cn-widget-wrapper__header-link:focus-visible {\n\ttext-decoration: underline;\n}\n\n.cn-widget-wrapper__header-link:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 2px;\n\tborder-radius: var(--border-radius-small, 4px);\n}\n\n/* Every widget's header icon is coloured — `--cn-widget-icon-color` is set on\n the header from titleIconStyle, and defaults to the theme colour if a\n wrapper is ever rendered without it. Material-design-icon components paint\n with `fill: currentColor`, so this reaches them; the :deep rule below makes\n it reach a raw <svg> in the slot too. */\n.cn-widget-wrapper__title-icon {\n\tdisplay: flex;\n\talign-items: center;\n\tflex-shrink: 0;\n\t/* COLOURED BY DEFAULT. `--cn-widget-icon-color` is set on the header from\n\t titleIconStyle; the theme colour is the fallback for a wrapper rendered\n\t without it. Before this, the prop defaulted to null and the icon inherited\n\t body text colour, which is why every widget header looked the same. */\n\tcolor: var(--cn-widget-icon-color, var(--color-primary-element));\n\n\t/* SPACE BETWEEN THE ICON AND THE TITLE.\n\t This element is a sibling of `__header-left` inside a space-between\n\t header, so it never received that element's `gap: 8px` — the icon sat\n\t flush against the title text. Matching the header-left gap rather than\n\t inventing a value keeps the two icon positions spaced identically. */\n\tmargin-inline-end: 8px;\n}\n\n/* Right-positioned title icons sit AFTER the actions, so the margin belongs on\n the other side or it pushes the icon off the header's right edge. */\n.cn-widget-wrapper__title-icon:last-child {\n\tmargin-inline-end: 0;\n\tmargin-inline-start: 8px;\n}\n\n/* Material-design-icon components paint with `fill: currentColor`, so the\n colour above reaches them; this makes it reach a raw <svg> in the slot too. */\n.cn-widget-wrapper__title-icon :deep(svg) {\n\tfill: currentcolor;\n}\n\n.cn-widget-wrapper__footer {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\tgap: 8px;\n\tpadding: 8px 16px;\n\tborder-top: 1px solid var(--color-border);\n\tflex-shrink: 0;\n}\n\n.cn-widget-wrapper__footer-link {\n\tfont-size: 13px;\n\tcolor: var(--color-primary-element);\n\ttext-decoration: none;\n}\n\n.cn-widget-wrapper__footer-link:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["CnActionsMenu","t"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { resolveComponent, openBlock, createElementBlock, normalizeClass, normalizeStyle, renderSlot, createCommentVNode, createElementVNode, toDisplayString, createBlock, createSlots, withCtx, Fragment, renderList } from 'vue';
1
+ import { resolveComponent, openBlock, createElementBlock, normalizeClass, normalizeStyle, renderSlot, createCommentVNode, createElementVNode, toDisplayString, createBlock, resolveDynamicComponent, mergeProps, withCtx, createTextVNode, createSlots, Fragment, renderList } from 'vue';
2
2
 
3
3
  const _hoisted_1 = ["data-cn-high-contrast"];
4
4
  const _hoisted_2 = {
@@ -87,10 +87,22 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
87
87
  : createCommentVNode("v-if", true),
88
88
  ($options.actionsAreVisible())
89
89
  ? (openBlock(), createElementBlock("div", _hoisted_7, [
90
+ ($options.headerLinkTag)
91
+ ? (openBlock(), createBlock(resolveDynamicComponent($options.headerLinkTag), mergeProps({
92
+ key: 0,
93
+ class: "cn-widget-wrapper__header-link",
94
+ "data-testid": "cn-widget-wrapper-header-link"
95
+ }, $options.headerLinkAttrs), {
96
+ default: withCtx(() => [
97
+ createTextVNode(toDisplayString($options.headerLinkLabel), 1 /* TEXT */)
98
+ ]),
99
+ _: 1 /* STABLE */
100
+ }, 16 /* FULL_PROPS */))
101
+ : createCommentVNode("v-if", true),
90
102
  renderSlot(_ctx.$slots, "actions"),
91
103
  ($props.showActions)
92
104
  ? (openBlock(), createBlock(_component_CnActionsMenu, {
93
- key: 0,
105
+ key: 1,
94
106
  showRefresh: $options.effectiveShowRefresh,
95
107
  showRequestFeature: $options.effectiveShowRequestFeature,
96
108
  showReportBug: $props.showReportBug,
@@ -1 +1 @@
1
- {"version":3,"file":"CnWidgetWrapper.vue3.js","sources":["../../../../src/components/CnWidgetWrapper/CnWidgetWrapper.vue?vue&type=template&id=4dd77f59&lang.js"],"sourcesContent":["<!--\n CnWidgetWrapper — Container shell around a dashboard widget.\n\n Provides header (icon + title), scrollable content area, and optional\n footer with action buttons. Applies style configuration for borders,\n backgrounds, and padding. The built-in overflow Actions menu (Refresh /\n Documentation / Request a feature) is delegated to the shared\n CnActionsMenu so widgets and page surfaces stay in lockstep.\n-->\n<template>\n\t<div\n\t\tclass=\"cn-widget-wrapper\"\n\t\t:class=\"{\n\t\t\t'cn-widget-wrapper--borderless': noBorder,\n\t\t\t'cn-widget-wrapper--flush': noPadding,\n\t\t\t'cn-widget-wrapper--chromeless': chromeless,\n\t\t\t'cn-widget-wrapper--nc-dashboard': chrome === 'nc-dashboard',\n\t\t\t'cn-widget-wrapper--high-contrast': highContrast,\n\t\t}\"\n\t\t:data-cn-high-contrast=\"highContrast ? 'true' : undefined\"\n\t\t:style=\"wrapperStyles\">\n\t\t<!-- Header.\n\t\t Rendered when a title earns it, or when controls that are actually\n\t\t on screen need a home. Three conditions rather than one, and each of\n\t\t the three is a bug this component has already shipped: gating on the\n\t\t title alone took the Save button for an inline edit away along with\n\t\t the doubled title; reading `showActions`, which defaults to true,\n\t\t handed a header to every headerless KPI tile; and reading the\n\t\t `actions` slot's mere PRESENCE gave a chromeless panel a 59px band\n\t\t holding nothing, because `CnObjectDataWidget` provides that template\n\t\t always and fills it only while an edit is unsaved.\n\t\t `headerIsWorthIt()` carries the reasoning. -->\n\t\t<div v-if=\"headerIsWorthIt()\"\n\t\t\tclass=\"cn-widget-wrapper__header\"\n\t\t\t:class=\"{ 'cn-widget-wrapper__header--actions-only': !titleVisible }\"\n\t\t\t:style=\"[headerStyles, titleIconStyle]\">\n\t\t\t<!-- Title icon — left: rendered before the title group. Moved\n\t\t\t INSIDE header-left so the header's `space-between` cannot pull\n\t\t\t it away from the title it belongs to; header-left's `gap` is\n\t\t\t what now separates icon from title (they used to touch). -->\n\t\t\t<div v-if=\"titleVisible\" class=\"cn-widget-wrapper__header-left\">\n\t\t\t\t<div v-if=\"$slots['title-icon'] && titleIconPosition === 'left'\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__title-icon\">\n\t\t\t\t\t<slot name=\"title-icon\" />\n\t\t\t\t</div>\n\t\t\t\t<img\n\t\t\t\t\tv-if=\"iconUrl\"\n\t\t\t\t\t:src=\"iconUrl\"\n\t\t\t\t\t:alt=\"resolvedTitle\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__icon\">\n\t\t\t\t<span\n\t\t\t\t\tv-else-if=\"iconClass\"\n\t\t\t\t\t:class=\"iconClass\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__icon\" />\n\t\t\t\t<h3 :id=\"titleId\" class=\"cn-widget-wrapper__title\">\n\t\t\t\t\t{{ resolvedTitle }}\n\t\t\t\t</h3>\n\t\t\t\t<!-- @slot title-meta Rendered inside the header's left group,\n\t\t\t\t after the title and before the spacer. Use for small\n\t\t\t\t contextual chips (e.g. a date-range badge on chart\n\t\t\t\t widgets). Kept inside header-left so the chip sits next\n\t\t\t\t to the title rather than floating right next to the\n\t\t\t\t actions menu. -->\n\t\t\t\t<div v-if=\"$slots['title-meta']\" class=\"cn-widget-wrapper__title-meta\">\n\t\t\t\t\t<slot name=\"title-meta\" />\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<!-- `showActions` gates the overflow MENU, not the slot beside it.\n\t\t\t It used to gate both, so a caller who switched the menu off\n\t\t\t also lost the Save button for an inline edit, silently. -->\n\t\t\t<div v-if=\"actionsAreVisible()\" class=\"cn-widget-wrapper__actions\">\n\t\t\t\t<!-- @slot actions Custom action buttons rendered before the\n\t\t\t\t built-in overflow menu. -->\n\t\t\t\t<slot name=\"actions\" />\n\t\t\t\t<CnActionsMenu\n\t\t\t\t\tv-if=\"showActions\"\n\t\t\t\t\t:showRefresh=\"effectiveShowRefresh\"\n\t\t\t\t\t:showRequestFeature=\"effectiveShowRequestFeature\"\n\t\t\t\t\t:showReportBug=\"showReportBug\"\n\t\t\t\t\t:showDocumentation=\"showDocumentation\"\n\t\t\t\t\t:documentationUrl=\"documentationUrl\"\n\t\t\t\t\t:docsAnchor=\"docsAnchor\"\n\t\t\t\t\t:reportBugUrl=\"reportBugUrl\"\n\t\t\t\t\t:documentationLabel=\"documentationLabel\"\n\t\t\t\t\t:refreshLabel=\"refreshLabel\"\n\t\t\t\t\t:requestFeatureLabel=\"requestFeatureLabel\"\n\t\t\t\t\t:actionsMenuLabel=\"actionsMenuLabel\"\n\t\t\t\t\t:refreshing=\"refreshing\"\n\t\t\t\t\t:widgetId=\"resolvedWidgetId\"\n\t\t\t\t\t:title=\"displayTitle\"\n\t\t\t\t\t:surface=\"`widget:${resolvedWidgetId}`\"\n\t\t\t\t\t:specRef=\"specRef\"\n\t\t\t\t\trefreshChannel=\"cn:widget:refresh\"\n\t\t\t\t\ttestidBase=\"cn-widget-wrapper\"\n\t\t\t\t\t@refresh=\"onActionsRefresh\"\n\t\t\t\t\t@requestFeature=\"onActionsRequestFeature\">\n\t\t\t\t\t<!-- @slot action-items Additional NcActionButton-family\n\t\t\t\t\t items rendered inside the overflow menu, after the\n\t\t\t\t\t built-in Refresh / Documentation / Request-a-feature\n\t\t\t\t\t group. -->\n\t\t\t\t\t<template v-if=\"hasActionItemsSlot\" #action-items>\n\t\t\t\t\t\t<slot name=\"action-items\" />\n\t\t\t\t\t</template>\n\t\t\t\t</CnActionsMenu>\n\t\t\t</div>\n\t\t\t<!-- Title icon — right: rendered after actions, far right -->\n\t\t\t<div v-if=\"$slots['title-icon'] && titleIconPosition === 'right'\"\n\t\t\t\tclass=\"cn-widget-wrapper__title-icon\">\n\t\t\t\t<slot name=\"title-icon\" />\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Headerless meta (e.g. a KPI date chip on a flush card). When the\n\t\t header is hidden but a `title-meta` chip is provided, float it in the\n\t\t top-right corner over the content instead of dropping it — so a\n\t\t compact flush KPI tile can carry a date-range chip without growing a\n\t\t full header bar. -->\n\t\t<div\n\t\t\tv-if=\"!titleVisible && $slots['title-meta']\"\n\t\t\tclass=\"cn-widget-wrapper__floating-meta\">\n\t\t\t<slot name=\"title-meta\" />\n\t\t</div>\n\n\t\t<!-- Content -->\n\t\t<!--\n\t\t The content area is `overflow: auto`, so it can become a scrollable\n\t\t region. WCAG 2.1.1 (Keyboard) requires scrollable regions to be\n\t\t keyboard-focusable so keyboard-only users can scroll them, and 4.1.2\n\t\t requires an accessible name. We expose it as a labelled region and\n\t\t give it tabindex=\"0\". When the header (and its title) is hidden we\n\t\t fall back to an explicit aria-label from the widget title so the\n\t\t region is never anonymous.\n\t\t-->\n\t\t<div\n\t\t\tclass=\"cn-widget-wrapper__content\"\n\t\t\ttabindex=\"0\"\n\t\t\trole=\"region\"\n\t\t\t:aria-labelledby=\"titleVisible ? titleId : null\"\n\t\t\t:aria-label=\"titleVisible ? null : resolvedTitle\">\n\t\t\t<slot />\n\t\t</div>\n\n\t\t<!-- Footer -->\n\t\t<div v-if=\"$slots.footer || (buttons && buttons.length > 0)\" class=\"cn-widget-wrapper__footer\">\n\t\t\t<slot name=\"footer\">\n\t\t\t\t<a\n\t\t\t\t\tv-for=\"button in buttons\"\n\t\t\t\t\t:key=\"button.link\"\n\t\t\t\t\t:href=\"button.link\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__footer-link\">\n\t\t\t\t\t{{ button.text }}\n\t\t\t\t</a>\n\t\t\t</slot>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { slotRenders } from '../../utils/slotContent.js'\nimport { CnActionsMenu } from '../CnActionsMenu/index.js'\n\n/**\n * CnWidgetWrapper — Widget container with header, content, and footer.\n *\n * ```vue\n * <CnWidgetWrapper title=\"My Cases\" :icon-url=\"casesIconUrl\">\n * <MyCasesChart :data=\"chartData\" />\n * </CnWidgetWrapper>\n * ```\n *\n * With NC widget object\n * ```vue\n * <CnWidgetWrapper\n * :title=\"widget.title\"\n * :icon-url=\"widget.iconUrl\"\n * :icon-class=\"widget.iconClass\"\n * :documentation-url=\"widget.documentationUrl\"\n * :buttons=\"widget.buttons\">\n * <CnWidgetRenderer :widget=\"widget\" />\n * </CnWidgetWrapper>\n * ```\n */\n/**\n * Semantic variant → CSS colour token for the widget header icon. Tokens\n * only — the nldesign app themes by overriding the Nextcloud variables, so a\n * literal hex here would ignore government theming entirely (ADR-062 / NL\n * Design System).\n *\n * @type {Record<string, string>}\n */\nconst TITLE_ICON_VARIANT_COLORS = {\n\tprimary: 'var(--color-primary-element)',\n\tsuccess: 'var(--color-success)',\n\twarning: 'var(--color-warning)',\n\terror: 'var(--color-error)',\n\tinfo: 'var(--color-info, var(--color-primary-element))',\n\tneutral: 'var(--color-text-maxcontrast)',\n}\n\nexport default {\n\tname: 'CnWidgetWrapper',\n\n\tcomponents: {\n\t\tCnActionsMenu,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Host translate function provided by CnAppRoot as\n\t\t * `cnTranslate: this.translate` (bound to the host app's id). The\n\t\t * manifest-authored widget title is run through it so the card\n\t\t * heading follows the user's language. Defaults to an identity\n\t\t * function so an untranslated key renders as itself.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** Widget title */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Widget'),\n\t\t},\n\n\t\t/** Whether to show the header with title */\n\t\tshowTitle: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Chrome variant for the wrapper card.\n\t\t * - `'default'` — the library's own card chrome (opaque background,\n\t\t * 1px border, compact header).\n\t\t * - `'nc-dashboard'` — matches the native Nextcloud Dashboard panel\n\t\t * exactly using the same design tokens: translucent blurred\n\t\t * background (`--color-main-background-blur` + `--filter-background-blur`),\n\t\t * `--border-radius-container-large` corners, no border/shadow, a 16px\n\t\t * header with a 20px/700 title and 32px leading icon, and content\n\t\t * inset 16px on the sides + bottom. `styleConfig` overrides still\n\t\t * layer on top so a user can customise any token.\n\t\t *\n\t\t * @type {'default'|'nc-dashboard'}\n\t\t */\n\t\tchrome: {\n\t\t\ttype: String,\n\t\t\tdefault: 'default',\n\t\t\tvalidator: (v) => ['default', 'nc-dashboard'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Remove border and background — makes the wrapper transparent.\n\t\t * Useful for widgets that are self-contained cards (e.g. CnStatsBlock).\n\t\t */\n\t\tborderless: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Remove content padding — allows content to go edge-to-edge.\n\t\t * Useful for list-style widgets where items should span the full width.\n\t\t */\n\t\tflush: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Draw NO card of this widget's own: no border, no background, no\n\t\t * content padding, no title row and no header divider. For a surface\n\t\t * that already supplies the card and the heading — a tab panel being\n\t\t * the case that matters, where a nested card repeats both.\n\t\t *\n\t\t * The wrapper ELEMENT still renders. That is the contract, not an\n\t\t * oversight: `CnObjectDataWidget` measures its overflow against\n\t\t * `.cn-widget-wrapper__content` (`closest()` plus a ResizeObserver on\n\t\t * that node), `src/css/table.css` gives a table inside it its\n\t\t * edge-to-edge treatment, and `src/css/detail-page.css` and\n\t\t * `dashboard.css` size their content areas through it. Dropping the\n\t\t * node would take all of that out silently — `closest()` returning\n\t\t * null reads as \"nothing overflows\", never as an error.\n\t\t *\n\t\t * Controls in the `actions` slot are NOT chrome and keep their header:\n\t\t * the Save button that commits an inline edit lives there, and a panel\n\t\t * that hides it is silently unsaveable.\n\t\t *\n\t\t * `chromeless` is what an integration provider already asks for through\n\t\t * `CnDetailWidgetHost` (`bareWidget`), so this is the same word for the\n\t\t * same thing on every path into the wrapper.\n\t\t */\n\t\tchromeless: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Icon URL (image) */\n\t\ticonUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Icon CSS class (e.g., Nextcloud icon class) */\n\t\ticonClass: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Position of the title-icon slot in the header.\n\t\t * 'left' places it before the title; 'right' places it after the actions.\n\t\t */\n\t\ttitleIconPosition: {\n\t\t\ttype: String,\n\t\t\tdefault: 'right',\n\t\t\tvalidator: (v) => ['left', 'right'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Explicit CSS colour for the header icon. Overrides `titleIconVariant`.\n\t\t * Must be a CSS custom property or a theme token — never a literal hex\n\t\t * (NL Design System: the nldesign app re-themes by overriding the\n\t\t * Nextcloud variables, and a hardcoded colour ignores that).\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\ttitleIconColor: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Semantic colour for the header icon. Every widget's icon is\n\t\t * coloured — `primary` (the theme colour) is the default, and a\n\t\t * widget whose subject already carries a semantic meaning names it\n\t\t * here so the icon says so at a glance (a Depublished list is\n\t\t * `error`, a Concepts list `warning`, a Published list `success`).\n\t\t *\n\t\t * @type {'primary'|'success'|'warning'|'error'|'info'|'neutral'}\n\t\t */\n\t\ttitleIconVariant: {\n\t\t\ttype: String,\n\t\t\tdefault: 'primary',\n\t\t\tvalidator: (v) => ['primary', 'success', 'warning', 'error', 'info', 'neutral'].includes(v),\n\t\t},\n\n\t\t/** Footer action buttons: [{ text, link }] */\n\t\tbuttons: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * Whether the header's overflow action menu (Refresh / Documentation /\n\t\t * Request-a-feature + any `#action-items`) renders. Shown by default;\n\t\t * set `false` for compact surfaces — e.g. a KPI tile whose only header\n\t\t * affordance is a date chip — to drop the menu and free header width.\n\t\t */\n\t\tshowActions: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Style configuration for the wrapper.\n\t\t *\n\t\t * @type {{ backgroundColor: string, borderStyle: string, borderWidth: number, borderColor: string, borderRadius: number, padding: { top: number, right: number, bottom: number, left: number } }}\n\t\t */\n\t\tstyleConfig: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Hide the built-in Refresh item from the overflow action menu.\n\t\t * The Refresh item is shown by default — set this when the widget\n\t\t * has no refreshable data source (e.g. a static tile). Alias for\n\t\t * the inverse `:show-refresh=\"false\"`; either form opts out.\n\t\t */\n\t\thideRefresh: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Hide the built-in Request-a-feature item from the overflow\n\t\t * action menu. Shown by default; set when the consuming app has\n\t\t * no public issue tracker to link out to. Alias for the inverse\n\t\t * `:show-request-feature=\"false\"`; either form opts out.\n\t\t */\n\t\thideRequestFeature: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Whether to show the built-in Refresh item. Tri-state:\n\t\t * - `true` / `false` — force the action on or off.\n\t\t * - `null` (the default) — **auto**: show the action only when a\n\t\t * parent has attached an `@refresh` listener (i.e. something will\n\t\t * actually handle the refresh). This keeps widgets that can't\n\t\t * refresh — e.g. a prop-driven `CnObjectDataWidget` — from showing\n\t\t * a dead button. Widgets that refresh themselves via the\n\t\t * `cn:widget:refresh` event bus (with no `@refresh` listener)\n\t\t * should set `:show-refresh=\"true\"` explicitly.\n\t\t *\n\t\t * `hideRefresh` (or `:show-refresh=\"false\"`) always wins.\n\t\t *\n\t\t * @type {boolean|null}\n\t\t */\n\t\tshowRefresh: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Inverse of `hideRequestFeature`. Defaults to `true` so the\n\t\t * action renders. Set `:show-request-feature=\"false\"` to hide it.\n\t\t * Either flag hides the action.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tshowRequestFeature: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Whether the built-in \"Report a bug\" item renders. On by default —\n\t\t * the trio Request a feature / Report a bug / Documentation is the\n\t\t * contract for every widget; this exists for the rare surface that\n\t\t * must suppress one deliberately.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tshowReportBug: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Whether the built-in \"Documentation\" item renders. On by default,\n\t\t * for the same reason as `showReportBug`. The item's target is\n\t\t * resolved by the shared menu (see `docsAnchor`), so leaving it on\n\t\t * costs the host nothing.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tshowDocumentation: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Explicit documentation link for this widget, opened in a new tab.\n\t\t * Usually unnecessary: leave it empty and set `docsAnchor` instead,\n\t\t * so the link is built from the app-wide documentation base and\n\t\t * lands on this widget's own section.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdocumentationUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional pre-translated label for the Documentation action.\n\t\t * Defaults to the lib's translation of \"Documentation\".\n\t\t */\n\t\tdocumentationLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Documentation'),\n\t\t},\n\n\t\t/**\n\t\t * Widget id for the built-in default Refresh / Request-a-feature\n\t\t * handlers (B2). Forwarded as the `surface: \"widget:<id>\"` value\n\t\t * on the Actions menu (the English headline fallback for its forge\n\t\t * deep-links) AND as the `widgetId` field on the\n\t\t * `cn:widget:refresh` event-bus payload. When unset, the wrapper\n\t\t * falls back to a slugified `displayTitle`, which works but is\n\t\t * less stable than an explicit id.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\twidgetId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * This widget's own section in the app's documentation, appended to\n\t\t * the app-wide documentation base URL (provided by CnAppRoot) to build\n\t\t * the Actions menu's Documentation deep-link. A bare slug becomes a\n\t\t * `#fragment`. Supply it per widget type — without it the item still\n\t\t * renders, but lands on the docs homepage rather than this widget.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdocsAnchor: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Explicit \"Report a bug\" target for the Actions menu. Empty (the\n\t\t * default) builds a new-issue deep-link on the app's own forge.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\treportBugUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Declares that this widget wants a high contrast treatment. The flag\n\t\t * reaches the theme as `data-cn-high-contrast` and a `--high-contrast`\n\t\t * class; the component picks no colour of its own (ADR-003). A theme\n\t\t * that says nothing about the flag renders the widget unchanged.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\thighContrast: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Optional `specRef` slug, forwarded to the Actions menu. Accepted\n\t\t * for backward compatibility with the removed in-product suggestion\n\t\t * modal; the forge issue form asks for its own context.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tspecRef: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Whether a refresh is currently in flight. When bound by the host\n\t\t * (e.g. `:refreshing=\"loading\"` around its refetch), the Refresh\n\t\t * item is disabled and shows a loading spinner for exactly as long\n\t\t * as this stays true — so the spinner reflects the real refresh time.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\trefreshing: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Optional pre-translated label for the Refresh action. Defaults\n\t\t * to the lib's translation of \"Refresh\" so callers usually don't\n\t\t * need to set this.\n\t\t */\n\t\trefreshLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Refresh'),\n\t\t},\n\n\t\t/**\n\t\t * Optional pre-translated label for the Request-a-feature action.\n\t\t * Defaults to the lib's translation of \"Request a feature\".\n\t\t */\n\t\trequestFeatureLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Request a feature'),\n\t\t},\n\n\t\t/**\n\t\t * Pre-translated aria-label / tooltip for the overflow menu\n\t\t * trigger. Defaults to \"Actions\".\n\t\t */\n\t\tactionsMenuLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Actions'),\n\t\t},\n\n\t\t/**\n\t\t * Translate function. Falls back to the injected `cnTranslate`,\n\t\t * which itself defaults to an identity function.\n\t\t *\n\t\t * @type {((key: string) => string)|null}\n\t\t */\n\t\ttranslate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['refresh', 'request-feature'],\n\n\tcomputed: {\n\t\tdisplayTitle() {\n\t\t\treturn this.title || 'Widget'\n\t\t},\n\n\t\t/**\n\t\t * The header's icon colour, published as ONE custom property on the\n\t\t * header rather than an inline `color` repeated on each icon element.\n\t\t * Everything under the header — the icon-class span, the `title-icon`\n\t\t * slot's content, an SVG inside it — inherits from that single\n\t\t * declaration, so the icon and any decoration around it cannot end up\n\t\t * different colours.\n\t\t *\n\t\t * `titleIconColor` wins when the host set one; otherwise the\n\t\t * `titleIconVariant` token does, which is why EVERY widget icon is\n\t\t * coloured and not just the ones an app remembered to configure. Every\n\t\t * value is a CSS variable, so the nldesign app's overrides re-theme\n\t\t * them for free (NL Design System).\n\t\t *\n\t\t * @return {object} A style object declaring `--cn-widget-icon-color`.\n\t\t */\n\t\ttitleIconStyle() {\n\t\t\treturn {\n\t\t\t\t'--cn-widget-icon-color': this.titleIconColor\n\t\t\t\t\t|| TITLE_ICON_VARIANT_COLORS[this.titleIconVariant]\n\t\t\t\t\t|| TITLE_ICON_VARIANT_COLORS.primary,\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Effective translate function: the explicit `translate` prop when\n\t\t * given, else the injected `cnTranslate` (identity by default).\n\t\t *\n\t\t * @return {(key: string) => string}\n\t\t */\n\t\teffectiveTranslate() {\n\t\t\treturn this.translate ?? this.cnTranslate\n\t\t},\n\n\t\t/**\n\t\t * The RENDERED widget title — `displayTitle` run through the host\n\t\t * translate function. Kept separate from `displayTitle` on purpose:\n\t\t * `resolvedWidgetId` slugifies the raw title, so identifiers (DOM\n\t\t * ids, refresh channels, action payloads) stay locale-independent\n\t\t * while only the visible/AT-facing text localises.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedTitle() {\n\t\t\treturn this.title ? this.effectiveTranslate(this.title) : this.displayTitle\n\t\t},\n\n\t\t/**\n\t\t * Stable DOM id for the header title `<h3>`, used as the\n\t\t * `aria-labelledby` target of the scrollable content region so the\n\t\t * region gets an accessible name from the widget title (WCAG 4.1.2).\n\t\t * Derived from `resolvedWidgetId` so it is stable and unique per\n\t\t * widget instance on the page.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\ttitleId() {\n\t\t\treturn `cn-widget-wrapper-title-${this.resolvedWidgetId}`\n\t\t},\n\n\t\t/**\n\t\t * Effective Refresh visibility. `hideRefresh: true` (or\n\t\t * `:show-refresh=\"false\"`) always hides it. When `showRefresh` is\n\t\t * left unset (`null`), auto-detect: show the action only when a\n\t\t * parent attached an `@refresh` listener — otherwise the refresh\n\t\t * would do nothing. `hideRefresh` remains a back-compat alias.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\teffectiveShowRefresh() {\n\t\t\tif (this.hideRefresh) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (this.showRefresh !== null) {\n\t\t\t\treturn this.showRefresh\n\t\t\t}\n\t\t\t// `$.vnode.props`, not `$attrs`: `refresh` is a declared emit, and\n\t\t\t// Vue keeps declared emits out of `$attrs`.\n\t\t\treturn Boolean(this.$.vnode.props?.onRefresh)\n\t\t},\n\n\t\t/**\n\t\t * Effective Request-a-feature visibility — same OR-of-opt-outs\n\t\t * pattern as `effectiveShowRefresh`.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\teffectiveShowRequestFeature() {\n\t\t\treturn this.showRequestFeature && !this.hideRequestFeature\n\t\t},\n\n\t\t/**\n\t\t * Stable id used in event-bus payloads and the\n\t\t * `surface: \"widget:<id>\"` field on the auto-mounted feature\n\t\t * modal. Prefers the explicit `widgetId` prop; falls back to a\n\t\t * slugified `displayTitle` so widgets without an explicit id\n\t\t * still get a usable identifier.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedWidgetId() {\n\t\t\tif (this.widgetId) {\n\t\t\t\treturn this.widgetId\n\t\t\t}\n\t\t\treturn this.displayTitle.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')\n\t\t},\n\n\t\t/**\n\t\t * Whether the caller supplied an `action-items` slot. Forwarded\n\t\t * conditionally so the shared CnActionsMenu doesn't treat an\n\t\t * always-present pass-through template as content (which would\n\t\t * keep the overflow menu visible even when every built-in is\n\t\t * opted out).\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\thasActionItemsSlot() {\n\t\t\treturn Boolean(this.$slots['action-items']) || Boolean(this.$slots && this.$slots['action-items'])\n\t\t},\n\n\t\t/**\n\t\t * Whether the caller put controls in the `actions` slot.\n\t\t *\n\t\t * Gates the header together with `showTitle`, so a surface that wants no\n\t\t * title still keeps controls that have nowhere else to go — the Save\n\t\t * button `CnObjectDataWidget` renders there being the case that matters.\n\t\t *\n\t\t * Deliberately NOT `showActions`: that prop defaults to true and governs\n\t\t * the overflow menu, so reading it here would give a header to every\n\t\t * headerless KPI tile that never had one.\n\t\t *\n\t\t * @return {boolean} true when the slot is filled.\n\t\t */\n\t\thasActionsSlot() {\n\t\t\treturn Boolean(this.$slots.actions)\n\t\t},\n\n\t\t/**\n\t\t * Whether the card outline is drawn. `chromeless` implies `borderless`\n\t\t * so a caller names the intent once instead of remembering the three\n\t\t * props it decomposes into.\n\t\t *\n\t\t * @return {boolean} true when border and background are suppressed.\n\t\t */\n\t\tnoBorder() {\n\t\t\treturn this.borderless || this.chromeless\n\t\t},\n\n\t\t/**\n\t\t * Whether the content area goes edge to edge. `chromeless` implies\n\t\t * `flush` for the same reason as `noBorder`.\n\t\t *\n\t\t * @return {boolean} true when content padding is suppressed.\n\t\t */\n\t\tnoPadding() {\n\t\t\treturn this.flush || this.chromeless\n\t\t},\n\n\t\t/**\n\t\t * Whether the title row renders. `chromeless` overrides `showTitle`,\n\t\t * which defaults to true: a surface that already names the widget must\n\t\t * not have to also remember to turn the title off.\n\t\t *\n\t\t * Read everywhere the template used to read `showTitle` directly,\n\t\t * including the two ARIA bindings on the content region — a hidden\n\t\t * title cannot be the region's `aria-labelledby` target.\n\t\t *\n\t\t * @return {boolean} true when the header's title group renders.\n\t\t */\n\t\ttitleVisible() {\n\t\t\treturn this.showTitle && !this.chromeless\n\t\t},\n\n\t\twrapperStyles() {\n\t\t\tconst styles = {}\n\n\t\t\tif (this.styleConfig.backgroundColor) {\n\t\t\t\tstyles.backgroundColor = this.styleConfig.backgroundColor\n\t\t\t}\n\n\t\t\tif (this.styleConfig.borderStyle && this.styleConfig.borderStyle !== 'none') {\n\t\t\t\tstyles.border = `${this.styleConfig.borderWidth || 1}px ${this.styleConfig.borderStyle} ${this.styleConfig.borderColor || 'var(--color-border)'}`\n\t\t\t}\n\n\t\t\tif (this.styleConfig.borderRadius !== undefined) {\n\t\t\t\tstyles.borderRadius = `${this.styleConfig.borderRadius}px`\n\t\t\t}\n\n\t\t\tif (this.styleConfig.padding) {\n\t\t\t\tconst p = this.styleConfig.padding\n\t\t\t\tstyles.padding = `${p.top || 0}px ${p.right || 0}px ${p.bottom || 0}px ${p.left || 0}px`\n\t\t\t}\n\n\t\t\treturn styles\n\t\t},\n\n\t\t/**\n\t\t * Inline styles for the header bar, derived from\n\t\t * `styleConfig.headerStyle.{backgroundColor, textColor}`. Lets a host\n\t\t * give individual widgets a custom header colour without a per-app CSS\n\t\t * workaround. Empty object when no header style is configured.\n\t\t *\n\t\t * @return {object} the header style bindings.\n\t\t */\n\t\theaderStyles() {\n\t\t\tconst hs = this.styleConfig.headerStyle\n\t\t\tif (!hs || typeof hs !== 'object') {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\tconst styles = {}\n\t\t\tif (hs.backgroundColor) {\n\t\t\t\tstyles.backgroundColor = hs.backgroundColor\n\t\t\t}\n\t\t\tif (hs.textColor) {\n\t\t\t\tstyles.color = hs.textColor\n\t\t\t}\n\t\t\treturn styles\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether the `actions` slot renders anything a reader can see.\n\t\t *\n\t\t * `CnObjectDataWidget` always PROVIDES the template and fills it only\n\t\t * while an edit is unsaved, so the slot has to be called and its vnodes\n\t\t * inspected. Reading `$slots.actions` as a boolean counts the template\n\t\t * itself as content, which is what gave a titleless tab panel a 59px\n\t\t * band holding nothing but a second Actions menu.\n\t\t *\n\t\t * A method rather than a computed on purpose. The answer changes when the\n\t\t * PARENT's state changes, and a computed reading `$slots` does not\n\t\t * re-track that; a method re-runs on every render, and a slot whose\n\t\t * content depends on parent state already forces this component to\n\t\t * re-render.\n\t\t *\n\t\t * @return {boolean} True when the slot yields visible content.\n\t\t */\n\t\tactionsSlotRenders() {\n\t\t\treturn slotRenders(this.$slots.actions)\n\t\t},\n\n\t\t/**\n\t\t * Whether the header band is worth the vertical space it costs.\n\t\t *\n\t\t * A VISIBLE title always earns it, which `chromeless` rules out. Otherwise\n\t\t * it is earned by controls that are actually on screen: content in the\n\t\t * `actions` slot, or the overflow menu when a caller both asked for it and\n\t\t * provided the slot it sits beside.\n\t\t *\n\t\t * That last clause looks redundant and is not. `showActions` defaults to\n\t\t * TRUE, so reading it alone hands a header to every headerless KPI tile\n\t\t * in the fleet, which is what the floating title-meta test caught when\n\t\t * this was first written the short way.\n\t\t *\n\t\t * @return {boolean} True when the header should render.\n\t\t */\n\t\theaderIsWorthIt() {\n\t\t\tif (this.titleVisible) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tif (this.actionsSlotRenders()) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn this.showActions && this.hasActionsSlot\n\t\t},\n\n\t\t/**\n\t\t * Whether the actions group holds anything.\n\t\t *\n\t\t * The overflow menu and the slot beside it are separate conditions now.\n\t\t * `showActions` used to gate both, so a caller who only wanted the menu\n\t\t * gone also lost the button that commits an inline edit.\n\t\t *\n\t\t * @return {boolean} True when the group would hold a visible control.\n\t\t */\n\t\tactionsAreVisible() {\n\t\t\treturn this.showActions || this.actionsSlotRenders()\n\t\t},\n\n\t\t/**\n\t\t * Re-emit the shared CnActionsMenu `@refresh` to the host, passing\n\t\t * the synthetic event through unchanged so a host listener can\n\t\t * still `preventDefault()` the built-in default (event-bus emit on\n\t\t * `cn:widget:refresh`).\n\t\t *\n\t\t * @param {{ widgetId: string, title: string }} payload Action payload.\n\t\t * @param {{ defaultPrevented: boolean, preventDefault: () => void }} ev Synthetic event.\n\t\t * @return {void}\n\t\t */\n\t\tonActionsRefresh(payload, ev) {\n\t\t\t/**\n\t\t\t * @event refresh User clicked the Refresh item in the overflow\n\t\t\t * action menu. Payload: `{ widgetId, title }`. Handlers may call\n\t\t\t * the second arg's `preventDefault()` to suppress the built-in\n\t\t\t * default (event-bus emit on `cn:widget:refresh`).\n\t\t\t * @type {{ widgetId: string, title: string }}\n\t\t\t */\n\t\t\tthis.$emit('refresh', payload, ev)\n\t\t},\n\n\t\t/**\n\t\t * Re-emit the shared CnActionsMenu `@request-feature` to the host,\n\t\t * passing the synthetic event through so a host listener can\n\t\t * `preventDefault()` the built-in default (opening the forge's\n\t\t * feature-request issue form).\n\t\t *\n\t\t * @param {{ widgetId: string, title: string }} payload Action payload.\n\t\t * @param {{ defaultPrevented: boolean, preventDefault: () => void }} ev Synthetic event.\n\t\t * @return {void}\n\t\t */\n\t\tonActionsRequestFeature(payload, ev) {\n\t\t\t/**\n\t\t\t * @event request-feature User clicked the Request a feature\n\t\t\t * item. Payload: `{ widgetId, title }`. Handlers may call the\n\t\t\t * second arg's `preventDefault()` to suppress the built-in\n\t\t\t * default (opening the forge's feature-request issue form).\n\t\t\t * @type {{ widgetId: string, title: string }}\n\t\t\t */\n\t\t\tthis.$emit('request-feature', payload, ev)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-widget-wrapper {\n\tposition: relative;\n\theight: 100%;\n\tdisplay: flex;\n\tflex-direction: column;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\t/* NC design system: every content card is rounded — same token as\n\t CnDetailCard so detail pages read as ONE card family (ADR-062). */\n\tborder-radius: var(--border-radius-large, 8px);\n\toverflow: hidden;\n}\n\n/* Headerless date chip — floated over the top-right of a flush card. */\n.cn-widget-wrapper__floating-meta {\n\tposition: absolute;\n\ttop: 8px;\n\tinset-inline-end: 10px;\n\tz-index: 2;\n}\n\n.cn-widget-wrapper__content {\n\tflex: 1;\n\toverflow: auto;\n\tmin-height: 0;\n\tpadding: 16px;\n}\n\n/* A TABLE GOES EDGE TO EDGE. ALWAYS.\n A table drawn inside 16px of padding renders a border inside a border — two\n rectangles a few pixels apart, which reads as a mistake because it is one.\n The wrapper already has a `flush` prop that zeroes this padding, but a prop\n is something an app has to remember on every table it ever adds, and the\n apps did not: the padding was visible in pipelinq's table widgets.\n So it is structural instead. `:has()` keys on the table component's own root\n class, which means a table is flush by construction and no caller can\n reintroduce the gap by forgetting a prop. */\n.cn-widget-wrapper__content:has(> .cn-table-container),\n.cn-widget-wrapper__content:has(> .cn-widget-object-table) {\n\tpadding: 0;\n}\n\n/* Keyboard focus ring for the now-focusable scrollable content region. */\n.cn-widget-wrapper__content:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: -2px;\n}\n\n.cn-widget-wrapper--borderless {\n\tborder: none;\n\tbackground: transparent;\n}\n\n.cn-widget-wrapper--borderless .cn-widget-wrapper__content {\n\tpadding: 0;\n}\n\n.cn-widget-wrapper--flush .cn-widget-wrapper__content {\n\tpadding: 0;\n}\n\n/* A form or a search list keeps its padding, the reverse of the table rule\n above. A dashboard renders every widget `flush`, and without this their\n fields touch the card's edges. Borderless and chromeless cards, and the\n Nextcloud dashboard variant, keep their own padding. */\n.cn-widget-wrapper--flush:not(.cn-widget-wrapper--borderless, .cn-widget-wrapper--nc-dashboard) > .cn-widget-wrapper__content:has(> .cn-form-widget),\n.cn-widget-wrapper--flush:not(.cn-widget-wrapper--borderless, .cn-widget-wrapper--nc-dashboard) > .cn-widget-wrapper__content:has(> .cn-kb-search-widget) {\n\tpadding: 16px;\n}\n\n/* `chromeless` — border and background are already gone through borderless,\n and the content padding through flush. What is left on the root is the\n corner radius, which still clips: with `overflow: hidden` above, a child\n that paints its own background (a table header) gets rounded corners inside\n a square panel, which is the last visible piece of a card that is supposed\n to be absent. `CnDetailCard.cn-detail-card--chromeless` zeroes the same\n thing for the same reason. */\n.cn-widget-wrapper--chromeless {\n\tborder-radius: 0;\n}\n\n/*\n * `chrome=\"nc-dashboard\"` — reproduce the native Nextcloud Dashboard panel\n * (apps/dashboard) exactly, using the same design tokens so an un-customised\n * widget is pixel-identical to a core dashboard panel. `styleConfig` inline\n * overrides (wrapperStyles / headerStyles) still win over these class rules,\n * so users can override any token. Combined selector raises specificity above\n * the base `.cn-widget-wrapper` rules it supersedes.\n */\n.cn-widget-wrapper__header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\t/* The header is `space-between`, so without an explicit gap a right-hand\n\t title-icon sits flush against whatever precedes it. The left-hand icon's\n\t spacing comes from header-left's own gap. */\n\tgap: 8px;\n\tpadding: 12px 16px;\n\tborder-bottom: 1px solid var(--color-border);\n\tflex-shrink: 0;\n}\n\n/* Actions with no title beside them. `header-left` is not rendered at all in\n this mode, and the header is `space-between`, so a single child would sit\n flush LEFT where every other surface puts its actions on the right. */\n.cn-widget-wrapper__header--actions-only {\n\tjustify-content: flex-end;\n}\n\n/* `chromeless` — the surface around this widget already drew the card, so\n nothing here may draw a second one. Border, background and content padding\n are already gone (chromeless implies borderless + flush); what is left is\n the header, which still renders when there are controls to hold. Its bottom\n rule is the card's own divider and reads as a second card edge inside the\n panel, and its 16px side inset leaves those controls hanging off the grid\n that now starts at the panel's edge. Both go.\n\n Placed AFTER the rules it overrides, which is what stylelint's\n no-descending-specificity asks for. The cascade did not need it (this\n selector is more specific either way), a reader does: an override written\n above the thing it overrides has to be read twice. */\n.cn-widget-wrapper--chromeless .cn-widget-wrapper__header {\n\tborder-bottom: none;\n\tpadding-inline: 0;\n}\n\n.cn-widget-wrapper__header-left {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmin-width: 0;\n\t/* Grow so the title hugs the (optional) title-icon on the left instead\n\t of centering between icon and actions (header is space-between). */\n\tflex: 1 1 auto;\n}\n\n.cn-widget-wrapper__icon {\n\twidth: 24px;\n\theight: 24px;\n\tflex-shrink: 0;\n\tcolor: var(--cn-widget-icon-color, var(--color-primary-element));\n}\n\n.cn-widget-wrapper__title {\n\tfont-weight: 600;\n\tfont-size: 14px;\n\tmargin: 0;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-widget-wrapper.cn-widget-wrapper--nc-dashboard {\n\tbackground: var(--color-main-background-blur, var(--color-main-background));\n\t-webkit-backdrop-filter: var(--filter-background-blur, none);\n\tbackdrop-filter: var(--filter-background-blur, none);\n\tborder: none;\n\tborder-radius: var(--border-radius-container-large, 16px);\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__header {\n\tpadding: 16px;\n\tborder-bottom: none;\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__header-left {\n\tgap: 16px;\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__icon {\n\twidth: 32px;\n\theight: 32px;\n\tbackground-size: 32px;\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__title {\n\tfont-size: 20px;\n\tfont-weight: 700;\n\tline-height: 24px;\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__content {\n\tpadding: 0 16px 16px;\n}\n\n.cn-widget-wrapper__title-meta {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 4px;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n\twhite-space: nowrap;\n\tflex-shrink: 0;\n}\n\n.cn-widget-wrapper__actions {\n\tdisplay: flex;\n\tgap: 4px;\n\tflex-shrink: 0;\n}\n\n/* Every widget's header icon is coloured — `--cn-widget-icon-color` is set on\n the header from titleIconStyle, and defaults to the theme colour if a\n wrapper is ever rendered without it. Material-design-icon components paint\n with `fill: currentColor`, so this reaches them; the :deep rule below makes\n it reach a raw <svg> in the slot too. */\n.cn-widget-wrapper__title-icon {\n\tdisplay: flex;\n\talign-items: center;\n\tflex-shrink: 0;\n\t/* COLOURED BY DEFAULT. `--cn-widget-icon-color` is set on the header from\n\t titleIconStyle; the theme colour is the fallback for a wrapper rendered\n\t without it. Before this, the prop defaulted to null and the icon inherited\n\t body text colour, which is why every widget header looked the same. */\n\tcolor: var(--cn-widget-icon-color, var(--color-primary-element));\n\n\t/* SPACE BETWEEN THE ICON AND THE TITLE.\n\t This element is a sibling of `__header-left` inside a space-between\n\t header, so it never received that element's `gap: 8px` — the icon sat\n\t flush against the title text. Matching the header-left gap rather than\n\t inventing a value keeps the two icon positions spaced identically. */\n\tmargin-inline-end: 8px;\n}\n\n/* Right-positioned title icons sit AFTER the actions, so the margin belongs on\n the other side or it pushes the icon off the header's right edge. */\n.cn-widget-wrapper__title-icon:last-child {\n\tmargin-inline-end: 0;\n\tmargin-inline-start: 8px;\n}\n\n/* Material-design-icon components paint with `fill: currentColor`, so the\n colour above reaches them; this makes it reach a raw <svg> in the slot too. */\n.cn-widget-wrapper__title-icon :deep(svg) {\n\tfill: currentcolor;\n}\n\n.cn-widget-wrapper__footer {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\tgap: 8px;\n\tpadding: 8px 16px;\n\tborder-top: 1px solid var(--color-border);\n\tflex-shrink: 0;\n}\n\n.cn-widget-wrapper__footer-link {\n\tfont-size: 13px;\n\tcolor: var(--color-primary-element);\n\ttext-decoration: none;\n}\n\n.cn-widget-wrapper__footer-link:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["_normalizeClass","_normalizeStyle","_openBlock","_createElementBlock","_renderSlot","_createElementVNode","_createBlock"],"mappings":"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"}
1
+ {"version":3,"file":"CnWidgetWrapper.vue3.js","sources":["../../../../src/components/CnWidgetWrapper/CnWidgetWrapper.vue?vue&type=template&id=4dd77f59&lang.js"],"sourcesContent":["<!--\n CnWidgetWrapper — Container shell around a dashboard widget.\n\n Provides header (icon + title), scrollable content area, and optional\n footer with action buttons. Applies style configuration for borders,\n backgrounds, and padding. The built-in overflow Actions menu (Refresh /\n Documentation / Request a feature) is delegated to the shared\n CnActionsMenu so widgets and page surfaces stay in lockstep.\n-->\n<template>\n\t<div\n\t\tclass=\"cn-widget-wrapper\"\n\t\t:class=\"{\n\t\t\t'cn-widget-wrapper--borderless': noBorder,\n\t\t\t'cn-widget-wrapper--flush': noPadding,\n\t\t\t'cn-widget-wrapper--chromeless': chromeless,\n\t\t\t'cn-widget-wrapper--nc-dashboard': chrome === 'nc-dashboard',\n\t\t\t'cn-widget-wrapper--high-contrast': highContrast,\n\t\t}\"\n\t\t:data-cn-high-contrast=\"highContrast ? 'true' : undefined\"\n\t\t:style=\"wrapperStyles\">\n\t\t<!-- Header.\n\t\t Rendered when a title earns it, or when controls that are actually\n\t\t on screen need a home. Three conditions rather than one, and each of\n\t\t the three is a bug this component has already shipped: gating on the\n\t\t title alone took the Save button for an inline edit away along with\n\t\t the doubled title; reading `showActions`, which defaults to true,\n\t\t handed a header to every headerless KPI tile; and reading the\n\t\t `actions` slot's mere PRESENCE gave a chromeless panel a 59px band\n\t\t holding nothing, because `CnObjectDataWidget` provides that template\n\t\t always and fills it only while an edit is unsaved.\n\t\t `headerIsWorthIt()` carries the reasoning. -->\n\t\t<div v-if=\"headerIsWorthIt()\"\n\t\t\tclass=\"cn-widget-wrapper__header\"\n\t\t\t:class=\"{ 'cn-widget-wrapper__header--actions-only': !titleVisible }\"\n\t\t\t:style=\"[headerStyles, titleIconStyle]\">\n\t\t\t<!-- Title icon — left: rendered before the title group. Moved\n\t\t\t INSIDE header-left so the header's `space-between` cannot pull\n\t\t\t it away from the title it belongs to; header-left's `gap` is\n\t\t\t what now separates icon from title (they used to touch). -->\n\t\t\t<div v-if=\"titleVisible\" class=\"cn-widget-wrapper__header-left\">\n\t\t\t\t<div v-if=\"$slots['title-icon'] && titleIconPosition === 'left'\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__title-icon\">\n\t\t\t\t\t<slot name=\"title-icon\" />\n\t\t\t\t</div>\n\t\t\t\t<img\n\t\t\t\t\tv-if=\"iconUrl\"\n\t\t\t\t\t:src=\"iconUrl\"\n\t\t\t\t\t:alt=\"resolvedTitle\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__icon\">\n\t\t\t\t<span\n\t\t\t\t\tv-else-if=\"iconClass\"\n\t\t\t\t\t:class=\"iconClass\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__icon\" />\n\t\t\t\t<h3 :id=\"titleId\" class=\"cn-widget-wrapper__title\">\n\t\t\t\t\t{{ resolvedTitle }}\n\t\t\t\t</h3>\n\t\t\t\t<!-- @slot title-meta Rendered inside the header's left group,\n\t\t\t\t after the title and before the spacer. Use for small\n\t\t\t\t contextual chips (e.g. a date-range badge on chart\n\t\t\t\t widgets). Kept inside header-left so the chip sits next\n\t\t\t\t to the title rather than floating right next to the\n\t\t\t\t actions menu. -->\n\t\t\t\t<div v-if=\"$slots['title-meta']\" class=\"cn-widget-wrapper__title-meta\">\n\t\t\t\t\t<slot name=\"title-meta\" />\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<!-- `showActions` gates the overflow MENU, not the slot beside it.\n\t\t\t It used to gate both, so a caller who switched the menu off\n\t\t\t also lost the Save button for an inline edit, silently. -->\n\t\t\t<div v-if=\"actionsAreVisible()\" class=\"cn-widget-wrapper__actions\">\n\t\t\t\t<!-- One text link in the header (`headerLink`): \"All deadlines\",\n\t\t\t\t \"To the board\". A route is a router link, an href a plain\n\t\t\t\t anchor. Sits before the slot and the overflow menu. -->\n\t\t\t\t<component\n\t\t\t\t\t:is=\"headerLinkTag\"\n\t\t\t\t\tv-if=\"headerLinkTag\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__header-link\"\n\t\t\t\t\tdata-testid=\"cn-widget-wrapper-header-link\"\n\t\t\t\t\tv-bind=\"headerLinkAttrs\">\n\t\t\t\t\t{{ headerLinkLabel }}\n\t\t\t\t</component>\n\t\t\t\t<!-- @slot actions Custom action buttons rendered before the\n\t\t\t\t built-in overflow menu. -->\n\t\t\t\t<slot name=\"actions\" />\n\t\t\t\t<CnActionsMenu\n\t\t\t\t\tv-if=\"showActions\"\n\t\t\t\t\t:showRefresh=\"effectiveShowRefresh\"\n\t\t\t\t\t:showRequestFeature=\"effectiveShowRequestFeature\"\n\t\t\t\t\t:showReportBug=\"showReportBug\"\n\t\t\t\t\t:showDocumentation=\"showDocumentation\"\n\t\t\t\t\t:documentationUrl=\"documentationUrl\"\n\t\t\t\t\t:docsAnchor=\"docsAnchor\"\n\t\t\t\t\t:reportBugUrl=\"reportBugUrl\"\n\t\t\t\t\t:documentationLabel=\"documentationLabel\"\n\t\t\t\t\t:refreshLabel=\"refreshLabel\"\n\t\t\t\t\t:requestFeatureLabel=\"requestFeatureLabel\"\n\t\t\t\t\t:actionsMenuLabel=\"actionsMenuLabel\"\n\t\t\t\t\t:refreshing=\"refreshing\"\n\t\t\t\t\t:widgetId=\"resolvedWidgetId\"\n\t\t\t\t\t:title=\"displayTitle\"\n\t\t\t\t\t:surface=\"`widget:${resolvedWidgetId}`\"\n\t\t\t\t\t:specRef=\"specRef\"\n\t\t\t\t\trefreshChannel=\"cn:widget:refresh\"\n\t\t\t\t\ttestidBase=\"cn-widget-wrapper\"\n\t\t\t\t\t@refresh=\"onActionsRefresh\"\n\t\t\t\t\t@requestFeature=\"onActionsRequestFeature\">\n\t\t\t\t\t<!-- @slot action-items Additional NcActionButton-family\n\t\t\t\t\t items rendered inside the overflow menu, after the\n\t\t\t\t\t built-in Refresh / Documentation / Request-a-feature\n\t\t\t\t\t group. -->\n\t\t\t\t\t<template v-if=\"hasActionItemsSlot\" #action-items>\n\t\t\t\t\t\t<slot name=\"action-items\" />\n\t\t\t\t\t</template>\n\t\t\t\t</CnActionsMenu>\n\t\t\t</div>\n\t\t\t<!-- Title icon — right: rendered after actions, far right -->\n\t\t\t<div v-if=\"$slots['title-icon'] && titleIconPosition === 'right'\"\n\t\t\t\tclass=\"cn-widget-wrapper__title-icon\">\n\t\t\t\t<slot name=\"title-icon\" />\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- Headerless meta (e.g. a KPI date chip on a flush card). When the\n\t\t header is hidden but a `title-meta` chip is provided, float it in the\n\t\t top-right corner over the content instead of dropping it — so a\n\t\t compact flush KPI tile can carry a date-range chip without growing a\n\t\t full header bar. -->\n\t\t<div\n\t\t\tv-if=\"!titleVisible && $slots['title-meta']\"\n\t\t\tclass=\"cn-widget-wrapper__floating-meta\">\n\t\t\t<slot name=\"title-meta\" />\n\t\t</div>\n\n\t\t<!-- Content -->\n\t\t<!--\n\t\t The content area is `overflow: auto`, so it can become a scrollable\n\t\t region. WCAG 2.1.1 (Keyboard) requires scrollable regions to be\n\t\t keyboard-focusable so keyboard-only users can scroll them, and 4.1.2\n\t\t requires an accessible name. We expose it as a labelled region and\n\t\t give it tabindex=\"0\". When the header (and its title) is hidden we\n\t\t fall back to an explicit aria-label from the widget title so the\n\t\t region is never anonymous.\n\t\t-->\n\t\t<div\n\t\t\tclass=\"cn-widget-wrapper__content\"\n\t\t\ttabindex=\"0\"\n\t\t\trole=\"region\"\n\t\t\t:aria-labelledby=\"titleVisible ? titleId : null\"\n\t\t\t:aria-label=\"titleVisible ? null : resolvedTitle\">\n\t\t\t<slot />\n\t\t</div>\n\n\t\t<!-- Footer -->\n\t\t<div v-if=\"$slots.footer || (buttons && buttons.length > 0)\" class=\"cn-widget-wrapper__footer\">\n\t\t\t<slot name=\"footer\">\n\t\t\t\t<a\n\t\t\t\t\tv-for=\"button in buttons\"\n\t\t\t\t\t:key=\"button.link\"\n\t\t\t\t\t:href=\"button.link\"\n\t\t\t\t\tclass=\"cn-widget-wrapper__footer-link\">\n\t\t\t\t\t{{ button.text }}\n\t\t\t\t</a>\n\t\t\t</slot>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { slotRenders } from '../../utils/slotContent.js'\nimport { CnActionsMenu } from '../CnActionsMenu/index.js'\n\n/**\n * CnWidgetWrapper — Widget container with header, content, and footer.\n *\n * ```vue\n * <CnWidgetWrapper title=\"My Cases\" :icon-url=\"casesIconUrl\">\n * <MyCasesChart :data=\"chartData\" />\n * </CnWidgetWrapper>\n * ```\n *\n * With NC widget object\n * ```vue\n * <CnWidgetWrapper\n * :title=\"widget.title\"\n * :icon-url=\"widget.iconUrl\"\n * :icon-class=\"widget.iconClass\"\n * :documentation-url=\"widget.documentationUrl\"\n * :buttons=\"widget.buttons\">\n * <CnWidgetRenderer :widget=\"widget\" />\n * </CnWidgetWrapper>\n * ```\n */\n/**\n * Semantic variant → CSS colour token for the widget header icon. Tokens\n * only — the nldesign app themes by overriding the Nextcloud variables, so a\n * literal hex here would ignore government theming entirely (ADR-062 / NL\n * Design System).\n *\n * @type {Record<string, string>}\n */\nconst TITLE_ICON_VARIANT_COLORS = {\n\tprimary: 'var(--color-primary-element)',\n\tsuccess: 'var(--color-success)',\n\twarning: 'var(--color-warning)',\n\terror: 'var(--color-error)',\n\tinfo: 'var(--color-info, var(--color-primary-element))',\n\tneutral: 'var(--color-text-maxcontrast)',\n}\n\nexport default {\n\tname: 'CnWidgetWrapper',\n\n\tcomponents: {\n\t\tCnActionsMenu,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Host translate function provided by CnAppRoot as\n\t\t * `cnTranslate: this.translate` (bound to the host app's id). The\n\t\t * manifest-authored widget title is run through it so the card\n\t\t * heading follows the user's language. Defaults to an identity\n\t\t * function so an untranslated key renders as itself.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** Widget title */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Widget'),\n\t\t},\n\n\t\t/** Whether to show the header with title */\n\t\tshowTitle: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Chrome variant for the wrapper card.\n\t\t * - `'default'` — the library's own card chrome (opaque background,\n\t\t * 1px border, compact header).\n\t\t * - `'nc-dashboard'` — matches the native Nextcloud Dashboard panel\n\t\t * exactly using the same design tokens: translucent blurred\n\t\t * background (`--color-main-background-blur` + `--filter-background-blur`),\n\t\t * `--border-radius-container-large` corners, no border/shadow, a 16px\n\t\t * header with a 20px/700 title and 32px leading icon, and content\n\t\t * inset 16px on the sides + bottom. `styleConfig` overrides still\n\t\t * layer on top so a user can customise any token.\n\t\t *\n\t\t * @type {'default'|'nc-dashboard'}\n\t\t */\n\t\tchrome: {\n\t\t\ttype: String,\n\t\t\tdefault: 'default',\n\t\t\tvalidator: (v) => ['default', 'nc-dashboard'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Remove border and background — makes the wrapper transparent.\n\t\t * Useful for widgets that are self-contained cards (e.g. CnStatsBlock).\n\t\t */\n\t\tborderless: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Remove content padding — allows content to go edge-to-edge.\n\t\t * Useful for list-style widgets where items should span the full width.\n\t\t */\n\t\tflush: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Draw NO card of this widget's own: no border, no background, no\n\t\t * content padding, no title row and no header divider. For a surface\n\t\t * that already supplies the card and the heading — a tab panel being\n\t\t * the case that matters, where a nested card repeats both.\n\t\t *\n\t\t * The wrapper ELEMENT still renders. That is the contract, not an\n\t\t * oversight: `CnObjectDataWidget` measures its overflow against\n\t\t * `.cn-widget-wrapper__content` (`closest()` plus a ResizeObserver on\n\t\t * that node), `src/css/table.css` gives a table inside it its\n\t\t * edge-to-edge treatment, and `src/css/detail-page.css` and\n\t\t * `dashboard.css` size their content areas through it. Dropping the\n\t\t * node would take all of that out silently — `closest()` returning\n\t\t * null reads as \"nothing overflows\", never as an error.\n\t\t *\n\t\t * Controls in the `actions` slot are NOT chrome and keep their header:\n\t\t * the Save button that commits an inline edit lives there, and a panel\n\t\t * that hides it is silently unsaveable.\n\t\t *\n\t\t * `chromeless` is what an integration provider already asks for through\n\t\t * `CnDetailWidgetHost` (`bareWidget`), so this is the same word for the\n\t\t * same thing on every path into the wrapper.\n\t\t */\n\t\tchromeless: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Icon URL (image) */\n\t\ticonUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Icon CSS class (e.g., Nextcloud icon class) */\n\t\ticonClass: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Position of the title-icon slot in the header.\n\t\t * 'left' places it before the title; 'right' places it after the actions.\n\t\t */\n\t\ttitleIconPosition: {\n\t\t\ttype: String,\n\t\t\tdefault: 'right',\n\t\t\tvalidator: (v) => ['left', 'right'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Explicit CSS colour for the header icon. Overrides `titleIconVariant`.\n\t\t * Must be a CSS custom property or a theme token — never a literal hex\n\t\t * (NL Design System: the nldesign app re-themes by overriding the\n\t\t * Nextcloud variables, and a hardcoded colour ignores that).\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\ttitleIconColor: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Semantic colour for the header icon. Every widget's icon is\n\t\t * coloured — `primary` (the theme colour) is the default, and a\n\t\t * widget whose subject already carries a semantic meaning names it\n\t\t * here so the icon says so at a glance (a Depublished list is\n\t\t * `error`, a Concepts list `warning`, a Published list `success`).\n\t\t *\n\t\t * @type {'primary'|'success'|'warning'|'error'|'info'|'neutral'}\n\t\t */\n\t\ttitleIconVariant: {\n\t\t\ttype: String,\n\t\t\tdefault: 'primary',\n\t\t\tvalidator: (v) => ['primary', 'success', 'warning', 'error', 'info', 'neutral'].includes(v),\n\t\t},\n\n\t\t/** Footer action buttons: [{ text, link }] */\n\t\tbuttons: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * One text link in the header's actions group, before the overflow\n\t\t * menu: `{ label, route?, params?, query?, href? }`. A `route` is a\n\t\t * named router link (needs a router), an `href` a plain anchor. The\n\t\t * label goes through the host translate function. Null (the\n\t\t * default) draws nothing. Manifest: `headerLink` on the widget\n\t\t * definition or its layout entry.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-widget-header-carries-a-text-link\n\t\t * @type {{label: string, route?: string, params?: object, query?: object, href?: string}|null}\n\t\t */\n\t\theaderLink: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether the header's overflow action menu (Refresh / Documentation /\n\t\t * Request-a-feature + any `#action-items`) renders. Shown by default;\n\t\t * set `false` for compact surfaces — e.g. a KPI tile whose only header\n\t\t * affordance is a date chip — to drop the menu and free header width.\n\t\t */\n\t\tshowActions: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Style configuration for the wrapper.\n\t\t *\n\t\t * @type {{ backgroundColor: string, borderStyle: string, borderWidth: number, borderColor: string, borderRadius: number, padding: { top: number, right: number, bottom: number, left: number } }}\n\t\t */\n\t\tstyleConfig: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Hide the built-in Refresh item from the overflow action menu.\n\t\t * The Refresh item is shown by default — set this when the widget\n\t\t * has no refreshable data source (e.g. a static tile). Alias for\n\t\t * the inverse `:show-refresh=\"false\"`; either form opts out.\n\t\t */\n\t\thideRefresh: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Hide the built-in Request-a-feature item from the overflow\n\t\t * action menu. Shown by default; set when the consuming app has\n\t\t * no public issue tracker to link out to. Alias for the inverse\n\t\t * `:show-request-feature=\"false\"`; either form opts out.\n\t\t */\n\t\thideRequestFeature: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Whether to show the built-in Refresh item. Tri-state:\n\t\t * - `true` / `false` — force the action on or off.\n\t\t * - `null` (the default) — **auto**: show the action only when a\n\t\t * parent has attached an `@refresh` listener (i.e. something will\n\t\t * actually handle the refresh). This keeps widgets that can't\n\t\t * refresh — e.g. a prop-driven `CnObjectDataWidget` — from showing\n\t\t * a dead button. Widgets that refresh themselves via the\n\t\t * `cn:widget:refresh` event bus (with no `@refresh` listener)\n\t\t * should set `:show-refresh=\"true\"` explicitly.\n\t\t *\n\t\t * `hideRefresh` (or `:show-refresh=\"false\"`) always wins.\n\t\t *\n\t\t * @type {boolean|null}\n\t\t */\n\t\tshowRefresh: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Inverse of `hideRequestFeature`. Defaults to `true` so the\n\t\t * action renders. Set `:show-request-feature=\"false\"` to hide it.\n\t\t * Either flag hides the action.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tshowRequestFeature: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Whether the built-in \"Report a bug\" item renders. On by default —\n\t\t * the trio Request a feature / Report a bug / Documentation is the\n\t\t * contract for every widget; this exists for the rare surface that\n\t\t * must suppress one deliberately.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tshowReportBug: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Whether the built-in \"Documentation\" item renders. On by default,\n\t\t * for the same reason as `showReportBug`. The item's target is\n\t\t * resolved by the shared menu (see `docsAnchor`), so leaving it on\n\t\t * costs the host nothing.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tshowDocumentation: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Explicit documentation link for this widget, opened in a new tab.\n\t\t * Usually unnecessary: leave it empty and set `docsAnchor` instead,\n\t\t * so the link is built from the app-wide documentation base and\n\t\t * lands on this widget's own section.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdocumentationUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Optional pre-translated label for the Documentation action.\n\t\t * Defaults to the lib's translation of \"Documentation\".\n\t\t */\n\t\tdocumentationLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Documentation'),\n\t\t},\n\n\t\t/**\n\t\t * Widget id for the built-in default Refresh / Request-a-feature\n\t\t * handlers (B2). Forwarded as the `surface: \"widget:<id>\"` value\n\t\t * on the Actions menu (the English headline fallback for its forge\n\t\t * deep-links) AND as the `widgetId` field on the\n\t\t * `cn:widget:refresh` event-bus payload. When unset, the wrapper\n\t\t * falls back to a slugified `displayTitle`, which works but is\n\t\t * less stable than an explicit id.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\twidgetId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * This widget's own section in the app's documentation, appended to\n\t\t * the app-wide documentation base URL (provided by CnAppRoot) to build\n\t\t * the Actions menu's Documentation deep-link. A bare slug becomes a\n\t\t * `#fragment`. Supply it per widget type — without it the item still\n\t\t * renders, but lands on the docs homepage rather than this widget.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdocsAnchor: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Explicit \"Report a bug\" target for the Actions menu. Empty (the\n\t\t * default) builds a new-issue deep-link on the app's own forge.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\treportBugUrl: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Declares that this widget wants a high contrast treatment. The flag\n\t\t * reaches the theme as `data-cn-high-contrast` and a `--high-contrast`\n\t\t * class; the component picks no colour of its own (ADR-003). A theme\n\t\t * that says nothing about the flag renders the widget unchanged.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\thighContrast: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Optional `specRef` slug, forwarded to the Actions menu. Accepted\n\t\t * for backward compatibility with the removed in-product suggestion\n\t\t * modal; the forge issue form asks for its own context.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tspecRef: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Whether a refresh is currently in flight. When bound by the host\n\t\t * (e.g. `:refreshing=\"loading\"` around its refetch), the Refresh\n\t\t * item is disabled and shows a loading spinner for exactly as long\n\t\t * as this stays true — so the spinner reflects the real refresh time.\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\trefreshing: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Optional pre-translated label for the Refresh action. Defaults\n\t\t * to the lib's translation of \"Refresh\" so callers usually don't\n\t\t * need to set this.\n\t\t */\n\t\trefreshLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Refresh'),\n\t\t},\n\n\t\t/**\n\t\t * Optional pre-translated label for the Request-a-feature action.\n\t\t * Defaults to the lib's translation of \"Request a feature\".\n\t\t */\n\t\trequestFeatureLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Request a feature'),\n\t\t},\n\n\t\t/**\n\t\t * Pre-translated aria-label / tooltip for the overflow menu\n\t\t * trigger. Defaults to \"Actions\".\n\t\t */\n\t\tactionsMenuLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Actions'),\n\t\t},\n\n\t\t/**\n\t\t * Translate function. Falls back to the injected `cnTranslate`,\n\t\t * which itself defaults to an identity function.\n\t\t *\n\t\t * @type {((key: string) => string)|null}\n\t\t */\n\t\ttranslate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['refresh', 'request-feature'],\n\n\tcomputed: {\n\t\tdisplayTitle() {\n\t\t\treturn this.title || 'Widget'\n\t\t},\n\n\t\t/**\n\t\t * The header's icon colour, published as ONE custom property on the\n\t\t * header rather than an inline `color` repeated on each icon element.\n\t\t * Everything under the header — the icon-class span, the `title-icon`\n\t\t * slot's content, an SVG inside it — inherits from that single\n\t\t * declaration, so the icon and any decoration around it cannot end up\n\t\t * different colours.\n\t\t *\n\t\t * `titleIconColor` wins when the host set one; otherwise the\n\t\t * `titleIconVariant` token does, which is why EVERY widget icon is\n\t\t * coloured and not just the ones an app remembered to configure. Every\n\t\t * value is a CSS variable, so the nldesign app's overrides re-theme\n\t\t * them for free (NL Design System).\n\t\t *\n\t\t * @return {object} A style object declaring `--cn-widget-icon-color`.\n\t\t */\n\t\ttitleIconStyle() {\n\t\t\treturn {\n\t\t\t\t'--cn-widget-icon-color': this.titleIconColor\n\t\t\t\t\t|| TITLE_ICON_VARIANT_COLORS[this.titleIconVariant]\n\t\t\t\t\t|| TITLE_ICON_VARIANT_COLORS.primary,\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Effective translate function: the explicit `translate` prop when\n\t\t * given, else the injected `cnTranslate` (identity by default).\n\t\t *\n\t\t * @return {(key: string) => string}\n\t\t */\n\t\teffectiveTranslate() {\n\t\t\treturn this.translate ?? this.cnTranslate\n\t\t},\n\n\t\t/**\n\t\t * The header link's tag: `router-link` for a route (when a router is\n\t\t * mounted), `a` for an href, '' when there is no usable link.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-widget-header-carries-a-text-link\n\t\t * @return {string}\n\t\t */\n\t\theaderLinkTag() {\n\t\t\tconst link = this.headerLink\n\t\t\tif (!link || typeof link.label !== 'string' || link.label === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (link.route && this.$router) {\n\t\t\t\treturn 'router-link'\n\t\t\t}\n\t\t\tif (typeof link.href === 'string' && link.href !== '') {\n\t\t\t\treturn 'a'\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/**\n\t\t * The header link's attributes for its tag.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-widget-header-carries-a-text-link\n\t\t * @return {object}\n\t\t */\n\t\theaderLinkAttrs() {\n\t\t\tconst link = this.headerLink || {}\n\t\t\tif (this.headerLinkTag === 'router-link') {\n\t\t\t\tconst to = { name: link.route }\n\t\t\t\tif (link.params && typeof link.params === 'object') {\n\t\t\t\t\tto.params = link.params\n\t\t\t\t}\n\t\t\t\tif (link.query && typeof link.query === 'object') {\n\t\t\t\t\tto.query = link.query\n\t\t\t\t}\n\t\t\t\treturn { to }\n\t\t\t}\n\t\t\tif (this.headerLinkTag === 'a') {\n\t\t\t\treturn { href: link.href }\n\t\t\t}\n\t\t\treturn {}\n\t\t},\n\n\t\t/**\n\t\t * The header link's text, through the host translate function.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-widget-header-carries-a-text-link\n\t\t * @return {string}\n\t\t */\n\t\theaderLinkLabel() {\n\t\t\tconst label = this.headerLink && this.headerLink.label\n\t\t\treturn label ? this.effectiveTranslate(label) : ''\n\t\t},\n\n\t\t/**\n\t\t * The RENDERED widget title — `displayTitle` run through the host\n\t\t * translate function. Kept separate from `displayTitle` on purpose:\n\t\t * `resolvedWidgetId` slugifies the raw title, so identifiers (DOM\n\t\t * ids, refresh channels, action payloads) stay locale-independent\n\t\t * while only the visible/AT-facing text localises.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedTitle() {\n\t\t\treturn this.title ? this.effectiveTranslate(this.title) : this.displayTitle\n\t\t},\n\n\t\t/**\n\t\t * Stable DOM id for the header title `<h3>`, used as the\n\t\t * `aria-labelledby` target of the scrollable content region so the\n\t\t * region gets an accessible name from the widget title (WCAG 4.1.2).\n\t\t * Derived from `resolvedWidgetId` so it is stable and unique per\n\t\t * widget instance on the page.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\ttitleId() {\n\t\t\treturn `cn-widget-wrapper-title-${this.resolvedWidgetId}`\n\t\t},\n\n\t\t/**\n\t\t * Effective Refresh visibility. `hideRefresh: true` (or\n\t\t * `:show-refresh=\"false\"`) always hides it. When `showRefresh` is\n\t\t * left unset (`null`), auto-detect: show the action only when a\n\t\t * parent attached an `@refresh` listener — otherwise the refresh\n\t\t * would do nothing. `hideRefresh` remains a back-compat alias.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\teffectiveShowRefresh() {\n\t\t\tif (this.hideRefresh) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif (this.showRefresh !== null) {\n\t\t\t\treturn this.showRefresh\n\t\t\t}\n\t\t\t// `$.vnode.props`, not `$attrs`: `refresh` is a declared emit, and\n\t\t\t// Vue keeps declared emits out of `$attrs`.\n\t\t\treturn Boolean(this.$.vnode.props?.onRefresh)\n\t\t},\n\n\t\t/**\n\t\t * Effective Request-a-feature visibility — same OR-of-opt-outs\n\t\t * pattern as `effectiveShowRefresh`.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\teffectiveShowRequestFeature() {\n\t\t\treturn this.showRequestFeature && !this.hideRequestFeature\n\t\t},\n\n\t\t/**\n\t\t * Stable id used in event-bus payloads and the\n\t\t * `surface: \"widget:<id>\"` field on the auto-mounted feature\n\t\t * modal. Prefers the explicit `widgetId` prop; falls back to a\n\t\t * slugified `displayTitle` so widgets without an explicit id\n\t\t * still get a usable identifier.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedWidgetId() {\n\t\t\tif (this.widgetId) {\n\t\t\t\treturn this.widgetId\n\t\t\t}\n\t\t\treturn this.displayTitle.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')\n\t\t},\n\n\t\t/**\n\t\t * Whether the caller supplied an `action-items` slot. Forwarded\n\t\t * conditionally so the shared CnActionsMenu doesn't treat an\n\t\t * always-present pass-through template as content (which would\n\t\t * keep the overflow menu visible even when every built-in is\n\t\t * opted out).\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\thasActionItemsSlot() {\n\t\t\treturn Boolean(this.$slots['action-items']) || Boolean(this.$slots && this.$slots['action-items'])\n\t\t},\n\n\t\t/**\n\t\t * Whether the caller put controls in the `actions` slot.\n\t\t *\n\t\t * Gates the header together with `showTitle`, so a surface that wants no\n\t\t * title still keeps controls that have nowhere else to go — the Save\n\t\t * button `CnObjectDataWidget` renders there being the case that matters.\n\t\t *\n\t\t * Deliberately NOT `showActions`: that prop defaults to true and governs\n\t\t * the overflow menu, so reading it here would give a header to every\n\t\t * headerless KPI tile that never had one.\n\t\t *\n\t\t * @return {boolean} true when the slot is filled.\n\t\t */\n\t\thasActionsSlot() {\n\t\t\treturn Boolean(this.$slots.actions)\n\t\t},\n\n\t\t/**\n\t\t * Whether the card outline is drawn. `chromeless` implies `borderless`\n\t\t * so a caller names the intent once instead of remembering the three\n\t\t * props it decomposes into.\n\t\t *\n\t\t * @return {boolean} true when border and background are suppressed.\n\t\t */\n\t\tnoBorder() {\n\t\t\treturn this.borderless || this.chromeless\n\t\t},\n\n\t\t/**\n\t\t * Whether the content area goes edge to edge. `chromeless` implies\n\t\t * `flush` for the same reason as `noBorder`.\n\t\t *\n\t\t * @return {boolean} true when content padding is suppressed.\n\t\t */\n\t\tnoPadding() {\n\t\t\treturn this.flush || this.chromeless\n\t\t},\n\n\t\t/**\n\t\t * Whether the title row renders. `chromeless` overrides `showTitle`,\n\t\t * which defaults to true: a surface that already names the widget must\n\t\t * not have to also remember to turn the title off.\n\t\t *\n\t\t * Read everywhere the template used to read `showTitle` directly,\n\t\t * including the two ARIA bindings on the content region — a hidden\n\t\t * title cannot be the region's `aria-labelledby` target.\n\t\t *\n\t\t * @return {boolean} true when the header's title group renders.\n\t\t */\n\t\ttitleVisible() {\n\t\t\treturn this.showTitle && !this.chromeless\n\t\t},\n\n\t\twrapperStyles() {\n\t\t\tconst styles = {}\n\n\t\t\tif (this.styleConfig.backgroundColor) {\n\t\t\t\tstyles.backgroundColor = this.styleConfig.backgroundColor\n\t\t\t}\n\n\t\t\tif (this.styleConfig.borderStyle && this.styleConfig.borderStyle !== 'none') {\n\t\t\t\tstyles.border = `${this.styleConfig.borderWidth || 1}px ${this.styleConfig.borderStyle} ${this.styleConfig.borderColor || 'var(--color-border)'}`\n\t\t\t}\n\n\t\t\tif (this.styleConfig.borderRadius !== undefined) {\n\t\t\t\tstyles.borderRadius = `${this.styleConfig.borderRadius}px`\n\t\t\t}\n\n\t\t\tif (this.styleConfig.padding) {\n\t\t\t\tconst p = this.styleConfig.padding\n\t\t\t\tstyles.padding = `${p.top || 0}px ${p.right || 0}px ${p.bottom || 0}px ${p.left || 0}px`\n\t\t\t}\n\n\t\t\treturn styles\n\t\t},\n\n\t\t/**\n\t\t * Inline styles for the header bar, derived from\n\t\t * `styleConfig.headerStyle.{backgroundColor, textColor}`. Lets a host\n\t\t * give individual widgets a custom header colour without a per-app CSS\n\t\t * workaround. Empty object when no header style is configured.\n\t\t *\n\t\t * @return {object} the header style bindings.\n\t\t */\n\t\theaderStyles() {\n\t\t\tconst hs = this.styleConfig.headerStyle\n\t\t\tif (!hs || typeof hs !== 'object') {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\tconst styles = {}\n\t\t\tif (hs.backgroundColor) {\n\t\t\t\tstyles.backgroundColor = hs.backgroundColor\n\t\t\t}\n\t\t\tif (hs.textColor) {\n\t\t\t\tstyles.color = hs.textColor\n\t\t\t}\n\t\t\treturn styles\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether the `actions` slot renders anything a reader can see.\n\t\t *\n\t\t * `CnObjectDataWidget` always PROVIDES the template and fills it only\n\t\t * while an edit is unsaved, so the slot has to be called and its vnodes\n\t\t * inspected. Reading `$slots.actions` as a boolean counts the template\n\t\t * itself as content, which is what gave a titleless tab panel a 59px\n\t\t * band holding nothing but a second Actions menu.\n\t\t *\n\t\t * A method rather than a computed on purpose. The answer changes when the\n\t\t * PARENT's state changes, and a computed reading `$slots` does not\n\t\t * re-track that; a method re-runs on every render, and a slot whose\n\t\t * content depends on parent state already forces this component to\n\t\t * re-render.\n\t\t *\n\t\t * @return {boolean} True when the slot yields visible content.\n\t\t */\n\t\tactionsSlotRenders() {\n\t\t\treturn slotRenders(this.$slots.actions)\n\t\t},\n\n\t\t/**\n\t\t * Whether the header band is worth the vertical space it costs.\n\t\t *\n\t\t * A VISIBLE title always earns it, which `chromeless` rules out. Otherwise\n\t\t * it is earned by controls that are actually on screen: content in the\n\t\t * `actions` slot, or the overflow menu when a caller both asked for it and\n\t\t * provided the slot it sits beside.\n\t\t *\n\t\t * That last clause looks redundant and is not. `showActions` defaults to\n\t\t * TRUE, so reading it alone hands a header to every headerless KPI tile\n\t\t * in the fleet, which is what the floating title-meta test caught when\n\t\t * this was first written the short way.\n\t\t *\n\t\t * @return {boolean} True when the header should render.\n\t\t */\n\t\theaderIsWorthIt() {\n\t\t\tif (this.titleVisible) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tif (this.actionsSlotRenders()) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn this.showActions && this.hasActionsSlot\n\t\t},\n\n\t\t/**\n\t\t * Whether the actions group holds anything.\n\t\t *\n\t\t * The overflow menu and the slot beside it are separate conditions now.\n\t\t * `showActions` used to gate both, so a caller who only wanted the menu\n\t\t * gone also lost the button that commits an inline edit.\n\t\t *\n\t\t * @return {boolean} True when the group would hold a visible control.\n\t\t */\n\t\tactionsAreVisible() {\n\t\t\treturn this.showActions || this.actionsSlotRenders() || Boolean(this.headerLinkTag)\n\t\t},\n\n\t\t/**\n\t\t * Re-emit the shared CnActionsMenu `@refresh` to the host, passing\n\t\t * the synthetic event through unchanged so a host listener can\n\t\t * still `preventDefault()` the built-in default (event-bus emit on\n\t\t * `cn:widget:refresh`).\n\t\t *\n\t\t * @param {{ widgetId: string, title: string }} payload Action payload.\n\t\t * @param {{ defaultPrevented: boolean, preventDefault: () => void }} ev Synthetic event.\n\t\t * @return {void}\n\t\t */\n\t\tonActionsRefresh(payload, ev) {\n\t\t\t/**\n\t\t\t * @event refresh User clicked the Refresh item in the overflow\n\t\t\t * action menu. Payload: `{ widgetId, title }`. Handlers may call\n\t\t\t * the second arg's `preventDefault()` to suppress the built-in\n\t\t\t * default (event-bus emit on `cn:widget:refresh`).\n\t\t\t * @type {{ widgetId: string, title: string }}\n\t\t\t */\n\t\t\tthis.$emit('refresh', payload, ev)\n\t\t},\n\n\t\t/**\n\t\t * Re-emit the shared CnActionsMenu `@request-feature` to the host,\n\t\t * passing the synthetic event through so a host listener can\n\t\t * `preventDefault()` the built-in default (opening the forge's\n\t\t * feature-request issue form).\n\t\t *\n\t\t * @param {{ widgetId: string, title: string }} payload Action payload.\n\t\t * @param {{ defaultPrevented: boolean, preventDefault: () => void }} ev Synthetic event.\n\t\t * @return {void}\n\t\t */\n\t\tonActionsRequestFeature(payload, ev) {\n\t\t\t/**\n\t\t\t * @event request-feature User clicked the Request a feature\n\t\t\t * item. Payload: `{ widgetId, title }`. Handlers may call the\n\t\t\t * second arg's `preventDefault()` to suppress the built-in\n\t\t\t * default (opening the forge's feature-request issue form).\n\t\t\t * @type {{ widgetId: string, title: string }}\n\t\t\t */\n\t\t\tthis.$emit('request-feature', payload, ev)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-widget-wrapper {\n\tposition: relative;\n\theight: 100%;\n\tdisplay: flex;\n\tflex-direction: column;\n\tbackground: var(--color-main-background);\n\tborder: 1px solid var(--color-border);\n\t/* NC design system: every content card is rounded — same token as\n\t CnDetailCard so detail pages read as ONE card family (ADR-062). */\n\tborder-radius: var(--border-radius-large, 8px);\n\toverflow: hidden;\n}\n\n/* Headerless date chip — floated over the top-right of a flush card. */\n.cn-widget-wrapper__floating-meta {\n\tposition: absolute;\n\ttop: 8px;\n\tinset-inline-end: 10px;\n\tz-index: 2;\n}\n\n.cn-widget-wrapper__content {\n\tflex: 1;\n\toverflow: auto;\n\tmin-height: 0;\n\tpadding: 16px;\n}\n\n/* A TABLE GOES EDGE TO EDGE. ALWAYS.\n A table drawn inside 16px of padding renders a border inside a border — two\n rectangles a few pixels apart, which reads as a mistake because it is one.\n The wrapper already has a `flush` prop that zeroes this padding, but a prop\n is something an app has to remember on every table it ever adds, and the\n apps did not: the padding was visible in pipelinq's table widgets.\n So it is structural instead. `:has()` keys on the table component's own root\n class, which means a table is flush by construction and no caller can\n reintroduce the gap by forgetting a prop. */\n.cn-widget-wrapper__content:has(> .cn-table-container),\n.cn-widget-wrapper__content:has(> .cn-widget-object-table) {\n\tpadding: 0;\n}\n\n/* Keyboard focus ring for the now-focusable scrollable content region. */\n.cn-widget-wrapper__content:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: -2px;\n}\n\n.cn-widget-wrapper--borderless {\n\tborder: none;\n\tbackground: transparent;\n}\n\n.cn-widget-wrapper--borderless .cn-widget-wrapper__content {\n\tpadding: 0;\n}\n\n.cn-widget-wrapper--flush .cn-widget-wrapper__content {\n\tpadding: 0;\n}\n\n/* A form or a search list keeps its padding, the reverse of the table rule\n above. A dashboard renders every widget `flush`, and without this their\n fields touch the card's edges. Borderless and chromeless cards, and the\n Nextcloud dashboard variant, keep their own padding. */\n.cn-widget-wrapper--flush:not(.cn-widget-wrapper--borderless, .cn-widget-wrapper--nc-dashboard) > .cn-widget-wrapper__content:has(> .cn-form-widget),\n.cn-widget-wrapper--flush:not(.cn-widget-wrapper--borderless, .cn-widget-wrapper--nc-dashboard) > .cn-widget-wrapper__content:has(> .cn-kb-search-widget) {\n\tpadding: 16px;\n}\n\n/* `chromeless` — border and background are already gone through borderless,\n and the content padding through flush. What is left on the root is the\n corner radius, which still clips: with `overflow: hidden` above, a child\n that paints its own background (a table header) gets rounded corners inside\n a square panel, which is the last visible piece of a card that is supposed\n to be absent. `CnDetailCard.cn-detail-card--chromeless` zeroes the same\n thing for the same reason. */\n.cn-widget-wrapper--chromeless {\n\tborder-radius: 0;\n}\n\n/*\n * `chrome=\"nc-dashboard\"` — reproduce the native Nextcloud Dashboard panel\n * (apps/dashboard) exactly, using the same design tokens so an un-customised\n * widget is pixel-identical to a core dashboard panel. `styleConfig` inline\n * overrides (wrapperStyles / headerStyles) still win over these class rules,\n * so users can override any token. Combined selector raises specificity above\n * the base `.cn-widget-wrapper` rules it supersedes.\n */\n.cn-widget-wrapper__header {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\t/* The header is `space-between`, so without an explicit gap a right-hand\n\t title-icon sits flush against whatever precedes it. The left-hand icon's\n\t spacing comes from header-left's own gap. */\n\tgap: 8px;\n\tpadding: 12px 16px;\n\tborder-bottom: 1px solid var(--color-border);\n\tflex-shrink: 0;\n}\n\n/* Actions with no title beside them. `header-left` is not rendered at all in\n this mode, and the header is `space-between`, so a single child would sit\n flush LEFT where every other surface puts its actions on the right. */\n.cn-widget-wrapper__header--actions-only {\n\tjustify-content: flex-end;\n}\n\n/* `chromeless` — the surface around this widget already drew the card, so\n nothing here may draw a second one. Border, background and content padding\n are already gone (chromeless implies borderless + flush); what is left is\n the header, which still renders when there are controls to hold. Its bottom\n rule is the card's own divider and reads as a second card edge inside the\n panel, and its 16px side inset leaves those controls hanging off the grid\n that now starts at the panel's edge. Both go.\n\n Placed AFTER the rules it overrides, which is what stylelint's\n no-descending-specificity asks for. The cascade did not need it (this\n selector is more specific either way), a reader does: an override written\n above the thing it overrides has to be read twice. */\n.cn-widget-wrapper--chromeless .cn-widget-wrapper__header {\n\tborder-bottom: none;\n\tpadding-inline: 0;\n}\n\n.cn-widget-wrapper__header-left {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmin-width: 0;\n\t/* Grow so the title hugs the (optional) title-icon on the left instead\n\t of centering between icon and actions (header is space-between). */\n\tflex: 1 1 auto;\n}\n\n.cn-widget-wrapper__icon {\n\twidth: 24px;\n\theight: 24px;\n\tflex-shrink: 0;\n\tcolor: var(--cn-widget-icon-color, var(--color-primary-element));\n}\n\n.cn-widget-wrapper__title {\n\tfont-weight: 600;\n\tfont-size: 14px;\n\tmargin: 0;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-widget-wrapper.cn-widget-wrapper--nc-dashboard {\n\tbackground: var(--color-main-background-blur, var(--color-main-background));\n\t-webkit-backdrop-filter: var(--filter-background-blur, none);\n\tbackdrop-filter: var(--filter-background-blur, none);\n\tborder: none;\n\tborder-radius: var(--border-radius-container-large, 16px);\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__header {\n\tpadding: 16px;\n\tborder-bottom: none;\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__header-left {\n\tgap: 16px;\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__icon {\n\twidth: 32px;\n\theight: 32px;\n\tbackground-size: 32px;\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__title {\n\tfont-size: 20px;\n\tfont-weight: 700;\n\tline-height: 24px;\n}\n\n.cn-widget-wrapper--nc-dashboard .cn-widget-wrapper__content {\n\tpadding: 0 16px 16px;\n}\n\n.cn-widget-wrapper__title-meta {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 4px;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n\twhite-space: nowrap;\n\tflex-shrink: 0;\n}\n\n.cn-widget-wrapper__actions {\n\tdisplay: flex;\n\tgap: 4px;\n\tflex-shrink: 0;\n}\n\n/* The header's text link (`headerLink`). Theme hooks: --cn-widget-header-link-size\n and --cn-widget-header-link-weight. */\n.cn-widget-wrapper__header-link {\n\talign-self: center;\n\tpadding: 0 calc(1 * var(--default-grid-baseline));\n\tcolor: var(--color-primary-element);\n\tfont-size: var(--cn-widget-header-link-size, 14px);\n\tfont-weight: var(--cn-widget-header-link-weight, 600);\n\ttext-decoration: none;\n\twhite-space: nowrap;\n}\n\n.cn-widget-wrapper__header-link:hover,\n.cn-widget-wrapper__header-link:focus-visible {\n\ttext-decoration: underline;\n}\n\n.cn-widget-wrapper__header-link:focus-visible {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 2px;\n\tborder-radius: var(--border-radius-small, 4px);\n}\n\n/* Every widget's header icon is coloured — `--cn-widget-icon-color` is set on\n the header from titleIconStyle, and defaults to the theme colour if a\n wrapper is ever rendered without it. Material-design-icon components paint\n with `fill: currentColor`, so this reaches them; the :deep rule below makes\n it reach a raw <svg> in the slot too. */\n.cn-widget-wrapper__title-icon {\n\tdisplay: flex;\n\talign-items: center;\n\tflex-shrink: 0;\n\t/* COLOURED BY DEFAULT. `--cn-widget-icon-color` is set on the header from\n\t titleIconStyle; the theme colour is the fallback for a wrapper rendered\n\t without it. Before this, the prop defaulted to null and the icon inherited\n\t body text colour, which is why every widget header looked the same. */\n\tcolor: var(--cn-widget-icon-color, var(--color-primary-element));\n\n\t/* SPACE BETWEEN THE ICON AND THE TITLE.\n\t This element is a sibling of `__header-left` inside a space-between\n\t header, so it never received that element's `gap: 8px` — the icon sat\n\t flush against the title text. Matching the header-left gap rather than\n\t inventing a value keeps the two icon positions spaced identically. */\n\tmargin-inline-end: 8px;\n}\n\n/* Right-positioned title icons sit AFTER the actions, so the margin belongs on\n the other side or it pushes the icon off the header's right edge. */\n.cn-widget-wrapper__title-icon:last-child {\n\tmargin-inline-end: 0;\n\tmargin-inline-start: 8px;\n}\n\n/* Material-design-icon components paint with `fill: currentColor`, so the\n colour above reaches them; this makes it reach a raw <svg> in the slot too. */\n.cn-widget-wrapper__title-icon :deep(svg) {\n\tfill: currentcolor;\n}\n\n.cn-widget-wrapper__footer {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\tgap: 8px;\n\tpadding: 8px 16px;\n\tborder-top: 1px solid var(--color-border);\n\tflex-shrink: 0;\n}\n\n.cn-widget-wrapper__footer-link {\n\tfont-size: 13px;\n\tcolor: var(--color-primary-element);\n\ttext-decoration: none;\n}\n\n.cn-widget-wrapper__footer-link:hover {\n\ttext-decoration: underline;\n}\n</style>\n"],"names":["_normalizeClass","_normalizeStyle","_openBlock","_createElementBlock","_renderSlot","_createElementVNode","_resolveDynamicComponent","_createBlock"],"mappings":"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"}
@@ -47,6 +47,9 @@ var translations = {
47
47
  "No integrations available yet.": "No integrations available yet.",
48
48
  "This integration is not available.": "This integration is not available.",
49
49
  Integrations: "Integrations",
50
+ View: "View",
51
+ Views: "Views",
52
+ "current step": "current step",
50
53
  "{app} not available": "{app} not available",
51
54
  "{app} not configured": "{app} not configured",
52
55
  "This integration is not available or not configured yet.": "This integration is not available or not configured yet.",
@@ -219,7 +222,6 @@ var translations = {
219
222
  "Drop files here or click to browse": "Drop files here or click to browse",
220
223
  Duration: "Duration",
221
224
  Edit: "Edit",
222
- View: "View",
223
225
  "Edit {title}": "Edit {title}",
224
226
  "Enter a custom value": "Enter a custom value",
225
227
  "Enumeration ({count})": "Enumeration ({count})",
@@ -403,6 +405,7 @@ var translations = {
403
405
  Any: "Any",
404
406
  To: "To",
405
407
  Equals: "Equals",
408
+ Contains: "Contains",
406
409
  Clear: "Clear",
407
410
  Apply: "Apply",
408
411
  "This column cannot list its objects.": "This column cannot list its objects.",
@@ -1071,6 +1074,7 @@ var translations = {
1071
1074
  "The actions of an attention card are declared in the manifest.": "The actions of an attention card are declared in the manifest.",
1072
1075
  "An attention card needs a title": "An attention card needs a title",
1073
1076
  "Good morning, {name}": "Good morning, {name}",
1077
+ "This view has no widgets yet.": "This view has no widgets yet.",
1074
1078
  "Good morning": "Good morning",
1075
1079
  "Good afternoon, {name}": "Good afternoon, {name}",
1076
1080
  "Good afternoon": "Good afternoon",
@@ -1 +1 @@
1
- {"version":3,"file":"en.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"en.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}