@conduction/nextcloud-vue 2.114.0 → 2.115.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 (118) hide show
  1. package/dist/esm/components/CnAdvancedFormDialog/CnPropertyValueCell.vue.js +1 -1
  2. package/dist/esm/components/CnAdvancedFormDialog/CnPropertyValueCell.vue2.js +1 -1
  3. package/dist/esm/components/CnAdvancedFormDialog/CnPropertyValueCell.vue2.js.map +1 -1
  4. package/dist/esm/components/CnAdvancedFormDialog/CnPropertyValueCell.vue3.js.map +1 -1
  5. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
  6. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +6 -2
  7. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  8. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  9. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
  10. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js +2 -1
  11. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  12. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  13. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  14. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +19 -2
  15. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  16. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +22 -3
  17. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  18. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  19. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +2 -2
  20. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  21. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  22. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +37 -1
  23. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  24. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  25. package/dist/esm/components/CnIndexPage/rowsToCsv.js +90 -0
  26. package/dist/esm/components/CnIndexPage/rowsToCsv.js.map +1 -0
  27. package/dist/esm/components/CnMacrosWidget/CnMacrosWidget.vue.js +9 -0
  28. package/dist/esm/components/CnMacrosWidget/CnMacrosWidget.vue.js.map +1 -0
  29. package/dist/esm/components/CnMacrosWidget/CnMacrosWidget.vue2.js +161 -0
  30. package/dist/esm/components/CnMacrosWidget/CnMacrosWidget.vue2.js.map +1 -0
  31. package/dist/esm/components/CnMacrosWidget/CnMacrosWidget.vue3.js +97 -0
  32. package/dist/esm/components/CnMacrosWidget/CnMacrosWidget.vue3.js.map +1 -0
  33. package/dist/esm/components/CnRetentionWidget/CnRetentionRecomputeDialog.vue.js +9 -0
  34. package/dist/esm/components/CnRetentionWidget/CnRetentionRecomputeDialog.vue.js.map +1 -0
  35. package/dist/esm/components/CnRetentionWidget/CnRetentionRecomputeDialog.vue2.js +63 -0
  36. package/dist/esm/components/CnRetentionWidget/CnRetentionRecomputeDialog.vue2.js.map +1 -0
  37. package/dist/esm/components/CnRetentionWidget/CnRetentionRecomputeDialog.vue3.js +54 -0
  38. package/dist/esm/components/CnRetentionWidget/CnRetentionRecomputeDialog.vue3.js.map +1 -0
  39. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue.js +1 -1
  40. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue2.js +15 -9
  41. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue2.js.map +1 -1
  42. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js +55 -63
  43. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js.map +1 -1
  44. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue.js +1 -1
  45. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js +2 -1
  46. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue2.js.map +1 -1
  47. package/dist/esm/components/CnSegmentedControl/CnSegmentedControl.vue3.js.map +1 -1
  48. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
  49. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +13 -4
  50. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  51. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +2 -1
  52. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  53. package/dist/esm/components/CnStepper/CnStepper.vue.js +1 -1
  54. package/dist/esm/components/CnStepper/CnStepper.vue2.js.map +1 -1
  55. package/dist/esm/components/CnStepper/CnStepper.vue3.js.map +1 -1
  56. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  57. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +10 -5
  58. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  59. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +1 -1
  60. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  61. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue.js +1 -1
  62. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
  63. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +2 -2
  64. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
  65. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +32 -20
  66. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  67. package/dist/esm/composables/indexSources.js +43 -11
  68. package/dist/esm/composables/indexSources.js.map +1 -1
  69. package/dist/esm/composables/useTaskInboxStore.js +41 -0
  70. package/dist/esm/composables/useTaskInboxStore.js.map +1 -1
  71. package/dist/esm/index.js +1 -0
  72. package/dist/esm/index.js.map +1 -1
  73. package/dist/esm/l10n/en.json.js +4 -1
  74. package/dist/esm/l10n/en.json.js.map +1 -1
  75. package/dist/esm/l10n/nl.json.js +9 -1
  76. package/dist/esm/l10n/nl.json.js.map +1 -1
  77. package/dist/esm/library-style.js +1 -1
  78. package/dist/esm/nextcloud-vue.css +482 -330
  79. package/dist/esm/utils/contentHeaderLink.js +37 -1
  80. package/dist/esm/utils/contentHeaderLink.js.map +1 -1
  81. package/dist/esm/utils/declaredMacros.js +31 -0
  82. package/dist/esm/utils/declaredMacros.js.map +1 -0
  83. package/dist/esm/utils/libraryWidgetKeys.js +1 -0
  84. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  85. package/dist/nextcloud-vue.cjs.js +2673 -1977
  86. package/dist/nextcloud-vue.cjs.js.map +2 -2
  87. package/dist/nextcloud-vue.css +482 -330
  88. package/l10n/en.json +4 -1
  89. package/l10n/nl.json +9 -1
  90. package/package.json +1 -1
  91. package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +1 -1
  92. package/src/components/CnCellRenderer/CnCellRenderer.vue +6 -2
  93. package/src/components/CnChoiceCards/CnChoiceCards.vue +2 -1
  94. package/src/components/CnDashboardPage/CnDashboardPage.vue +45 -3
  95. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +2 -2
  96. package/src/components/CnIndexPage/CnIndexPage.vue +37 -1
  97. package/src/components/CnIndexPage/rowsToCsv.js +87 -0
  98. package/src/components/CnMacrosWidget/CnMacrosWidget.md +30 -0
  99. package/src/components/CnMacrosWidget/CnMacrosWidget.vue +266 -0
  100. package/src/components/CnMacrosWidget/index.js +6 -0
  101. package/src/components/CnRetentionWidget/CnRetentionRecomputeDialog.vue +104 -0
  102. package/src/components/CnRetentionWidget/CnRetentionWidget.vue +53 -38
  103. package/src/components/CnSegmentedControl/CnSegmentedControl.vue +2 -1
  104. package/src/components/CnSetupWizard/CnSetupWizard.vue +28 -4
  105. package/src/components/CnStepper/CnStepper.vue +2 -1
  106. package/src/components/CnTabsWidget/CnTabsWidget.vue +11 -6
  107. package/src/components/CnTasksWidget/CnTasksWidget.vue +2 -2
  108. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +12 -0
  109. package/src/components/index.js +1 -0
  110. package/src/composables/indexSources.js +43 -11
  111. package/src/composables/useTaskInboxStore.js +41 -0
  112. package/src/css/look-board-detail.css +4 -3
  113. package/src/css/look-board-index.css +62 -0
  114. package/src/css/look-board.css +20 -0
  115. package/src/index.js +1 -0
  116. package/src/utils/contentHeaderLink.js +35 -0
  117. package/src/utils/declaredMacros.js +28 -0
  118. package/src/utils/libraryWidgetKeys.js +1 -0
