@conduction/nextcloud-vue 2.74.0 → 2.75.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +19 -14
  2. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +2 -2
  4. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  5. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +77 -26
  6. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  7. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +22 -14
  8. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  9. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +23 -4
  10. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  11. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  12. package/dist/esm/components/CnBrandBar/CnBrandBar.vue.js +9 -0
  13. package/dist/esm/components/CnBrandBar/CnBrandBar.vue.js.map +1 -0
  14. package/dist/esm/components/CnBrandBar/CnBrandBar.vue2.js +107 -0
  15. package/dist/esm/components/CnBrandBar/CnBrandBar.vue2.js.map +1 -0
  16. package/dist/esm/components/CnBrandBar/CnBrandBar.vue3.js +79 -0
  17. package/dist/esm/components/CnBrandBar/CnBrandBar.vue3.js.map +1 -0
  18. package/dist/esm/components/CnEnvironmentBanner/CnEnvironmentBanner.vue2.js +4 -1
  19. package/dist/esm/components/CnEnvironmentBanner/CnEnvironmentBanner.vue2.js.map +1 -1
  20. package/dist/esm/components/CnEnvironmentBanner/CnEnvironmentBanner.vue3.js.map +1 -1
  21. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +7 -0
  22. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  23. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +14 -7
  24. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  25. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js +32 -0
  26. package/dist/esm/components/CnFormPage/CnFormPage.vue2.js.map +1 -1
  27. package/dist/esm/components/CnFormPage/CnFormPage.vue3.js +6 -3
  28. package/dist/esm/components/CnFormPage/CnFormPage.vue3.js.map +1 -1
  29. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  30. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +2 -2
  31. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  32. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js +18 -2
  33. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue2.js.map +1 -1
  34. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js +2 -2
  35. package/dist/esm/components/CnQuickFilterBar/CnQuickFilterBar.vue3.js.map +1 -1
  36. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js +16 -0
  37. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js.map +1 -1
  38. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js +2 -2
  39. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js.map +1 -1
  40. package/dist/esm/directives/cnSelectAria.js +55 -0
  41. package/dist/esm/directives/cnSelectAria.js.map +1 -0
  42. package/dist/esm/nextcloud-vue.css +66 -1
  43. package/dist/esm/utils/cnFetch.js +17 -2
  44. package/dist/esm/utils/cnFetch.js.map +1 -1
  45. package/dist/esm/utils/resolveBrand.js +38 -0
  46. package/dist/esm/utils/resolveBrand.js.map +1 -0
  47. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  48. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  49. package/dist/nextcloud-vue.cjs.js +8775 -8327
  50. package/dist/nextcloud-vue.cjs.js.map +2 -2
  51. package/dist/nextcloud-vue.css +66 -1
  52. package/package.json +1 -1
  53. package/src/components/CnAppNav/CnAppNav.vue +21 -16
  54. package/src/components/CnAppRoot/CnAppRoot.vue +53 -0
  55. package/src/components/CnBannerWidget/CnBannerWidget.vue +23 -4
  56. package/src/components/CnBrandBar/CnBrandBar.vue +230 -0
  57. package/src/components/CnEnvironmentBanner/CnEnvironmentBanner.vue +4 -1
  58. package/src/components/CnFormDialog/CnFormDialog.vue +10 -0
  59. package/src/components/CnFormPage/CnFormPage.vue +33 -0
  60. package/src/components/CnIndexPage/CnIndexPage.vue +2 -2
  61. package/src/components/CnQuickFilterBar/CnQuickFilterBar.vue +20 -4
  62. package/src/components/CnStatsBlock/CnStatsBlock.vue +18 -2
  63. package/src/css/form-field.css +1 -1
  64. package/src/directives/cnSelectAria.js +54 -0
  65. package/src/schemas/app-manifest-v2.schema.json +6 -1
  66. package/src/utils/cnFetch.js +16 -1
  67. package/src/utils/resolveBrand.js +37 -0
  68. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -35,6 +35,11 @@ var script = {
35
35
  inject: {
36
36
  /** The look CnAppRoot provides; `board` draws the tabs as pressed chips. */
37
37
  cnLook: { default: 'nextcloud' },
38
+ /**
39
+ * The host's label lookup (CnAppRoot provides it); identity without one, so a
40
+ * label that is not a key renders as written.
41
+ */
42
+ cnTranslate: { default: () => (key) => key },
38
43
  },
39
44
 
40
45
  // NO `model: { prop, event }` OPTION.
@@ -219,7 +224,7 @@ var script = {
219
224
  overflowLabel() {
220
225
  const active = this.activeOverflowEntries;
221
226
  if (active.length === 1) {
222
- return active[0].tab.label
227
+ return this.tl(active[0].tab.label)
223
228
  }
224
229
  if (active.length > 1) {
225
230
  return translate('nextcloud-vue', '{count} filters', { count: active.length })
@@ -253,7 +258,7 @@ var script = {
253
258
  .map((tab, index) => {
254
259
  const count = this.countOf({ tab, index });
255
260
  return {
256
- label: count === null ? tab.label : `${tab.label} (${count})`,
261
+ label: count === null ? this.tl(tab.label) : `${this.tl(tab.label)} (${count})`,
257
262
  icon: tab.icon,
258
263
  index,
259
264
  _empty: this.isEmptyFilter(tab),
@@ -277,6 +282,17 @@ var script = {
277
282
  },
278
283
 
279
284
  methods: {
285
+ /**
286
+ * A manifest string through the host's label lookup (`cnTranslate`), so a key
287
+ * such as "Mine" reads in the user's language.
288
+ *
289
+ * @param {string} text The text as written in the manifest.
290
+ * @return {string} The translated text.
291
+ */
292
+ tl(text) {
293
+ return typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text
294
+ },
295
+
280
296
  t: translate,
281
297
 
282
298
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"CnQuickFilterBar.vue2.js","sources":["../../../../src/components/CnQuickFilterBar/CnQuickFilterBar.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: AGPL-3.0-or-later\n -->\n\n<template>\n\t<!-- Dropdown mode: a single NcSelect (multi or single) instead of the chip strip. -->\n\t<div\n\t\tv-if=\"mode === 'dropdown'\"\n\t\tclass=\"cn-quick-filter-bar cn-quick-filter-bar--dropdown\"\n\t\t:class=\"{ 'cn-quick-filter-bar--inline': inline }\">\n\t\t<NcSelect\n\t\t\t:modelValue=\"dropdownValue\"\n\t\t\t:options=\"dropdownOptions\"\n\t\t\t:multiple=\"multiple\"\n\t\t\t:keepOpen=\"multiple\"\n\t\t\t:clearable=\"true\"\n\t\t\tlabel=\"label\"\n\t\t\tclass=\"cn-quick-filter-bar__select\"\n\t\t\t:inputLabel=\"selectLabel\"\n\t\t\t:aria-label-combobox=\"selectLabel\"\n\t\t\t:placeholder=\"placeholder || selectLabel\"\n\t\t\t@update:modelValue=\"onSelectInput\" />\n\t</div>\n\t<!-- Chips mode (default): the clickable tab strip. -->\n\t<div v-else\n\t\tclass=\"cn-quick-filter-bar\"\n\t\t:class=\"{ 'cn-quick-filter-bar--inline': inline }\">\n\t\t<!-- The tablist holds ONLY tabs: the overflow chip is a sibling, not a\n\t\t child, because a role=\"tablist\" may not contain a menu button. -->\n\t\t<!-- Board look: the tabs are chips, pressed buttons in a group, not a\n\t\t tablist (the chips filter the list, they do not switch a panel). -->\n\t\t<div class=\"cn-quick-filter-bar__tabs\" :role=\"isBoardLook ? 'group' : 'tablist'\" :aria-label=\"isBoardLook ? t('nextcloud-vue', 'Quick filters') : null\">\n\t\t\t<button\n\t\t\t\tv-for=\"entry in visibleEntries\"\n\t\t\t\t:key=\"entry.tab.label + ':' + entry.index\"\n\t\t\t\ttype=\"button\"\n\t\t\t\t:role=\"isBoardLook ? null : 'tab'\"\n\t\t\t\t:aria-selected=\"isBoardLook ? null : (isChipActive(entry.index) ? 'true' : 'false')\"\n\t\t\t\t:aria-pressed=\"isBoardLook ? (isChipActive(entry.index) ? 'true' : 'false') : null\"\n\t\t\t\tclass=\"cn-quick-filter-bar__tab\"\n\t\t\t\t:class=\"[{ 'cn-quick-filter-bar__tab--active': isChipActive(entry.index) }]\"\n\t\t\t\t@click=\"onClick(entry.index)\">\n\t\t\t\t<CnIcon\n\t\t\t\t\tv-if=\"entry.tab.icon\"\n\t\t\t\t\t:name=\"entry.tab.icon\"\n\t\t\t\t\t:size=\"16\"\n\t\t\t\t\tclass=\"cn-quick-filter-bar__icon\" />\n\t\t\t\t<span class=\"cn-quick-filter-bar__label\">{{ entry.tab.label }}</span>\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"countOf(entry) !== null\"\n\t\t\t\t\tclass=\"cn-quick-filter-bar__count\"\n\t\t\t\t\tdata-testid=\"cn-quick-filter-count\">{{ ' ' + countOf(entry) }}</span>\n\t\t\t</button>\n\t\t</div>\n\t\t<!-- The overflow is a CHIP and not a toolbar button: it is one of the\n\t\t lenses, so it carries the same pill, the same active fill and the\n\t\t label of whichever hidden lens is on. -->\n\t\t<NcPopover\n\t\t\tv-if=\"overflowEntries.length > 0\"\n\t\t\tv-model:shown=\"moreOpen\"\n\t\t\t:triggers=\"[]\"\n\t\t\tpopupRole=\"dialog\"\n\t\t\tplacement=\"bottom-end\"\n\t\t\tpopoverBaseClass=\"cn-quick-filter-bar__popper\">\n\t\t\t<template #trigger>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tdata-testid=\"cn-quick-filter-more\"\n\t\t\t\t\tclass=\"cn-quick-filter-bar__tab cn-quick-filter-bar__more\"\n\t\t\t\t\t:class=\"{\n\t\t\t\t\t\t'cn-quick-filter-bar__tab--active': activeOverflowEntries.length > 0,\n\t\t\t\t\t\t'cn-quick-filter-bar__more--icon-only': !overflowLabel,\n\t\t\t\t\t}\"\n\t\t\t\t\t:aria-expanded=\"moreOpen ? 'true' : 'false'\"\n\t\t\t\t\taria-haspopup=\"dialog\"\n\t\t\t\t\t:aria-label=\"overflowAriaLabel\"\n\t\t\t\t\t:title=\"overflowAriaLabel\"\n\t\t\t\t\t@click=\"moreOpen = !moreOpen\">\n\t\t\t\t\t<span v-if=\"overflowLabel\" class=\"cn-quick-filter-bar__label\">{{ overflowLabel }}</span>\n\t\t\t\t\t<DotsHorizontal :size=\"16\" class=\"cn-quick-filter-bar__icon\" />\n\t\t\t\t</button>\n\t\t\t</template>\n\t\t\t<div\n\t\t\t\tclass=\"cn-quick-filter-bar__more-panel\"\n\t\t\t\t@keydown.escape.stop=\"moreOpen = false\">\n\t\t\t\t<p class=\"cn-quick-filter-bar__more-heading\">\n\t\t\t\t\t{{ moreHeading }}\n\t\t\t\t</p>\n\t\t\t\t<div class=\"cn-quick-filter-bar__more-list\">\n\t\t\t\t\t<button\n\t\t\t\t\t\tv-for=\"entry in overflowEntries\"\n\t\t\t\t\t\t:key=\"entry.tab.label + ':' + entry.index\"\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tdata-testid=\"cn-quick-filter-more-item\"\n\t\t\t\t\t\tclass=\"cn-quick-filter-bar__tab cn-quick-filter-bar__more-item\"\n\t\t\t\t\t\t:class=\"{ 'cn-quick-filter-bar__tab--active': isChipActive(entry.index) }\"\n\t\t\t\t\t\t:aria-pressed=\"isChipActive(entry.index) ? 'true' : 'false'\"\n\t\t\t\t\t\t@click=\"onOverflowClick(entry.index)\">\n\t\t\t\t\t\t<CnIcon\n\t\t\t\t\t\t\tv-if=\"entry.tab.icon\"\n\t\t\t\t\t\t\t:name=\"entry.tab.icon\"\n\t\t\t\t\t\t\t:size=\"16\"\n\t\t\t\t\t\t\tclass=\"cn-quick-filter-bar__icon\" />\n\t\t\t\t\t\t<span class=\"cn-quick-filter-bar__label\">{{ entry.tab.label }}</span>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tv-if=\"countOf(entry) !== null\"\n\t\t\t\t\t\t\tclass=\"cn-quick-filter-bar__count\"\n\t\t\t\t\t\t\tdata-testid=\"cn-quick-filter-count\">{{ ' ' + countOf(entry) }}</span>\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</NcPopover>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcPopover, NcSelect } from '@nextcloud/vue'\nimport DotsHorizontal from 'vue-material-design-icons/DotsHorizontal.vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport { normalizeLook } from '../../composables/useLook.js'\n\n/**\n * CnQuickFilterBar — quick-filter control rendered above a `type:\"index\"`\n * page's table. Each tab carries a manifest `filter` map that `CnIndexPage`\n * merges into the `useListView` fetch when the tab is active.\n *\n * Two presentations, chosen by `mode`:\n * - `'chips'` (default) — a horizontal row of pill-shaped buttons; the\n * active one is filled, the rest outlined. `maxVisible` caps how many\n * pills render and moves the rest into an overflow menu.\n * - `'dropdown'` — a single `NcSelect`. The \"All\" / empty-filter tab is\n * dropped from the options (an empty selection means \"all\").\n *\n * Two selection cardinalities, chosen by `multiple`:\n * - single (default) — one active tab; uses the `activeIndex` v-model\n * (`update:active-index`).\n * - `multiple` — several tabs at once; uses the `selectedIndices` array\n * prop + `update:selected-indices` event. `CnIndexPage` ORs the selected\n * tabs' filters together.\n *\n * Implementation is deliberately a thin styled `<button>` list (chips) /\n * `NcSelect` (dropdown) rather than `NcAppNavigation*` — this lives INSIDE\n * the index page, not as the app's main nav.\n */\nexport default {\n\tname: 'CnQuickFilterBar',\n\n\tcomponents: { CnIcon, DotsHorizontal, NcPopover, NcSelect },\n\n\tinject: {\n\t\t/** The look CnAppRoot provides; `board` draws the tabs as pressed chips. */\n\t\tcnLook: { default: 'nextcloud' },\n\t},\n\n\t// NO `model: { prop, event }` OPTION.\n\t//\n\t// The Vue-2 `model` option is REMOVED in Vue 3 — read by nothing, warned\n\t// about by nothing — so it survived the migration as a declaration that\n\t// looked authoritative while doing exactly zero. The component's real\n\t// contract is the explicit `:active-index` / `@update:active-index` pair\n\t// (and `:selected-indices` / `@update:selected-indices` in `multiple`\n\t// mode), which is what `CnIndexPage` binds. Surfaced by\n\t// `vue/no-deprecated-model-definition`, which this repo now arms through\n\t// its own published preset.\n\n\tprops: {\n\t\t/**\n\t\t * Tab definitions — same shape `pages[].config.quickFilters[]` uses.\n\t\t * `filter` is consumed by the parent (CnIndexPage), not this component.\n\t\t */\n\t\ttabs: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t\tvalidator: (arr) => Array.isArray(arr) && arr.every((t) => t && typeof t.label === 'string'),\n\t\t},\n\n\t\t/**\n\t\t * Render bare (no padding / bottom border / background) for embedding\n\t\t * inline inside another bar — e.g. the `#filters` slot of `CnActionsBar`,\n\t\t * so the tabs sit beside the view toggle instead of as a separate row.\n\t\t */\n\t\tinline: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Index of the currently active tab, or `null` for none active. Single-select only. */\n\t\tactiveIndex: {\n\t\t\ttype: Number,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Presentation: `'chips'` (pill button strip, default) or\n\t\t * `'dropdown'` (a single `NcSelect`).\n\t\t *\n\t\t * @type {'chips'|'dropdown'}\n\t\t */\n\t\tmode: {\n\t\t\ttype: String,\n\t\t\tdefault: 'chips',\n\t\t\tvalidator: (v) => ['chips', 'dropdown'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Allow more than one tab active at once. Selection is exposed via\n\t\t * `selectedIndices` + `update:selected-indices`; the parent ORs the\n\t\t * selected tabs' filters together.\n\t\t */\n\t\tmultiple: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Active tab indices when `multiple` is set (the array v-model).\n\t\t *\n\t\t * @type {number[]}\n\t\t */\n\t\tselectedIndices: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Accessible label / placeholder for the dropdown control. */\n\t\tselectLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: 'Filter',\n\t\t},\n\n\t\t/** Placeholder text for the dropdown (falls back to `selectLabel`). */\n\t\tplaceholder: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Chips mode only: how many pills render inline before the rest move\n\t\t * into an overflow menu. `0` (the default) renders every tab, which is\n\t\t * the behaviour this component has always had. The visible set is the\n\t\t * FIRST `maxVisible` tabs in declared order — reorder `tabs` to change\n\t\t * which ones stay out. Ignored in `dropdown` mode.\n\t\t */\n\t\tmaxVisible: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t\tvalidator: (v) => Number.isFinite(v) && v >= 0,\n\t\t},\n\n\t\t/**\n\t\t * How many records each tab matches, keyed by tab index (an array in\n\t\t * tab order works too). A tab with a number here shows it after its\n\t\t * label; a tab without one shows no count. A tab can also carry its\n\t\t * own `count`. `null` (the default) shows no counts at all.\n\t\t *\n\t\t * @type {{[index: number]: number}|Array<number|null>|null}\n\t\t */\n\t\tcounts: {\n\t\t\ttype: [Object, Array],\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['update:active-index', 'update:selected-indices'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Whether the overflow chip's panel is open. */\n\t\t\tmoreOpen: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the tabs draw as board chips (`aria-pressed`, no tablist).\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\t/**\n\t\t * Every tab paired with its own index, so the overflow split can slice\n\t\t * the list without losing the index the parent's filters are keyed on.\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\tentries() {\n\t\t\treturn this.tabs.map((tab, index) => ({ tab, index }))\n\t\t},\n\n\t\t/**\n\t\t * The tabs rendered as pills — all of them unless `maxVisible` caps it.\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\tvisibleEntries() {\n\t\t\tif (!this.maxVisible || this.tabs.length <= this.maxVisible) {\n\t\t\t\treturn this.entries\n\t\t\t}\n\t\t\treturn this.entries.slice(0, this.maxVisible)\n\t\t},\n\n\t\t/**\n\t\t * The tabs moved into the overflow menu (empty when `maxVisible` is 0\n\t\t * or there is nothing to hide).\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\toverflowEntries() {\n\t\t\tif (!this.maxVisible || this.tabs.length <= this.maxVisible) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn this.entries.slice(this.maxVisible)\n\t\t},\n\n\t\t/**\n\t\t * The active tabs that live in the overflow menu.\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\tactiveOverflowEntries() {\n\t\t\treturn this.overflowEntries.filter((e) => this.isChipActive(e.index))\n\t\t},\n\n\t\t/**\n\t\t * Text on the overflow chip — an active hidden tab names itself there,\n\t\t * so the strip never reads \"All\" while the list is narrowed. Nothing\n\t\t * active leaves the chip as the bare `⋯` glyph.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\toverflowLabel() {\n\t\t\tconst active = this.activeOverflowEntries\n\t\t\tif (active.length === 1) {\n\t\t\t\treturn active[0].tab.label\n\t\t\t}\n\t\t\tif (active.length > 1) {\n\t\t\t\treturn t('nextcloud-vue', '{count} filters', { count: active.length })\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/** @return {string} Accessible name for the overflow chip. */\n\t\toverflowAriaLabel() {\n\t\t\tif (this.overflowLabel) {\n\t\t\t\treturn t('nextcloud-vue', '{label} — show the other filters', { label: this.overflowLabel })\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', '{count} more filters', { count: this.overflowEntries.length })\n\t\t},\n\n\t\t/** @return {string} Heading above the hidden chips in the panel. */\n\t\tmoreHeading() {\n\t\t\treturn t('nextcloud-vue', 'More filters')\n\t\t},\n\n\t\t/**\n\t\t * Dropdown options: every tab that carries a non-empty `filter`,\n\t\t * tagged with its original index. The \"All\" / empty-filter tab is\n\t\t * omitted — an empty selection already means \"all\".\n\t\t *\n\t\t * @return {Array<{label: string, index: number, icon: string}>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n\t\t */\n\t\tdropdownOptions() {\n\t\t\treturn this.tabs\n\t\t\t\t.map((tab, index) => {\n\t\t\t\t\tconst count = this.countOf({ tab, index })\n\t\t\t\t\treturn {\n\t\t\t\t\t\tlabel: count === null ? tab.label : `${tab.label} (${count})`,\n\t\t\t\t\t\ticon: tab.icon,\n\t\t\t\t\t\tindex,\n\t\t\t\t\t\t_empty: this.isEmptyFilter(tab),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.filter((o) => !o._empty)\n\t\t},\n\n\t\t/**\n\t\t * Current NcSelect value — an array of option objects when\n\t\t * `multiple`, a single option object (or null) otherwise.\n\t\t *\n\t\t * @return {object|object[]|null}\n\t\t */\n\t\tdropdownValue() {\n\t\t\tif (this.multiple) {\n\t\t\t\treturn this.dropdownOptions.filter((o) => this.selectedIndices.includes(o.index))\n\t\t\t}\n\t\t\treturn this.dropdownOptions.find((o) => o.index === this.activeIndex) || null\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * The count to show on a tab, or null for none: `counts[index]`\n\t\t * first, else the tab's own `count`.\n\t\t *\n\t\t * @param {{tab: object, index: number}} entry The tab and its index.\n\t\t * @return {number|null}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n\t\t */\n\t\tcountOf(entry) {\n\t\t\tconst fromProp = this.counts ? this.counts[entry.index] : undefined\n\t\t\tconst value = (fromProp !== undefined && fromProp !== null) ? fromProp : entry.tab.count\n\t\t\treturn (typeof value === 'number' && Number.isFinite(value)) ? value : null\n\t\t},\n\n\t\t/**\n\t\t * Is tab `i` rendered active in chips mode (handles both single\n\t\t * `activeIndex` and `multiple` `selectedIndices`).\n\t\t *\n\t\t * @param {number} i Zero-based tab index.\n\t\t * @return {boolean}\n\t\t */\n\t\tisChipActive(i) {\n\t\t\treturn this.multiple ? this.selectedIndices.includes(i) : i === this.activeIndex\n\t\t},\n\n\t\t/**\n\t\t * Whether a tab's `filter` map is absent/empty (the \"All\" tab).\n\t\t *\n\t\t * @param {{label: string, filter?: object}} tab An entry from the `tabs` prop.\n\t\t * @return {boolean} True when the tab applies no filter, so selecting it\n\t\t * clears the multi-selection rather than adding to it.\n\t\t */\n\t\tisEmptyFilter(tab) {\n\t\t\treturn !tab || !tab.filter || Object.keys(tab.filter).length === 0\n\t\t},\n\n\t\t/**\n\t\t * Chip-click handler. In single mode emits the new active index; in\n\t\t * multiple mode toggles the index in/out of the selection. Clicking\n\t\t * an empty-filter (\"All\") chip clears the multi-selection.\n\t\t *\n\t\t * @param {number} i Zero-based tab index.\n\t\t */\n\t\tonClick(i) {\n\t\t\tif (this.multiple) {\n\t\t\t\tif (this.isEmptyFilter(this.tabs[i])) {\n\t\t\t\t\t/**\n\t\t\t\t\t * @event update:selected-indices Active tab indices changed (multiple mode).\n\t\t\t\t\t * @type {number[]}\n\t\t\t\t\t */\n\t\t\t\t\tthis.$emit('update:selected-indices', [])\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst next = this.selectedIndices.includes(i)\n\t\t\t\t\t? this.selectedIndices.filter((x) => x !== i)\n\t\t\t\t\t: [...this.selectedIndices, i]\n\t\t\t\tthis.$emit('update:selected-indices', next)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (i === this.activeIndex) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('update:active-index', i)\n\t\t},\n\n\t\t/**\n\t\t * A chip clicked inside the overflow panel. Single-select closes the\n\t\t * panel on the way out; multi-select keeps it open so several lenses\n\t\t * can be toggled in one visit.\n\t\t *\n\t\t * @param {number} i Zero-based tab index.\n\t\t */\n\t\tonOverflowClick(i) {\n\t\t\tthis.onClick(i)\n\t\t\tif (!this.multiple) {\n\t\t\t\tthis.moreOpen = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * NcSelect input handler — normalises the option object(s) back to\n\t\t * indices and emits the matching model event.\n\t\t *\n\t\t * @param {object|object[]|null} val Selected option object(s).\n\t\t */\n\t\tonSelectInput(val) {\n\t\t\tif (this.multiple) {\n\t\t\t\tconst arr = Array.isArray(val) ? val : []\n\t\t\t\tthis.$emit('update:selected-indices', arr.map((o) => o.index))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('update:active-index', val ? val.index : null)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-quick-filter-bar {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 12px;\n\tborder-bottom: 1px solid var(--color-border);\n\tbackground: var(--color-main-background);\n}\n\n.cn-quick-filter-bar__tabs {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n}\n\n/* Inline variant — bare strip for embedding inside another bar (e.g. the\n CnActionsBar #filters slot), so it reads as a control beside the view toggle\n rather than a separate bordered row. */\n.cn-quick-filter-bar--inline {\n\tpadding: 0;\n\tborder-bottom: none;\n\tbackground: transparent;\n}\n\n/* Dropdown variant — give the NcSelect a sensible minimum so multi-select\n chips have room without stretching the whole bar. */\n.cn-quick-filter-bar--dropdown {\n\talign-items: center;\n}\n\n.cn-quick-filter-bar__select {\n\tmin-width: 220px;\n}\n\n.cn-quick-filter-bar__tab {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-pill, 999px);\n\tbackground: transparent;\n\tcolor: var(--color-main-text);\n\tfont-size: 13px;\n\tfont-weight: 500;\n\tcursor: pointer;\n\ttransition: background-color 120ms, color 120ms, border-color 120ms;\n}\n\n.cn-quick-filter-bar__tab:hover,\n.cn-quick-filter-bar__tab:focus-visible {\n\tbackground: var(--color-background-hover);\n\toutline: none;\n}\n\n.cn-quick-filter-bar__tab--active {\n\tbackground: var(--color-primary-element, var(--color-primary, #4376fc));\n\tcolor: var(--color-primary-element-text, #fff);\n\tborder-color: var(--color-primary-element, var(--color-primary, #4376fc));\n}\n\n.cn-quick-filter-bar__icon {\n\tdisplay: inline-flex;\n}\n\n.cn-quick-filter-bar__label {\n\tline-height: 1;\n}\n\n/* Inherits the chip's own text colour, so it stays readable on the active\n (primary) fill as well as on the resting one. The count's text starts with\n a space: the chip is a flex row, so the space is not drawn, but it keeps a\n screen reader from reading label and count as one word. */\n.cn-quick-filter-bar__count {\n\tmin-width: 1.5em;\n\tpadding: 2px 6px;\n\tborder: 1px solid currentColor;\n\tborder-radius: var(--border-radius-pill, 10px);\n\tfont-size: 0.85em;\n\tfont-weight: 600;\n\tline-height: 1;\n\ttext-align: center;\n}\n\n/* NcPopover wraps its trigger in a plain `<div class=\"v-popper\">`, which as a\n block would drop the chip onto its own line under the strip. */\n.cn-quick-filter-bar > :deep(.v-popper) {\n\tdisplay: inline-flex;\n}\n\n/* The `⋯` glyph is the chip's whole content while no hidden lens is on, so the\n pill tightens to a circle rather than sitting as a wide empty capsule. */\n.cn-quick-filter-bar__more {\n\tgap: 4px;\n}\n\n.cn-quick-filter-bar__more--icon-only {\n\tpadding: 4px 8px;\n}\n\n.cn-quick-filter-bar__more[aria-expanded='true'] {\n\tborder-color: var(--color-primary-element, var(--color-primary, #4376fc));\n}\n\n.cn-quick-filter-bar__more-panel {\n\tpadding: 10px 12px 12px;\n\tmax-width: 280px;\n}\n\n.cn-quick-filter-bar__more-heading {\n\tmargin: 0 0 8px;\n\tfont-size: 12px;\n\tfont-weight: 600;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* The hidden lenses stay chips — the panel is more of the strip, not a\n different control that happens to hold the same filters. */\n.cn-quick-filter-bar__more-list {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 8px;\n\tmax-height: 320px;\n\toverflow-y: auto;\n}\n\n.cn-quick-filter-bar__more-item {\n\tmax-width: 100%;\n}\n\n.cn-quick-filter-bar__more-item .cn-quick-filter-bar__label {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["CnIcon","t"],"mappings":";;;;;;AA2HA;AACC;AACA;;AAEA;AACA;;;AAGA;;;AAGA;AACA;;;;AAIA;AACA;AACA;;AAEA;AACA;;AAED,aAAe;CACd,IAAI,EAAE,kBAAkB;;CAExB,UAAU,EAAE,UAAEA,UAAM,EAAE,cAAc,EAAE,SAAS,EAAE,QAAO,EAAG;;AAE3D,CAAA,MAAM,EAAE;AACP;EACA,MAAM,EAAE,EAAE,OAAO,EAAE,WAAU,EAAG;EAChC;;AAED;;;;;AAKA;AACA;;AAEA;;;AAGA,CAAA,KAAK,EAAE;AACN;;;;EAIA,IAAI,EAAE;GACL,IAAI,EAAE,KAAK;GACX,QAAQ,EAAE,IAAI;AACd,GAAA,SAAS,EAAE,CAAC,GAAG,KAAK,KAAK,CAAC,OAAO,CAAC,GAAG,KAAK,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC,UAAU,QAAQ,CAAC;GAC5F;;AAED;;;;;AAKA,EAAA,MAAM,EAAE;GACP,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,KAAK;GACd;;AAED;EACA,WAAW,EAAE;GACZ,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,IAAI;GACb;;AAED;;AAEC;AACA;;;EAGD,IAAI,EAAE;GACL,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,OAAO;GAChB,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;GACnD;;AAED;;;;;AAKA,EAAA,QAAQ,EAAE;GACT,IAAI,EAAE,OAAO;GACb,OAAO,EAAE,KAAK;GACd;;AAED;;AAEC;AACA;;AAED,EAAA,eAAe,EAAE;GAChB,IAAI,EAAE,KAAK;AACX,GAAA,OAAO,EAAE,MAAM,EAAE;GACjB;;;EAGD,WAAW,EAAE;GACZ,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,QAAQ;GACjB;;;EAGD,WAAW,EAAE;GACZ,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;;;AAKC;;EAED,UAAU,EAAE;GACX,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,CAAC;AACV,GAAA,SAAS,EAAE,CAAC,CAAC,KAAK,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAE,IAAG,CAAE,IAAG,CAAC;GAC9C;;AAED;;;;;AAKC;;;AAGD,EAAA,MAAM,EAAE;AACP,GAAA,IAAI,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC;GACrB,OAAO,EAAE,IAAI;GACb;EACD;;CAED,KAAK,EAAE,CAAC,qBAAqB,EAAE,yBAAyB,CAAC;;AAEzD,CAAA,IAAI,GAAG;AACN,EAAA,OAAO;AACN;AACA,GAAA,QAAQ,EAAE,KAAK;AAChB,GAAA;EACA;;AAED,CAAA,QAAQ,EAAE;AACT;;AAEC;AACA;;EAED,WAAW,GAAG;AACb,GAAA,OAAO,aAAa,CAAC,IAAI,CAAC,MAAM,CAAE,KAAI,OAAM;GAC5C;;AAED;;;AAGC;AACA;;AAED,EAAA,OAAO,GAAG;GACT,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,MAAM,EAAE,GAAG,EAAE,KAAI,EAAG,CAAC,CAAA;GACrD;;AAED;;AAEC;AACA;;AAED,EAAA,cAAc,GAAG;GAChB,IAAI,CAAC,IAAI,CAAC,UAAW,IAAG,IAAI,CAAC,IAAI,CAAC,MAAK,IAAK,IAAI,CAAC,UAAU,EAAE;AAC5D,IAAA,OAAO,IAAI,CAAC,OAAM;AACnB,IAAA;AACA,GAAA,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,CAAA;GAC5C;;AAED;;;AAGC;AACA;;AAED,EAAA,eAAe,GAAG;GACjB,IAAI,CAAC,IAAI,CAAC,UAAW,IAAG,IAAI,CAAC,IAAI,CAAC,MAAK,IAAK,IAAI,CAAC,UAAU,EAAE;AAC5D,IAAA,OAAO,EAAC;AACT,IAAA;AACA,GAAA,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAA;GACzC;;AAED;AACC;AACA;AACA;;EAED,qBAAqB,GAAG;AACvB,GAAA,OAAO,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;GACpE;;AAED;;;AAGC;AACA;AACA;;AAED,EAAA,aAAa,GAAG;AACf,GAAA,MAAM,SAAS,IAAI,CAAC,sBAAoB;AACxC,GAAA,IAAI,MAAM,CAAC,MAAK,KAAM,CAAC,EAAE;IACxB,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAI;AAC1B,IAAA;AACA,GAAA,IAAI,MAAM,CAAC,MAAO,GAAE,CAAC,EAAE;AACtB,IAAA,OAAOC,SAAC,CAAC,eAAe,EAAE,iBAAiB,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,MAAK,EAAG,CAAA;AACtE,IAAA;AACA,GAAA,OAAO,EAAC;GACR;;;AAGD,EAAA,iBAAiB,GAAG;GACnB,IAAI,IAAI,CAAC,aAAa,EAAE;AACvB,IAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,kCAAkC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,eAAe,CAAA;AAC5F,IAAA;AACA,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,sBAAsB,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAA;GACxF;;;EAGD,WAAW,GAAG;AACb,GAAA,OAAOA,SAAC,CAAC,eAAe,EAAE,cAAc,CAAA;GACxC;;AAED;;;AAGC;AACA;;AAEA;;AAED,EAAA,eAAe,GAAG;AACjB,GAAA,OAAO,IAAI,CAAC,IAAG;AACd,KAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,KAAK;AACpB,KAAA,MAAM,KAAI,GAAI,IAAI,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,OAAO,EAAA;AACzC,KAAA,OAAO;MACN,KAAK,EAAE,KAAM,KAAI,IAAK,GAAE,GAAG,CAAC,KAAM,GAAE,CAAC,EAAE,GAAG,CAAC,KAAK,CAAE,EAAC,EAAE,KAAK,CAAC,CAAC,CAAC;MAC7D,IAAI,EAAE,GAAG,CAAC,IAAI;MACd,KAAK;AACL,MAAA,MAAM,EAAE,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC;AAChC,MAAA;KACA,CAAA;KACA,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAA;GACzB;;AAED;;;AAGC;;;AAGD,EAAA,aAAa,GAAG;AACf,GAAA,IAAI,IAAI,CAAC,QAAQ,EAAE;AAClB,IAAA,OAAO,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;AACjF,IAAA;AACA,GAAA,OAAO,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAM,KAAI,IAAI,CAAC,WAAW,CAAE,IAAG,IAAG;GAC5E;EACD;;AAED,CAAA,OAAO,EAAE;KACRA,SAAC;;AAED;;;AAGC;;AAEA;AACA;;AAED,EAAA,OAAO,CAAC,KAAK,EAAE;GACd,MAAM,QAAS,GAAE,IAAI,CAAC,MAAK,GAAI,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAA,GAAI,UAAQ;AAClE,GAAA,MAAM,KAAI,GAAI,CAAC,QAAO,KAAM,SAAQ,IAAK,QAAS,KAAI,IAAI,IAAI,QAAS,GAAE,KAAK,CAAC,GAAG,CAAC,MAAI;AACvF,GAAA,OAAO,CAAC,OAAO,KAAM,KAAI,QAAO,IAAK,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAI,GAAI,IAAG;GAC1E;;AAED;;AAEC;AACA;AACA;AACA;;AAED,EAAA,YAAY,CAAC,CAAC,EAAE;AACf,GAAA,OAAO,IAAI,CAAC,QAAO,GAAI,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAA,KAAM,IAAI,CAAC,WAAU;GAC/E;;AAED;;AAEC;AACA;;AAEA;;AAED,EAAA,aAAa,CAAC,GAAG,EAAE;GAClB,OAAO,CAAC,GAAE,IAAK,CAAC,GAAG,CAAC,MAAK,IAAK,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,MAAK,KAAM,CAAA;GACjE;;AAED;;;;AAIC;AACA;;EAED,OAAO,CAAC,CAAC,EAAE;AACV,GAAA,IAAI,IAAI,CAAC,QAAQ,EAAE;AAClB,IAAA,IAAI,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE;AACrC;AACC;AACA;;AAED,KAAA,IAAI,CAAC,KAAK,CAAC,yBAAyB,EAAE,EAAE,EAAA;KACxC,MAAK;AACN,KAAA;AACA,IAAA,MAAM,IAAG,GAAI,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAA;AAC3C,OAAE,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAA;OAC1C,CAAC,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC,EAAA;AAC9B,IAAA,IAAI,CAAC,KAAK,CAAC,yBAAyB,EAAE,IAAI,EAAA;IAC1C,MAAK;AACN,IAAA;GACA,IAAI,CAAE,KAAI,IAAI,CAAC,WAAW,EAAE;IAC3B,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,KAAK,CAAC,qBAAqB,EAAE,CAAC,EAAA;GACnC;;AAED;;;;AAIC;AACA;;AAED,EAAA,eAAe,CAAC,CAAC,EAAE;AAClB,GAAA,IAAI,CAAC,OAAO,CAAC,CAAC,EAAA;AACd,GAAA,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;AACnB,IAAA,IAAI,CAAC,QAAS,GAAE,MAAI;AACrB,IAAA;GACA;;AAED;;AAEC;AACA;;;AAGD,EAAA,aAAa,CAAC,GAAG,EAAE;AAClB,GAAA,IAAI,IAAI,CAAC,QAAQ,EAAE;AAClB,IAAA,MAAM,GAAI,GAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAA,GAAI,GAAE,GAAI,GAAC;IACxC,IAAI,CAAC,KAAK,CAAC,yBAAyB,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,EAAA;IAC7D,MAAK;AACN,IAAA;GACA,IAAI,CAAC,KAAK,CAAC,qBAAqB,EAAE,GAAE,GAAI,GAAG,CAAC,QAAQ,IAAI,EAAA;GACxD;EACD;AACF;;;;"}
1
+ {"version":3,"file":"CnQuickFilterBar.vue2.js","sources":["../../../../src/components/CnQuickFilterBar/CnQuickFilterBar.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: AGPL-3.0-or-later\n -->\n\n<template>\n\t<!-- Dropdown mode: a single NcSelect (multi or single) instead of the chip strip. -->\n\t<div\n\t\tv-if=\"mode === 'dropdown'\"\n\t\tclass=\"cn-quick-filter-bar cn-quick-filter-bar--dropdown\"\n\t\t:class=\"{ 'cn-quick-filter-bar--inline': inline }\">\n\t\t<NcSelect\n\t\t\t:modelValue=\"dropdownValue\"\n\t\t\t:options=\"dropdownOptions\"\n\t\t\t:multiple=\"multiple\"\n\t\t\t:keepOpen=\"multiple\"\n\t\t\t:clearable=\"true\"\n\t\t\tlabel=\"label\"\n\t\t\tclass=\"cn-quick-filter-bar__select\"\n\t\t\t:inputLabel=\"selectLabel\"\n\t\t\t:aria-label-combobox=\"selectLabel\"\n\t\t\t:placeholder=\"placeholder || selectLabel\"\n\t\t\t@update:modelValue=\"onSelectInput\" />\n\t</div>\n\t<!-- Chips mode (default): the clickable tab strip. -->\n\t<div v-else\n\t\tclass=\"cn-quick-filter-bar\"\n\t\t:class=\"{ 'cn-quick-filter-bar--inline': inline }\">\n\t\t<!-- The tablist holds ONLY tabs: the overflow chip is a sibling, not a\n\t\t child, because a role=\"tablist\" may not contain a menu button. -->\n\t\t<!-- Board look: the tabs are chips, pressed buttons in a group, not a\n\t\t tablist (the chips filter the list, they do not switch a panel). -->\n\t\t<div class=\"cn-quick-filter-bar__tabs\" :role=\"isBoardLook ? 'group' : 'tablist'\" :aria-label=\"isBoardLook ? t('nextcloud-vue', 'Quick filters') : null\">\n\t\t\t<button\n\t\t\t\tv-for=\"entry in visibleEntries\"\n\t\t\t\t:key=\"entry.tab.label + ':' + entry.index\"\n\t\t\t\ttype=\"button\"\n\t\t\t\t:role=\"isBoardLook ? null : 'tab'\"\n\t\t\t\t:aria-selected=\"isBoardLook ? null : (isChipActive(entry.index) ? 'true' : 'false')\"\n\t\t\t\t:aria-pressed=\"isBoardLook ? (isChipActive(entry.index) ? 'true' : 'false') : null\"\n\t\t\t\tclass=\"cn-quick-filter-bar__tab\"\n\t\t\t\t:class=\"[{ 'cn-quick-filter-bar__tab--active': isChipActive(entry.index) }]\"\n\t\t\t\t@click=\"onClick(entry.index)\">\n\t\t\t\t<CnIcon\n\t\t\t\t\tv-if=\"entry.tab.icon\"\n\t\t\t\t\t:name=\"entry.tab.icon\"\n\t\t\t\t\t:size=\"16\"\n\t\t\t\t\tclass=\"cn-quick-filter-bar__icon\" />\n\t\t\t\t<span class=\"cn-quick-filter-bar__label\">{{ tl(entry.tab.label) }}</span>\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"countOf(entry) !== null\"\n\t\t\t\t\tclass=\"cn-quick-filter-bar__count\"\n\t\t\t\t\tdata-testid=\"cn-quick-filter-count\">{{ ' ' + countOf(entry) }}</span>\n\t\t\t</button>\n\t\t</div>\n\t\t<!-- The overflow is a CHIP and not a toolbar button: it is one of the\n\t\t lenses, so it carries the same pill, the same active fill and the\n\t\t label of whichever hidden lens is on. -->\n\t\t<NcPopover\n\t\t\tv-if=\"overflowEntries.length > 0\"\n\t\t\tv-model:shown=\"moreOpen\"\n\t\t\t:triggers=\"[]\"\n\t\t\tpopupRole=\"dialog\"\n\t\t\tplacement=\"bottom-end\"\n\t\t\tpopoverBaseClass=\"cn-quick-filter-bar__popper\">\n\t\t\t<template #trigger>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tdata-testid=\"cn-quick-filter-more\"\n\t\t\t\t\tclass=\"cn-quick-filter-bar__tab cn-quick-filter-bar__more\"\n\t\t\t\t\t:class=\"{\n\t\t\t\t\t\t'cn-quick-filter-bar__tab--active': activeOverflowEntries.length > 0,\n\t\t\t\t\t\t'cn-quick-filter-bar__more--icon-only': !overflowLabel,\n\t\t\t\t\t}\"\n\t\t\t\t\t:aria-expanded=\"moreOpen ? 'true' : 'false'\"\n\t\t\t\t\taria-haspopup=\"dialog\"\n\t\t\t\t\t:aria-label=\"overflowAriaLabel\"\n\t\t\t\t\t:title=\"overflowAriaLabel\"\n\t\t\t\t\t@click=\"moreOpen = !moreOpen\">\n\t\t\t\t\t<span v-if=\"overflowLabel\" class=\"cn-quick-filter-bar__label\">{{ overflowLabel }}</span>\n\t\t\t\t\t<DotsHorizontal :size=\"16\" class=\"cn-quick-filter-bar__icon\" />\n\t\t\t\t</button>\n\t\t\t</template>\n\t\t\t<div\n\t\t\t\tclass=\"cn-quick-filter-bar__more-panel\"\n\t\t\t\t@keydown.escape.stop=\"moreOpen = false\">\n\t\t\t\t<p class=\"cn-quick-filter-bar__more-heading\">\n\t\t\t\t\t{{ moreHeading }}\n\t\t\t\t</p>\n\t\t\t\t<div class=\"cn-quick-filter-bar__more-list\">\n\t\t\t\t\t<button\n\t\t\t\t\t\tv-for=\"entry in overflowEntries\"\n\t\t\t\t\t\t:key=\"entry.tab.label + ':' + entry.index\"\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tdata-testid=\"cn-quick-filter-more-item\"\n\t\t\t\t\t\tclass=\"cn-quick-filter-bar__tab cn-quick-filter-bar__more-item\"\n\t\t\t\t\t\t:class=\"{ 'cn-quick-filter-bar__tab--active': isChipActive(entry.index) }\"\n\t\t\t\t\t\t:aria-pressed=\"isChipActive(entry.index) ? 'true' : 'false'\"\n\t\t\t\t\t\t@click=\"onOverflowClick(entry.index)\">\n\t\t\t\t\t\t<CnIcon\n\t\t\t\t\t\t\tv-if=\"entry.tab.icon\"\n\t\t\t\t\t\t\t:name=\"entry.tab.icon\"\n\t\t\t\t\t\t\t:size=\"16\"\n\t\t\t\t\t\t\tclass=\"cn-quick-filter-bar__icon\" />\n\t\t\t\t\t\t<span class=\"cn-quick-filter-bar__label\">{{ tl(entry.tab.label) }}</span>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tv-if=\"countOf(entry) !== null\"\n\t\t\t\t\t\t\tclass=\"cn-quick-filter-bar__count\"\n\t\t\t\t\t\t\tdata-testid=\"cn-quick-filter-count\">{{ ' ' + countOf(entry) }}</span>\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</NcPopover>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcPopover, NcSelect } from '@nextcloud/vue'\nimport DotsHorizontal from 'vue-material-design-icons/DotsHorizontal.vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport { normalizeLook } from '../../composables/useLook.js'\n\n/**\n * CnQuickFilterBar — quick-filter control rendered above a `type:\"index\"`\n * page's table. Each tab carries a manifest `filter` map that `CnIndexPage`\n * merges into the `useListView` fetch when the tab is active.\n *\n * Two presentations, chosen by `mode`:\n * - `'chips'` (default) — a horizontal row of pill-shaped buttons; the\n * active one is filled, the rest outlined. `maxVisible` caps how many\n * pills render and moves the rest into an overflow menu.\n * - `'dropdown'` — a single `NcSelect`. The \"All\" / empty-filter tab is\n * dropped from the options (an empty selection means \"all\").\n *\n * Two selection cardinalities, chosen by `multiple`:\n * - single (default) — one active tab; uses the `activeIndex` v-model\n * (`update:active-index`).\n * - `multiple` — several tabs at once; uses the `selectedIndices` array\n * prop + `update:selected-indices` event. `CnIndexPage` ORs the selected\n * tabs' filters together.\n *\n * Implementation is deliberately a thin styled `<button>` list (chips) /\n * `NcSelect` (dropdown) rather than `NcAppNavigation*` — this lives INSIDE\n * the index page, not as the app's main nav.\n */\nexport default {\n\tname: 'CnQuickFilterBar',\n\n\tcomponents: { CnIcon, DotsHorizontal, NcPopover, NcSelect },\n\n\tinject: {\n\t\t/** The look CnAppRoot provides; `board` draws the tabs as pressed chips. */\n\t\tcnLook: { default: 'nextcloud' },\n\t\t/**\n\t\t * The host's label lookup (CnAppRoot provides it); identity without one, so a\n\t\t * label that is not a key renders as written.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\t// NO `model: { prop, event }` OPTION.\n\t//\n\t// The Vue-2 `model` option is REMOVED in Vue 3 — read by nothing, warned\n\t// about by nothing — so it survived the migration as a declaration that\n\t// looked authoritative while doing exactly zero. The component's real\n\t// contract is the explicit `:active-index` / `@update:active-index` pair\n\t// (and `:selected-indices` / `@update:selected-indices` in `multiple`\n\t// mode), which is what `CnIndexPage` binds. Surfaced by\n\t// `vue/no-deprecated-model-definition`, which this repo now arms through\n\t// its own published preset.\n\n\tprops: {\n\t\t/**\n\t\t * Tab definitions — same shape `pages[].config.quickFilters[]` uses.\n\t\t * `filter` is consumed by the parent (CnIndexPage), not this component.\n\t\t */\n\t\ttabs: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t\tvalidator: (arr) => Array.isArray(arr) && arr.every((t) => t && typeof t.label === 'string'),\n\t\t},\n\n\t\t/**\n\t\t * Render bare (no padding / bottom border / background) for embedding\n\t\t * inline inside another bar — e.g. the `#filters` slot of `CnActionsBar`,\n\t\t * so the tabs sit beside the view toggle instead of as a separate row.\n\t\t */\n\t\tinline: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Index of the currently active tab, or `null` for none active. Single-select only. */\n\t\tactiveIndex: {\n\t\t\ttype: Number,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Presentation: `'chips'` (pill button strip, default) or\n\t\t * `'dropdown'` (a single `NcSelect`).\n\t\t *\n\t\t * @type {'chips'|'dropdown'}\n\t\t */\n\t\tmode: {\n\t\t\ttype: String,\n\t\t\tdefault: 'chips',\n\t\t\tvalidator: (v) => ['chips', 'dropdown'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Allow more than one tab active at once. Selection is exposed via\n\t\t * `selectedIndices` + `update:selected-indices`; the parent ORs the\n\t\t * selected tabs' filters together.\n\t\t */\n\t\tmultiple: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Active tab indices when `multiple` is set (the array v-model).\n\t\t *\n\t\t * @type {number[]}\n\t\t */\n\t\tselectedIndices: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Accessible label / placeholder for the dropdown control. */\n\t\tselectLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: 'Filter',\n\t\t},\n\n\t\t/** Placeholder text for the dropdown (falls back to `selectLabel`). */\n\t\tplaceholder: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Chips mode only: how many pills render inline before the rest move\n\t\t * into an overflow menu. `0` (the default) renders every tab, which is\n\t\t * the behaviour this component has always had. The visible set is the\n\t\t * FIRST `maxVisible` tabs in declared order — reorder `tabs` to change\n\t\t * which ones stay out. Ignored in `dropdown` mode.\n\t\t */\n\t\tmaxVisible: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t\tvalidator: (v) => Number.isFinite(v) && v >= 0,\n\t\t},\n\n\t\t/**\n\t\t * How many records each tab matches, keyed by tab index (an array in\n\t\t * tab order works too). A tab with a number here shows it after its\n\t\t * label; a tab without one shows no count. A tab can also carry its\n\t\t * own `count`. `null` (the default) shows no counts at all.\n\t\t *\n\t\t * @type {{[index: number]: number}|Array<number|null>|null}\n\t\t */\n\t\tcounts: {\n\t\t\ttype: [Object, Array],\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['update:active-index', 'update:selected-indices'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Whether the overflow chip's panel is open. */\n\t\t\tmoreOpen: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the tabs draw as board chips (`aria-pressed`, no tablist).\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\t/**\n\t\t * Every tab paired with its own index, so the overflow split can slice\n\t\t * the list without losing the index the parent's filters are keyed on.\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\tentries() {\n\t\t\treturn this.tabs.map((tab, index) => ({ tab, index }))\n\t\t},\n\n\t\t/**\n\t\t * The tabs rendered as pills — all of them unless `maxVisible` caps it.\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\tvisibleEntries() {\n\t\t\tif (!this.maxVisible || this.tabs.length <= this.maxVisible) {\n\t\t\t\treturn this.entries\n\t\t\t}\n\t\t\treturn this.entries.slice(0, this.maxVisible)\n\t\t},\n\n\t\t/**\n\t\t * The tabs moved into the overflow menu (empty when `maxVisible` is 0\n\t\t * or there is nothing to hide).\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\toverflowEntries() {\n\t\t\tif (!this.maxVisible || this.tabs.length <= this.maxVisible) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn this.entries.slice(this.maxVisible)\n\t\t},\n\n\t\t/**\n\t\t * The active tabs that live in the overflow menu.\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\tactiveOverflowEntries() {\n\t\t\treturn this.overflowEntries.filter((e) => this.isChipActive(e.index))\n\t\t},\n\n\t\t/**\n\t\t * Text on the overflow chip — an active hidden tab names itself there,\n\t\t * so the strip never reads \"All\" while the list is narrowed. Nothing\n\t\t * active leaves the chip as the bare `⋯` glyph.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\toverflowLabel() {\n\t\t\tconst active = this.activeOverflowEntries\n\t\t\tif (active.length === 1) {\n\t\t\t\treturn this.tl(active[0].tab.label)\n\t\t\t}\n\t\t\tif (active.length > 1) {\n\t\t\t\treturn t('nextcloud-vue', '{count} filters', { count: active.length })\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/** @return {string} Accessible name for the overflow chip. */\n\t\toverflowAriaLabel() {\n\t\t\tif (this.overflowLabel) {\n\t\t\t\treturn t('nextcloud-vue', '{label} — show the other filters', { label: this.overflowLabel })\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', '{count} more filters', { count: this.overflowEntries.length })\n\t\t},\n\n\t\t/** @return {string} Heading above the hidden chips in the panel. */\n\t\tmoreHeading() {\n\t\t\treturn t('nextcloud-vue', 'More filters')\n\t\t},\n\n\t\t/**\n\t\t * Dropdown options: every tab that carries a non-empty `filter`,\n\t\t * tagged with its original index. The \"All\" / empty-filter tab is\n\t\t * omitted — an empty selection already means \"all\".\n\t\t *\n\t\t * @return {Array<{label: string, index: number, icon: string}>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n\t\t */\n\t\tdropdownOptions() {\n\t\t\treturn this.tabs\n\t\t\t\t.map((tab, index) => {\n\t\t\t\t\tconst count = this.countOf({ tab, index })\n\t\t\t\t\treturn {\n\t\t\t\t\t\tlabel: count === null ? this.tl(tab.label) : `${this.tl(tab.label)} (${count})`,\n\t\t\t\t\t\ticon: tab.icon,\n\t\t\t\t\t\tindex,\n\t\t\t\t\t\t_empty: this.isEmptyFilter(tab),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.filter((o) => !o._empty)\n\t\t},\n\n\t\t/**\n\t\t * Current NcSelect value — an array of option objects when\n\t\t * `multiple`, a single option object (or null) otherwise.\n\t\t *\n\t\t * @return {object|object[]|null}\n\t\t */\n\t\tdropdownValue() {\n\t\t\tif (this.multiple) {\n\t\t\t\treturn this.dropdownOptions.filter((o) => this.selectedIndices.includes(o.index))\n\t\t\t}\n\t\t\treturn this.dropdownOptions.find((o) => o.index === this.activeIndex) || null\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * A manifest string through the host's label lookup (`cnTranslate`), so a key\n\t\t * such as \"Mine\" reads in the user's language.\n\t\t *\n\t\t * @param {string} text The text as written in the manifest.\n\t\t * @return {string} The translated text.\n\t\t */\n\t\ttl(text) {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\tt,\n\n\t\t/**\n\t\t * The count to show on a tab, or null for none: `counts[index]`\n\t\t * first, else the tab's own `count`.\n\t\t *\n\t\t * @param {{tab: object, index: number}} entry The tab and its index.\n\t\t * @return {number|null}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n\t\t */\n\t\tcountOf(entry) {\n\t\t\tconst fromProp = this.counts ? this.counts[entry.index] : undefined\n\t\t\tconst value = (fromProp !== undefined && fromProp !== null) ? fromProp : entry.tab.count\n\t\t\treturn (typeof value === 'number' && Number.isFinite(value)) ? value : null\n\t\t},\n\n\t\t/**\n\t\t * Is tab `i` rendered active in chips mode (handles both single\n\t\t * `activeIndex` and `multiple` `selectedIndices`).\n\t\t *\n\t\t * @param {number} i Zero-based tab index.\n\t\t * @return {boolean}\n\t\t */\n\t\tisChipActive(i) {\n\t\t\treturn this.multiple ? this.selectedIndices.includes(i) : i === this.activeIndex\n\t\t},\n\n\t\t/**\n\t\t * Whether a tab's `filter` map is absent/empty (the \"All\" tab).\n\t\t *\n\t\t * @param {{label: string, filter?: object}} tab An entry from the `tabs` prop.\n\t\t * @return {boolean} True when the tab applies no filter, so selecting it\n\t\t * clears the multi-selection rather than adding to it.\n\t\t */\n\t\tisEmptyFilter(tab) {\n\t\t\treturn !tab || !tab.filter || Object.keys(tab.filter).length === 0\n\t\t},\n\n\t\t/**\n\t\t * Chip-click handler. In single mode emits the new active index; in\n\t\t * multiple mode toggles the index in/out of the selection. Clicking\n\t\t * an empty-filter (\"All\") chip clears the multi-selection.\n\t\t *\n\t\t * @param {number} i Zero-based tab index.\n\t\t */\n\t\tonClick(i) {\n\t\t\tif (this.multiple) {\n\t\t\t\tif (this.isEmptyFilter(this.tabs[i])) {\n\t\t\t\t\t/**\n\t\t\t\t\t * @event update:selected-indices Active tab indices changed (multiple mode).\n\t\t\t\t\t * @type {number[]}\n\t\t\t\t\t */\n\t\t\t\t\tthis.$emit('update:selected-indices', [])\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst next = this.selectedIndices.includes(i)\n\t\t\t\t\t? this.selectedIndices.filter((x) => x !== i)\n\t\t\t\t\t: [...this.selectedIndices, i]\n\t\t\t\tthis.$emit('update:selected-indices', next)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (i === this.activeIndex) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('update:active-index', i)\n\t\t},\n\n\t\t/**\n\t\t * A chip clicked inside the overflow panel. Single-select closes the\n\t\t * panel on the way out; multi-select keeps it open so several lenses\n\t\t * can be toggled in one visit.\n\t\t *\n\t\t * @param {number} i Zero-based tab index.\n\t\t */\n\t\tonOverflowClick(i) {\n\t\t\tthis.onClick(i)\n\t\t\tif (!this.multiple) {\n\t\t\t\tthis.moreOpen = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * NcSelect input handler — normalises the option object(s) back to\n\t\t * indices and emits the matching model event.\n\t\t *\n\t\t * @param {object|object[]|null} val Selected option object(s).\n\t\t */\n\t\tonSelectInput(val) {\n\t\t\tif (this.multiple) {\n\t\t\t\tconst arr = Array.isArray(val) ? val : []\n\t\t\t\tthis.$emit('update:selected-indices', arr.map((o) => o.index))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('update:active-index', val ? val.index : null)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-quick-filter-bar {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 12px;\n\tborder-bottom: 1px solid var(--color-border);\n\tbackground: var(--color-main-background);\n}\n\n.cn-quick-filter-bar__tabs {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n}\n\n/* Inline variant — bare strip for embedding inside another bar (e.g. the\n CnActionsBar #filters slot), so it reads as a control beside the view toggle\n rather than a separate bordered row. */\n.cn-quick-filter-bar--inline {\n\tpadding: 0;\n\tborder-bottom: none;\n\tbackground: transparent;\n}\n\n/* Dropdown variant — give the NcSelect a sensible minimum so multi-select\n chips have room without stretching the whole bar. */\n.cn-quick-filter-bar--dropdown {\n\talign-items: center;\n}\n\n.cn-quick-filter-bar__select {\n\tmin-width: 220px;\n}\n\n.cn-quick-filter-bar__tab {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-pill, 999px);\n\tbackground: transparent;\n\tcolor: var(--color-main-text);\n\tfont-size: 13px;\n\tfont-weight: 500;\n\tcursor: pointer;\n\ttransition: background-color 120ms, color 120ms, border-color 120ms;\n}\n\n.cn-quick-filter-bar__tab:hover,\n.cn-quick-filter-bar__tab:focus-visible {\n\tbackground: var(--color-background-hover);\n\toutline: none;\n}\n\n.cn-quick-filter-bar__tab--active {\n\tbackground: var(--color-primary-element, var(--color-primary, #4376fc));\n\tcolor: var(--color-primary-element-text, #fff);\n\tborder-color: var(--color-primary-element, var(--color-primary, #4376fc));\n}\n\n.cn-quick-filter-bar__icon {\n\tdisplay: inline-flex;\n}\n\n.cn-quick-filter-bar__label {\n\tline-height: 1;\n}\n\n/* Inherits the chip's own text colour, so it stays readable on the active\n (primary) fill as well as on the resting one. The count's text starts with\n a space: the chip is a flex row, so the space is not drawn, but it keeps a\n screen reader from reading label and count as one word. */\n.cn-quick-filter-bar__count {\n\tmin-width: 1.5em;\n\tpadding: 2px 6px;\n\tborder: 1px solid currentColor;\n\tborder-radius: var(--border-radius-pill, 10px);\n\tfont-size: 0.85em;\n\tfont-weight: 600;\n\tline-height: 1;\n\ttext-align: center;\n}\n\n/* NcPopover wraps its trigger in a plain `<div class=\"v-popper\">`, which as a\n block would drop the chip onto its own line under the strip. */\n.cn-quick-filter-bar > :deep(.v-popper) {\n\tdisplay: inline-flex;\n}\n\n/* The `⋯` glyph is the chip's whole content while no hidden lens is on, so the\n pill tightens to a circle rather than sitting as a wide empty capsule. */\n.cn-quick-filter-bar__more {\n\tgap: 4px;\n}\n\n.cn-quick-filter-bar__more--icon-only {\n\tpadding: 4px 8px;\n}\n\n.cn-quick-filter-bar__more[aria-expanded='true'] {\n\tborder-color: var(--color-primary-element, var(--color-primary, #4376fc));\n}\n\n.cn-quick-filter-bar__more-panel {\n\tpadding: 10px 12px 12px;\n\tmax-width: 280px;\n}\n\n.cn-quick-filter-bar__more-heading {\n\tmargin: 0 0 8px;\n\tfont-size: 12px;\n\tfont-weight: 600;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* The hidden lenses stay chips — the panel is more of the strip, not a\n different control that happens to hold the same filters. */\n.cn-quick-filter-bar__more-list {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 8px;\n\tmax-height: 320px;\n\toverflow-y: auto;\n}\n\n.cn-quick-filter-bar__more-item {\n\tmax-width: 100%;\n}\n\n.cn-quick-filter-bar__more-item .cn-quick-filter-bar__label {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["CnIcon","t"],"mappings":"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"}
@@ -75,7 +75,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
75
75
  class: "cn-quick-filter-bar__icon"
76
76
  }, null, 8 /* PROPS */, ["name"]))
77
77
  : createCommentVNode("v-if", true),
78
- createElementVNode("span", _hoisted_3, toDisplayString(entry.tab.label), 1 /* TEXT */),
78
+ createElementVNode("span", _hoisted_3, toDisplayString($options.tl(entry.tab.label)), 1 /* TEXT */),
79
79
  ($options.countOf(entry) !== null)
80
80
  ? (openBlock(), createElementBlock("span", _hoisted_4, toDisplayString(' ' + $options.countOf(entry)), 1 /* TEXT */))
81
81
  : createCommentVNode("v-if", true)
@@ -139,7 +139,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
139
139
  class: "cn-quick-filter-bar__icon"
140
140
  }, null, 8 /* PROPS */, ["name"]))
141
141
  : createCommentVNode("v-if", true),
142
- createElementVNode("span", _hoisted_10, toDisplayString(entry.tab.label), 1 /* TEXT */),
142
+ createElementVNode("span", _hoisted_10, toDisplayString($options.tl(entry.tab.label)), 1 /* TEXT */),
143
143
  ($options.countOf(entry) !== null)
144
144
  ? (openBlock(), createElementBlock("span", _hoisted_11, toDisplayString(' ' + $options.countOf(entry)), 1 /* TEXT */))
145
145
  : createCommentVNode("v-if", true)
@@ -1 +1 @@
1
- {"version":3,"file":"CnQuickFilterBar.vue3.js","sources":["../../../../src/components/CnQuickFilterBar/CnQuickFilterBar.vue?vue&type=template&id=8e121822&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: AGPL-3.0-or-later\n -->\n\n<template>\n\t<!-- Dropdown mode: a single NcSelect (multi or single) instead of the chip strip. -->\n\t<div\n\t\tv-if=\"mode === 'dropdown'\"\n\t\tclass=\"cn-quick-filter-bar cn-quick-filter-bar--dropdown\"\n\t\t:class=\"{ 'cn-quick-filter-bar--inline': inline }\">\n\t\t<NcSelect\n\t\t\t:modelValue=\"dropdownValue\"\n\t\t\t:options=\"dropdownOptions\"\n\t\t\t:multiple=\"multiple\"\n\t\t\t:keepOpen=\"multiple\"\n\t\t\t:clearable=\"true\"\n\t\t\tlabel=\"label\"\n\t\t\tclass=\"cn-quick-filter-bar__select\"\n\t\t\t:inputLabel=\"selectLabel\"\n\t\t\t:aria-label-combobox=\"selectLabel\"\n\t\t\t:placeholder=\"placeholder || selectLabel\"\n\t\t\t@update:modelValue=\"onSelectInput\" />\n\t</div>\n\t<!-- Chips mode (default): the clickable tab strip. -->\n\t<div v-else\n\t\tclass=\"cn-quick-filter-bar\"\n\t\t:class=\"{ 'cn-quick-filter-bar--inline': inline }\">\n\t\t<!-- The tablist holds ONLY tabs: the overflow chip is a sibling, not a\n\t\t child, because a role=\"tablist\" may not contain a menu button. -->\n\t\t<!-- Board look: the tabs are chips, pressed buttons in a group, not a\n\t\t tablist (the chips filter the list, they do not switch a panel). -->\n\t\t<div class=\"cn-quick-filter-bar__tabs\" :role=\"isBoardLook ? 'group' : 'tablist'\" :aria-label=\"isBoardLook ? t('nextcloud-vue', 'Quick filters') : null\">\n\t\t\t<button\n\t\t\t\tv-for=\"entry in visibleEntries\"\n\t\t\t\t:key=\"entry.tab.label + ':' + entry.index\"\n\t\t\t\ttype=\"button\"\n\t\t\t\t:role=\"isBoardLook ? null : 'tab'\"\n\t\t\t\t:aria-selected=\"isBoardLook ? null : (isChipActive(entry.index) ? 'true' : 'false')\"\n\t\t\t\t:aria-pressed=\"isBoardLook ? (isChipActive(entry.index) ? 'true' : 'false') : null\"\n\t\t\t\tclass=\"cn-quick-filter-bar__tab\"\n\t\t\t\t:class=\"[{ 'cn-quick-filter-bar__tab--active': isChipActive(entry.index) }]\"\n\t\t\t\t@click=\"onClick(entry.index)\">\n\t\t\t\t<CnIcon\n\t\t\t\t\tv-if=\"entry.tab.icon\"\n\t\t\t\t\t:name=\"entry.tab.icon\"\n\t\t\t\t\t:size=\"16\"\n\t\t\t\t\tclass=\"cn-quick-filter-bar__icon\" />\n\t\t\t\t<span class=\"cn-quick-filter-bar__label\">{{ entry.tab.label }}</span>\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"countOf(entry) !== null\"\n\t\t\t\t\tclass=\"cn-quick-filter-bar__count\"\n\t\t\t\t\tdata-testid=\"cn-quick-filter-count\">{{ ' ' + countOf(entry) }}</span>\n\t\t\t</button>\n\t\t</div>\n\t\t<!-- The overflow is a CHIP and not a toolbar button: it is one of the\n\t\t lenses, so it carries the same pill, the same active fill and the\n\t\t label of whichever hidden lens is on. -->\n\t\t<NcPopover\n\t\t\tv-if=\"overflowEntries.length > 0\"\n\t\t\tv-model:shown=\"moreOpen\"\n\t\t\t:triggers=\"[]\"\n\t\t\tpopupRole=\"dialog\"\n\t\t\tplacement=\"bottom-end\"\n\t\t\tpopoverBaseClass=\"cn-quick-filter-bar__popper\">\n\t\t\t<template #trigger>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tdata-testid=\"cn-quick-filter-more\"\n\t\t\t\t\tclass=\"cn-quick-filter-bar__tab cn-quick-filter-bar__more\"\n\t\t\t\t\t:class=\"{\n\t\t\t\t\t\t'cn-quick-filter-bar__tab--active': activeOverflowEntries.length > 0,\n\t\t\t\t\t\t'cn-quick-filter-bar__more--icon-only': !overflowLabel,\n\t\t\t\t\t}\"\n\t\t\t\t\t:aria-expanded=\"moreOpen ? 'true' : 'false'\"\n\t\t\t\t\taria-haspopup=\"dialog\"\n\t\t\t\t\t:aria-label=\"overflowAriaLabel\"\n\t\t\t\t\t:title=\"overflowAriaLabel\"\n\t\t\t\t\t@click=\"moreOpen = !moreOpen\">\n\t\t\t\t\t<span v-if=\"overflowLabel\" class=\"cn-quick-filter-bar__label\">{{ overflowLabel }}</span>\n\t\t\t\t\t<DotsHorizontal :size=\"16\" class=\"cn-quick-filter-bar__icon\" />\n\t\t\t\t</button>\n\t\t\t</template>\n\t\t\t<div\n\t\t\t\tclass=\"cn-quick-filter-bar__more-panel\"\n\t\t\t\t@keydown.escape.stop=\"moreOpen = false\">\n\t\t\t\t<p class=\"cn-quick-filter-bar__more-heading\">\n\t\t\t\t\t{{ moreHeading }}\n\t\t\t\t</p>\n\t\t\t\t<div class=\"cn-quick-filter-bar__more-list\">\n\t\t\t\t\t<button\n\t\t\t\t\t\tv-for=\"entry in overflowEntries\"\n\t\t\t\t\t\t:key=\"entry.tab.label + ':' + entry.index\"\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tdata-testid=\"cn-quick-filter-more-item\"\n\t\t\t\t\t\tclass=\"cn-quick-filter-bar__tab cn-quick-filter-bar__more-item\"\n\t\t\t\t\t\t:class=\"{ 'cn-quick-filter-bar__tab--active': isChipActive(entry.index) }\"\n\t\t\t\t\t\t:aria-pressed=\"isChipActive(entry.index) ? 'true' : 'false'\"\n\t\t\t\t\t\t@click=\"onOverflowClick(entry.index)\">\n\t\t\t\t\t\t<CnIcon\n\t\t\t\t\t\t\tv-if=\"entry.tab.icon\"\n\t\t\t\t\t\t\t:name=\"entry.tab.icon\"\n\t\t\t\t\t\t\t:size=\"16\"\n\t\t\t\t\t\t\tclass=\"cn-quick-filter-bar__icon\" />\n\t\t\t\t\t\t<span class=\"cn-quick-filter-bar__label\">{{ entry.tab.label }}</span>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tv-if=\"countOf(entry) !== null\"\n\t\t\t\t\t\t\tclass=\"cn-quick-filter-bar__count\"\n\t\t\t\t\t\t\tdata-testid=\"cn-quick-filter-count\">{{ ' ' + countOf(entry) }}</span>\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</NcPopover>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcPopover, NcSelect } from '@nextcloud/vue'\nimport DotsHorizontal from 'vue-material-design-icons/DotsHorizontal.vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport { normalizeLook } from '../../composables/useLook.js'\n\n/**\n * CnQuickFilterBar — quick-filter control rendered above a `type:\"index\"`\n * page's table. Each tab carries a manifest `filter` map that `CnIndexPage`\n * merges into the `useListView` fetch when the tab is active.\n *\n * Two presentations, chosen by `mode`:\n * - `'chips'` (default) — a horizontal row of pill-shaped buttons; the\n * active one is filled, the rest outlined. `maxVisible` caps how many\n * pills render and moves the rest into an overflow menu.\n * - `'dropdown'` — a single `NcSelect`. The \"All\" / empty-filter tab is\n * dropped from the options (an empty selection means \"all\").\n *\n * Two selection cardinalities, chosen by `multiple`:\n * - single (default) — one active tab; uses the `activeIndex` v-model\n * (`update:active-index`).\n * - `multiple` — several tabs at once; uses the `selectedIndices` array\n * prop + `update:selected-indices` event. `CnIndexPage` ORs the selected\n * tabs' filters together.\n *\n * Implementation is deliberately a thin styled `<button>` list (chips) /\n * `NcSelect` (dropdown) rather than `NcAppNavigation*` — this lives INSIDE\n * the index page, not as the app's main nav.\n */\nexport default {\n\tname: 'CnQuickFilterBar',\n\n\tcomponents: { CnIcon, DotsHorizontal, NcPopover, NcSelect },\n\n\tinject: {\n\t\t/** The look CnAppRoot provides; `board` draws the tabs as pressed chips. */\n\t\tcnLook: { default: 'nextcloud' },\n\t},\n\n\t// NO `model: { prop, event }` OPTION.\n\t//\n\t// The Vue-2 `model` option is REMOVED in Vue 3 — read by nothing, warned\n\t// about by nothing — so it survived the migration as a declaration that\n\t// looked authoritative while doing exactly zero. The component's real\n\t// contract is the explicit `:active-index` / `@update:active-index` pair\n\t// (and `:selected-indices` / `@update:selected-indices` in `multiple`\n\t// mode), which is what `CnIndexPage` binds. Surfaced by\n\t// `vue/no-deprecated-model-definition`, which this repo now arms through\n\t// its own published preset.\n\n\tprops: {\n\t\t/**\n\t\t * Tab definitions — same shape `pages[].config.quickFilters[]` uses.\n\t\t * `filter` is consumed by the parent (CnIndexPage), not this component.\n\t\t */\n\t\ttabs: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t\tvalidator: (arr) => Array.isArray(arr) && arr.every((t) => t && typeof t.label === 'string'),\n\t\t},\n\n\t\t/**\n\t\t * Render bare (no padding / bottom border / background) for embedding\n\t\t * inline inside another bar — e.g. the `#filters` slot of `CnActionsBar`,\n\t\t * so the tabs sit beside the view toggle instead of as a separate row.\n\t\t */\n\t\tinline: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Index of the currently active tab, or `null` for none active. Single-select only. */\n\t\tactiveIndex: {\n\t\t\ttype: Number,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Presentation: `'chips'` (pill button strip, default) or\n\t\t * `'dropdown'` (a single `NcSelect`).\n\t\t *\n\t\t * @type {'chips'|'dropdown'}\n\t\t */\n\t\tmode: {\n\t\t\ttype: String,\n\t\t\tdefault: 'chips',\n\t\t\tvalidator: (v) => ['chips', 'dropdown'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Allow more than one tab active at once. Selection is exposed via\n\t\t * `selectedIndices` + `update:selected-indices`; the parent ORs the\n\t\t * selected tabs' filters together.\n\t\t */\n\t\tmultiple: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Active tab indices when `multiple` is set (the array v-model).\n\t\t *\n\t\t * @type {number[]}\n\t\t */\n\t\tselectedIndices: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Accessible label / placeholder for the dropdown control. */\n\t\tselectLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: 'Filter',\n\t\t},\n\n\t\t/** Placeholder text for the dropdown (falls back to `selectLabel`). */\n\t\tplaceholder: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Chips mode only: how many pills render inline before the rest move\n\t\t * into an overflow menu. `0` (the default) renders every tab, which is\n\t\t * the behaviour this component has always had. The visible set is the\n\t\t * FIRST `maxVisible` tabs in declared order — reorder `tabs` to change\n\t\t * which ones stay out. Ignored in `dropdown` mode.\n\t\t */\n\t\tmaxVisible: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t\tvalidator: (v) => Number.isFinite(v) && v >= 0,\n\t\t},\n\n\t\t/**\n\t\t * How many records each tab matches, keyed by tab index (an array in\n\t\t * tab order works too). A tab with a number here shows it after its\n\t\t * label; a tab without one shows no count. A tab can also carry its\n\t\t * own `count`. `null` (the default) shows no counts at all.\n\t\t *\n\t\t * @type {{[index: number]: number}|Array<number|null>|null}\n\t\t */\n\t\tcounts: {\n\t\t\ttype: [Object, Array],\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['update:active-index', 'update:selected-indices'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Whether the overflow chip's panel is open. */\n\t\t\tmoreOpen: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the tabs draw as board chips (`aria-pressed`, no tablist).\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\t/**\n\t\t * Every tab paired with its own index, so the overflow split can slice\n\t\t * the list without losing the index the parent's filters are keyed on.\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\tentries() {\n\t\t\treturn this.tabs.map((tab, index) => ({ tab, index }))\n\t\t},\n\n\t\t/**\n\t\t * The tabs rendered as pills — all of them unless `maxVisible` caps it.\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\tvisibleEntries() {\n\t\t\tif (!this.maxVisible || this.tabs.length <= this.maxVisible) {\n\t\t\t\treturn this.entries\n\t\t\t}\n\t\t\treturn this.entries.slice(0, this.maxVisible)\n\t\t},\n\n\t\t/**\n\t\t * The tabs moved into the overflow menu (empty when `maxVisible` is 0\n\t\t * or there is nothing to hide).\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\toverflowEntries() {\n\t\t\tif (!this.maxVisible || this.tabs.length <= this.maxVisible) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn this.entries.slice(this.maxVisible)\n\t\t},\n\n\t\t/**\n\t\t * The active tabs that live in the overflow menu.\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\tactiveOverflowEntries() {\n\t\t\treturn this.overflowEntries.filter((e) => this.isChipActive(e.index))\n\t\t},\n\n\t\t/**\n\t\t * Text on the overflow chip — an active hidden tab names itself there,\n\t\t * so the strip never reads \"All\" while the list is narrowed. Nothing\n\t\t * active leaves the chip as the bare `⋯` glyph.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\toverflowLabel() {\n\t\t\tconst active = this.activeOverflowEntries\n\t\t\tif (active.length === 1) {\n\t\t\t\treturn active[0].tab.label\n\t\t\t}\n\t\t\tif (active.length > 1) {\n\t\t\t\treturn t('nextcloud-vue', '{count} filters', { count: active.length })\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/** @return {string} Accessible name for the overflow chip. */\n\t\toverflowAriaLabel() {\n\t\t\tif (this.overflowLabel) {\n\t\t\t\treturn t('nextcloud-vue', '{label} — show the other filters', { label: this.overflowLabel })\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', '{count} more filters', { count: this.overflowEntries.length })\n\t\t},\n\n\t\t/** @return {string} Heading above the hidden chips in the panel. */\n\t\tmoreHeading() {\n\t\t\treturn t('nextcloud-vue', 'More filters')\n\t\t},\n\n\t\t/**\n\t\t * Dropdown options: every tab that carries a non-empty `filter`,\n\t\t * tagged with its original index. The \"All\" / empty-filter tab is\n\t\t * omitted — an empty selection already means \"all\".\n\t\t *\n\t\t * @return {Array<{label: string, index: number, icon: string}>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n\t\t */\n\t\tdropdownOptions() {\n\t\t\treturn this.tabs\n\t\t\t\t.map((tab, index) => {\n\t\t\t\t\tconst count = this.countOf({ tab, index })\n\t\t\t\t\treturn {\n\t\t\t\t\t\tlabel: count === null ? tab.label : `${tab.label} (${count})`,\n\t\t\t\t\t\ticon: tab.icon,\n\t\t\t\t\t\tindex,\n\t\t\t\t\t\t_empty: this.isEmptyFilter(tab),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.filter((o) => !o._empty)\n\t\t},\n\n\t\t/**\n\t\t * Current NcSelect value — an array of option objects when\n\t\t * `multiple`, a single option object (or null) otherwise.\n\t\t *\n\t\t * @return {object|object[]|null}\n\t\t */\n\t\tdropdownValue() {\n\t\t\tif (this.multiple) {\n\t\t\t\treturn this.dropdownOptions.filter((o) => this.selectedIndices.includes(o.index))\n\t\t\t}\n\t\t\treturn this.dropdownOptions.find((o) => o.index === this.activeIndex) || null\n\t\t},\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * The count to show on a tab, or null for none: `counts[index]`\n\t\t * first, else the tab's own `count`.\n\t\t *\n\t\t * @param {{tab: object, index: number}} entry The tab and its index.\n\t\t * @return {number|null}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n\t\t */\n\t\tcountOf(entry) {\n\t\t\tconst fromProp = this.counts ? this.counts[entry.index] : undefined\n\t\t\tconst value = (fromProp !== undefined && fromProp !== null) ? fromProp : entry.tab.count\n\t\t\treturn (typeof value === 'number' && Number.isFinite(value)) ? value : null\n\t\t},\n\n\t\t/**\n\t\t * Is tab `i` rendered active in chips mode (handles both single\n\t\t * `activeIndex` and `multiple` `selectedIndices`).\n\t\t *\n\t\t * @param {number} i Zero-based tab index.\n\t\t * @return {boolean}\n\t\t */\n\t\tisChipActive(i) {\n\t\t\treturn this.multiple ? this.selectedIndices.includes(i) : i === this.activeIndex\n\t\t},\n\n\t\t/**\n\t\t * Whether a tab's `filter` map is absent/empty (the \"All\" tab).\n\t\t *\n\t\t * @param {{label: string, filter?: object}} tab An entry from the `tabs` prop.\n\t\t * @return {boolean} True when the tab applies no filter, so selecting it\n\t\t * clears the multi-selection rather than adding to it.\n\t\t */\n\t\tisEmptyFilter(tab) {\n\t\t\treturn !tab || !tab.filter || Object.keys(tab.filter).length === 0\n\t\t},\n\n\t\t/**\n\t\t * Chip-click handler. In single mode emits the new active index; in\n\t\t * multiple mode toggles the index in/out of the selection. Clicking\n\t\t * an empty-filter (\"All\") chip clears the multi-selection.\n\t\t *\n\t\t * @param {number} i Zero-based tab index.\n\t\t */\n\t\tonClick(i) {\n\t\t\tif (this.multiple) {\n\t\t\t\tif (this.isEmptyFilter(this.tabs[i])) {\n\t\t\t\t\t/**\n\t\t\t\t\t * @event update:selected-indices Active tab indices changed (multiple mode).\n\t\t\t\t\t * @type {number[]}\n\t\t\t\t\t */\n\t\t\t\t\tthis.$emit('update:selected-indices', [])\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst next = this.selectedIndices.includes(i)\n\t\t\t\t\t? this.selectedIndices.filter((x) => x !== i)\n\t\t\t\t\t: [...this.selectedIndices, i]\n\t\t\t\tthis.$emit('update:selected-indices', next)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (i === this.activeIndex) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('update:active-index', i)\n\t\t},\n\n\t\t/**\n\t\t * A chip clicked inside the overflow panel. Single-select closes the\n\t\t * panel on the way out; multi-select keeps it open so several lenses\n\t\t * can be toggled in one visit.\n\t\t *\n\t\t * @param {number} i Zero-based tab index.\n\t\t */\n\t\tonOverflowClick(i) {\n\t\t\tthis.onClick(i)\n\t\t\tif (!this.multiple) {\n\t\t\t\tthis.moreOpen = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * NcSelect input handler — normalises the option object(s) back to\n\t\t * indices and emits the matching model event.\n\t\t *\n\t\t * @param {object|object[]|null} val Selected option object(s).\n\t\t */\n\t\tonSelectInput(val) {\n\t\t\tif (this.multiple) {\n\t\t\t\tconst arr = Array.isArray(val) ? val : []\n\t\t\t\tthis.$emit('update:selected-indices', arr.map((o) => o.index))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('update:active-index', val ? val.index : null)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-quick-filter-bar {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 12px;\n\tborder-bottom: 1px solid var(--color-border);\n\tbackground: var(--color-main-background);\n}\n\n.cn-quick-filter-bar__tabs {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n}\n\n/* Inline variant — bare strip for embedding inside another bar (e.g. the\n CnActionsBar #filters slot), so it reads as a control beside the view toggle\n rather than a separate bordered row. */\n.cn-quick-filter-bar--inline {\n\tpadding: 0;\n\tborder-bottom: none;\n\tbackground: transparent;\n}\n\n/* Dropdown variant — give the NcSelect a sensible minimum so multi-select\n chips have room without stretching the whole bar. */\n.cn-quick-filter-bar--dropdown {\n\talign-items: center;\n}\n\n.cn-quick-filter-bar__select {\n\tmin-width: 220px;\n}\n\n.cn-quick-filter-bar__tab {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-pill, 999px);\n\tbackground: transparent;\n\tcolor: var(--color-main-text);\n\tfont-size: 13px;\n\tfont-weight: 500;\n\tcursor: pointer;\n\ttransition: background-color 120ms, color 120ms, border-color 120ms;\n}\n\n.cn-quick-filter-bar__tab:hover,\n.cn-quick-filter-bar__tab:focus-visible {\n\tbackground: var(--color-background-hover);\n\toutline: none;\n}\n\n.cn-quick-filter-bar__tab--active {\n\tbackground: var(--color-primary-element, var(--color-primary, #4376fc));\n\tcolor: var(--color-primary-element-text, #fff);\n\tborder-color: var(--color-primary-element, var(--color-primary, #4376fc));\n}\n\n.cn-quick-filter-bar__icon {\n\tdisplay: inline-flex;\n}\n\n.cn-quick-filter-bar__label {\n\tline-height: 1;\n}\n\n/* Inherits the chip's own text colour, so it stays readable on the active\n (primary) fill as well as on the resting one. The count's text starts with\n a space: the chip is a flex row, so the space is not drawn, but it keeps a\n screen reader from reading label and count as one word. */\n.cn-quick-filter-bar__count {\n\tmin-width: 1.5em;\n\tpadding: 2px 6px;\n\tborder: 1px solid currentColor;\n\tborder-radius: var(--border-radius-pill, 10px);\n\tfont-size: 0.85em;\n\tfont-weight: 600;\n\tline-height: 1;\n\ttext-align: center;\n}\n\n/* NcPopover wraps its trigger in a plain `<div class=\"v-popper\">`, which as a\n block would drop the chip onto its own line under the strip. */\n.cn-quick-filter-bar > :deep(.v-popper) {\n\tdisplay: inline-flex;\n}\n\n/* The `⋯` glyph is the chip's whole content while no hidden lens is on, so the\n pill tightens to a circle rather than sitting as a wide empty capsule. */\n.cn-quick-filter-bar__more {\n\tgap: 4px;\n}\n\n.cn-quick-filter-bar__more--icon-only {\n\tpadding: 4px 8px;\n}\n\n.cn-quick-filter-bar__more[aria-expanded='true'] {\n\tborder-color: var(--color-primary-element, var(--color-primary, #4376fc));\n}\n\n.cn-quick-filter-bar__more-panel {\n\tpadding: 10px 12px 12px;\n\tmax-width: 280px;\n}\n\n.cn-quick-filter-bar__more-heading {\n\tmargin: 0 0 8px;\n\tfont-size: 12px;\n\tfont-weight: 600;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* The hidden lenses stay chips — the panel is more of the strip, not a\n different control that happens to hold the same filters. */\n.cn-quick-filter-bar__more-list {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 8px;\n\tmax-height: 320px;\n\toverflow-y: auto;\n}\n\n.cn-quick-filter-bar__more-item {\n\tmax-width: 100%;\n}\n\n.cn-quick-filter-bar__more-item .cn-quick-filter-bar__label {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["_normalizeClass","_createVNode","_createElementBlock","_Fragment","_renderList","_createBlock","_createElementVNode","_toDisplayString","_openBlock"],"mappings":"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"}
1
+ {"version":3,"file":"CnQuickFilterBar.vue3.js","sources":["../../../../src/components/CnQuickFilterBar/CnQuickFilterBar.vue?vue&type=template&id=8e121822&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: AGPL-3.0-or-later\n -->\n\n<template>\n\t<!-- Dropdown mode: a single NcSelect (multi or single) instead of the chip strip. -->\n\t<div\n\t\tv-if=\"mode === 'dropdown'\"\n\t\tclass=\"cn-quick-filter-bar cn-quick-filter-bar--dropdown\"\n\t\t:class=\"{ 'cn-quick-filter-bar--inline': inline }\">\n\t\t<NcSelect\n\t\t\t:modelValue=\"dropdownValue\"\n\t\t\t:options=\"dropdownOptions\"\n\t\t\t:multiple=\"multiple\"\n\t\t\t:keepOpen=\"multiple\"\n\t\t\t:clearable=\"true\"\n\t\t\tlabel=\"label\"\n\t\t\tclass=\"cn-quick-filter-bar__select\"\n\t\t\t:inputLabel=\"selectLabel\"\n\t\t\t:aria-label-combobox=\"selectLabel\"\n\t\t\t:placeholder=\"placeholder || selectLabel\"\n\t\t\t@update:modelValue=\"onSelectInput\" />\n\t</div>\n\t<!-- Chips mode (default): the clickable tab strip. -->\n\t<div v-else\n\t\tclass=\"cn-quick-filter-bar\"\n\t\t:class=\"{ 'cn-quick-filter-bar--inline': inline }\">\n\t\t<!-- The tablist holds ONLY tabs: the overflow chip is a sibling, not a\n\t\t child, because a role=\"tablist\" may not contain a menu button. -->\n\t\t<!-- Board look: the tabs are chips, pressed buttons in a group, not a\n\t\t tablist (the chips filter the list, they do not switch a panel). -->\n\t\t<div class=\"cn-quick-filter-bar__tabs\" :role=\"isBoardLook ? 'group' : 'tablist'\" :aria-label=\"isBoardLook ? t('nextcloud-vue', 'Quick filters') : null\">\n\t\t\t<button\n\t\t\t\tv-for=\"entry in visibleEntries\"\n\t\t\t\t:key=\"entry.tab.label + ':' + entry.index\"\n\t\t\t\ttype=\"button\"\n\t\t\t\t:role=\"isBoardLook ? null : 'tab'\"\n\t\t\t\t:aria-selected=\"isBoardLook ? null : (isChipActive(entry.index) ? 'true' : 'false')\"\n\t\t\t\t:aria-pressed=\"isBoardLook ? (isChipActive(entry.index) ? 'true' : 'false') : null\"\n\t\t\t\tclass=\"cn-quick-filter-bar__tab\"\n\t\t\t\t:class=\"[{ 'cn-quick-filter-bar__tab--active': isChipActive(entry.index) }]\"\n\t\t\t\t@click=\"onClick(entry.index)\">\n\t\t\t\t<CnIcon\n\t\t\t\t\tv-if=\"entry.tab.icon\"\n\t\t\t\t\t:name=\"entry.tab.icon\"\n\t\t\t\t\t:size=\"16\"\n\t\t\t\t\tclass=\"cn-quick-filter-bar__icon\" />\n\t\t\t\t<span class=\"cn-quick-filter-bar__label\">{{ tl(entry.tab.label) }}</span>\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"countOf(entry) !== null\"\n\t\t\t\t\tclass=\"cn-quick-filter-bar__count\"\n\t\t\t\t\tdata-testid=\"cn-quick-filter-count\">{{ ' ' + countOf(entry) }}</span>\n\t\t\t</button>\n\t\t</div>\n\t\t<!-- The overflow is a CHIP and not a toolbar button: it is one of the\n\t\t lenses, so it carries the same pill, the same active fill and the\n\t\t label of whichever hidden lens is on. -->\n\t\t<NcPopover\n\t\t\tv-if=\"overflowEntries.length > 0\"\n\t\t\tv-model:shown=\"moreOpen\"\n\t\t\t:triggers=\"[]\"\n\t\t\tpopupRole=\"dialog\"\n\t\t\tplacement=\"bottom-end\"\n\t\t\tpopoverBaseClass=\"cn-quick-filter-bar__popper\">\n\t\t\t<template #trigger>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tdata-testid=\"cn-quick-filter-more\"\n\t\t\t\t\tclass=\"cn-quick-filter-bar__tab cn-quick-filter-bar__more\"\n\t\t\t\t\t:class=\"{\n\t\t\t\t\t\t'cn-quick-filter-bar__tab--active': activeOverflowEntries.length > 0,\n\t\t\t\t\t\t'cn-quick-filter-bar__more--icon-only': !overflowLabel,\n\t\t\t\t\t}\"\n\t\t\t\t\t:aria-expanded=\"moreOpen ? 'true' : 'false'\"\n\t\t\t\t\taria-haspopup=\"dialog\"\n\t\t\t\t\t:aria-label=\"overflowAriaLabel\"\n\t\t\t\t\t:title=\"overflowAriaLabel\"\n\t\t\t\t\t@click=\"moreOpen = !moreOpen\">\n\t\t\t\t\t<span v-if=\"overflowLabel\" class=\"cn-quick-filter-bar__label\">{{ overflowLabel }}</span>\n\t\t\t\t\t<DotsHorizontal :size=\"16\" class=\"cn-quick-filter-bar__icon\" />\n\t\t\t\t</button>\n\t\t\t</template>\n\t\t\t<div\n\t\t\t\tclass=\"cn-quick-filter-bar__more-panel\"\n\t\t\t\t@keydown.escape.stop=\"moreOpen = false\">\n\t\t\t\t<p class=\"cn-quick-filter-bar__more-heading\">\n\t\t\t\t\t{{ moreHeading }}\n\t\t\t\t</p>\n\t\t\t\t<div class=\"cn-quick-filter-bar__more-list\">\n\t\t\t\t\t<button\n\t\t\t\t\t\tv-for=\"entry in overflowEntries\"\n\t\t\t\t\t\t:key=\"entry.tab.label + ':' + entry.index\"\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tdata-testid=\"cn-quick-filter-more-item\"\n\t\t\t\t\t\tclass=\"cn-quick-filter-bar__tab cn-quick-filter-bar__more-item\"\n\t\t\t\t\t\t:class=\"{ 'cn-quick-filter-bar__tab--active': isChipActive(entry.index) }\"\n\t\t\t\t\t\t:aria-pressed=\"isChipActive(entry.index) ? 'true' : 'false'\"\n\t\t\t\t\t\t@click=\"onOverflowClick(entry.index)\">\n\t\t\t\t\t\t<CnIcon\n\t\t\t\t\t\t\tv-if=\"entry.tab.icon\"\n\t\t\t\t\t\t\t:name=\"entry.tab.icon\"\n\t\t\t\t\t\t\t:size=\"16\"\n\t\t\t\t\t\t\tclass=\"cn-quick-filter-bar__icon\" />\n\t\t\t\t\t\t<span class=\"cn-quick-filter-bar__label\">{{ tl(entry.tab.label) }}</span>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tv-if=\"countOf(entry) !== null\"\n\t\t\t\t\t\t\tclass=\"cn-quick-filter-bar__count\"\n\t\t\t\t\t\t\tdata-testid=\"cn-quick-filter-count\">{{ ' ' + countOf(entry) }}</span>\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</NcPopover>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcPopover, NcSelect } from '@nextcloud/vue'\nimport DotsHorizontal from 'vue-material-design-icons/DotsHorizontal.vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport { normalizeLook } from '../../composables/useLook.js'\n\n/**\n * CnQuickFilterBar — quick-filter control rendered above a `type:\"index\"`\n * page's table. Each tab carries a manifest `filter` map that `CnIndexPage`\n * merges into the `useListView` fetch when the tab is active.\n *\n * Two presentations, chosen by `mode`:\n * - `'chips'` (default) — a horizontal row of pill-shaped buttons; the\n * active one is filled, the rest outlined. `maxVisible` caps how many\n * pills render and moves the rest into an overflow menu.\n * - `'dropdown'` — a single `NcSelect`. The \"All\" / empty-filter tab is\n * dropped from the options (an empty selection means \"all\").\n *\n * Two selection cardinalities, chosen by `multiple`:\n * - single (default) — one active tab; uses the `activeIndex` v-model\n * (`update:active-index`).\n * - `multiple` — several tabs at once; uses the `selectedIndices` array\n * prop + `update:selected-indices` event. `CnIndexPage` ORs the selected\n * tabs' filters together.\n *\n * Implementation is deliberately a thin styled `<button>` list (chips) /\n * `NcSelect` (dropdown) rather than `NcAppNavigation*` — this lives INSIDE\n * the index page, not as the app's main nav.\n */\nexport default {\n\tname: 'CnQuickFilterBar',\n\n\tcomponents: { CnIcon, DotsHorizontal, NcPopover, NcSelect },\n\n\tinject: {\n\t\t/** The look CnAppRoot provides; `board` draws the tabs as pressed chips. */\n\t\tcnLook: { default: 'nextcloud' },\n\t\t/**\n\t\t * The host's label lookup (CnAppRoot provides it); identity without one, so a\n\t\t * label that is not a key renders as written.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\t// NO `model: { prop, event }` OPTION.\n\t//\n\t// The Vue-2 `model` option is REMOVED in Vue 3 — read by nothing, warned\n\t// about by nothing — so it survived the migration as a declaration that\n\t// looked authoritative while doing exactly zero. The component's real\n\t// contract is the explicit `:active-index` / `@update:active-index` pair\n\t// (and `:selected-indices` / `@update:selected-indices` in `multiple`\n\t// mode), which is what `CnIndexPage` binds. Surfaced by\n\t// `vue/no-deprecated-model-definition`, which this repo now arms through\n\t// its own published preset.\n\n\tprops: {\n\t\t/**\n\t\t * Tab definitions — same shape `pages[].config.quickFilters[]` uses.\n\t\t * `filter` is consumed by the parent (CnIndexPage), not this component.\n\t\t */\n\t\ttabs: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t\tvalidator: (arr) => Array.isArray(arr) && arr.every((t) => t && typeof t.label === 'string'),\n\t\t},\n\n\t\t/**\n\t\t * Render bare (no padding / bottom border / background) for embedding\n\t\t * inline inside another bar — e.g. the `#filters` slot of `CnActionsBar`,\n\t\t * so the tabs sit beside the view toggle instead of as a separate row.\n\t\t */\n\t\tinline: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Index of the currently active tab, or `null` for none active. Single-select only. */\n\t\tactiveIndex: {\n\t\t\ttype: Number,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Presentation: `'chips'` (pill button strip, default) or\n\t\t * `'dropdown'` (a single `NcSelect`).\n\t\t *\n\t\t * @type {'chips'|'dropdown'}\n\t\t */\n\t\tmode: {\n\t\t\ttype: String,\n\t\t\tdefault: 'chips',\n\t\t\tvalidator: (v) => ['chips', 'dropdown'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Allow more than one tab active at once. Selection is exposed via\n\t\t * `selectedIndices` + `update:selected-indices`; the parent ORs the\n\t\t * selected tabs' filters together.\n\t\t */\n\t\tmultiple: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Active tab indices when `multiple` is set (the array v-model).\n\t\t *\n\t\t * @type {number[]}\n\t\t */\n\t\tselectedIndices: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Accessible label / placeholder for the dropdown control. */\n\t\tselectLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: 'Filter',\n\t\t},\n\n\t\t/** Placeholder text for the dropdown (falls back to `selectLabel`). */\n\t\tplaceholder: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Chips mode only: how many pills render inline before the rest move\n\t\t * into an overflow menu. `0` (the default) renders every tab, which is\n\t\t * the behaviour this component has always had. The visible set is the\n\t\t * FIRST `maxVisible` tabs in declared order — reorder `tabs` to change\n\t\t * which ones stay out. Ignored in `dropdown` mode.\n\t\t */\n\t\tmaxVisible: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t\tvalidator: (v) => Number.isFinite(v) && v >= 0,\n\t\t},\n\n\t\t/**\n\t\t * How many records each tab matches, keyed by tab index (an array in\n\t\t * tab order works too). A tab with a number here shows it after its\n\t\t * label; a tab without one shows no count. A tab can also carry its\n\t\t * own `count`. `null` (the default) shows no counts at all.\n\t\t *\n\t\t * @type {{[index: number]: number}|Array<number|null>|null}\n\t\t */\n\t\tcounts: {\n\t\t\ttype: [Object, Array],\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['update:active-index', 'update:selected-indices'],\n\n\tdata() {\n\t\treturn {\n\t\t\t/** Whether the overflow chip's panel is open. */\n\t\t\tmoreOpen: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the tabs draw as board chips (`aria-pressed`, no tablist).\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\t/**\n\t\t * Every tab paired with its own index, so the overflow split can slice\n\t\t * the list without losing the index the parent's filters are keyed on.\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\tentries() {\n\t\t\treturn this.tabs.map((tab, index) => ({ tab, index }))\n\t\t},\n\n\t\t/**\n\t\t * The tabs rendered as pills — all of them unless `maxVisible` caps it.\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\tvisibleEntries() {\n\t\t\tif (!this.maxVisible || this.tabs.length <= this.maxVisible) {\n\t\t\t\treturn this.entries\n\t\t\t}\n\t\t\treturn this.entries.slice(0, this.maxVisible)\n\t\t},\n\n\t\t/**\n\t\t * The tabs moved into the overflow menu (empty when `maxVisible` is 0\n\t\t * or there is nothing to hide).\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\toverflowEntries() {\n\t\t\tif (!this.maxVisible || this.tabs.length <= this.maxVisible) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn this.entries.slice(this.maxVisible)\n\t\t},\n\n\t\t/**\n\t\t * The active tabs that live in the overflow menu.\n\t\t *\n\t\t * @return {Array<{tab: object, index: number}>}\n\t\t */\n\t\tactiveOverflowEntries() {\n\t\t\treturn this.overflowEntries.filter((e) => this.isChipActive(e.index))\n\t\t},\n\n\t\t/**\n\t\t * Text on the overflow chip — an active hidden tab names itself there,\n\t\t * so the strip never reads \"All\" while the list is narrowed. Nothing\n\t\t * active leaves the chip as the bare `⋯` glyph.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\toverflowLabel() {\n\t\t\tconst active = this.activeOverflowEntries\n\t\t\tif (active.length === 1) {\n\t\t\t\treturn this.tl(active[0].tab.label)\n\t\t\t}\n\t\t\tif (active.length > 1) {\n\t\t\t\treturn t('nextcloud-vue', '{count} filters', { count: active.length })\n\t\t\t}\n\t\t\treturn ''\n\t\t},\n\n\t\t/** @return {string} Accessible name for the overflow chip. */\n\t\toverflowAriaLabel() {\n\t\t\tif (this.overflowLabel) {\n\t\t\t\treturn t('nextcloud-vue', '{label} — show the other filters', { label: this.overflowLabel })\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', '{count} more filters', { count: this.overflowEntries.length })\n\t\t},\n\n\t\t/** @return {string} Heading above the hidden chips in the panel. */\n\t\tmoreHeading() {\n\t\t\treturn t('nextcloud-vue', 'More filters')\n\t\t},\n\n\t\t/**\n\t\t * Dropdown options: every tab that carries a non-empty `filter`,\n\t\t * tagged with its original index. The \"All\" / empty-filter tab is\n\t\t * omitted — an empty selection already means \"all\".\n\t\t *\n\t\t * @return {Array<{label: string, index: number, icon: string}>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n\t\t */\n\t\tdropdownOptions() {\n\t\t\treturn this.tabs\n\t\t\t\t.map((tab, index) => {\n\t\t\t\t\tconst count = this.countOf({ tab, index })\n\t\t\t\t\treturn {\n\t\t\t\t\t\tlabel: count === null ? this.tl(tab.label) : `${this.tl(tab.label)} (${count})`,\n\t\t\t\t\t\ticon: tab.icon,\n\t\t\t\t\t\tindex,\n\t\t\t\t\t\t_empty: this.isEmptyFilter(tab),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.filter((o) => !o._empty)\n\t\t},\n\n\t\t/**\n\t\t * Current NcSelect value — an array of option objects when\n\t\t * `multiple`, a single option object (or null) otherwise.\n\t\t *\n\t\t * @return {object|object[]|null}\n\t\t */\n\t\tdropdownValue() {\n\t\t\tif (this.multiple) {\n\t\t\t\treturn this.dropdownOptions.filter((o) => this.selectedIndices.includes(o.index))\n\t\t\t}\n\t\t\treturn this.dropdownOptions.find((o) => o.index === this.activeIndex) || null\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * A manifest string through the host's label lookup (`cnTranslate`), so a key\n\t\t * such as \"Mine\" reads in the user's language.\n\t\t *\n\t\t * @param {string} text The text as written in the manifest.\n\t\t * @return {string} The translated text.\n\t\t */\n\t\ttl(text) {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\tt,\n\n\t\t/**\n\t\t * The count to show on a tab, or null for none: `counts[index]`\n\t\t * first, else the tab's own `count`.\n\t\t *\n\t\t * @param {{tab: object, index: number}} entry The tab and its index.\n\t\t * @return {number|null}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-counts-on-filters-and-views\n\t\t */\n\t\tcountOf(entry) {\n\t\t\tconst fromProp = this.counts ? this.counts[entry.index] : undefined\n\t\t\tconst value = (fromProp !== undefined && fromProp !== null) ? fromProp : entry.tab.count\n\t\t\treturn (typeof value === 'number' && Number.isFinite(value)) ? value : null\n\t\t},\n\n\t\t/**\n\t\t * Is tab `i` rendered active in chips mode (handles both single\n\t\t * `activeIndex` and `multiple` `selectedIndices`).\n\t\t *\n\t\t * @param {number} i Zero-based tab index.\n\t\t * @return {boolean}\n\t\t */\n\t\tisChipActive(i) {\n\t\t\treturn this.multiple ? this.selectedIndices.includes(i) : i === this.activeIndex\n\t\t},\n\n\t\t/**\n\t\t * Whether a tab's `filter` map is absent/empty (the \"All\" tab).\n\t\t *\n\t\t * @param {{label: string, filter?: object}} tab An entry from the `tabs` prop.\n\t\t * @return {boolean} True when the tab applies no filter, so selecting it\n\t\t * clears the multi-selection rather than adding to it.\n\t\t */\n\t\tisEmptyFilter(tab) {\n\t\t\treturn !tab || !tab.filter || Object.keys(tab.filter).length === 0\n\t\t},\n\n\t\t/**\n\t\t * Chip-click handler. In single mode emits the new active index; in\n\t\t * multiple mode toggles the index in/out of the selection. Clicking\n\t\t * an empty-filter (\"All\") chip clears the multi-selection.\n\t\t *\n\t\t * @param {number} i Zero-based tab index.\n\t\t */\n\t\tonClick(i) {\n\t\t\tif (this.multiple) {\n\t\t\t\tif (this.isEmptyFilter(this.tabs[i])) {\n\t\t\t\t\t/**\n\t\t\t\t\t * @event update:selected-indices Active tab indices changed (multiple mode).\n\t\t\t\t\t * @type {number[]}\n\t\t\t\t\t */\n\t\t\t\t\tthis.$emit('update:selected-indices', [])\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst next = this.selectedIndices.includes(i)\n\t\t\t\t\t? this.selectedIndices.filter((x) => x !== i)\n\t\t\t\t\t: [...this.selectedIndices, i]\n\t\t\t\tthis.$emit('update:selected-indices', next)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (i === this.activeIndex) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('update:active-index', i)\n\t\t},\n\n\t\t/**\n\t\t * A chip clicked inside the overflow panel. Single-select closes the\n\t\t * panel on the way out; multi-select keeps it open so several lenses\n\t\t * can be toggled in one visit.\n\t\t *\n\t\t * @param {number} i Zero-based tab index.\n\t\t */\n\t\tonOverflowClick(i) {\n\t\t\tthis.onClick(i)\n\t\t\tif (!this.multiple) {\n\t\t\t\tthis.moreOpen = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * NcSelect input handler — normalises the option object(s) back to\n\t\t * indices and emits the matching model event.\n\t\t *\n\t\t * @param {object|object[]|null} val Selected option object(s).\n\t\t */\n\t\tonSelectInput(val) {\n\t\t\tif (this.multiple) {\n\t\t\t\tconst arr = Array.isArray(val) ? val : []\n\t\t\t\tthis.$emit('update:selected-indices', arr.map((o) => o.index))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$emit('update:active-index', val ? val.index : null)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-quick-filter-bar {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 8px 12px;\n\tborder-bottom: 1px solid var(--color-border);\n\tbackground: var(--color-main-background);\n}\n\n.cn-quick-filter-bar__tabs {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n}\n\n/* Inline variant — bare strip for embedding inside another bar (e.g. the\n CnActionsBar #filters slot), so it reads as a control beside the view toggle\n rather than a separate bordered row. */\n.cn-quick-filter-bar--inline {\n\tpadding: 0;\n\tborder-bottom: none;\n\tbackground: transparent;\n}\n\n/* Dropdown variant — give the NcSelect a sensible minimum so multi-select\n chips have room without stretching the whole bar. */\n.cn-quick-filter-bar--dropdown {\n\talign-items: center;\n}\n\n.cn-quick-filter-bar__select {\n\tmin-width: 220px;\n}\n\n.cn-quick-filter-bar__tab {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tgap: 6px;\n\tpadding: 4px 12px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-pill, 999px);\n\tbackground: transparent;\n\tcolor: var(--color-main-text);\n\tfont-size: 13px;\n\tfont-weight: 500;\n\tcursor: pointer;\n\ttransition: background-color 120ms, color 120ms, border-color 120ms;\n}\n\n.cn-quick-filter-bar__tab:hover,\n.cn-quick-filter-bar__tab:focus-visible {\n\tbackground: var(--color-background-hover);\n\toutline: none;\n}\n\n.cn-quick-filter-bar__tab--active {\n\tbackground: var(--color-primary-element, var(--color-primary, #4376fc));\n\tcolor: var(--color-primary-element-text, #fff);\n\tborder-color: var(--color-primary-element, var(--color-primary, #4376fc));\n}\n\n.cn-quick-filter-bar__icon {\n\tdisplay: inline-flex;\n}\n\n.cn-quick-filter-bar__label {\n\tline-height: 1;\n}\n\n/* Inherits the chip's own text colour, so it stays readable on the active\n (primary) fill as well as on the resting one. The count's text starts with\n a space: the chip is a flex row, so the space is not drawn, but it keeps a\n screen reader from reading label and count as one word. */\n.cn-quick-filter-bar__count {\n\tmin-width: 1.5em;\n\tpadding: 2px 6px;\n\tborder: 1px solid currentColor;\n\tborder-radius: var(--border-radius-pill, 10px);\n\tfont-size: 0.85em;\n\tfont-weight: 600;\n\tline-height: 1;\n\ttext-align: center;\n}\n\n/* NcPopover wraps its trigger in a plain `<div class=\"v-popper\">`, which as a\n block would drop the chip onto its own line under the strip. */\n.cn-quick-filter-bar > :deep(.v-popper) {\n\tdisplay: inline-flex;\n}\n\n/* The `⋯` glyph is the chip's whole content while no hidden lens is on, so the\n pill tightens to a circle rather than sitting as a wide empty capsule. */\n.cn-quick-filter-bar__more {\n\tgap: 4px;\n}\n\n.cn-quick-filter-bar__more--icon-only {\n\tpadding: 4px 8px;\n}\n\n.cn-quick-filter-bar__more[aria-expanded='true'] {\n\tborder-color: var(--color-primary-element, var(--color-primary, #4376fc));\n}\n\n.cn-quick-filter-bar__more-panel {\n\tpadding: 10px 12px 12px;\n\tmax-width: 280px;\n}\n\n.cn-quick-filter-bar__more-heading {\n\tmargin: 0 0 8px;\n\tfont-size: 12px;\n\tfont-weight: 600;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* The hidden lenses stay chips — the panel is more of the strip, not a\n different control that happens to hold the same filters. */\n.cn-quick-filter-bar__more-list {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 8px;\n\tmax-height: 320px;\n\toverflow-y: auto;\n}\n\n.cn-quick-filter-bar__more-item {\n\tmax-width: 100%;\n}\n\n.cn-quick-filter-bar__more-item .cn-quick-filter-bar__label {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n</style>\n"],"names":["_normalizeClass","_createVNode","_createElementBlock","_Fragment","_renderList","_createBlock","_createElementVNode","_toDisplayString","_openBlock"],"mappings":"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"}
@@ -72,6 +72,11 @@ var script = {
72
72
  * @spec openspec/changes/screens-dashboard-parity/specs/dashboard-page/spec.md#requirement-the-board-kpi-tile
73
73
  */
74
74
  cnLook: { default: 'nextcloud' },
75
+ /**
76
+ * The host's label lookup (CnAppRoot provides it); identity without one, so a
77
+ * label that is not a key renders as written.
78
+ */
79
+ cnTranslate: { default: () => (key) => key },
75
80
  },
76
81
 
77
82
  props: {
@@ -361,6 +366,17 @@ var script = {
361
366
  },
362
367
 
363
368
  methods: {
369
+ /**
370
+ * A manifest string through the host's label lookup (`cnTranslate`), so a key
371
+ * such as "Mine" reads in the user's language.
372
+ *
373
+ * @param {string} text The text as written in the manifest.
374
+ * @return {string} The translated text.
375
+ */
376
+ tl(text) {
377
+ return typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text
378
+ },
379
+
364
380
  formatBreakdownLabel(key) {
365
381
  return key.charAt(0).toUpperCase() + key.slice(1) + ':'
366
382
  },
@@ -1 +1 @@
1
- {"version":3,"file":"CnStatsBlock.vue2.js","sources":["../../../../src/components/CnStatsBlock/CnStatsBlock.vue"],"sourcesContent":["<!--\n - CnStatsBlock renders the CANONICAL KPI card (src/css/kpi-card.css) — the\n - same markup and the same stylesheet CnStatWidget renders, so OpenCatalogi's\n - KPIs and dossiq's are one design rather than two. This component's own\n - `cn-stats-block*` classes stay on the same elements for backwards\n - compatibility with app CSS that targets them; the LOOK comes from the\n - shared sheet.\n -\n - The comment lives OUT here rather than inside <template>: a comment node\n - beside the root element makes the component multi-root in Vue 3, and a\n - multi-root component has no root to put class attributes on — `rootClasses`\n - silently stopped reaching the DOM.\n-->\n<template>\n\t<component\n\t\t:is=\"componentTag\"\n\t\tclass=\"cn-kpi-card cn-stats-block\"\n\t\t:class=\"rootClasses\"\n\t\tv-bind=\"componentAttrs\"\n\t\t@click=\"onClick\">\n\t\t<!-- Icon -->\n\t\t<div v-if=\"hasIcon && !isStacked && !isBoardLook\" class=\"cn-kpi-card__icon cn-stats-block__icon\" :class=\"iconClasses\">\n\t\t\t<slot name=\"icon\">\n\t\t\t\t<component :is=\"icon\" v-if=\"icon\" :size=\"iconSize\" />\n\t\t\t</slot>\n\t\t</div>\n\n\t\t<!-- Content -->\n\t\t<div class=\"cn-kpi-card__body cn-stats-block__content\">\n\t\t\t<div class=\"cn-stats-block__header\">\n\t\t\t\t<!-- Board look: an 18px glyph before the label, only on a link tile. -->\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"isBoardLook && isInteractive && hasIcon\"\n\t\t\t\t\tclass=\"cn-kpi-card__glyph cn-stats-block__glyph\"\n\t\t\t\t\tdata-testid=\"cn-stats-block-glyph\"\n\t\t\t\t\taria-hidden=\"true\">\n\t\t\t\t\t<slot name=\"icon\">\n\t\t\t\t\t\t<component :is=\"icon\" v-if=\"icon\" :size=\"18\" />\n\t\t\t\t\t</slot>\n\t\t\t\t</span>\n\t\t\t\t<h4 class=\"cn-kpi-card__title\" :title=\"title || undefined\">\n\t\t\t\t\t{{ title || t('nextcloud-vue', 'Objects') }}\n\t\t\t\t</h4>\n\t\t\t</div>\n\n\t\t\t<div v-if=\"hasError\" class=\"cn-stats-block__count cn-stats-block__count--error\">\n\t\t\t\t<span class=\"cn-stats-block__count-value\">&mdash;</span>\n\t\t\t\t<span class=\"cn-stats-block__count-label\">{{ errorLabel }}</span>\n\t\t\t</div>\n\t\t\t<div v-else-if=\"hasValueSlot || count > 0 || (showZeroCount && count === 0)\" class=\"cn-kpi-card__value-row cn-stats-block__count\">\n\t\t\t\t<span class=\"cn-kpi-card__value cn-stats-block__count-value\">\n\t\t\t\t\t<!-- @slot Override the prominently-displayed value — render a pre-formatted string (currency, percent, a \"—\" placeholder, …). `count` stays the raw number; this is presentation only. Defaults to the localized count. -->\n\t\t\t\t\t<!-- @binding {number} count The raw numeric count. -->\n\t\t\t\t\t<!-- @binding {string} formatted The default localized count string. -->\n\t\t\t\t\t<slot name=\"value\" :count=\"count\" :formatted=\"formattedCount\">{{ formattedCount }}</slot>\n\t\t\t\t</span>\n\t\t\t\t<span class=\"cn-kpi-card__label cn-stats-block__count-label\">{{ countLabel }}</span>\n\t\t\t</div>\n\t\t\t<div v-else-if=\"loading\" class=\"cn-stats-block__loading\">\n\t\t\t\t<NcLoadingIcon :size=\"16\" />\n\t\t\t\t{{ loadingLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-stats-block__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\n\t\t\t<!-- Breakdown details -->\n\t\t\t<div\n\t\t\t\tv-if=\"hasError === false && breakdown && (hasValueSlot || count > 0 || showZeroCount)\"\n\t\t\t\tclass=\"cn-stats-block__breakdown\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"(value, key) in breakdown\"\n\t\t\t\t\t:key=\"key\"\n\t\t\t\t\tclass=\"cn-stats-block__breakdown-item\">\n\t\t\t\t\t<span class=\"cn-stats-block__breakdown-label\">{{ formatBreakdownLabel(key) }}</span>\n\t\t\t\t\t<span\n\t\t\t\t\t\tclass=\"cn-stats-block__breakdown-value\"\n\t\t\t\t\t\t:class=\"'cn-stats-block__breakdown-value--' + key\">\n\t\t\t\t\t\t{{ value }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</component>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { normalizeLook } from '../../composables/useLook.js'\n\n// The canonical KPI look, shared with CnStatWidget. Imported here so the\n// card is styled even when the consuming app has not pulled in the\n// library's global css/index.css.\nimport '../../css/kpi-card.css'\n\n/**\n * CnStatsBlock — Statistics display card with icon, count, and optional breakdown.\n *\n * Renders the canonical KPI card: a large circular icon beside a left-aligned\n * number, drawing no box of its own because the wrapper around it already\n * draws one. Supports colour variants, icons and clickable state. Use in a\n * CnKpiGrid for responsive dashboard layouts.\n *\n * Basic (canonical horizontal card)\n * ```vue\n * <CnStatsBlock title=\"Cases\" :count=\"42\" count-label=\"open cases\" />\n * ```\n *\n * With icon and variant\n * ```vue\n * <CnStatsBlock\n * title=\"Open Cases\"\n * :count=\"42\"\n * :icon=\"BriefcaseOutline\"\n * variant=\"primary\"\n * clickable\n * @click=\"goToCases\" />\n * ```\n *\n * Stacked and boxed — for a block mounted with no wrapper around it\n * ```vue\n * <CnStatsBlock title=\"Cases\" :count=\"42\" vertical filled />\n * ```\n *\n * With route-based navigation (renders as <router-link>)\n * ```vue\n * <CnStatsBlock\n * title=\"Open Cases\"\n * :count=\"42\"\n * :icon=\"BriefcaseOutline\"\n * variant=\"primary\"\n * :route=\"{ name: 'Cases', query: { status: 'open' } }\" />\n * ```\n *\n * With breakdown\n * ```vue\n * <CnStatsBlock\n * title=\"Cases\"\n * :count=\"42\"\n * :breakdown=\"{ total: 100, invalid: 3, deleted: 5, published: 92 }\" />\n * ```\n *\n * Custom icon slot\n * ```vue\n * <CnStatsBlock title=\"Files\" :count=\"128\">\n * <template #icon>\n * <FileDocumentOutline :size=\"24\" />\n * </template>\n * </CnStatsBlock>\n * ```\n */\nexport default {\n\tname: 'CnStatsBlock',\n\n\tcomponents: {\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * The look the app is drawn in, provided by CnAppRoot.\n\t\t *\n\t\t * @spec openspec/changes/screens-dashboard-parity/specs/dashboard-page/spec.md#requirement-the-board-kpi-tile\n\t\t */\n\t\tcnLook: { default: 'nextcloud' },\n\t},\n\n\tprops: {\n\t\t/** Block title */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The main count number to display prominently */\n\t\tcount: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t},\n\n\t\t/** Label displayed next to the count */\n\t\tcountLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'objects'),\n\t\t},\n\n\t\t/** Detailed breakdown object (key-value pairs) */\n\t\tbreakdown: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Whether data is currently loading */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Text shown while loading */\n\t\tloadingLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Loading…'),\n\t\t},\n\n\t\t/** Text shown when count is 0 */\n\t\temptyLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No items found'),\n\t\t},\n\n\t\t/**\n\t\t * The tile could not load its number. Anything truthy counts — pass the\n\t\t * caught error itself, or just `true`.\n\t\t *\n\t\t * The tile then shows a dash and `errorLabel` INSTEAD of a count, and\n\t\t * tints itself `error`. This takes precedence over `count`, `loading`\n\t\t * and `emptyLabel` deliberately: a stale or defaulted number rendered\n\t\t * during a failure is the exact thing this prop exists to stop. A\n\t\t * dashboard showing 0 because the backend is down is worse than one\n\t\t * showing nothing, because 0 is a number a reader will believe.\n\t\t *\n\t\t * @type {boolean|string|Error|null}\n\t\t */\n\t\terror: {\n\t\t\t// TYPE ORDER IS SEMANTIC HERE. Vue casts an empty value to `true`\n\t\t\t// when Boolean appears BEFORE String in the union, so\n\t\t\t// `[Boolean, String]` would read `error=\"\"` as an error. A caller\n\t\t\t// clearing its message back to '' is reporting RECOVERY, and that\n\t\t\t// would have pinned the tile in its error state after the fetch\n\t\t\t// succeeded again. String first keeps '' falsy.\n\t\t\t// eslint-disable-next-line vue/prefer-prop-type-boolean-first -- Boolean first would make `error=\"\"` truthy, which is the recovery signal.\n\t\t\ttype: [String, Boolean, Object],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Text shown in place of the count when `error` is set. */\n\t\terrorLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Unavailable'),\n\t\t},\n\n\t\t/** Icon component (e.g., imported MDI icon) */\n\t\ticon: {\n\t\t\ttype: [Object, Function],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Icon size in pixels */\n\t\ticonSize: {\n\t\t\ttype: Number,\n\t\t\tdefault: 24,\n\t\t},\n\n\t\t/** Color variant: 'default', 'primary', 'success', 'warning', 'error' */\n\t\tvariant: {\n\t\t\ttype: String,\n\t\t\tdefault: 'default',\n\t\t\tvalidator: (v) => ['default', 'primary', 'success', 'warning', 'error'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Stack the icon above a centred number instead of placing it beside\n\t\t * one. The canonical KPI card is horizontal, so this is the opt-out;\n\t\t * `horizontal` below is kept only for callers that already pass it.\n\t\t */\n\t\tvertical: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * `stacked` is the board's tile, as CnStatWidget `content.layout:\n\t\t * \"stacked\"`: no icon circle, a 14px muted title, the number at\n\t\t * 34px/700 in the text colour. Empty (the default) keeps the KPI card.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stats-block-can-take-the-stacked-board-look\n\t\t */\n\t\tlayout: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Lay the icon left of the content. No longer needed — this is the\n\t\t * canonical card's own layout — and kept so existing callers that pass\n\t\t * `horizontal` keep working. Pass `vertical` to stack instead.\n\t\t *\n\t\t * @deprecated since 2.25.0, the horizontal layout is the default.\n\t\t */\n\t\thorizontal: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Draw the card's own grey box. Off by default: a stats block is\n\t\t * normally rendered inside a wrapper that already draws a card, and a\n\t\t * second box reads as a card inside a card. Turn it on for a block\n\t\t * mounted with no wrapper around it.\n\t\t */\n\t\tfilled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether the card is clickable */\n\t\tclickable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether to display 0 as a count value instead of the empty label */\n\t\tshowZeroCount: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Vue Router location object for declarative navigation.\n\t\t * When set, the card renders as a <router-link> and clickable styles are implied.\n\t\t * { name: 'Cases', query: { status: 'open' } }\n\t\t * { path: '/catalogi' }\n\t\t */\n\t\troute: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['click'],\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the app takes the board look.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\thasIcon() {\n\t\t\treturn this.icon !== null || this.$slots.icon || this.$slots.icon\n\t\t},\n\n\t\t/**\n\t\t * Whether a consumer provided the `value` slot. When set, the value\n\t\t * area always renders (the slot decides what to show, even at count 0).\n\t\t */\n\t\thasValueSlot() {\n\t\t\treturn !!this.$slots.value || !!this.$slots.value\n\t\t},\n\n\t\t/**\n\t\t * Whether the tile is in its error state.\n\t\t *\n\t\t * Anything truthy counts, so a caller can pass the caught error itself\n\t\t * rather than converting it to a boolean first. An empty string is NOT\n\t\t * an error — that is a caller who cleared the message, not one\n\t\t * reporting a failure.\n\t\t *\n\t\t * @return {boolean} True when the tile should show `errorLabel`.\n\t\t */\n\t\thasError() {\n\t\t\treturn Boolean(this.error)\n\t\t},\n\n\t\tformattedCount() {\n\t\t\treturn this.count.toLocaleString()\n\t\t},\n\n\t\t/**\n\t\t * Whether the card is interactive (clickable or has a route).\n\t\t * Used for applying hover/focus styles.\n\t\t */\n\t\tisInteractive() {\n\t\t\treturn !!this.route || this.clickable\n\t\t},\n\n\t\t/**\n\t\t * Determines which HTML element or component to render.\n\t\t * - route set → 'router-link' (SPA navigation)\n\t\t * - clickable → 'a' (app handles click via event)\n\t\t * - neither → 'div' (static display)\n\t\t */\n\t\tcomponentTag() {\n\t\t\tif (this.route) {\n\t\t\t\treturn 'router-link'\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\treturn 'a'\n\t\t\t}\n\t\t\treturn 'div'\n\t\t},\n\n\t\t/**\n\t\t * Dynamic attributes for the root element based on rendering mode.\n\t\t */\n\t\tcomponentAttrs() {\n\t\t\tif (this.route) {\n\t\t\t\treturn { to: this.route, tabindex: '0' }\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\treturn { href: '#', role: 'button', tabindex: '0' }\n\t\t\t}\n\t\t\treturn {}\n\t\t},\n\n\t\t/**\n\t\t * Whether the block takes the stacked board look (`layout`).\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stats-block-can-take-the-stacked-board-look\n\t\t * @return {boolean}\n\t\t */\n\t\tisStacked() {\n\t\t\treturn this.layout === 'stacked'\n\t\t},\n\n\t\trootClasses() {\n\t\t\treturn {\n\t\t\t\t// Canonical + legacy, in pairs: the canonical class carries the\n\t\t\t\t// look, the legacy one keeps existing app CSS matching.\n\t\t\t\t// The canonical card is already horizontal, so the legacy pair\n\t\t\t\t// is emitted for app CSS but carries no look of its own.\n\t\t\t\t'cn-kpi-card--horizontal': !this.isStacked && (this.horizontal || !this.vertical),\n\t\t\t\t'cn-kpi-card--stacked': this.isStacked,\n\t\t\t\t'cn-kpi-card--board': this.isBoardLook,\n\t\t\t\t'cn-stats-block--horizontal': this.horizontal || !this.vertical,\n\t\t\t\t'cn-kpi-card--vertical': this.vertical,\n\t\t\t\t'cn-stats-block--vertical': this.vertical,\n\t\t\t\t'cn-kpi-card--filled': this.filled,\n\t\t\t\t'cn-kpi-card--clickable': this.isInteractive,\n\t\t\t\t'cn-stats-block--clickable': this.isInteractive,\n\t\t\t\t'cn-kpi-card--error': this.hasError,\n\t\t\t\t[`cn-kpi-card--${this.variant}`]: this.variant !== 'default' && this.hasError === false,\n\t\t\t\t// An errored tile tints itself. Requiring the consumer to also\n\t\t\t\t// pass `variant=\"error\"` would mean every caller remembering\n\t\t\t\t// two props to express one state, and forgetting the second is\n\t\t\t\t// invisible — a tile that reads \"Unavailable\" in the default\n\t\t\t\t// colour looks like ordinary content.\n\t\t\t\t'cn-stats-block--error': this.hasError,\n\t\t\t\t[`cn-stats-block--${this.variant}`]: this.variant !== 'default' && this.hasError === false,\n\t\t\t}\n\t\t},\n\n\t\ticonClasses() {\n\t\t\treturn {\n\t\t\t\t[`cn-stats-block__icon--${this.variant}`]: this.variant !== 'default',\n\t\t\t}\n\t\t},\n\n\t},\n\n\tmethods: {\n\t\tformatBreakdownLabel(key) {\n\t\t\treturn key.charAt(0).toUpperCase() + key.slice(1) + ':'\n\t\t},\n\n\t\tonClick(event) {\n\t\t\t// When route is set, router-link handles navigation — do not emit click\n\t\t\tif (this.route) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.$emit('click', event)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/*\n * The card's own look — box, body stack, icon circle, title, value, variants,\n * hover — is the CANONICAL KPI card in src/css/kpi-card.css, shared with\n * CnStatWidget. The rules that lived here duplicated it in different numbers\n * (a 1.2rem count row, a 40px icon, hardcoded rgba() tints that ignored a\n * re-themed palette), and that duplication IS how the two KPI looks drifted.\n * What is left below is this component's own extras: the breakdown block, the\n * loading line and the empty line.\n *\n * Those extras are secondary KPI text, so they read the shared\n * `--cn-kpi-label-*` scale rather than carrying sizes of their own — an empty\n * state that is a different size from the label it replaces is the same drift\n * in miniature.\n */\n\n.cn-stats-block__header {\n\tmax-width: 100%;\n\tmin-width: 0;\n}\n\n.cn-stats-block__loading {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: flex-start;\n\tgap: 6px;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tcolor: var(--cn-kpi-label-color, var(--color-text-maxcontrast));\n}\n\n.cn-stats-block__empty {\n\ttext-align: start;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tcolor: var(--cn-kpi-label-color, var(--color-text-maxcontrast));\n\tfont-style: italic;\n}\n\n.cn-stats-block__breakdown {\n\tmargin-top: 4px;\n\tpadding: 8px;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tbackground: var(--color-background-hover);\n\tborder-radius: var(--border-radius);\n\tborder: 1px solid var(--color-border);\n\twidth: 100%;\n\ttext-align: start;\n}\n\n.cn-stats-block__breakdown-item {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-bottom: 4px;\n}\n\n.cn-stats-block__breakdown-item:last-child {\n\tmargin-bottom: 0;\n}\n\n.cn-stats-block__breakdown-label {\n\tfont-weight: 500;\n\tcolor: var(--color-main-text);\n}\n\n/* The value takes the card's accent, so a breakdown reads as part of the tile\n it sits in rather than as a table that happened to land there. It used to\n carry a `--color-background-hover` chip — on a `--color-background-hover`\n breakdown box, which is to say an invisible chip drawn at the cost of two\n extra declarations and a differently-shaped hit area. The per-key modifiers\n below still win: `invalid`, `deleted` and `published` mean something\n specific and outrank the tile's own colour. */\n.cn-stats-block__breakdown-value {\n\tfont-weight: var(--cn-kpi-title-weight, 600);\n\tcolor: var(--cn-kpi-accent, var(--color-primary-element));\n}\n\n.cn-stats-block__breakdown-value--invalid { color: var(--color-element-warning); }\n\n.cn-stats-block__breakdown-value--deleted { color: var(--color-element-error); }\n\n.cn-stats-block__breakdown-value--published { color: var(--color-element-success); }\n</style>\n"],"names":["t"],"mappings":"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"}
1
+ {"version":3,"file":"CnStatsBlock.vue2.js","sources":["../../../../src/components/CnStatsBlock/CnStatsBlock.vue"],"sourcesContent":["<!--\n - CnStatsBlock renders the CANONICAL KPI card (src/css/kpi-card.css) — the\n - same markup and the same stylesheet CnStatWidget renders, so OpenCatalogi's\n - KPIs and dossiq's are one design rather than two. This component's own\n - `cn-stats-block*` classes stay on the same elements for backwards\n - compatibility with app CSS that targets them; the LOOK comes from the\n - shared sheet.\n -\n - The comment lives OUT here rather than inside <template>: a comment node\n - beside the root element makes the component multi-root in Vue 3, and a\n - multi-root component has no root to put class attributes on — `rootClasses`\n - silently stopped reaching the DOM.\n-->\n<template>\n\t<component\n\t\t:is=\"componentTag\"\n\t\tclass=\"cn-kpi-card cn-stats-block\"\n\t\t:class=\"rootClasses\"\n\t\tv-bind=\"componentAttrs\"\n\t\t@click=\"onClick\">\n\t\t<!-- Icon -->\n\t\t<div v-if=\"hasIcon && !isStacked && !isBoardLook\" class=\"cn-kpi-card__icon cn-stats-block__icon\" :class=\"iconClasses\">\n\t\t\t<slot name=\"icon\">\n\t\t\t\t<component :is=\"icon\" v-if=\"icon\" :size=\"iconSize\" />\n\t\t\t</slot>\n\t\t</div>\n\n\t\t<!-- Content -->\n\t\t<div class=\"cn-kpi-card__body cn-stats-block__content\">\n\t\t\t<div class=\"cn-stats-block__header\">\n\t\t\t\t<!-- Board look: an 18px glyph before the label, only on a link tile. -->\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"isBoardLook && isInteractive && hasIcon\"\n\t\t\t\t\tclass=\"cn-kpi-card__glyph cn-stats-block__glyph\"\n\t\t\t\t\tdata-testid=\"cn-stats-block-glyph\"\n\t\t\t\t\taria-hidden=\"true\">\n\t\t\t\t\t<slot name=\"icon\">\n\t\t\t\t\t\t<component :is=\"icon\" v-if=\"icon\" :size=\"18\" />\n\t\t\t\t\t</slot>\n\t\t\t\t</span>\n\t\t\t\t<h4 class=\"cn-kpi-card__title\" :title=\"title || undefined\">\n\t\t\t\t\t{{ title ? tl(title) : t('nextcloud-vue', 'Objects') }}\n\t\t\t\t</h4>\n\t\t\t</div>\n\n\t\t\t<div v-if=\"hasError\" class=\"cn-stats-block__count cn-stats-block__count--error\">\n\t\t\t\t<span class=\"cn-stats-block__count-value\">&mdash;</span>\n\t\t\t\t<span class=\"cn-stats-block__count-label\">{{ errorLabel }}</span>\n\t\t\t</div>\n\t\t\t<div v-else-if=\"hasValueSlot || count > 0 || (showZeroCount && count === 0)\" class=\"cn-kpi-card__value-row cn-stats-block__count\">\n\t\t\t\t<span class=\"cn-kpi-card__value cn-stats-block__count-value\">\n\t\t\t\t\t<!-- @slot Override the prominently-displayed value — render a pre-formatted string (currency, percent, a \"—\" placeholder, …). `count` stays the raw number; this is presentation only. Defaults to the localized count. -->\n\t\t\t\t\t<!-- @binding {number} count The raw numeric count. -->\n\t\t\t\t\t<!-- @binding {string} formatted The default localized count string. -->\n\t\t\t\t\t<slot name=\"value\" :count=\"count\" :formatted=\"formattedCount\">{{ formattedCount }}</slot>\n\t\t\t\t</span>\n\t\t\t\t<span class=\"cn-kpi-card__label cn-stats-block__count-label\">{{ countLabel ? tl(countLabel) : countLabel }}</span>\n\t\t\t</div>\n\t\t\t<div v-else-if=\"loading\" class=\"cn-stats-block__loading\">\n\t\t\t\t<NcLoadingIcon :size=\"16\" />\n\t\t\t\t{{ loadingLabel }}\n\t\t\t</div>\n\t\t\t<div v-else class=\"cn-stats-block__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</div>\n\n\t\t\t<!-- Breakdown details -->\n\t\t\t<div\n\t\t\t\tv-if=\"hasError === false && breakdown && (hasValueSlot || count > 0 || showZeroCount)\"\n\t\t\t\tclass=\"cn-stats-block__breakdown\">\n\t\t\t\t<div\n\t\t\t\t\tv-for=\"(value, key) in breakdown\"\n\t\t\t\t\t:key=\"key\"\n\t\t\t\t\tclass=\"cn-stats-block__breakdown-item\">\n\t\t\t\t\t<span class=\"cn-stats-block__breakdown-label\">{{ formatBreakdownLabel(key) }}</span>\n\t\t\t\t\t<span\n\t\t\t\t\t\tclass=\"cn-stats-block__breakdown-value\"\n\t\t\t\t\t\t:class=\"'cn-stats-block__breakdown-value--' + key\">\n\t\t\t\t\t\t{{ value }}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</component>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { normalizeLook } from '../../composables/useLook.js'\n\n// The canonical KPI look, shared with CnStatWidget. Imported here so the\n// card is styled even when the consuming app has not pulled in the\n// library's global css/index.css.\nimport '../../css/kpi-card.css'\n\n/**\n * CnStatsBlock — Statistics display card with icon, count, and optional breakdown.\n *\n * Renders the canonical KPI card: a large circular icon beside a left-aligned\n * number, drawing no box of its own because the wrapper around it already\n * draws one. Supports colour variants, icons and clickable state. Use in a\n * CnKpiGrid for responsive dashboard layouts.\n *\n * Basic (canonical horizontal card)\n * ```vue\n * <CnStatsBlock title=\"Cases\" :count=\"42\" count-label=\"open cases\" />\n * ```\n *\n * With icon and variant\n * ```vue\n * <CnStatsBlock\n * title=\"Open Cases\"\n * :count=\"42\"\n * :icon=\"BriefcaseOutline\"\n * variant=\"primary\"\n * clickable\n * @click=\"goToCases\" />\n * ```\n *\n * Stacked and boxed — for a block mounted with no wrapper around it\n * ```vue\n * <CnStatsBlock title=\"Cases\" :count=\"42\" vertical filled />\n * ```\n *\n * With route-based navigation (renders as <router-link>)\n * ```vue\n * <CnStatsBlock\n * title=\"Open Cases\"\n * :count=\"42\"\n * :icon=\"BriefcaseOutline\"\n * variant=\"primary\"\n * :route=\"{ name: 'Cases', query: { status: 'open' } }\" />\n * ```\n *\n * With breakdown\n * ```vue\n * <CnStatsBlock\n * title=\"Cases\"\n * :count=\"42\"\n * :breakdown=\"{ total: 100, invalid: 3, deleted: 5, published: 92 }\" />\n * ```\n *\n * Custom icon slot\n * ```vue\n * <CnStatsBlock title=\"Files\" :count=\"128\">\n * <template #icon>\n * <FileDocumentOutline :size=\"24\" />\n * </template>\n * </CnStatsBlock>\n * ```\n */\nexport default {\n\tname: 'CnStatsBlock',\n\n\tcomponents: {\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * The look the app is drawn in, provided by CnAppRoot.\n\t\t *\n\t\t * @spec openspec/changes/screens-dashboard-parity/specs/dashboard-page/spec.md#requirement-the-board-kpi-tile\n\t\t */\n\t\tcnLook: { default: 'nextcloud' },\n\t\t/**\n\t\t * The host's label lookup (CnAppRoot provides it); identity without one, so a\n\t\t * label that is not a key renders as written.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/** Block title */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The main count number to display prominently */\n\t\tcount: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t},\n\n\t\t/** Label displayed next to the count */\n\t\tcountLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'objects'),\n\t\t},\n\n\t\t/** Detailed breakdown object (key-value pairs) */\n\t\tbreakdown: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Whether data is currently loading */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Text shown while loading */\n\t\tloadingLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Loading…'),\n\t\t},\n\n\t\t/** Text shown when count is 0 */\n\t\temptyLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No items found'),\n\t\t},\n\n\t\t/**\n\t\t * The tile could not load its number. Anything truthy counts — pass the\n\t\t * caught error itself, or just `true`.\n\t\t *\n\t\t * The tile then shows a dash and `errorLabel` INSTEAD of a count, and\n\t\t * tints itself `error`. This takes precedence over `count`, `loading`\n\t\t * and `emptyLabel` deliberately: a stale or defaulted number rendered\n\t\t * during a failure is the exact thing this prop exists to stop. A\n\t\t * dashboard showing 0 because the backend is down is worse than one\n\t\t * showing nothing, because 0 is a number a reader will believe.\n\t\t *\n\t\t * @type {boolean|string|Error|null}\n\t\t */\n\t\terror: {\n\t\t\t// TYPE ORDER IS SEMANTIC HERE. Vue casts an empty value to `true`\n\t\t\t// when Boolean appears BEFORE String in the union, so\n\t\t\t// `[Boolean, String]` would read `error=\"\"` as an error. A caller\n\t\t\t// clearing its message back to '' is reporting RECOVERY, and that\n\t\t\t// would have pinned the tile in its error state after the fetch\n\t\t\t// succeeded again. String first keeps '' falsy.\n\t\t\t// eslint-disable-next-line vue/prefer-prop-type-boolean-first -- Boolean first would make `error=\"\"` truthy, which is the recovery signal.\n\t\t\ttype: [String, Boolean, Object],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Text shown in place of the count when `error` is set. */\n\t\terrorLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Unavailable'),\n\t\t},\n\n\t\t/** Icon component (e.g., imported MDI icon) */\n\t\ticon: {\n\t\t\ttype: [Object, Function],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Icon size in pixels */\n\t\ticonSize: {\n\t\t\ttype: Number,\n\t\t\tdefault: 24,\n\t\t},\n\n\t\t/** Color variant: 'default', 'primary', 'success', 'warning', 'error' */\n\t\tvariant: {\n\t\t\ttype: String,\n\t\t\tdefault: 'default',\n\t\t\tvalidator: (v) => ['default', 'primary', 'success', 'warning', 'error'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Stack the icon above a centred number instead of placing it beside\n\t\t * one. The canonical KPI card is horizontal, so this is the opt-out;\n\t\t * `horizontal` below is kept only for callers that already pass it.\n\t\t */\n\t\tvertical: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * `stacked` is the board's tile, as CnStatWidget `content.layout:\n\t\t * \"stacked\"`: no icon circle, a 14px muted title, the number at\n\t\t * 34px/700 in the text colour. Empty (the default) keeps the KPI card.\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stats-block-can-take-the-stacked-board-look\n\t\t */\n\t\tlayout: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Lay the icon left of the content. No longer needed — this is the\n\t\t * canonical card's own layout — and kept so existing callers that pass\n\t\t * `horizontal` keep working. Pass `vertical` to stack instead.\n\t\t *\n\t\t * @deprecated since 2.25.0, the horizontal layout is the default.\n\t\t */\n\t\thorizontal: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Draw the card's own grey box. Off by default: a stats block is\n\t\t * normally rendered inside a wrapper that already draws a card, and a\n\t\t * second box reads as a card inside a card. Turn it on for a block\n\t\t * mounted with no wrapper around it.\n\t\t */\n\t\tfilled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether the card is clickable */\n\t\tclickable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Whether to display 0 as a count value instead of the empty label */\n\t\tshowZeroCount: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Vue Router location object for declarative navigation.\n\t\t * When set, the card renders as a <router-link> and clickable styles are implied.\n\t\t * { name: 'Cases', query: { status: 'open' } }\n\t\t * { path: '/catalogi' }\n\t\t */\n\t\troute: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['click'],\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the app takes the board look.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisBoardLook() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\thasIcon() {\n\t\t\treturn this.icon !== null || this.$slots.icon || this.$slots.icon\n\t\t},\n\n\t\t/**\n\t\t * Whether a consumer provided the `value` slot. When set, the value\n\t\t * area always renders (the slot decides what to show, even at count 0).\n\t\t */\n\t\thasValueSlot() {\n\t\t\treturn !!this.$slots.value || !!this.$slots.value\n\t\t},\n\n\t\t/**\n\t\t * Whether the tile is in its error state.\n\t\t *\n\t\t * Anything truthy counts, so a caller can pass the caught error itself\n\t\t * rather than converting it to a boolean first. An empty string is NOT\n\t\t * an error — that is a caller who cleared the message, not one\n\t\t * reporting a failure.\n\t\t *\n\t\t * @return {boolean} True when the tile should show `errorLabel`.\n\t\t */\n\t\thasError() {\n\t\t\treturn Boolean(this.error)\n\t\t},\n\n\t\tformattedCount() {\n\t\t\treturn this.count.toLocaleString()\n\t\t},\n\n\t\t/**\n\t\t * Whether the card is interactive (clickable or has a route).\n\t\t * Used for applying hover/focus styles.\n\t\t */\n\t\tisInteractive() {\n\t\t\treturn !!this.route || this.clickable\n\t\t},\n\n\t\t/**\n\t\t * Determines which HTML element or component to render.\n\t\t * - route set → 'router-link' (SPA navigation)\n\t\t * - clickable → 'a' (app handles click via event)\n\t\t * - neither → 'div' (static display)\n\t\t */\n\t\tcomponentTag() {\n\t\t\tif (this.route) {\n\t\t\t\treturn 'router-link'\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\treturn 'a'\n\t\t\t}\n\t\t\treturn 'div'\n\t\t},\n\n\t\t/**\n\t\t * Dynamic attributes for the root element based on rendering mode.\n\t\t */\n\t\tcomponentAttrs() {\n\t\t\tif (this.route) {\n\t\t\t\treturn { to: this.route, tabindex: '0' }\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\treturn { href: '#', role: 'button', tabindex: '0' }\n\t\t\t}\n\t\t\treturn {}\n\t\t},\n\n\t\t/**\n\t\t * Whether the block takes the stacked board look (`layout`).\n\t\t *\n\t\t * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stats-block-can-take-the-stacked-board-look\n\t\t * @return {boolean}\n\t\t */\n\t\tisStacked() {\n\t\t\treturn this.layout === 'stacked'\n\t\t},\n\n\t\trootClasses() {\n\t\t\treturn {\n\t\t\t\t// Canonical + legacy, in pairs: the canonical class carries the\n\t\t\t\t// look, the legacy one keeps existing app CSS matching.\n\t\t\t\t// The canonical card is already horizontal, so the legacy pair\n\t\t\t\t// is emitted for app CSS but carries no look of its own.\n\t\t\t\t'cn-kpi-card--horizontal': !this.isStacked && (this.horizontal || !this.vertical),\n\t\t\t\t'cn-kpi-card--stacked': this.isStacked,\n\t\t\t\t'cn-kpi-card--board': this.isBoardLook,\n\t\t\t\t'cn-stats-block--horizontal': this.horizontal || !this.vertical,\n\t\t\t\t'cn-kpi-card--vertical': this.vertical,\n\t\t\t\t'cn-stats-block--vertical': this.vertical,\n\t\t\t\t'cn-kpi-card--filled': this.filled,\n\t\t\t\t'cn-kpi-card--clickable': this.isInteractive,\n\t\t\t\t'cn-stats-block--clickable': this.isInteractive,\n\t\t\t\t'cn-kpi-card--error': this.hasError,\n\t\t\t\t[`cn-kpi-card--${this.variant}`]: this.variant !== 'default' && this.hasError === false,\n\t\t\t\t// An errored tile tints itself. Requiring the consumer to also\n\t\t\t\t// pass `variant=\"error\"` would mean every caller remembering\n\t\t\t\t// two props to express one state, and forgetting the second is\n\t\t\t\t// invisible — a tile that reads \"Unavailable\" in the default\n\t\t\t\t// colour looks like ordinary content.\n\t\t\t\t'cn-stats-block--error': this.hasError,\n\t\t\t\t[`cn-stats-block--${this.variant}`]: this.variant !== 'default' && this.hasError === false,\n\t\t\t}\n\t\t},\n\n\t\ticonClasses() {\n\t\t\treturn {\n\t\t\t\t[`cn-stats-block__icon--${this.variant}`]: this.variant !== 'default',\n\t\t\t}\n\t\t},\n\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * A manifest string through the host's label lookup (`cnTranslate`), so a key\n\t\t * such as \"Mine\" reads in the user's language.\n\t\t *\n\t\t * @param {string} text The text as written in the manifest.\n\t\t * @return {string} The translated text.\n\t\t */\n\t\ttl(text) {\n\t\t\treturn typeof this.cnTranslate === 'function' ? this.cnTranslate(text) : text\n\t\t},\n\n\t\tformatBreakdownLabel(key) {\n\t\t\treturn key.charAt(0).toUpperCase() + key.slice(1) + ':'\n\t\t},\n\n\t\tonClick(event) {\n\t\t\t// When route is set, router-link handles navigation — do not emit click\n\t\t\tif (this.route) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.clickable) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.$emit('click', event)\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n/*\n * The card's own look — box, body stack, icon circle, title, value, variants,\n * hover — is the CANONICAL KPI card in src/css/kpi-card.css, shared with\n * CnStatWidget. The rules that lived here duplicated it in different numbers\n * (a 1.2rem count row, a 40px icon, hardcoded rgba() tints that ignored a\n * re-themed palette), and that duplication IS how the two KPI looks drifted.\n * What is left below is this component's own extras: the breakdown block, the\n * loading line and the empty line.\n *\n * Those extras are secondary KPI text, so they read the shared\n * `--cn-kpi-label-*` scale rather than carrying sizes of their own — an empty\n * state that is a different size from the label it replaces is the same drift\n * in miniature.\n */\n\n.cn-stats-block__header {\n\tmax-width: 100%;\n\tmin-width: 0;\n}\n\n.cn-stats-block__loading {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: flex-start;\n\tgap: 6px;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tcolor: var(--cn-kpi-label-color, var(--color-text-maxcontrast));\n}\n\n.cn-stats-block__empty {\n\ttext-align: start;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tcolor: var(--cn-kpi-label-color, var(--color-text-maxcontrast));\n\tfont-style: italic;\n}\n\n.cn-stats-block__breakdown {\n\tmargin-top: 4px;\n\tpadding: 8px;\n\tfont-size: var(--cn-kpi-label-size, 13px);\n\tbackground: var(--color-background-hover);\n\tborder-radius: var(--border-radius);\n\tborder: 1px solid var(--color-border);\n\twidth: 100%;\n\ttext-align: start;\n}\n\n.cn-stats-block__breakdown-item {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-bottom: 4px;\n}\n\n.cn-stats-block__breakdown-item:last-child {\n\tmargin-bottom: 0;\n}\n\n.cn-stats-block__breakdown-label {\n\tfont-weight: 500;\n\tcolor: var(--color-main-text);\n}\n\n/* The value takes the card's accent, so a breakdown reads as part of the tile\n it sits in rather than as a table that happened to land there. It used to\n carry a `--color-background-hover` chip — on a `--color-background-hover`\n breakdown box, which is to say an invisible chip drawn at the cost of two\n extra declarations and a differently-shaped hit area. The per-key modifiers\n below still win: `invalid`, `deleted` and `published` mean something\n specific and outrank the tile's own colour. */\n.cn-stats-block__breakdown-value {\n\tfont-weight: var(--cn-kpi-title-weight, 600);\n\tcolor: var(--cn-kpi-accent, var(--color-primary-element));\n}\n\n.cn-stats-block__breakdown-value--invalid { color: var(--color-element-warning); }\n\n.cn-stats-block__breakdown-value--deleted { color: var(--color-element-error); }\n\n.cn-stats-block__breakdown-value--published { color: var(--color-element-success); }\n</style>\n"],"names":["t"],"mappings":"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"}
@@ -73,7 +73,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
73
73
  createElementVNode("h4", {
74
74
  class: "cn-kpi-card__title",
75
75
  title: $props.title || undefined
76
- }, toDisplayString($props.title || _ctx.t('nextcloud-vue', 'Objects')), 9 /* TEXT, PROPS */, _hoisted_4)
76
+ }, toDisplayString($props.title ? $options.tl($props.title) : _ctx.t('nextcloud-vue', 'Objects')), 9 /* TEXT, PROPS */, _hoisted_4)
77
77
  ]),
78
78
  ($options.hasError)
79
79
  ? (openBlock(), createElementBlock("div", _hoisted_5, [
@@ -90,7 +90,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
90
90
  createTextVNode(toDisplayString($options.formattedCount), 1 /* TEXT */)
91
91
  ])
92
92
  ]),
93
- createElementVNode("span", _hoisted_9, toDisplayString($props.countLabel), 1 /* TEXT */)
93
+ createElementVNode("span", _hoisted_9, toDisplayString($props.countLabel ? $options.tl($props.countLabel) : $props.countLabel), 1 /* TEXT */)
94
94
  ]))
95
95
  : ($props.loading)
96
96
  ? (openBlock(), createElementBlock("div", _hoisted_10, [