@@ -1 +1 @@
1
- {"version":3,"file":"CnChoiceCards.vue2.js","sources":["../../../../src/components/CnChoiceCards/CnChoiceCards.vue"],"sourcesContent":["<!-- SPDX-License-Identifier: EUPL-1.2 -->\n<!-- SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl> -->\n<template>\n\t<fieldset class=\"cn-choice-cards\" :aria-busy=\"loading ? 'true' : 'false'\">\n\t\t<legend v-if=\"label\" class=\"cn-choice-cards__legend\">\n\t\t\t{{ label }}\n\t\t</legend>\n\n\t\t<div v-if=\"loading\" class=\"cn-choice-cards__loading\">\n\t\t\t<NcLoadingIcon :size=\"32\" />\n\t\t</div>\n\n\t\t<NcNoteCard v-else-if=\"normalizedOptions.length === 0\" type=\"warning\">\n\t\t\t{{ emptyText }}\n\t\t</NcNoteCard>\n\n\t\t<!-- Rows: one full-width option per row, the input on the left and the\n\t\t title over a muted description, no card chrome. -->\n\t\t<div v-else-if=\"layout === 'rows'\" class=\"cn-choice-cards__rows\" data-testid=\"cn-choice-cards-rows\">\n\t\t\t<div\n\t\t\t\tv-for=\"option in normalizedOptions\"\n\t\t\t\t:key=\"String(option.value)\"\n\t\t\t\tclass=\"cn-choice-cards__cell\"\n\t\t\t\t:class=\"{ 'cn-choice-cards__cell--with-details': hasDetails(option) }\">\n\t\t\t\t<label\n\t\t\t\t\tclass=\"cn-choice-cards__row\"\n\t\t\t\t\t:class=\"{ 'cn-choice-cards__row--selected': isSelected(option), 'cn-choice-cards__row--disabled': isDisabled(option), 'cn-choice-cards__row--readonly': readonly }\">\n\t\t\t\t\t<input\n\t\t\t\t\t\tclass=\"cn-choice-cards__row-input\"\n\t\t\t\t\t\t:type=\"multiple ? 'checkbox' : 'radio'\"\n\t\t\t\t\t\t:name=\"groupName\"\n\t\t\t\t\t\t:value=\"String(option.value)\"\n\t\t\t\t\t\t:checked=\"isSelected(option)\"\n\t\t\t\t\t\t:disabled=\"isDisabled(option) || readonly\"\n\t\t\t\t\t\t:aria-describedby=\"option.disabled && option.disabledReason ? reasonId(option) : null\"\n\t\t\t\t\t\t@change=\"onToggle(option)\">\n\t\t\t\t\t<CnIcon v-if=\"option.icon\"\n\t\t\t\t\t\t:name=\"option.icon\"\n\t\t\t\t\t\t:size=\"20\"\n\t\t\t\t\t\tclass=\"cn-choice-cards__row-icon\" />\n\t\t\t\t\t<span class=\"cn-choice-cards__row-text\">\n\t\t\t\t\t\t<span class=\"cn-choice-cards__row-title\">{{ option.label }}</span>\n\t\t\t\t\t\t<span v-if=\"option.description\" class=\"cn-choice-cards__row-description\">{{ option.description }}</span>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tv-if=\"option.disabled && option.disabledReason\"\n\t\t\t\t\t\t\t:id=\"reasonId(option)\"\n\t\t\t\t\t\t\tclass=\"cn-choice-cards__reason\"\n\t\t\t\t\t\t\tdata-testid=\"cn-choice-cards-reason\">{{ option.disabledReason }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t</label>\n\t\t\t\t<div v-if=\"hasDetails(option)\"\n\t\t\t\t\tclass=\"cn-choice-cards__details\"\n\t\t\t\t\t:data-testid=\"`cn-choice-cards-details-${option.value}`\">\n\t\t\t\t\t<!-- @slot option-details The chosen option's own fields, drawn\n\t\t\t\t\t inside the selected row's frame under its text (DqVolgendeStap:\n\t\t\t\t\t Opschorten's reason, days and return date). Rendered only for a\n\t\t\t\t\t selected option, outside the label so a field is not part of\n\t\t\t\t\t the radio's label. Scope: `{ option }`. -->\n\t\t\t\t\t<slot name=\"option-details\" :option=\"option\" />\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"$slots['option-actions']\" class=\"cn-choice-cards__actions\">\n\t\t\t\t\t<slot name=\"option-actions\" :option=\"option\" :selected=\"isSelected(option)\" />\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<div v-else class=\"cn-choice-cards__grid\" :style=\"gridStyle\">\n\t\t\t<!-- One cell per option: the card's label, and below it whatever the\n\t\t\t `#option-actions` slot adds. The slot sits OUTSIDE the label on\n\t\t\t purpose: a button inside a label is interactive content inside\n\t\t\t interactive content, and a click on it would toggle the radio. -->\n\t\t\t<div\n\t\t\t\tv-for=\"option in normalizedOptions\"\n\t\t\t\t:key=\"String(option.value)\"\n\t\t\t\tclass=\"cn-choice-cards__cell\">\n\t\t\t\t<label\n\t\t\t\t\tclass=\"cn-choice-cards__option\"\n\t\t\t\t\t:class=\"{ 'cn-choice-cards__option--selected': isSelected(option), 'cn-choice-cards__option--disabled': isDisabled(option) }\">\n\t\t\t\t\t<!-- A REAL, VISIBLE input, not a styled div with a click handler.\n\t\t\t\t\t It carries the checked state to assistive technology and to\n\t\t\t\t\t Windows high-contrast mode for free, and it keeps the\n\t\t\t\t\t selection perceivable without colour (WCAG 1.4.1) — the\n\t\t\t\t\t card's active border alone would not be. -->\n\t\t\t\t\t<input\n\t\t\t\t\t\tclass=\"cn-choice-cards__input\"\n\t\t\t\t\t\t:type=\"multiple ? 'checkbox' : 'radio'\"\n\t\t\t\t\t\t:name=\"groupName\"\n\t\t\t\t\t\t:value=\"String(option.value)\"\n\t\t\t\t\t\t:checked=\"isSelected(option)\"\n\t\t\t\t\t\t:disabled=\"isDisabled(option) || readonly\"\n\t\t\t\t\t\t:aria-describedby=\"option.disabled && option.disabledReason ? reasonId(option) : null\"\n\t\t\t\t\t\t@change=\"onToggle(option)\">\n\t\t\t\t\t<CnCard\n\t\t\t\t\t\tclass=\"cn-choice-cards__card\"\n\t\t\t\t\t\t:title=\"option.label\"\n\t\t\t\t\t\ttitleTag=\"span\"\n\t\t\t\t\t\t:description=\"option.description\"\n\t\t\t\t\t\t:stats=\"option.stats || []\"\n\t\t\t\t\t\t:tags=\"option.tags || []\"\n\t\t\t\t\t\t:active=\"isSelected(option)\"\n\t\t\t\t\t\tactiveVariant=\"primary\"\n\t\t\t\t\t\t:descriptionLines=\"descriptionLines\">\n\t\t\t\t\t\t<template v-if=\"option.icon\" #icon>\n\t\t\t\t\t\t\t<CnIcon :name=\"option.icon\" :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</CnCard>\n\t\t\t\t\t<span\n\t\t\t\t\t\tv-if=\"option.disabled && option.disabledReason\"\n\t\t\t\t\t\t:id=\"reasonId(option)\"\n\t\t\t\t\t\tclass=\"cn-choice-cards__reason cn-choice-cards__reason--card\"\n\t\t\t\t\t\tdata-testid=\"cn-choice-cards-reason\">{{ option.disabledReason }}</span>\n\t\t\t\t</label>\n\t\t\t\t<div v-if=\"$slots['option-actions']\" class=\"cn-choice-cards__actions\">\n\t\t\t\t\t<!-- @slot option-actions Content below one card, outside its label\n\t\t\t\t\t (e.g. a per-option Load button). Scope: `{ option, selected }`. -->\n\t\t\t\t\t<slot name=\"option-actions\" :option=\"option\" :selected=\"isSelected(option)\" />\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</fieldset>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\nimport CnCard from '../CnCard/CnCard.vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\n\nlet groupSeq = 0\n\n/**\n * CnChoiceCards — pick one option, or several, from a grid of cards.\n *\n * A card carries what a dropdown entry cannot: a description of what the\n * option actually is, and a few numbers about it. Use it where the choice\n * deserves explaining — the setup wizard's example-data step, an onboarding\n * template picker — and a plain `NcSelect` where a label already says enough.\n *\n * Each card is a `<label>` around a real radio (or checkbox when `multiple`),\n * so keyboard navigation, the checked state and high-contrast rendering are\n * the browser's, not a reimplementation.\n *\n * ```vue\n * <CnChoiceCards\n * v-model=\"picked\"\n * label=\"Which kind of organisation is this for?\"\n * :options=\"[\n * { value: 'municipality', label: 'Municipality',\n * description: 'A city council with committees and a decision list.',\n * stats: [{ label: 'Objects', value: 170 }] },\n * { value: 'none', label: 'None, I will set this up myself' },\n * ]\" />\n * ```\n */\nexport default {\n\tname: 'CnChoiceCards',\n\n\tcomponents: {\n\t\tCnCard,\n\t\tCnIcon,\n\t\tNcLoadingIcon,\n\t\tNcNoteCard,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The options to offer. Each: `{ value, label, description?, stats?,\n\t\t * tags?, icon?, disabled?, disabledReason? }` — `icon` is a PascalCase\n\t\t * MDI name resolved by CnIcon; a `disabled` option is greyed out and\n\t\t * cannot be picked, with `disabledReason` shown under it.\n\t\t */\n\t\toptions: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * The selected value: a scalar, or an array when `multiple`.\n\t\t *\n\t\t * @type {string|number|boolean|Array|null}\n\t\t */\n\t\tmodelValue: {\n\t\t\ttype: [Boolean, String, Number, Array, Object],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Allow selecting several cards; the model becomes an array. */\n\t\tmultiple: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Group label, rendered as the fieldset's legend. */\n\t\tlabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Disable every card (e.g. a dependent choice with no parent value yet). */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Show the choice without letting it change: every option at full\n\t\t * contrast, the selected one marked, nothing pickable (DqBesluit's\n\t\t * recorded outcome). `disabled` greys the options out instead.\n\t\t */\n\t\treadonly: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Show a spinner instead of the grid while options are being fetched. */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Message shown when there is nothing to choose from. */\n\t\temptyText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Nothing to choose from here.'),\n\t\t},\n\n\t\t/**\n\t\t * `cards`: a grid of cards with stats and tags. `rows`: one option per\n\t\t * row, full width, the input on the left and the title over a muted\n\t\t * description; with `multiple` and one option it is a checkbox card.\n\t\t */\n\t\tlayout: {\n\t\t\ttype: String,\n\t\t\tdefault: 'cards',\n\t\t\tvalidator: (value) => ['cards', 'rows'].includes(value),\n\t\t},\n\n\t\t/** Columns of the card grid; 0 lets the grid fit as many as the width allows. */\n\t\tcolumns: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t},\n\n\t\t/** Lines of description shown before clamping. */\n\t\tdescriptionLines: {\n\t\t\ttype: Number,\n\t\t\tdefault: 4,\n\t\t},\n\t},\n\n\temits: ['update:modelValue'],\n\n\tdata() {\n\t\tgroupSeq += 1\n\t\treturn {\n\t\t\t// Radios only group by `name`, so two card groups on one page would\n\t\t\t// otherwise share a selection.\n\t\t\tgroupName: 'cn-choice-cards-' + groupSeq,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Options with a usable `value` and `label`, tolerating the shapes a\n\t\t * server list arrives in: `id` instead of `value`, `name` instead of\n\t\t * `label`, and a bare string for a value that is its own label.\n\t\t *\n\t\t * @return {Array<object>} The normalised options.\n\t\t */\n\t\tnormalizedOptions() {\n\t\t\treturn (this.options || [])\n\t\t\t\t.filter((option) => option !== null && option !== undefined)\n\t\t\t\t.map((option) => {\n\t\t\t\t\tif (typeof option !== 'object') {\n\t\t\t\t\t\treturn { value: option, label: String(option) }\n\t\t\t\t\t}\n\t\t\t\t\tconst value = option.value !== undefined ? option.value : option.id\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...option,\n\t\t\t\t\t\tvalue,\n\t\t\t\t\t\tlabel: option.label || option.name || String(value),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.filter((option) => option.value !== undefined)\n\t\t},\n\n\t\t/**\n\t\t * The current selection as an array of values, whatever the model shape.\n\t\t *\n\t\t * @return {Array} The selected values.\n\t\t */\n\t\tselectedValues() {\n\t\t\tif (this.multiple) {\n\t\t\t\treturn Array.isArray(this.modelValue) ? this.modelValue : []\n\t\t\t}\n\t\t\treturn (this.modelValue === null || this.modelValue === undefined || this.modelValue === '')\n\t\t\t\t? []\n\t\t\t\t: [this.modelValue]\n\t\t},\n\n\t\t/**\n\t\t * Inline override for the grid's column count. `auto-fit` with a\n\t\t * 260px minimum wraps a small option count onto its own row well\n\t\t * before the container is actually too narrow for them side by\n\t\t * side — two cards would stack at ~550px wide even though halving\n\t\t * the available width still leaves each one legible. A binary (or\n\t\t * near-binary) choice should always sit in one row and fill the\n\t\t * available width; a larger option count keeps the responsive\n\t\t * `auto-fit` wrapping from the stylesheet (`{}` leaves it alone).\n\t\t *\n\t\t * @return {object} A `gridTemplateColumns` override, or `{}`.\n\t\t */\n\t\tgridStyle() {\n\t\t\tif (this.columns > 0) {\n\t\t\t\treturn { gridTemplateColumns: `repeat(${this.columns}, minmax(0, 1fr))` }\n\t\t\t}\n\t\t\tconst count = this.normalizedOptions.length\n\t\t\tif (count > 0 && count <= 2) {\n\t\t\t\treturn { gridTemplateColumns: `repeat(${count}, minmax(0, 1fr))` }\n\t\t\t}\n\t\t\treturn {}\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether an option is currently selected.\n\t\t *\n\t\t * Compares as strings: a value that made the round trip through the\n\t\t * server as `\"1\"` must still match the `1` it was sent as.\n\t\t *\n\t\t * @param {object} option The normalised option.\n\t\t * @return {boolean} True when selected.\n\t\t */\n\t\t/**\n\t\t * Whether a rows option draws the `option-details` slot in its frame:\n\t\t * the slot is given and the option is the chosen one.\n\t\t *\n\t\t * @param {object} option The option.\n\t\t * @return {boolean}\n\t\t */\n\t\thasDetails(option) {\n\t\t\treturn Boolean(this.$slots['option-details']) && this.isSelected(option)\n\t\t},\n\n\t\t/**\n\t\t * Whether an option cannot be picked: the whole group is disabled, or the option is.\n\t\t *\n\t\t * @param {object} option The normalised option.\n\t\t * @return {boolean} True when disabled.\n\t\t */\n\t\tisDisabled(option) {\n\t\t\treturn this.disabled || option.disabled === true\n\t\t},\n\n\t\t/**\n\t\t * Id of an option's disabled reason, for `aria-describedby`.\n\t\t *\n\t\t * @param {object} option The normalised option.\n\t\t * @return {string} The id.\n\t\t */\n\t\treasonId(option) {\n\t\t\treturn `${this.groupName}-reason-${String(option.value).replace(/[^A-Za-z0-9_-]/g, '-')}`\n\t\t},\n\n\t\tisSelected(option) {\n\t\t\treturn this.selectedValues.some((v) => String(v) === String(option.value))\n\t\t},\n\n\t\t/**\n\t\t * Select (single) or toggle (multiple) an option and emit the new model.\n\t\t *\n\t\t * @param {object} option The normalised option.\n\t\t * @return {void}\n\t\t */\n\t\tonToggle(option) {\n\t\t\tif (this.readonly || this.isDisabled(option)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!this.multiple) {\n\t\t\t\t/**\n\t\t\t\t * @event update:modelValue The selected value, or the array of values when `multiple`.\n\t\t\t\t * @type {string|number|boolean|Array}\n\t\t\t\t */\n\t\t\t\t// Never the option OBJECT: a parent has to be able to hand back\n\t\t\t\t// what it receives as `modelValue`.\n\t\t\t\tthis.$emit('update:modelValue', option.value)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst next = this.selectedValues.filter((v) => String(v) !== String(option.value))\n\t\t\tif (next.length === this.selectedValues.length) {\n\t\t\t\tnext.push(option.value)\n\t\t\t}\n\t\t\tthis.$emit('update:modelValue', next)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped lang=\"scss\">\n.cn-choice-cards {\n\tborder: none;\n\tpadding: 0;\n\tmargin: 0;\n\tmin-inline-size: 0;\n}\n\n.cn-choice-cards__legend {\n\tpadding: 0;\n\tmargin-block-end: 8px;\n\tfont-weight: bold;\n}\n\n.cn-choice-cards__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 24px 0;\n}\n\n.cn-choice-cards__grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fit, minmax(260px, 1fr));\n\tgap: 12px;\n\t/* Room for the card shadow. `src/css/card.css` is loaded globally by every\n\t consumer and gives EVERY `.cn-card` a `box-shadow` — including these — so\n\t a grid flush against its container gets the shadow shaved off wherever\n\t that container clips, which the setup wizard's dialog body does on the\n\t sides and the bottom. 12px clears the widest of those shadows. */\n\tpadding: 12px;\n}\n\n.cn-choice-cards__cell {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tmin-inline-size: 0;\n}\n\n.cn-choice-cards__actions {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-choice-cards__option {\n\tposition: relative;\n\tdisplay: block;\n\tcursor: pointer;\n}\n\n.cn-choice-cards__input {\n\tposition: absolute;\n\tinset-block-start: 16px;\n\tinset-inline-end: 14px;\n\tz-index: 1;\n\tmargin: 0;\n\tcursor: pointer;\n}\n\n/* Room for the input, so a long title never runs underneath it. */\n.cn-choice-cards__option :deep(.cn-card) {\n\tpadding-inline-end: 44px;\n\ttransition: box-shadow 0.2s ease, border-color 0.2s ease;\n}\n\n/* The same global `card.css` also scales `.cn-card` up on hover. A transform\n counts towards the scrollable overflow of the nearest scroll container, so in\n a dialog body sized to its content a scrollbar appears the moment the pointer\n touches a card, and the grid twitches. The border colour below already\n carries hover, so the scale is dropped: at (0,3,0) this beats the global\n `.cn-card:hover` at (0,2,0). */\n.cn-choice-cards__option:hover :deep(.cn-card) {\n\tborder-color: var(--color-primary-element);\n\ttransform: none;\n}\n\n/* The focus ring belongs on the card, not on the small input inside it. */\n.cn-choice-cards__input:focus-visible + :deep(.cn-card) {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 2px;\n}\n\n.cn-choice-cards__input:disabled {\n\tcursor: default;\n}\n\n.cn-choice-cards__option--disabled .cn-choice-cards__input + :deep(.cn-card) {\n\topacity: 0.6;\n}\n\n.cn-choice-cards__rows {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n\n/* 12px 14px, as the boards draw an option row (DqVolgendeStap, DqBesluit). */\n.cn-choice-cards__row {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 12px;\n\tpadding: 12px 14px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 8px);\n\tbackground: var(--color-main-background);\n\tcursor: pointer;\n}\n\n.cn-choice-cards__row:hover {\n\tborder-color: var(--color-border-dark);\n}\n\n.cn-choice-cards__row--selected,\n.cn-choice-cards__row--selected:hover {\n\tborder-color: var(--color-primary-element);\n\t/* The second pixel of the 2px selected border, without moving the content. */\n\tbox-shadow: inset 0 0 0 1px var(--color-primary-element);\n\tbackground: var(--color-primary-element-light);\n}\n\n/* An option this record does not allow: the grey ground and the muted ink,\n still legible (DqVolgendeStap), not faded. */\n.cn-choice-cards__row--disabled,\n.cn-choice-cards__row--disabled:hover {\n\tborder-color: var(--color-border);\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-text-maxcontrast);\n\tcursor: default;\n}\n\n/* The title takes the muted ink itself: a theme that colours every label\n with `!important` (thematiq) would otherwise hand it the label colour. */\n.cn-choice-cards__row--disabled .cn-choice-cards__row-title {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* The chosen option's fields share the selected row's frame: the cell draws\n the 2px primary border and the ground, the row inside it draws none, and\n the fields sit under the text, indented to it (DqVolgendeStap). */\n.cn-choice-cards__cell--with-details {\n\tborder: 1px solid var(--color-primary-element);\n\tborder-radius: var(--border-radius-large, 8px);\n\tbox-shadow: inset 0 0 0 1px var(--color-primary-element);\n\tbackground: var(--color-primary-element-light);\n}\n\n.cn-choice-cards__cell--with-details .cn-choice-cards__row,\n.cn-choice-cards__cell--with-details .cn-choice-cards__row:hover {\n\tborder-color: transparent;\n\tbox-shadow: none;\n\tbackground: transparent;\n}\n\n.cn-choice-cards__details {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 14px;\n\tpadding: 0 14px 14px 44px;\n}\n\n.cn-choice-cards__row--readonly {\n\tcursor: default;\n}\n\n.cn-choice-cards__row--readonly:not(.cn-choice-cards__row--selected):hover {\n\tborder-color: var(--color-border);\n}\n\n.cn-choice-cards__row:has(.cn-choice-cards__row-input:focus-visible) {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 2px;\n}\n\n.cn-choice-cards__row-input {\n\tflex: none;\n\tmargin: 2px 0 0;\n\taccent-color: var(--color-primary-element);\n\tcursor: inherit;\n}\n\n.cn-choice-cards__row-icon {\n\tflex: none;\n}\n\n.cn-choice-cards__row-text {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-choice-cards__row-title {\n\tfont-size: 15px;\n\tfont-weight: 600;\n}\n\n/* Regular weight on the span itself: a theme that sets every label bold\n with `!important` (thematiq) reaches a span that only inherits. */\n.cn-choice-cards__row-description,\n.cn-choice-cards__reason {\n\tfont-size: 13px;\n\tfont-weight: 400;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-choice-cards__reason--card {\n\tdisplay: block;\n\tmargin-block-start: 4px;\n}\n</style>\n"],"names":["CnCard","CnIcon","t"],"mappings":"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"}
1
+ {"version":3,"file":"CnChoiceCards.vue2.js","sources":["../../../../src/components/CnChoiceCards/CnChoiceCards.vue"],"sourcesContent":["<!-- SPDX-License-Identifier: EUPL-1.2 -->\n<!-- SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl> -->\n<template>\n\t<fieldset class=\"cn-choice-cards\" :aria-busy=\"loading ? 'true' : 'false'\">\n\t\t<legend v-if=\"label\" class=\"cn-choice-cards__legend\">\n\t\t\t{{ label }}\n\t\t</legend>\n\n\t\t<div v-if=\"loading\" class=\"cn-choice-cards__loading\">\n\t\t\t<NcLoadingIcon :size=\"32\" />\n\t\t</div>\n\n\t\t<NcNoteCard v-else-if=\"normalizedOptions.length === 0\" type=\"warning\">\n\t\t\t{{ emptyText }}\n\t\t</NcNoteCard>\n\n\t\t<!-- Rows: one full-width option per row, the input on the left and the\n\t\t title over a muted description, no card chrome. -->\n\t\t<div v-else-if=\"layout === 'rows'\" class=\"cn-choice-cards__rows\" data-testid=\"cn-choice-cards-rows\">\n\t\t\t<div\n\t\t\t\tv-for=\"option in normalizedOptions\"\n\t\t\t\t:key=\"String(option.value)\"\n\t\t\t\tclass=\"cn-choice-cards__cell\"\n\t\t\t\t:class=\"{ 'cn-choice-cards__cell--with-details': hasDetails(option) }\">\n\t\t\t\t<label\n\t\t\t\t\tclass=\"cn-choice-cards__row\"\n\t\t\t\t\t:class=\"{ 'cn-choice-cards__row--selected': isSelected(option), 'cn-choice-cards__row--disabled': isDisabled(option), 'cn-choice-cards__row--readonly': readonly }\">\n\t\t\t\t\t<input\n\t\t\t\t\t\tclass=\"cn-choice-cards__row-input\"\n\t\t\t\t\t\t:type=\"multiple ? 'checkbox' : 'radio'\"\n\t\t\t\t\t\t:name=\"groupName\"\n\t\t\t\t\t\t:value=\"String(option.value)\"\n\t\t\t\t\t\t:checked=\"isSelected(option)\"\n\t\t\t\t\t\t:disabled=\"isDisabled(option) || readonly\"\n\t\t\t\t\t\t:aria-describedby=\"option.disabled && option.disabledReason ? reasonId(option) : null\"\n\t\t\t\t\t\t@change=\"onToggle(option)\">\n\t\t\t\t\t<CnIcon v-if=\"option.icon\"\n\t\t\t\t\t\t:name=\"option.icon\"\n\t\t\t\t\t\t:size=\"20\"\n\t\t\t\t\t\tclass=\"cn-choice-cards__row-icon\" />\n\t\t\t\t\t<span class=\"cn-choice-cards__row-text\">\n\t\t\t\t\t\t<span class=\"cn-choice-cards__row-title\">{{ option.label }}</span>\n\t\t\t\t\t\t<span v-if=\"option.description\" class=\"cn-choice-cards__row-description\">{{ option.description }}</span>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tv-if=\"option.disabled && option.disabledReason\"\n\t\t\t\t\t\t\t:id=\"reasonId(option)\"\n\t\t\t\t\t\t\tclass=\"cn-choice-cards__reason\"\n\t\t\t\t\t\t\tdata-testid=\"cn-choice-cards-reason\">{{ option.disabledReason }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t</label>\n\t\t\t\t<div v-if=\"hasDetails(option)\"\n\t\t\t\t\tclass=\"cn-choice-cards__details\"\n\t\t\t\t\t:data-testid=\"`cn-choice-cards-details-${option.value}`\">\n\t\t\t\t\t<!-- @slot option-details The chosen option's own fields, drawn\n\t\t\t\t\t inside the selected row's frame under its text (DqVolgendeStap:\n\t\t\t\t\t Opschorten's reason, days and return date). Rendered only for a\n\t\t\t\t\t selected option, outside the label so a field is not part of\n\t\t\t\t\t the radio's label. Scope: `{ option }`. -->\n\t\t\t\t\t<slot name=\"option-details\" :option=\"option\" />\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"$slots['option-actions']\" class=\"cn-choice-cards__actions\">\n\t\t\t\t\t<slot name=\"option-actions\" :option=\"option\" :selected=\"isSelected(option)\" />\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<div v-else class=\"cn-choice-cards__grid\" :style=\"gridStyle\">\n\t\t\t<!-- One cell per option: the card's label, and below it whatever the\n\t\t\t `#option-actions` slot adds. The slot sits OUTSIDE the label on\n\t\t\t purpose: a button inside a label is interactive content inside\n\t\t\t interactive content, and a click on it would toggle the radio. -->\n\t\t\t<div\n\t\t\t\tv-for=\"option in normalizedOptions\"\n\t\t\t\t:key=\"String(option.value)\"\n\t\t\t\tclass=\"cn-choice-cards__cell\">\n\t\t\t\t<label\n\t\t\t\t\tclass=\"cn-choice-cards__option\"\n\t\t\t\t\t:class=\"{ 'cn-choice-cards__option--selected': isSelected(option), 'cn-choice-cards__option--disabled': isDisabled(option) }\">\n\t\t\t\t\t<!-- A REAL, VISIBLE input, not a styled div with a click handler.\n\t\t\t\t\t It carries the checked state to assistive technology and to\n\t\t\t\t\t Windows high-contrast mode for free, and it keeps the\n\t\t\t\t\t selection perceivable without colour (WCAG 1.4.1) — the\n\t\t\t\t\t card's active border alone would not be. -->\n\t\t\t\t\t<input\n\t\t\t\t\t\tclass=\"cn-choice-cards__input\"\n\t\t\t\t\t\t:type=\"multiple ? 'checkbox' : 'radio'\"\n\t\t\t\t\t\t:name=\"groupName\"\n\t\t\t\t\t\t:value=\"String(option.value)\"\n\t\t\t\t\t\t:checked=\"isSelected(option)\"\n\t\t\t\t\t\t:disabled=\"isDisabled(option) || readonly\"\n\t\t\t\t\t\t:aria-describedby=\"option.disabled && option.disabledReason ? reasonId(option) : null\"\n\t\t\t\t\t\t@change=\"onToggle(option)\">\n\t\t\t\t\t<CnCard\n\t\t\t\t\t\tclass=\"cn-choice-cards__card\"\n\t\t\t\t\t\t:title=\"option.label\"\n\t\t\t\t\t\ttitleTag=\"span\"\n\t\t\t\t\t\t:description=\"option.description\"\n\t\t\t\t\t\t:stats=\"option.stats || []\"\n\t\t\t\t\t\t:tags=\"option.tags || []\"\n\t\t\t\t\t\t:active=\"isSelected(option)\"\n\t\t\t\t\t\tactiveVariant=\"primary\"\n\t\t\t\t\t\t:descriptionLines=\"descriptionLines\">\n\t\t\t\t\t\t<template v-if=\"option.icon\" #icon>\n\t\t\t\t\t\t\t<CnIcon :name=\"option.icon\" :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</CnCard>\n\t\t\t\t\t<span\n\t\t\t\t\t\tv-if=\"option.disabled && option.disabledReason\"\n\t\t\t\t\t\t:id=\"reasonId(option)\"\n\t\t\t\t\t\tclass=\"cn-choice-cards__reason cn-choice-cards__reason--card\"\n\t\t\t\t\t\tdata-testid=\"cn-choice-cards-reason\">{{ option.disabledReason }}</span>\n\t\t\t\t</label>\n\t\t\t\t<div v-if=\"$slots['option-actions']\" class=\"cn-choice-cards__actions\">\n\t\t\t\t\t<!-- @slot option-actions Content below one card, outside its label\n\t\t\t\t\t (e.g. a per-option Load button). Scope: `{ option, selected }`. -->\n\t\t\t\t\t<slot name=\"option-actions\" :option=\"option\" :selected=\"isSelected(option)\" />\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</fieldset>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\nimport CnCard from '../CnCard/CnCard.vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\n\nlet groupSeq = 0\n\n/**\n * CnChoiceCards — pick one option, or several, from a grid of cards.\n *\n * A card carries what a dropdown entry cannot: a description of what the\n * option actually is, and a few numbers about it. Use it where the choice\n * deserves explaining — the setup wizard's example-data step, an onboarding\n * template picker — and a plain `NcSelect` where a label already says enough.\n *\n * Each card is a `<label>` around a real radio (or checkbox when `multiple`),\n * so keyboard navigation, the checked state and high-contrast rendering are\n * the browser's, not a reimplementation.\n *\n * ```vue\n * <CnChoiceCards\n * v-model=\"picked\"\n * label=\"Which kind of organisation is this for?\"\n * :options=\"[\n * { value: 'municipality', label: 'Municipality',\n * description: 'A city council with committees and a decision list.',\n * stats: [{ label: 'Objects', value: 170 }] },\n * { value: 'none', label: 'None, I will set this up myself' },\n * ]\" />\n * ```\n */\nexport default {\n\tname: 'CnChoiceCards',\n\n\tcomponents: {\n\t\tCnCard,\n\t\tCnIcon,\n\t\tNcLoadingIcon,\n\t\tNcNoteCard,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The options to offer. Each: `{ value, label, description?, stats?,\n\t\t * tags?, icon?, disabled?, disabledReason? }` — `icon` is a PascalCase\n\t\t * MDI name resolved by CnIcon; a `disabled` option is greyed out and\n\t\t * cannot be picked, with `disabledReason` shown under it.\n\t\t */\n\t\toptions: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * The selected value: a scalar, or an array when `multiple`.\n\t\t *\n\t\t * @type {string|number|boolean|Array|null}\n\t\t */\n\t\tmodelValue: {\n\t\t\t// eslint-disable-next-line vue/prefer-prop-type-boolean-first -- an option value of '' must stay a string\n\t\t\ttype: [String, Number, Boolean, Array, Object],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Allow selecting several cards; the model becomes an array. */\n\t\tmultiple: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Group label, rendered as the fieldset's legend. */\n\t\tlabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Disable every card (e.g. a dependent choice with no parent value yet). */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Show the choice without letting it change: every option at full\n\t\t * contrast, the selected one marked, nothing pickable (DqBesluit's\n\t\t * recorded outcome). `disabled` greys the options out instead.\n\t\t */\n\t\treadonly: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Show a spinner instead of the grid while options are being fetched. */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Message shown when there is nothing to choose from. */\n\t\temptyText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Nothing to choose from here.'),\n\t\t},\n\n\t\t/**\n\t\t * `cards`: a grid of cards with stats and tags. `rows`: one option per\n\t\t * row, full width, the input on the left and the title over a muted\n\t\t * description; with `multiple` and one option it is a checkbox card.\n\t\t */\n\t\tlayout: {\n\t\t\ttype: String,\n\t\t\tdefault: 'cards',\n\t\t\tvalidator: (value) => ['cards', 'rows'].includes(value),\n\t\t},\n\n\t\t/** Columns of the card grid; 0 lets the grid fit as many as the width allows. */\n\t\tcolumns: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t},\n\n\t\t/** Lines of description shown before clamping. */\n\t\tdescriptionLines: {\n\t\t\ttype: Number,\n\t\t\tdefault: 4,\n\t\t},\n\t},\n\n\temits: ['update:modelValue'],\n\n\tdata() {\n\t\tgroupSeq += 1\n\t\treturn {\n\t\t\t// Radios only group by `name`, so two card groups on one page would\n\t\t\t// otherwise share a selection.\n\t\t\tgroupName: 'cn-choice-cards-' + groupSeq,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Options with a usable `value` and `label`, tolerating the shapes a\n\t\t * server list arrives in: `id` instead of `value`, `name` instead of\n\t\t * `label`, and a bare string for a value that is its own label.\n\t\t *\n\t\t * @return {Array<object>} The normalised options.\n\t\t */\n\t\tnormalizedOptions() {\n\t\t\treturn (this.options || [])\n\t\t\t\t.filter((option) => option !== null && option !== undefined)\n\t\t\t\t.map((option) => {\n\t\t\t\t\tif (typeof option !== 'object') {\n\t\t\t\t\t\treturn { value: option, label: String(option) }\n\t\t\t\t\t}\n\t\t\t\t\tconst value = option.value !== undefined ? option.value : option.id\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...option,\n\t\t\t\t\t\tvalue,\n\t\t\t\t\t\tlabel: option.label || option.name || String(value),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.filter((option) => option.value !== undefined)\n\t\t},\n\n\t\t/**\n\t\t * The current selection as an array of values, whatever the model shape.\n\t\t *\n\t\t * @return {Array} The selected values.\n\t\t */\n\t\tselectedValues() {\n\t\t\tif (this.multiple) {\n\t\t\t\treturn Array.isArray(this.modelValue) ? this.modelValue : []\n\t\t\t}\n\t\t\treturn (this.modelValue === null || this.modelValue === undefined || this.modelValue === '')\n\t\t\t\t? []\n\t\t\t\t: [this.modelValue]\n\t\t},\n\n\t\t/**\n\t\t * Inline override for the grid's column count. `auto-fit` with a\n\t\t * 260px minimum wraps a small option count onto its own row well\n\t\t * before the container is actually too narrow for them side by\n\t\t * side — two cards would stack at ~550px wide even though halving\n\t\t * the available width still leaves each one legible. A binary (or\n\t\t * near-binary) choice should always sit in one row and fill the\n\t\t * available width; a larger option count keeps the responsive\n\t\t * `auto-fit` wrapping from the stylesheet (`{}` leaves it alone).\n\t\t *\n\t\t * @return {object} A `gridTemplateColumns` override, or `{}`.\n\t\t */\n\t\tgridStyle() {\n\t\t\tif (this.columns > 0) {\n\t\t\t\treturn { gridTemplateColumns: `repeat(${this.columns}, minmax(0, 1fr))` }\n\t\t\t}\n\t\t\tconst count = this.normalizedOptions.length\n\t\t\tif (count > 0 && count <= 2) {\n\t\t\t\treturn { gridTemplateColumns: `repeat(${count}, minmax(0, 1fr))` }\n\t\t\t}\n\t\t\treturn {}\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether an option is currently selected.\n\t\t *\n\t\t * Compares as strings: a value that made the round trip through the\n\t\t * server as `\"1\"` must still match the `1` it was sent as.\n\t\t *\n\t\t * @param {object} option The normalised option.\n\t\t * @return {boolean} True when selected.\n\t\t */\n\t\t/**\n\t\t * Whether a rows option draws the `option-details` slot in its frame:\n\t\t * the slot is given and the option is the chosen one.\n\t\t *\n\t\t * @param {object} option The option.\n\t\t * @return {boolean}\n\t\t */\n\t\thasDetails(option) {\n\t\t\treturn Boolean(this.$slots['option-details']) && this.isSelected(option)\n\t\t},\n\n\t\t/**\n\t\t * Whether an option cannot be picked: the whole group is disabled, or the option is.\n\t\t *\n\t\t * @param {object} option The normalised option.\n\t\t * @return {boolean} True when disabled.\n\t\t */\n\t\tisDisabled(option) {\n\t\t\treturn this.disabled || option.disabled === true\n\t\t},\n\n\t\t/**\n\t\t * Id of an option's disabled reason, for `aria-describedby`.\n\t\t *\n\t\t * @param {object} option The normalised option.\n\t\t * @return {string} The id.\n\t\t */\n\t\treasonId(option) {\n\t\t\treturn `${this.groupName}-reason-${String(option.value).replace(/[^A-Za-z0-9_-]/g, '-')}`\n\t\t},\n\n\t\tisSelected(option) {\n\t\t\treturn this.selectedValues.some((v) => String(v) === String(option.value))\n\t\t},\n\n\t\t/**\n\t\t * Select (single) or toggle (multiple) an option and emit the new model.\n\t\t *\n\t\t * @param {object} option The normalised option.\n\t\t * @return {void}\n\t\t */\n\t\tonToggle(option) {\n\t\t\tif (this.readonly || this.isDisabled(option)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!this.multiple) {\n\t\t\t\t/**\n\t\t\t\t * @event update:modelValue The selected value, or the array of values when `multiple`.\n\t\t\t\t * @type {string|number|boolean|Array}\n\t\t\t\t */\n\t\t\t\t// Never the option OBJECT: a parent has to be able to hand back\n\t\t\t\t// what it receives as `modelValue`.\n\t\t\t\tthis.$emit('update:modelValue', option.value)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst next = this.selectedValues.filter((v) => String(v) !== String(option.value))\n\t\t\tif (next.length === this.selectedValues.length) {\n\t\t\t\tnext.push(option.value)\n\t\t\t}\n\t\t\tthis.$emit('update:modelValue', next)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped lang=\"scss\">\n.cn-choice-cards {\n\tborder: none;\n\tpadding: 0;\n\tmargin: 0;\n\tmin-inline-size: 0;\n}\n\n.cn-choice-cards__legend {\n\tpadding: 0;\n\tmargin-block-end: 8px;\n\tfont-weight: bold;\n}\n\n.cn-choice-cards__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 24px 0;\n}\n\n.cn-choice-cards__grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fit, minmax(260px, 1fr));\n\tgap: 12px;\n\t/* Room for the card shadow. `src/css/card.css` is loaded globally by every\n\t consumer and gives EVERY `.cn-card` a `box-shadow` — including these — so\n\t a grid flush against its container gets the shadow shaved off wherever\n\t that container clips, which the setup wizard's dialog body does on the\n\t sides and the bottom. 12px clears the widest of those shadows. */\n\tpadding: 12px;\n}\n\n.cn-choice-cards__cell {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tmin-inline-size: 0;\n}\n\n.cn-choice-cards__actions {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-choice-cards__option {\n\tposition: relative;\n\tdisplay: block;\n\tcursor: pointer;\n}\n\n.cn-choice-cards__input {\n\tposition: absolute;\n\tinset-block-start: 16px;\n\tinset-inline-end: 14px;\n\tz-index: 1;\n\tmargin: 0;\n\tcursor: pointer;\n}\n\n/* Room for the input, so a long title never runs underneath it. */\n.cn-choice-cards__option :deep(.cn-card) {\n\tpadding-inline-end: 44px;\n\ttransition: box-shadow 0.2s ease, border-color 0.2s ease;\n}\n\n/* The same global `card.css` also scales `.cn-card` up on hover. A transform\n counts towards the scrollable overflow of the nearest scroll container, so in\n a dialog body sized to its content a scrollbar appears the moment the pointer\n touches a card, and the grid twitches. The border colour below already\n carries hover, so the scale is dropped: at (0,3,0) this beats the global\n `.cn-card:hover` at (0,2,0). */\n.cn-choice-cards__option:hover :deep(.cn-card) {\n\tborder-color: var(--color-primary-element);\n\ttransform: none;\n}\n\n/* The focus ring belongs on the card, not on the small input inside it. */\n.cn-choice-cards__input:focus-visible + :deep(.cn-card) {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 2px;\n}\n\n.cn-choice-cards__input:disabled {\n\tcursor: default;\n}\n\n.cn-choice-cards__option--disabled .cn-choice-cards__input + :deep(.cn-card) {\n\topacity: 0.6;\n}\n\n.cn-choice-cards__rows {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n\n/* 12px 14px, as the boards draw an option row (DqVolgendeStap, DqBesluit). */\n.cn-choice-cards__row {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 12px;\n\tpadding: 12px 14px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 8px);\n\tbackground: var(--color-main-background);\n\tcursor: pointer;\n}\n\n.cn-choice-cards__row:hover {\n\tborder-color: var(--color-border-dark);\n}\n\n.cn-choice-cards__row--selected,\n.cn-choice-cards__row--selected:hover {\n\tborder-color: var(--color-primary-element);\n\t/* The second pixel of the 2px selected border, without moving the content. */\n\tbox-shadow: inset 0 0 0 1px var(--color-primary-element);\n\tbackground: var(--color-primary-element-light);\n}\n\n/* An option this record does not allow: the grey ground and the muted ink,\n still legible (DqVolgendeStap), not faded. */\n.cn-choice-cards__row--disabled,\n.cn-choice-cards__row--disabled:hover {\n\tborder-color: var(--color-border);\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-text-maxcontrast);\n\tcursor: default;\n}\n\n/* The title takes the muted ink itself: a theme that colours every label\n with `!important` (thematiq) would otherwise hand it the label colour. */\n.cn-choice-cards__row--disabled .cn-choice-cards__row-title {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* The chosen option's fields share the selected row's frame: the cell draws\n the 2px primary border and the ground, the row inside it draws none, and\n the fields sit under the text, indented to it (DqVolgendeStap). */\n.cn-choice-cards__cell--with-details {\n\tborder: 1px solid var(--color-primary-element);\n\tborder-radius: var(--border-radius-large, 8px);\n\tbox-shadow: inset 0 0 0 1px var(--color-primary-element);\n\tbackground: var(--color-primary-element-light);\n}\n\n.cn-choice-cards__cell--with-details .cn-choice-cards__row,\n.cn-choice-cards__cell--with-details .cn-choice-cards__row:hover {\n\tborder-color: transparent;\n\tbox-shadow: none;\n\tbackground: transparent;\n}\n\n.cn-choice-cards__details {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 14px;\n\tpadding: 0 14px 14px 44px;\n}\n\n.cn-choice-cards__row--readonly {\n\tcursor: default;\n}\n\n.cn-choice-cards__row--readonly:not(.cn-choice-cards__row--selected):hover {\n\tborder-color: var(--color-border);\n}\n\n.cn-choice-cards__row:has(.cn-choice-cards__row-input:focus-visible) {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 2px;\n}\n\n.cn-choice-cards__row-input {\n\tflex: none;\n\tmargin: 2px 0 0;\n\taccent-color: var(--color-primary-element);\n\tcursor: inherit;\n}\n\n.cn-choice-cards__row-icon {\n\tflex: none;\n}\n\n.cn-choice-cards__row-text {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-choice-cards__row-title {\n\tfont-size: 15px;\n\tfont-weight: 600;\n}\n\n/* Regular weight on the span itself: a theme that sets every label bold\n with `!important` (thematiq) reaches a span that only inherits. */\n.cn-choice-cards__row-description,\n.cn-choice-cards__reason {\n\tfont-size: 13px;\n\tfont-weight: 400;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-choice-cards__reason--card {\n\tdisplay: block;\n\tmargin-block-start: 4px;\n}\n</style>\n"],"names":["CnCard","CnIcon","t"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnChoiceCards.vue3.js","sources":["../../../../src/components/CnChoiceCards/CnChoiceCards.vue?vue&type=template&id=59d71736&lang.js"],"sourcesContent":["<!-- SPDX-License-Identifier: EUPL-1.2 -->\n<!-- SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl> -->\n<template>\n\t<fieldset class=\"cn-choice-cards\" :aria-busy=\"loading ? 'true' : 'false'\">\n\t\t<legend v-if=\"label\" class=\"cn-choice-cards__legend\">\n\t\t\t{{ label }}\n\t\t</legend>\n\n\t\t<div v-if=\"loading\" class=\"cn-choice-cards__loading\">\n\t\t\t<NcLoadingIcon :size=\"32\" />\n\t\t</div>\n\n\t\t<NcNoteCard v-else-if=\"normalizedOptions.length === 0\" type=\"warning\">\n\t\t\t{{ emptyText }}\n\t\t</NcNoteCard>\n\n\t\t<!-- Rows: one full-width option per row, the input on the left and the\n\t\t title over a muted description, no card chrome. -->\n\t\t<div v-else-if=\"layout === 'rows'\" class=\"cn-choice-cards__rows\" data-testid=\"cn-choice-cards-rows\">\n\t\t\t<div\n\t\t\t\tv-for=\"option in normalizedOptions\"\n\t\t\t\t:key=\"String(option.value)\"\n\t\t\t\tclass=\"cn-choice-cards__cell\"\n\t\t\t\t:class=\"{ 'cn-choice-cards__cell--with-details': hasDetails(option) }\">\n\t\t\t\t<label\n\t\t\t\t\tclass=\"cn-choice-cards__row\"\n\t\t\t\t\t:class=\"{ 'cn-choice-cards__row--selected': isSelected(option), 'cn-choice-cards__row--disabled': isDisabled(option), 'cn-choice-cards__row--readonly': readonly }\">\n\t\t\t\t\t<input\n\t\t\t\t\t\tclass=\"cn-choice-cards__row-input\"\n\t\t\t\t\t\t:type=\"multiple ? 'checkbox' : 'radio'\"\n\t\t\t\t\t\t:name=\"groupName\"\n\t\t\t\t\t\t:value=\"String(option.value)\"\n\t\t\t\t\t\t:checked=\"isSelected(option)\"\n\t\t\t\t\t\t:disabled=\"isDisabled(option) || readonly\"\n\t\t\t\t\t\t:aria-describedby=\"option.disabled && option.disabledReason ? reasonId(option) : null\"\n\t\t\t\t\t\t@change=\"onToggle(option)\">\n\t\t\t\t\t<CnIcon v-if=\"option.icon\"\n\t\t\t\t\t\t:name=\"option.icon\"\n\t\t\t\t\t\t:size=\"20\"\n\t\t\t\t\t\tclass=\"cn-choice-cards__row-icon\" />\n\t\t\t\t\t<span class=\"cn-choice-cards__row-text\">\n\t\t\t\t\t\t<span class=\"cn-choice-cards__row-title\">{{ option.label }}</span>\n\t\t\t\t\t\t<span v-if=\"option.description\" class=\"cn-choice-cards__row-description\">{{ option.description }}</span>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tv-if=\"option.disabled && option.disabledReason\"\n\t\t\t\t\t\t\t:id=\"reasonId(option)\"\n\t\t\t\t\t\t\tclass=\"cn-choice-cards__reason\"\n\t\t\t\t\t\t\tdata-testid=\"cn-choice-cards-reason\">{{ option.disabledReason }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t</label>\n\t\t\t\t<div v-if=\"hasDetails(option)\"\n\t\t\t\t\tclass=\"cn-choice-cards__details\"\n\t\t\t\t\t:data-testid=\"`cn-choice-cards-details-${option.value}`\">\n\t\t\t\t\t<!-- @slot option-details The chosen option's own fields, drawn\n\t\t\t\t\t inside the selected row's frame under its text (DqVolgendeStap:\n\t\t\t\t\t Opschorten's reason, days and return date). Rendered only for a\n\t\t\t\t\t selected option, outside the label so a field is not part of\n\t\t\t\t\t the radio's label. Scope: `{ option }`. -->\n\t\t\t\t\t<slot name=\"option-details\" :option=\"option\" />\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"$slots['option-actions']\" class=\"cn-choice-cards__actions\">\n\t\t\t\t\t<slot name=\"option-actions\" :option=\"option\" :selected=\"isSelected(option)\" />\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<div v-else class=\"cn-choice-cards__grid\" :style=\"gridStyle\">\n\t\t\t<!-- One cell per option: the card's label, and below it whatever the\n\t\t\t `#option-actions` slot adds. The slot sits OUTSIDE the label on\n\t\t\t purpose: a button inside a label is interactive content inside\n\t\t\t interactive content, and a click on it would toggle the radio. -->\n\t\t\t<div\n\t\t\t\tv-for=\"option in normalizedOptions\"\n\t\t\t\t:key=\"String(option.value)\"\n\t\t\t\tclass=\"cn-choice-cards__cell\">\n\t\t\t\t<label\n\t\t\t\t\tclass=\"cn-choice-cards__option\"\n\t\t\t\t\t:class=\"{ 'cn-choice-cards__option--selected': isSelected(option), 'cn-choice-cards__option--disabled': isDisabled(option) }\">\n\t\t\t\t\t<!-- A REAL, VISIBLE input, not a styled div with a click handler.\n\t\t\t\t\t It carries the checked state to assistive technology and to\n\t\t\t\t\t Windows high-contrast mode for free, and it keeps the\n\t\t\t\t\t selection perceivable without colour (WCAG 1.4.1) — the\n\t\t\t\t\t card's active border alone would not be. -->\n\t\t\t\t\t<input\n\t\t\t\t\t\tclass=\"cn-choice-cards__input\"\n\t\t\t\t\t\t:type=\"multiple ? 'checkbox' : 'radio'\"\n\t\t\t\t\t\t:name=\"groupName\"\n\t\t\t\t\t\t:value=\"String(option.value)\"\n\t\t\t\t\t\t:checked=\"isSelected(option)\"\n\t\t\t\t\t\t:disabled=\"isDisabled(option) || readonly\"\n\t\t\t\t\t\t:aria-describedby=\"option.disabled && option.disabledReason ? reasonId(option) : null\"\n\t\t\t\t\t\t@change=\"onToggle(option)\">\n\t\t\t\t\t<CnCard\n\t\t\t\t\t\tclass=\"cn-choice-cards__card\"\n\t\t\t\t\t\t:title=\"option.label\"\n\t\t\t\t\t\ttitleTag=\"span\"\n\t\t\t\t\t\t:description=\"option.description\"\n\t\t\t\t\t\t:stats=\"option.stats || []\"\n\t\t\t\t\t\t:tags=\"option.tags || []\"\n\t\t\t\t\t\t:active=\"isSelected(option)\"\n\t\t\t\t\t\tactiveVariant=\"primary\"\n\t\t\t\t\t\t:descriptionLines=\"descriptionLines\">\n\t\t\t\t\t\t<template v-if=\"option.icon\" #icon>\n\t\t\t\t\t\t\t<CnIcon :name=\"option.icon\" :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</CnCard>\n\t\t\t\t\t<span\n\t\t\t\t\t\tv-if=\"option.disabled && option.disabledReason\"\n\t\t\t\t\t\t:id=\"reasonId(option)\"\n\t\t\t\t\t\tclass=\"cn-choice-cards__reason cn-choice-cards__reason--card\"\n\t\t\t\t\t\tdata-testid=\"cn-choice-cards-reason\">{{ option.disabledReason }}</span>\n\t\t\t\t</label>\n\t\t\t\t<div v-if=\"$slots['option-actions']\" class=\"cn-choice-cards__actions\">\n\t\t\t\t\t<!-- @slot option-actions Content below one card, outside its label\n\t\t\t\t\t (e.g. a per-option Load button). Scope: `{ option, selected }`. -->\n\t\t\t\t\t<slot name=\"option-actions\" :option=\"option\" :selected=\"isSelected(option)\" />\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</fieldset>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\nimport CnCard from '../CnCard/CnCard.vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\n\nlet groupSeq = 0\n\n/**\n * CnChoiceCards — pick one option, or several, from a grid of cards.\n *\n * A card carries what a dropdown entry cannot: a description of what the\n * option actually is, and a few numbers about it. Use it where the choice\n * deserves explaining — the setup wizard's example-data step, an onboarding\n * template picker — and a plain `NcSelect` where a label already says enough.\n *\n * Each card is a `<label>` around a real radio (or checkbox when `multiple`),\n * so keyboard navigation, the checked state and high-contrast rendering are\n * the browser's, not a reimplementation.\n *\n * ```vue\n * <CnChoiceCards\n * v-model=\"picked\"\n * label=\"Which kind of organisation is this for?\"\n * :options=\"[\n * { value: 'municipality', label: 'Municipality',\n * description: 'A city council with committees and a decision list.',\n * stats: [{ label: 'Objects', value: 170 }] },\n * { value: 'none', label: 'None, I will set this up myself' },\n * ]\" />\n * ```\n */\nexport default {\n\tname: 'CnChoiceCards',\n\n\tcomponents: {\n\t\tCnCard,\n\t\tCnIcon,\n\t\tNcLoadingIcon,\n\t\tNcNoteCard,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The options to offer. Each: `{ value, label, description?, stats?,\n\t\t * tags?, icon?, disabled?, disabledReason? }` — `icon` is a PascalCase\n\t\t * MDI name resolved by CnIcon; a `disabled` option is greyed out and\n\t\t * cannot be picked, with `disabledReason` shown under it.\n\t\t */\n\t\toptions: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * The selected value: a scalar, or an array when `multiple`.\n\t\t *\n\t\t * @type {string|number|boolean|Array|null}\n\t\t */\n\t\tmodelValue: {\n\t\t\ttype: [Boolean, String, Number, Array, Object],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Allow selecting several cards; the model becomes an array. */\n\t\tmultiple: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Group label, rendered as the fieldset's legend. */\n\t\tlabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Disable every card (e.g. a dependent choice with no parent value yet). */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Show the choice without letting it change: every option at full\n\t\t * contrast, the selected one marked, nothing pickable (DqBesluit's\n\t\t * recorded outcome). `disabled` greys the options out instead.\n\t\t */\n\t\treadonly: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Show a spinner instead of the grid while options are being fetched. */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Message shown when there is nothing to choose from. */\n\t\temptyText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Nothing to choose from here.'),\n\t\t},\n\n\t\t/**\n\t\t * `cards`: a grid of cards with stats and tags. `rows`: one option per\n\t\t * row, full width, the input on the left and the title over a muted\n\t\t * description; with `multiple` and one option it is a checkbox card.\n\t\t */\n\t\tlayout: {\n\t\t\ttype: String,\n\t\t\tdefault: 'cards',\n\t\t\tvalidator: (value) => ['cards', 'rows'].includes(value),\n\t\t},\n\n\t\t/** Columns of the card grid; 0 lets the grid fit as many as the width allows. */\n\t\tcolumns: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t},\n\n\t\t/** Lines of description shown before clamping. */\n\t\tdescriptionLines: {\n\t\t\ttype: Number,\n\t\t\tdefault: 4,\n\t\t},\n\t},\n\n\temits: ['update:modelValue'],\n\n\tdata() {\n\t\tgroupSeq += 1\n\t\treturn {\n\t\t\t// Radios only group by `name`, so two card groups on one page would\n\t\t\t// otherwise share a selection.\n\t\t\tgroupName: 'cn-choice-cards-' + groupSeq,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Options with a usable `value` and `label`, tolerating the shapes a\n\t\t * server list arrives in: `id` instead of `value`, `name` instead of\n\t\t * `label`, and a bare string for a value that is its own label.\n\t\t *\n\t\t * @return {Array<object>} The normalised options.\n\t\t */\n\t\tnormalizedOptions() {\n\t\t\treturn (this.options || [])\n\t\t\t\t.filter((option) => option !== null && option !== undefined)\n\t\t\t\t.map((option) => {\n\t\t\t\t\tif (typeof option !== 'object') {\n\t\t\t\t\t\treturn { value: option, label: String(option) }\n\t\t\t\t\t}\n\t\t\t\t\tconst value = option.value !== undefined ? option.value : option.id\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...option,\n\t\t\t\t\t\tvalue,\n\t\t\t\t\t\tlabel: option.label || option.name || String(value),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.filter((option) => option.value !== undefined)\n\t\t},\n\n\t\t/**\n\t\t * The current selection as an array of values, whatever the model shape.\n\t\t *\n\t\t * @return {Array} The selected values.\n\t\t */\n\t\tselectedValues() {\n\t\t\tif (this.multiple) {\n\t\t\t\treturn Array.isArray(this.modelValue) ? this.modelValue : []\n\t\t\t}\n\t\t\treturn (this.modelValue === null || this.modelValue === undefined || this.modelValue === '')\n\t\t\t\t? []\n\t\t\t\t: [this.modelValue]\n\t\t},\n\n\t\t/**\n\t\t * Inline override for the grid's column count. `auto-fit` with a\n\t\t * 260px minimum wraps a small option count onto its own row well\n\t\t * before the container is actually too narrow for them side by\n\t\t * side — two cards would stack at ~550px wide even though halving\n\t\t * the available width still leaves each one legible. A binary (or\n\t\t * near-binary) choice should always sit in one row and fill the\n\t\t * available width; a larger option count keeps the responsive\n\t\t * `auto-fit` wrapping from the stylesheet (`{}` leaves it alone).\n\t\t *\n\t\t * @return {object} A `gridTemplateColumns` override, or `{}`.\n\t\t */\n\t\tgridStyle() {\n\t\t\tif (this.columns > 0) {\n\t\t\t\treturn { gridTemplateColumns: `repeat(${this.columns}, minmax(0, 1fr))` }\n\t\t\t}\n\t\t\tconst count = this.normalizedOptions.length\n\t\t\tif (count > 0 && count <= 2) {\n\t\t\t\treturn { gridTemplateColumns: `repeat(${count}, minmax(0, 1fr))` }\n\t\t\t}\n\t\t\treturn {}\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether an option is currently selected.\n\t\t *\n\t\t * Compares as strings: a value that made the round trip through the\n\t\t * server as `\"1\"` must still match the `1` it was sent as.\n\t\t *\n\t\t * @param {object} option The normalised option.\n\t\t * @return {boolean} True when selected.\n\t\t */\n\t\t/**\n\t\t * Whether a rows option draws the `option-details` slot in its frame:\n\t\t * the slot is given and the option is the chosen one.\n\t\t *\n\t\t * @param {object} option The option.\n\t\t * @return {boolean}\n\t\t */\n\t\thasDetails(option) {\n\t\t\treturn Boolean(this.$slots['option-details']) && this.isSelected(option)\n\t\t},\n\n\t\t/**\n\t\t * Whether an option cannot be picked: the whole group is disabled, or the option is.\n\t\t *\n\t\t * @param {object} option The normalised option.\n\t\t * @return {boolean} True when disabled.\n\t\t */\n\t\tisDisabled(option) {\n\t\t\treturn this.disabled || option.disabled === true\n\t\t},\n\n\t\t/**\n\t\t * Id of an option's disabled reason, for `aria-describedby`.\n\t\t *\n\t\t * @param {object} option The normalised option.\n\t\t * @return {string} The id.\n\t\t */\n\t\treasonId(option) {\n\t\t\treturn `${this.groupName}-reason-${String(option.value).replace(/[^A-Za-z0-9_-]/g, '-')}`\n\t\t},\n\n\t\tisSelected(option) {\n\t\t\treturn this.selectedValues.some((v) => String(v) === String(option.value))\n\t\t},\n\n\t\t/**\n\t\t * Select (single) or toggle (multiple) an option and emit the new model.\n\t\t *\n\t\t * @param {object} option The normalised option.\n\t\t * @return {void}\n\t\t */\n\t\tonToggle(option) {\n\t\t\tif (this.readonly || this.isDisabled(option)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!this.multiple) {\n\t\t\t\t/**\n\t\t\t\t * @event update:modelValue The selected value, or the array of values when `multiple`.\n\t\t\t\t * @type {string|number|boolean|Array}\n\t\t\t\t */\n\t\t\t\t// Never the option OBJECT: a parent has to be able to hand back\n\t\t\t\t// what it receives as `modelValue`.\n\t\t\t\tthis.$emit('update:modelValue', option.value)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst next = this.selectedValues.filter((v) => String(v) !== String(option.value))\n\t\t\tif (next.length === this.selectedValues.length) {\n\t\t\t\tnext.push(option.value)\n\t\t\t}\n\t\t\tthis.$emit('update:modelValue', next)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped lang=\"scss\">\n.cn-choice-cards {\n\tborder: none;\n\tpadding: 0;\n\tmargin: 0;\n\tmin-inline-size: 0;\n}\n\n.cn-choice-cards__legend {\n\tpadding: 0;\n\tmargin-block-end: 8px;\n\tfont-weight: bold;\n}\n\n.cn-choice-cards__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 24px 0;\n}\n\n.cn-choice-cards__grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fit, minmax(260px, 1fr));\n\tgap: 12px;\n\t/* Room for the card shadow. `src/css/card.css` is loaded globally by every\n\t consumer and gives EVERY `.cn-card` a `box-shadow` — including these — so\n\t a grid flush against its container gets the shadow shaved off wherever\n\t that container clips, which the setup wizard's dialog body does on the\n\t sides and the bottom. 12px clears the widest of those shadows. */\n\tpadding: 12px;\n}\n\n.cn-choice-cards__cell {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tmin-inline-size: 0;\n}\n\n.cn-choice-cards__actions {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-choice-cards__option {\n\tposition: relative;\n\tdisplay: block;\n\tcursor: pointer;\n}\n\n.cn-choice-cards__input {\n\tposition: absolute;\n\tinset-block-start: 16px;\n\tinset-inline-end: 14px;\n\tz-index: 1;\n\tmargin: 0;\n\tcursor: pointer;\n}\n\n/* Room for the input, so a long title never runs underneath it. */\n.cn-choice-cards__option :deep(.cn-card) {\n\tpadding-inline-end: 44px;\n\ttransition: box-shadow 0.2s ease, border-color 0.2s ease;\n}\n\n/* The same global `card.css` also scales `.cn-card` up on hover. A transform\n counts towards the scrollable overflow of the nearest scroll container, so in\n a dialog body sized to its content a scrollbar appears the moment the pointer\n touches a card, and the grid twitches. The border colour below already\n carries hover, so the scale is dropped: at (0,3,0) this beats the global\n `.cn-card:hover` at (0,2,0). */\n.cn-choice-cards__option:hover :deep(.cn-card) {\n\tborder-color: var(--color-primary-element);\n\ttransform: none;\n}\n\n/* The focus ring belongs on the card, not on the small input inside it. */\n.cn-choice-cards__input:focus-visible + :deep(.cn-card) {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 2px;\n}\n\n.cn-choice-cards__input:disabled {\n\tcursor: default;\n}\n\n.cn-choice-cards__option--disabled .cn-choice-cards__input + :deep(.cn-card) {\n\topacity: 0.6;\n}\n\n.cn-choice-cards__rows {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n\n/* 12px 14px, as the boards draw an option row (DqVolgendeStap, DqBesluit). */\n.cn-choice-cards__row {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 12px;\n\tpadding: 12px 14px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 8px);\n\tbackground: var(--color-main-background);\n\tcursor: pointer;\n}\n\n.cn-choice-cards__row:hover {\n\tborder-color: var(--color-border-dark);\n}\n\n.cn-choice-cards__row--selected,\n.cn-choice-cards__row--selected:hover {\n\tborder-color: var(--color-primary-element);\n\t/* The second pixel of the 2px selected border, without moving the content. */\n\tbox-shadow: inset 0 0 0 1px var(--color-primary-element);\n\tbackground: var(--color-primary-element-light);\n}\n\n/* An option this record does not allow: the grey ground and the muted ink,\n still legible (DqVolgendeStap), not faded. */\n.cn-choice-cards__row--disabled,\n.cn-choice-cards__row--disabled:hover {\n\tborder-color: var(--color-border);\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-text-maxcontrast);\n\tcursor: default;\n}\n\n/* The title takes the muted ink itself: a theme that colours every label\n with `!important` (thematiq) would otherwise hand it the label colour. */\n.cn-choice-cards__row--disabled .cn-choice-cards__row-title {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* The chosen option's fields share the selected row's frame: the cell draws\n the 2px primary border and the ground, the row inside it draws none, and\n the fields sit under the text, indented to it (DqVolgendeStap). */\n.cn-choice-cards__cell--with-details {\n\tborder: 1px solid var(--color-primary-element);\n\tborder-radius: var(--border-radius-large, 8px);\n\tbox-shadow: inset 0 0 0 1px var(--color-primary-element);\n\tbackground: var(--color-primary-element-light);\n}\n\n.cn-choice-cards__cell--with-details .cn-choice-cards__row,\n.cn-choice-cards__cell--with-details .cn-choice-cards__row:hover {\n\tborder-color: transparent;\n\tbox-shadow: none;\n\tbackground: transparent;\n}\n\n.cn-choice-cards__details {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 14px;\n\tpadding: 0 14px 14px 44px;\n}\n\n.cn-choice-cards__row--readonly {\n\tcursor: default;\n}\n\n.cn-choice-cards__row--readonly:not(.cn-choice-cards__row--selected):hover {\n\tborder-color: var(--color-border);\n}\n\n.cn-choice-cards__row:has(.cn-choice-cards__row-input:focus-visible) {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 2px;\n}\n\n.cn-choice-cards__row-input {\n\tflex: none;\n\tmargin: 2px 0 0;\n\taccent-color: var(--color-primary-element);\n\tcursor: inherit;\n}\n\n.cn-choice-cards__row-icon {\n\tflex: none;\n}\n\n.cn-choice-cards__row-text {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-choice-cards__row-title {\n\tfont-size: 15px;\n\tfont-weight: 600;\n}\n\n/* Regular weight on the span itself: a theme that sets every label bold\n with `!important` (thematiq) reaches a span that only inherits. */\n.cn-choice-cards__row-description,\n.cn-choice-cards__reason {\n\tfont-size: 13px;\n\tfont-weight: 400;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-choice-cards__reason--card {\n\tdisplay: block;\n\tmargin-block-start: 4px;\n}\n</style>\n"],"names":["_createElementBlock","_toDisplayString","_openBlock","_createBlock","_normalizeClass","_createElementVNode","_renderSlot","_Fragment","_renderList","_createVNode"],"mappings":"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"}
1
+ {"version":3,"file":"CnChoiceCards.vue3.js","sources":["../../../../src/components/CnChoiceCards/CnChoiceCards.vue?vue&type=template&id=5af5bf60&lang.js"],"sourcesContent":["<!-- SPDX-License-Identifier: EUPL-1.2 -->\n<!-- SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl> -->\n<template>\n\t<fieldset class=\"cn-choice-cards\" :aria-busy=\"loading ? 'true' : 'false'\">\n\t\t<legend v-if=\"label\" class=\"cn-choice-cards__legend\">\n\t\t\t{{ label }}\n\t\t</legend>\n\n\t\t<div v-if=\"loading\" class=\"cn-choice-cards__loading\">\n\t\t\t<NcLoadingIcon :size=\"32\" />\n\t\t</div>\n\n\t\t<NcNoteCard v-else-if=\"normalizedOptions.length === 0\" type=\"warning\">\n\t\t\t{{ emptyText }}\n\t\t</NcNoteCard>\n\n\t\t<!-- Rows: one full-width option per row, the input on the left and the\n\t\t title over a muted description, no card chrome. -->\n\t\t<div v-else-if=\"layout === 'rows'\" class=\"cn-choice-cards__rows\" data-testid=\"cn-choice-cards-rows\">\n\t\t\t<div\n\t\t\t\tv-for=\"option in normalizedOptions\"\n\t\t\t\t:key=\"String(option.value)\"\n\t\t\t\tclass=\"cn-choice-cards__cell\"\n\t\t\t\t:class=\"{ 'cn-choice-cards__cell--with-details': hasDetails(option) }\">\n\t\t\t\t<label\n\t\t\t\t\tclass=\"cn-choice-cards__row\"\n\t\t\t\t\t:class=\"{ 'cn-choice-cards__row--selected': isSelected(option), 'cn-choice-cards__row--disabled': isDisabled(option), 'cn-choice-cards__row--readonly': readonly }\">\n\t\t\t\t\t<input\n\t\t\t\t\t\tclass=\"cn-choice-cards__row-input\"\n\t\t\t\t\t\t:type=\"multiple ? 'checkbox' : 'radio'\"\n\t\t\t\t\t\t:name=\"groupName\"\n\t\t\t\t\t\t:value=\"String(option.value)\"\n\t\t\t\t\t\t:checked=\"isSelected(option)\"\n\t\t\t\t\t\t:disabled=\"isDisabled(option) || readonly\"\n\t\t\t\t\t\t:aria-describedby=\"option.disabled && option.disabledReason ? reasonId(option) : null\"\n\t\t\t\t\t\t@change=\"onToggle(option)\">\n\t\t\t\t\t<CnIcon v-if=\"option.icon\"\n\t\t\t\t\t\t:name=\"option.icon\"\n\t\t\t\t\t\t:size=\"20\"\n\t\t\t\t\t\tclass=\"cn-choice-cards__row-icon\" />\n\t\t\t\t\t<span class=\"cn-choice-cards__row-text\">\n\t\t\t\t\t\t<span class=\"cn-choice-cards__row-title\">{{ option.label }}</span>\n\t\t\t\t\t\t<span v-if=\"option.description\" class=\"cn-choice-cards__row-description\">{{ option.description }}</span>\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tv-if=\"option.disabled && option.disabledReason\"\n\t\t\t\t\t\t\t:id=\"reasonId(option)\"\n\t\t\t\t\t\t\tclass=\"cn-choice-cards__reason\"\n\t\t\t\t\t\t\tdata-testid=\"cn-choice-cards-reason\">{{ option.disabledReason }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t</label>\n\t\t\t\t<div v-if=\"hasDetails(option)\"\n\t\t\t\t\tclass=\"cn-choice-cards__details\"\n\t\t\t\t\t:data-testid=\"`cn-choice-cards-details-${option.value}`\">\n\t\t\t\t\t<!-- @slot option-details The chosen option's own fields, drawn\n\t\t\t\t\t inside the selected row's frame under its text (DqVolgendeStap:\n\t\t\t\t\t Opschorten's reason, days and return date). Rendered only for a\n\t\t\t\t\t selected option, outside the label so a field is not part of\n\t\t\t\t\t the radio's label. Scope: `{ option }`. -->\n\t\t\t\t\t<slot name=\"option-details\" :option=\"option\" />\n\t\t\t\t</div>\n\t\t\t\t<div v-if=\"$slots['option-actions']\" class=\"cn-choice-cards__actions\">\n\t\t\t\t\t<slot name=\"option-actions\" :option=\"option\" :selected=\"isSelected(option)\" />\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<div v-else class=\"cn-choice-cards__grid\" :style=\"gridStyle\">\n\t\t\t<!-- One cell per option: the card's label, and below it whatever the\n\t\t\t `#option-actions` slot adds. The slot sits OUTSIDE the label on\n\t\t\t purpose: a button inside a label is interactive content inside\n\t\t\t interactive content, and a click on it would toggle the radio. -->\n\t\t\t<div\n\t\t\t\tv-for=\"option in normalizedOptions\"\n\t\t\t\t:key=\"String(option.value)\"\n\t\t\t\tclass=\"cn-choice-cards__cell\">\n\t\t\t\t<label\n\t\t\t\t\tclass=\"cn-choice-cards__option\"\n\t\t\t\t\t:class=\"{ 'cn-choice-cards__option--selected': isSelected(option), 'cn-choice-cards__option--disabled': isDisabled(option) }\">\n\t\t\t\t\t<!-- A REAL, VISIBLE input, not a styled div with a click handler.\n\t\t\t\t\t It carries the checked state to assistive technology and to\n\t\t\t\t\t Windows high-contrast mode for free, and it keeps the\n\t\t\t\t\t selection perceivable without colour (WCAG 1.4.1) — the\n\t\t\t\t\t card's active border alone would not be. -->\n\t\t\t\t\t<input\n\t\t\t\t\t\tclass=\"cn-choice-cards__input\"\n\t\t\t\t\t\t:type=\"multiple ? 'checkbox' : 'radio'\"\n\t\t\t\t\t\t:name=\"groupName\"\n\t\t\t\t\t\t:value=\"String(option.value)\"\n\t\t\t\t\t\t:checked=\"isSelected(option)\"\n\t\t\t\t\t\t:disabled=\"isDisabled(option) || readonly\"\n\t\t\t\t\t\t:aria-describedby=\"option.disabled && option.disabledReason ? reasonId(option) : null\"\n\t\t\t\t\t\t@change=\"onToggle(option)\">\n\t\t\t\t\t<CnCard\n\t\t\t\t\t\tclass=\"cn-choice-cards__card\"\n\t\t\t\t\t\t:title=\"option.label\"\n\t\t\t\t\t\ttitleTag=\"span\"\n\t\t\t\t\t\t:description=\"option.description\"\n\t\t\t\t\t\t:stats=\"option.stats || []\"\n\t\t\t\t\t\t:tags=\"option.tags || []\"\n\t\t\t\t\t\t:active=\"isSelected(option)\"\n\t\t\t\t\t\tactiveVariant=\"primary\"\n\t\t\t\t\t\t:descriptionLines=\"descriptionLines\">\n\t\t\t\t\t\t<template v-if=\"option.icon\" #icon>\n\t\t\t\t\t\t\t<CnIcon :name=\"option.icon\" :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</CnCard>\n\t\t\t\t\t<span\n\t\t\t\t\t\tv-if=\"option.disabled && option.disabledReason\"\n\t\t\t\t\t\t:id=\"reasonId(option)\"\n\t\t\t\t\t\tclass=\"cn-choice-cards__reason cn-choice-cards__reason--card\"\n\t\t\t\t\t\tdata-testid=\"cn-choice-cards-reason\">{{ option.disabledReason }}</span>\n\t\t\t\t</label>\n\t\t\t\t<div v-if=\"$slots['option-actions']\" class=\"cn-choice-cards__actions\">\n\t\t\t\t\t<!-- @slot option-actions Content below one card, outside its label\n\t\t\t\t\t (e.g. a per-option Load button). Scope: `{ option, selected }`. -->\n\t\t\t\t\t<slot name=\"option-actions\" :option=\"option\" :selected=\"isSelected(option)\" />\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</fieldset>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\nimport CnCard from '../CnCard/CnCard.vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\n\nlet groupSeq = 0\n\n/**\n * CnChoiceCards — pick one option, or several, from a grid of cards.\n *\n * A card carries what a dropdown entry cannot: a description of what the\n * option actually is, and a few numbers about it. Use it where the choice\n * deserves explaining — the setup wizard's example-data step, an onboarding\n * template picker — and a plain `NcSelect` where a label already says enough.\n *\n * Each card is a `<label>` around a real radio (or checkbox when `multiple`),\n * so keyboard navigation, the checked state and high-contrast rendering are\n * the browser's, not a reimplementation.\n *\n * ```vue\n * <CnChoiceCards\n * v-model=\"picked\"\n * label=\"Which kind of organisation is this for?\"\n * :options=\"[\n * { value: 'municipality', label: 'Municipality',\n * description: 'A city council with committees and a decision list.',\n * stats: [{ label: 'Objects', value: 170 }] },\n * { value: 'none', label: 'None, I will set this up myself' },\n * ]\" />\n * ```\n */\nexport default {\n\tname: 'CnChoiceCards',\n\n\tcomponents: {\n\t\tCnCard,\n\t\tCnIcon,\n\t\tNcLoadingIcon,\n\t\tNcNoteCard,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The options to offer. Each: `{ value, label, description?, stats?,\n\t\t * tags?, icon?, disabled?, disabledReason? }` — `icon` is a PascalCase\n\t\t * MDI name resolved by CnIcon; a `disabled` option is greyed out and\n\t\t * cannot be picked, with `disabledReason` shown under it.\n\t\t */\n\t\toptions: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * The selected value: a scalar, or an array when `multiple`.\n\t\t *\n\t\t * @type {string|number|boolean|Array|null}\n\t\t */\n\t\tmodelValue: {\n\t\t\t// eslint-disable-next-line vue/prefer-prop-type-boolean-first -- an option value of '' must stay a string\n\t\t\ttype: [String, Number, Boolean, Array, Object],\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** Allow selecting several cards; the model becomes an array. */\n\t\tmultiple: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Group label, rendered as the fieldset's legend. */\n\t\tlabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Disable every card (e.g. a dependent choice with no parent value yet). */\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * Show the choice without letting it change: every option at full\n\t\t * contrast, the selected one marked, nothing pickable (DqBesluit's\n\t\t * recorded outcome). `disabled` greys the options out instead.\n\t\t */\n\t\treadonly: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Show a spinner instead of the grid while options are being fetched. */\n\t\tloading: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Message shown when there is nothing to choose from. */\n\t\temptyText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Nothing to choose from here.'),\n\t\t},\n\n\t\t/**\n\t\t * `cards`: a grid of cards with stats and tags. `rows`: one option per\n\t\t * row, full width, the input on the left and the title over a muted\n\t\t * description; with `multiple` and one option it is a checkbox card.\n\t\t */\n\t\tlayout: {\n\t\t\ttype: String,\n\t\t\tdefault: 'cards',\n\t\t\tvalidator: (value) => ['cards', 'rows'].includes(value),\n\t\t},\n\n\t\t/** Columns of the card grid; 0 lets the grid fit as many as the width allows. */\n\t\tcolumns: {\n\t\t\ttype: Number,\n\t\t\tdefault: 0,\n\t\t},\n\n\t\t/** Lines of description shown before clamping. */\n\t\tdescriptionLines: {\n\t\t\ttype: Number,\n\t\t\tdefault: 4,\n\t\t},\n\t},\n\n\temits: ['update:modelValue'],\n\n\tdata() {\n\t\tgroupSeq += 1\n\t\treturn {\n\t\t\t// Radios only group by `name`, so two card groups on one page would\n\t\t\t// otherwise share a selection.\n\t\t\tgroupName: 'cn-choice-cards-' + groupSeq,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Options with a usable `value` and `label`, tolerating the shapes a\n\t\t * server list arrives in: `id` instead of `value`, `name` instead of\n\t\t * `label`, and a bare string for a value that is its own label.\n\t\t *\n\t\t * @return {Array<object>} The normalised options.\n\t\t */\n\t\tnormalizedOptions() {\n\t\t\treturn (this.options || [])\n\t\t\t\t.filter((option) => option !== null && option !== undefined)\n\t\t\t\t.map((option) => {\n\t\t\t\t\tif (typeof option !== 'object') {\n\t\t\t\t\t\treturn { value: option, label: String(option) }\n\t\t\t\t\t}\n\t\t\t\t\tconst value = option.value !== undefined ? option.value : option.id\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...option,\n\t\t\t\t\t\tvalue,\n\t\t\t\t\t\tlabel: option.label || option.name || String(value),\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t.filter((option) => option.value !== undefined)\n\t\t},\n\n\t\t/**\n\t\t * The current selection as an array of values, whatever the model shape.\n\t\t *\n\t\t * @return {Array} The selected values.\n\t\t */\n\t\tselectedValues() {\n\t\t\tif (this.multiple) {\n\t\t\t\treturn Array.isArray(this.modelValue) ? this.modelValue : []\n\t\t\t}\n\t\t\treturn (this.modelValue === null || this.modelValue === undefined || this.modelValue === '')\n\t\t\t\t? []\n\t\t\t\t: [this.modelValue]\n\t\t},\n\n\t\t/**\n\t\t * Inline override for the grid's column count. `auto-fit` with a\n\t\t * 260px minimum wraps a small option count onto its own row well\n\t\t * before the container is actually too narrow for them side by\n\t\t * side — two cards would stack at ~550px wide even though halving\n\t\t * the available width still leaves each one legible. A binary (or\n\t\t * near-binary) choice should always sit in one row and fill the\n\t\t * available width; a larger option count keeps the responsive\n\t\t * `auto-fit` wrapping from the stylesheet (`{}` leaves it alone).\n\t\t *\n\t\t * @return {object} A `gridTemplateColumns` override, or `{}`.\n\t\t */\n\t\tgridStyle() {\n\t\t\tif (this.columns > 0) {\n\t\t\t\treturn { gridTemplateColumns: `repeat(${this.columns}, minmax(0, 1fr))` }\n\t\t\t}\n\t\t\tconst count = this.normalizedOptions.length\n\t\t\tif (count > 0 && count <= 2) {\n\t\t\t\treturn { gridTemplateColumns: `repeat(${count}, minmax(0, 1fr))` }\n\t\t\t}\n\t\t\treturn {}\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether an option is currently selected.\n\t\t *\n\t\t * Compares as strings: a value that made the round trip through the\n\t\t * server as `\"1\"` must still match the `1` it was sent as.\n\t\t *\n\t\t * @param {object} option The normalised option.\n\t\t * @return {boolean} True when selected.\n\t\t */\n\t\t/**\n\t\t * Whether a rows option draws the `option-details` slot in its frame:\n\t\t * the slot is given and the option is the chosen one.\n\t\t *\n\t\t * @param {object} option The option.\n\t\t * @return {boolean}\n\t\t */\n\t\thasDetails(option) {\n\t\t\treturn Boolean(this.$slots['option-details']) && this.isSelected(option)\n\t\t},\n\n\t\t/**\n\t\t * Whether an option cannot be picked: the whole group is disabled, or the option is.\n\t\t *\n\t\t * @param {object} option The normalised option.\n\t\t * @return {boolean} True when disabled.\n\t\t */\n\t\tisDisabled(option) {\n\t\t\treturn this.disabled || option.disabled === true\n\t\t},\n\n\t\t/**\n\t\t * Id of an option's disabled reason, for `aria-describedby`.\n\t\t *\n\t\t * @param {object} option The normalised option.\n\t\t * @return {string} The id.\n\t\t */\n\t\treasonId(option) {\n\t\t\treturn `${this.groupName}-reason-${String(option.value).replace(/[^A-Za-z0-9_-]/g, '-')}`\n\t\t},\n\n\t\tisSelected(option) {\n\t\t\treturn this.selectedValues.some((v) => String(v) === String(option.value))\n\t\t},\n\n\t\t/**\n\t\t * Select (single) or toggle (multiple) an option and emit the new model.\n\t\t *\n\t\t * @param {object} option The normalised option.\n\t\t * @return {void}\n\t\t */\n\t\tonToggle(option) {\n\t\t\tif (this.readonly || this.isDisabled(option)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!this.multiple) {\n\t\t\t\t/**\n\t\t\t\t * @event update:modelValue The selected value, or the array of values when `multiple`.\n\t\t\t\t * @type {string|number|boolean|Array}\n\t\t\t\t */\n\t\t\t\t// Never the option OBJECT: a parent has to be able to hand back\n\t\t\t\t// what it receives as `modelValue`.\n\t\t\t\tthis.$emit('update:modelValue', option.value)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst next = this.selectedValues.filter((v) => String(v) !== String(option.value))\n\t\t\tif (next.length === this.selectedValues.length) {\n\t\t\t\tnext.push(option.value)\n\t\t\t}\n\t\t\tthis.$emit('update:modelValue', next)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped lang=\"scss\">\n.cn-choice-cards {\n\tborder: none;\n\tpadding: 0;\n\tmargin: 0;\n\tmin-inline-size: 0;\n}\n\n.cn-choice-cards__legend {\n\tpadding: 0;\n\tmargin-block-end: 8px;\n\tfont-weight: bold;\n}\n\n.cn-choice-cards__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 24px 0;\n}\n\n.cn-choice-cards__grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fit, minmax(260px, 1fr));\n\tgap: 12px;\n\t/* Room for the card shadow. `src/css/card.css` is loaded globally by every\n\t consumer and gives EVERY `.cn-card` a `box-shadow` — including these — so\n\t a grid flush against its container gets the shadow shaved off wherever\n\t that container clips, which the setup wizard's dialog body does on the\n\t sides and the bottom. 12px clears the widest of those shadows. */\n\tpadding: 12px;\n}\n\n.cn-choice-cards__cell {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n\tmin-inline-size: 0;\n}\n\n.cn-choice-cards__actions {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-choice-cards__option {\n\tposition: relative;\n\tdisplay: block;\n\tcursor: pointer;\n}\n\n.cn-choice-cards__input {\n\tposition: absolute;\n\tinset-block-start: 16px;\n\tinset-inline-end: 14px;\n\tz-index: 1;\n\tmargin: 0;\n\tcursor: pointer;\n}\n\n/* Room for the input, so a long title never runs underneath it. */\n.cn-choice-cards__option :deep(.cn-card) {\n\tpadding-inline-end: 44px;\n\ttransition: box-shadow 0.2s ease, border-color 0.2s ease;\n}\n\n/* The same global `card.css` also scales `.cn-card` up on hover. A transform\n counts towards the scrollable overflow of the nearest scroll container, so in\n a dialog body sized to its content a scrollbar appears the moment the pointer\n touches a card, and the grid twitches. The border colour below already\n carries hover, so the scale is dropped: at (0,3,0) this beats the global\n `.cn-card:hover` at (0,2,0). */\n.cn-choice-cards__option:hover :deep(.cn-card) {\n\tborder-color: var(--color-primary-element);\n\ttransform: none;\n}\n\n/* The focus ring belongs on the card, not on the small input inside it. */\n.cn-choice-cards__input:focus-visible + :deep(.cn-card) {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 2px;\n}\n\n.cn-choice-cards__input:disabled {\n\tcursor: default;\n}\n\n.cn-choice-cards__option--disabled .cn-choice-cards__input + :deep(.cn-card) {\n\topacity: 0.6;\n}\n\n.cn-choice-cards__rows {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 8px;\n}\n\n/* 12px 14px, as the boards draw an option row (DqVolgendeStap, DqBesluit). */\n.cn-choice-cards__row {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 12px;\n\tpadding: 12px 14px;\n\tborder: 1px solid var(--color-border);\n\tborder-radius: var(--border-radius-large, 8px);\n\tbackground: var(--color-main-background);\n\tcursor: pointer;\n}\n\n.cn-choice-cards__row:hover {\n\tborder-color: var(--color-border-dark);\n}\n\n.cn-choice-cards__row--selected,\n.cn-choice-cards__row--selected:hover {\n\tborder-color: var(--color-primary-element);\n\t/* The second pixel of the 2px selected border, without moving the content. */\n\tbox-shadow: inset 0 0 0 1px var(--color-primary-element);\n\tbackground: var(--color-primary-element-light);\n}\n\n/* An option this record does not allow: the grey ground and the muted ink,\n still legible (DqVolgendeStap), not faded. */\n.cn-choice-cards__row--disabled,\n.cn-choice-cards__row--disabled:hover {\n\tborder-color: var(--color-border);\n\tbackground: var(--color-background-hover);\n\tcolor: var(--color-text-maxcontrast);\n\tcursor: default;\n}\n\n/* The title takes the muted ink itself: a theme that colours every label\n with `!important` (thematiq) would otherwise hand it the label colour. */\n.cn-choice-cards__row--disabled .cn-choice-cards__row-title {\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* The chosen option's fields share the selected row's frame: the cell draws\n the 2px primary border and the ground, the row inside it draws none, and\n the fields sit under the text, indented to it (DqVolgendeStap). */\n.cn-choice-cards__cell--with-details {\n\tborder: 1px solid var(--color-primary-element);\n\tborder-radius: var(--border-radius-large, 8px);\n\tbox-shadow: inset 0 0 0 1px var(--color-primary-element);\n\tbackground: var(--color-primary-element-light);\n}\n\n.cn-choice-cards__cell--with-details .cn-choice-cards__row,\n.cn-choice-cards__cell--with-details .cn-choice-cards__row:hover {\n\tborder-color: transparent;\n\tbox-shadow: none;\n\tbackground: transparent;\n}\n\n.cn-choice-cards__details {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 14px;\n\tpadding: 0 14px 14px 44px;\n}\n\n.cn-choice-cards__row--readonly {\n\tcursor: default;\n}\n\n.cn-choice-cards__row--readonly:not(.cn-choice-cards__row--selected):hover {\n\tborder-color: var(--color-border);\n}\n\n.cn-choice-cards__row:has(.cn-choice-cards__row-input:focus-visible) {\n\toutline: 2px solid var(--color-primary-element);\n\toutline-offset: 2px;\n}\n\n.cn-choice-cards__row-input {\n\tflex: none;\n\tmargin: 2px 0 0;\n\taccent-color: var(--color-primary-element);\n\tcursor: inherit;\n}\n\n.cn-choice-cards__row-icon {\n\tflex: none;\n}\n\n.cn-choice-cards__row-text {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-choice-cards__row-title {\n\tfont-size: 15px;\n\tfont-weight: 600;\n}\n\n/* Regular weight on the span itself: a theme that sets every label bold\n with `!important` (thematiq) reaches a span that only inherits. */\n.cn-choice-cards__row-description,\n.cn-choice-cards__reason {\n\tfont-size: 13px;\n\tfont-weight: 400;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-choice-cards__reason--card {\n\tdisplay: block;\n\tmargin-block-start: 4px;\n}\n</style>\n"],"names":["_createElementBlock","_toDisplayString","_openBlock","_createBlock","_normalizeClass","_createElementVNode","_renderSlot","_Fragment","_renderList","_createVNode"],"mappings":"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"}
@@ -2,7 +2,7 @@ import script from './CnDashboardPage.vue2.js';
2
2
  import { render } from './CnDashboardPage.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-0270abf8";
5
+ script.__scopeId = "data-v-5b3dc7b0";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -706,7 +706,8 @@ var script = {
706
706
  * mount. The control is a select, or with `control: 'segmented'` a
707
707
  * compact segmented group; `placement: 'description'` puts a segmented
708
708
  * group on the header's description line, after the period group
709
- * (DqProcesanalyse "Gemiddeld | Mediaan"). An endpoint widget can read
709
+ * (DqProcesanalyse "Gemiddeld | Mediaan"), and a select before it
710
+ * (DqDoorlooptijd "alle zaaktypen"). An endpoint widget can read
710
711
  * the value in its `responsePath` too (`phaseDays.@workspace.measure`).
711
712
  * `optionsFrom` reads the options instead of listing them: `{ url,
712
713
  * params?, responsePath?, valueField?, labelField? }` from an app
@@ -1431,10 +1432,26 @@ var script = {
1431
1432
  * @return {Array<object>}
1432
1433
  */
1433
1434
  rowPageFilters() {
1434
- const inDescription = this.descriptionPageFilters;
1435
+ const inDescription = [...this.descriptionPageFilters, ...this.descriptionSelectFilters];
1435
1436
  return (this.pageFilters || []).filter((pf) => pf && pf.key && !inDescription.includes(pf))
1436
1437
  },
1437
1438
 
1439
+ /**
1440
+ * The select page filters drawn on the description line
1441
+ * (`placement: "description"`, any control but `segmented`), before
1442
+ * the period group: DqDoorlooptijd's "alle zaaktypen". Without a
1443
+ * header row they fall back to the filter row.
1444
+ *
1445
+ * @spec openspec/changes/dashboard-select-filter-in-description/specs/dashboard-select-filter-in-description/spec.md#requirement-a-select-page-filter-can-sit-on-the-description-line
1446
+ * @return {Array<object>}
1447
+ */
1448
+ descriptionSelectFilters() {
1449
+ if (!this.showHeader) {
1450
+ return []
1451
+ }
1452
+ return (this.pageFilters || []).filter((pf) => pf && pf.key && pf.control !== 'segmented' && pf.placement === 'description')
1453
+ },
1454
+
1438
1455
  /**
1439
1456
  * The edit button's label through the app's translate: a manifest's
1440
1457
  * `editLabel` ("Edit layout", DqDashboard) reads in the user's language.