@conduction/nextcloud-vue 2.104.0 → 2.106.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.
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue.js +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +18 -0
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js +3 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +18 -0
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +93 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +6 -3
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +3 -3
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +112 -5
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue.js +9 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue.js.map +1 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js +24 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js.map +1 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js +49 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js.map +1 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +36 -18
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +17 -38
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue.js +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js +1 -1
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js +2 -0
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js +32 -29
- package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +53 -2
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +119 -111
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue.js +1 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +62 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue.js +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js +73 -4
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js +74 -27
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +208 -46
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +121 -98
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +24 -0
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +20 -4
- package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js +25 -6
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +112 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +100 -44
- package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue.js +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js +67 -4
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js +1 -1
- package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue.js +9 -0
- package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue.js.map +1 -0
- package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue2.js +88 -0
- package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue3.js +39 -0
- package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue.js +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +295 -12
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +267 -144
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +4 -0
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +3 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +93 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +322 -246
- package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue.js +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js +122 -7
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js +189 -93
- package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue.js +1 -1
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js.map +1 -1
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js +3 -3
- package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +16 -4
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue.js +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +42 -0
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +38 -19
- package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue.js +9 -0
- package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue.js.map +1 -0
- package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue2.js +258 -0
- package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue3.js +69 -0
- package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js +11 -0
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +4 -4
- package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +16 -14
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +12 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +24 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +24 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +1369 -748
- package/dist/esm/utils/dueText.js +18 -3
- package/dist/esm/utils/dueText.js.map +1 -1
- package/dist/esm/utils/libraryWidgetKeys.js +2 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/nextcloudApps.js +45 -0
- package/dist/esm/utils/nextcloudApps.js.map +1 -0
- package/dist/esm/utils/scopeListLayout.js +9 -2
- package/dist/esm/utils/scopeListLayout.js.map +1 -1
- package/dist/esm/utils/sortOrder.js +11 -1
- package/dist/esm/utils/sortOrder.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/visibleWhen.js +12 -4
- package/dist/esm/utils/visibleWhen.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +8965 -6497
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +1369 -748
- package/l10n/en.json +24 -1
- package/l10n/nl.json +24 -1
- package/package.json +1 -1
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -1
- package/src/components/CnCardGrid/CnCardGrid.md +2 -0
- package/src/components/CnCardGrid/CnCardGrid.vue +20 -0
- package/src/components/CnCellRenderer/CnCellRenderer.vue +19 -1
- package/src/components/CnChartWidget/CnChartWidget.md +2 -0
- package/src/components/CnChartWidget/CnChartWidget.vue +93 -1
- package/src/components/CnDashboardPage/CnDashboardPage.vue +9 -6
- package/src/components/CnDetailPage/CnDetailPage.md +3 -2
- package/src/components/CnDetailPage/CnDetailPage.vue +112 -5
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue +60 -0
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +36 -29
- package/src/components/CnFactsWidget/CnFactsWidget.vue +4 -0
- package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -1
- package/src/components/CnFeaturesBoard/CnFeaturesBoard.vue +4 -1
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +70 -4
- package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +62 -1
- package/src/components/CnFlowsPage/CnFlowBoard.vue +132 -8
- package/src/components/CnIndexPage/CnIndexPage.md +8 -5
- package/src/components/CnIndexPage/CnIndexPage.vue +236 -54
- package/src/components/CnIndexPage/CnIndexPageWidgets.vue +36 -1
- package/src/components/CnIndexPage/useSelfFetchList.js +25 -6
- package/src/components/CnIndexSidebar/CnIndexSidebar.md +2 -0
- package/src/components/CnIndexSidebar/CnIndexSidebar.vue +148 -1
- package/src/components/CnNcWidgetWidget/CnNcWidgetWidget.vue +68 -5
- package/src/components/CnNoticeWidget/CnNoticeWidget.vue +153 -0
- package/src/components/CnNoticeWidget/index.js +12 -0
- package/src/components/CnObjectCard/CnObjectCard.md +1 -0
- package/src/components/CnObjectCard/CnObjectCard.vue +354 -13
- package/src/components/CnObjectSidebar/CnFilesTab.vue +6 -0
- package/src/components/CnRecordListWidget/CnRecordListWidget.vue +190 -4
- package/src/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue +209 -9
- package/src/components/CnSettingsField/CnSettingsField.vue +4 -1
- package/src/components/CnSetupWizard/CnSetupWizard.vue +9 -1
- package/src/components/CnTabs/CnTabs.vue +11 -0
- package/src/components/CnTabsWidget/CnTabsWidget.vue +91 -18
- package/src/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.md +11 -0
- package/src/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue +382 -0
- package/src/components/CnTeamWorkloadWidget/index.js +14 -0
- package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +15 -4
- package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +12 -0
- package/src/components/index.js +2 -0
- package/src/css/dialog.css +20 -2
- package/src/css/index-page.css +14 -2
- package/src/css/look-board-detail.css +125 -8
- package/src/css/look-board-index.css +100 -4
- package/src/css/look-board.css +122 -14
- package/src/index.js +2 -1
- package/src/schemas/app-manifest-v2.schema.json +110 -8
- package/src/utils/dueText.js +18 -3
- package/src/utils/libraryWidgetKeys.js +2 -0
- package/src/utils/nextcloudApps.js +50 -0
- package/src/utils/scopeListLayout.js +9 -2
- package/src/utils/sortOrder.js +10 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
- package/src/utils/visibleWhen.js +12 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnSetupWizard.vue3.js","sources":["../../../../src/components/CnSetupWizard/CnSetupWizard.vue?vue&type=template&id=130330d9&lang.js"],"sourcesContent":["<!-- SPDX-License-Identifier: EUPL-1.2 -->\n<!-- SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl> -->\n<template>\n\t<CnWizardDialog\n\t\tref=\"wizard\"\n\t\t:steps=\"wizardSteps\"\n\t\t:dialogTitle=\"resolvedDialogTitle\"\n\t\tstepHeading\n\t\t:submitLabel=\"submitLabel\"\n\t\t:cancelLabel=\"cancelLabel\"\n\t\t:nextLabel=\"nextLabel\"\n\t\t:backLabel=\"backLabel\"\n\t\t:successText=\"successText\"\n\t\t:validate=\"validateStep\"\n\t\t:cancellable=\"cancellable\"\n\t\t:initialStep=\"dependencyBlocked ? '' : initialStepId\"\n\t\t:nextDisabled=\"dependencyBlocked\"\n\t\t@stepChange=\"onStepChange\"\n\t\t@submit=\"onSubmit\"\n\t\t@close=\"onClose\">\n\t\t<!-- This `<template v-for>` defines dynamic SLOTS, not a rendered list.\n\t\t `@vue/compiler-sfc` DISCARDS a `:key` on a slot-defining\n\t\t `<template>` (verified against the generated `createSlots` call)\n\t\t and honours it only on the child, so the rule's advice is inverted\n\t\t here — moving the key up would throw it away. -->\n\t\t<!-- A required app is missing: this is the only step, and Next stays\n\t\t disabled until the app is there. -->\n\t\t<template v-if=\"dependencyBlocked\" #[dependencyStepSlot]>\n\t\t\t<div class=\"cn-setup-step\" data-step-type=\"dependencies\" data-testid=\"cn-setup-dependencies\">\n\t\t\t\t<NcNoteCard type=\"warning\">\n\t\t\t\t\t{{ dependencyBlockedText }}\n\t\t\t\t</NcNoteCard>\n\t\t\t\t<CnLeafDependencySettings\n\t\t\t\t\t:appId=\"appId\"\n\t\t\t\t\t:dependencies=\"dependencyEntries\"\n\t\t\t\t\t:isAdmin=\"isAdmin\"\n\t\t\t\t\t:sectionName=\"dependencySectionName\"\n\t\t\t\t\tsectionDescription=\"\"\n\t\t\t\t\t@installed=\"onDependencyInstalled\" />\n\t\t\t</div>\n\t\t</template>\n\t\t<!-- eslint-disable vue/no-v-for-template-key-on-child -->\n\t\t<template v-for=\"step in setupSteps\" #[stepSlot(step)]=\"scope\">\n\t\t\t<div :key=\"step.id\" class=\"cn-setup-step\" :data-step-type=\"step.type\">\n\t\t\t\t<!-- Optional apps that are missing never block, but the admin\n\t\t\t\t reads about them once, on the first step. -->\n\t\t\t\t<NcNoteCard\n\t\t\t\t\tv-if=\"step.id === firstStepId && missingOptional.length > 0\"\n\t\t\t\t\ttype=\"info\"\n\t\t\t\t\tdata-testid=\"cn-setup-optional-dependencies\">\n\t\t\t\t\t{{ optionalMissingText }}\n\t\t\t\t</NcNoteCard>\n\t\t\t\t<!-- @slot step-{id} Override a step's body (for `component` steps or\n\t\t\t\t any bespoke step). Scope: the CnWizardDialog step scope plus\n\t\t\t\t `{ step, runAction, saveConfig }`. -->\n\t\t\t\t<slot\n\t\t\t\t\tv-if=\"hasCustomSlot(step.id)\"\n\t\t\t\t\t:name=\"stepSlot(step)\"\n\t\t\t\t\tv-bind=\"scope\"\n\t\t\t\t\t:step=\"step\"\n\t\t\t\t\t:runAction=\"() => runAction(step)\"\n\t\t\t\t\t:saveConfig=\"(patch) => saveConfig(patch)\" />\n\n\t\t\t\t<!-- info -->\n\t\t\t\t<template v-else-if=\"step.type === 'info'\">\n\t\t\t\t\t<NcNoteCard type=\"info\" :heading=\"stepTitle(step)\">\n\t\t\t\t\t\t{{ stepBody(step) }}\n\t\t\t\t\t</NcNoteCard>\n\t\t\t\t</template>\n\n\t\t\t\t<!-- choice -->\n\t\t\t\t<template v-else-if=\"step.type === 'choice'\">\n\t\t\t\t\t<!-- The board sets the intro as a plain paragraph above the\n\t\t\t\t\t cards (DqInstellen); the Nextcloud look keeps the note. -->\n\t\t\t\t\t<p v-if=\"step.body && isBoard\" class=\"cn-setup-step__intro\" data-testid=\"cn-setup-step-intro\">\n\t\t\t\t\t\t{{ stepBody(step) }}\n\t\t\t\t\t</p>\n\t\t\t\t\t<NcNoteCard v-else-if=\"step.body\" type=\"info\">\n\t\t\t\t\t\t{{ stepBody(step) }}\n\t\t\t\t\t</NcNoteCard>\n\t\t\t\t\t<CnChoiceCards\n\t\t\t\t\t\tv-if=\"isCardChoice(step)\"\n\t\t\t\t\t\t:layout=\"step.display === 'rows' ? 'rows' : 'cards'\"\n\t\t\t\t\t\t:label=\"stepTitle(step)\"\n\t\t\t\t\t\t:options=\"optionsFor(step)\"\n\t\t\t\t\t\t:multiple=\"step.multiple === true\"\n\t\t\t\t\t\t:disabled=\"isChoiceDisabled(step)\"\n\t\t\t\t\t\t:loading=\"isOptionsLoading(step)\"\n\t\t\t\t\t\t:modelValue=\"cardModel(step)\"\n\t\t\t\t\t\t@update:modelValue=\"(v) => onChoice(step, v)\">\n\t\t\t\t\t\t<!-- `loadAction`: every dataset card except \"none\" loads\n\t\t\t\t\t\t itself, so no separate run-action step is needed. -->\n\t\t\t\t\t\t<template v-if=\"step.loadAction\" #option-actions=\"{ option }\">\n\t\t\t\t\t\t\t<template v-if=\"isLoadableOption(step, option)\">\n\t\t\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t\t\t:disabled=\"isDatasetLoading(step, option)\"\n\t\t\t\t\t\t\t\t\t:aria-label=\"loadAriaLabel(option)\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-setup-load-dataset\"\n\t\t\t\t\t\t\t\t\t@click=\"loadDataset(step, option)\">\n\t\t\t\t\t\t\t\t\t<template v-if=\"isDatasetLoading(step, option)\" #icon>\n\t\t\t\t\t\t\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t\t\t{{ loadButtonLabel(step, option) }}\n\t\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t\t\t<p\n\t\t\t\t\t\t\t\t\tv-if=\"datasetResultFor(step, option)\"\n\t\t\t\t\t\t\t\t\trole=\"status\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-setup-load-result\"\n\t\t\t\t\t\t\t\t\t:class=\"datasetResultFor(step, option).success ? 'cn-setup-load-result--success' : 'cn-setup-load-result--error'\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-setup-load-result\">\n\t\t\t\t\t\t\t\t\t{{ datasetResultFor(step, option).message }}\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</CnChoiceCards>\n\t\t\t\t\t<NcSelect\n\t\t\t\t\t\tv-else\n\t\t\t\t\t\t:inputLabel=\"stepTitle(step)\"\n\t\t\t\t\t\t:options=\"optionsFor(step)\"\n\t\t\t\t\t\t:multiple=\"step.multiple === true\"\n\t\t\t\t\t\t:disabled=\"isChoiceDisabled(step)\"\n\t\t\t\t\t\tlabel=\"label\"\n\t\t\t\t\t\t:modelValue=\"choiceModel[step.id]\"\n\t\t\t\t\t\t@update:modelValue=\"(v) => onChoice(step, v)\" />\n\t\t\t\t\t<NcNoteCard v-if=\"isChoiceDisabled(step)\" type=\"warning\">\n\t\t\t\t\t\t{{ dependsOnHint(step) }}\n\t\t\t\t\t</NcNoteCard>\n\t\t\t\t\t<p v-if=\"stepNote(step)\" class=\"cn-setup-step__note\" data-testid=\"cn-setup-step-note\">\n\t\t\t\t\t\t{{ stepNote(step) }}\n\t\t\t\t\t</p>\n\t\t\t\t</template>\n\n\t\t\t\t<!-- config-fields -->\n\t\t\t\t<template v-else-if=\"step.type === 'config-fields'\">\n\t\t\t\t\t<!-- The step's intro, drawn like choice and run-action steps.\n\t\t\t\t\t It was left out here, so pipelinq's organisation step\n\t\t\t\t\t (\"Your organisation details…\") showed fields with no\n\t\t\t\t\t word about what they were for. -->\n\t\t\t\t\t<NcNoteCard v-if=\"step.body\" type=\"info\" data-testid=\"cn-setup-step-intro\">\n\t\t\t\t\t\t{{ stepBody(step) }}\n\t\t\t\t\t</NcNoteCard>\n\t\t\t\t\t<div\n\t\t\t\t\t\tv-for=\"field in fieldsFor(step)\"\n\t\t\t\t\t\t:key=\"field.key\"\n\t\t\t\t\t\tclass=\"cn-setup-field\">\n\t\t\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\t\t\tv-if=\"field.widget === 'checkbox'\"\n\t\t\t\t\t\t\t:modelValue=\"!!configModel[field.key]\"\n\t\t\t\t\t\t\t@update:modelValue=\"(v) => configModel[field.key] = v\">\n\t\t\t\t\t\t\t{{ field.label }}\n\t\t\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t\t\t\t<NcSelect\n\t\t\t\t\t\t\tv-else-if=\"field.widget === 'select'\"\n\t\t\t\t\t\t\t:inputLabel=\"field.label\"\n\t\t\t\t\t\t\t:options=\"field.enum || []\"\n\t\t\t\t\t\t\t:modelValue=\"configModel[field.key]\"\n\t\t\t\t\t\t\t@update:modelValue=\"(v) => configModel[field.key] = v\" />\n\t\t\t\t\t\t<NcTextField\n\t\t\t\t\t\t\tv-else\n\t\t\t\t\t\t\t:label=\"field.label\"\n\t\t\t\t\t\t\t:type=\"field.widget === 'number' ? 'number' : 'text'\"\n\t\t\t\t\t\t\t:modelValue=\"configModel[field.key] != null ? String(configModel[field.key]) : ''\"\n\t\t\t\t\t\t\t@update:modelValue=\"(v) => configModel[field.key] = v\" />\n\t\t\t\t\t</div>\n\t\t\t\t</template>\n\n\t\t\t\t<!-- run-action -->\n\t\t\t\t<template v-else-if=\"step.type === 'run-action'\">\n\t\t\t\t\t<NcNoteCard v-if=\"step.body\" type=\"info\">\n\t\t\t\t\t\t{{ stepBody(step) }}\n\t\t\t\t\t</NcNoteCard>\n\t\t\t\t\t<!-- The action starts automatically on entering this step\n\t\t\t\t\t (see `maybeAutoRunStep`), so the default state is the\n\t\t\t\t\t spinner, not an idle button. -->\n\t\t\t\t\t<div v-if=\"running[step.id]\" class=\"cn-setup-step__running\">\n\t\t\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t\t\t\t<span>{{ runningLabel }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<template v-else>\n\t\t\t\t\t\t<NcNoteCard\n\t\t\t\t\t\t\tv-if=\"actionResult[step.id]\"\n\t\t\t\t\t\t\t:type=\"actionResult[step.id].success ? 'success' : 'error'\">\n\t\t\t\t\t\t\t{{ actionResult[step.id].message }}\n\t\t\t\t\t\t</NcNoteCard>\n\t\t\t\t\t\t<div class=\"cn-setup-step__nav\">\n\t\t\t\t\t\t\t<NcButton :variant=\"actionResult[step.id] ? 'secondary' : 'primary'\" @click=\"runAction(step)\">\n\t\t\t\t\t\t\t\t{{ actionResult[step.id] ? rerunLabel : runLabel }}\n\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</template>\n\t\t\t\t</template>\n\n\t\t\t\t<!-- summary -->\n\t\t\t\t<template v-else-if=\"step.type === 'summary'\">\n\t\t\t\t\t<NcNoteCard v-if=\"step.body\" type=\"info\">\n\t\t\t\t\t\t{{ stepBody(step) }}\n\t\t\t\t\t</NcNoteCard>\n\t\t\t\t\t<ul class=\"cn-setup-summary\">\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tv-for=\"item in summaryItems\"\n\t\t\t\t\t\t\t:key=\"item.id\"\n\t\t\t\t\t\t\tclass=\"cn-setup-summary__item\"\n\t\t\t\t\t\t\t:class=\"{ 'cn-setup-summary__item--done': item.done, 'cn-setup-summary__item--not-run': item.notRun }\">\n\t\t\t\t\t\t\t<span class=\"cn-setup-summary__mark\">{{ item.done ? '✓' : '○' }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-setup-summary__label\">{{ item.title }}</span>\n\t\t\t\t\t\t\t<span v-if=\"item.value\" class=\"cn-setup-summary__value\">{{ item.value }}</span>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</template>\n\t\t\t</div>\n\t\t</template>\n\t\t<!-- eslint-enable vue/no-v-for-template-key-on-child -->\n\t</CnWizardDialog>\n</template>\n\n<script>\nimport { getCurrentUser } from '@nextcloud/auth'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcNoteCard, NcSelect, NcTextField } from '@nextcloud/vue'\nimport CnChoiceCards from '../CnChoiceCards/CnChoiceCards.vue'\nimport CnLeafDependencySettings from '../CnLeafDependencySettings/CnLeafDependencySettings.vue'\nimport CnWizardDialog from '../CnWizardDialog/CnWizardDialog.vue'\nimport { checkDependencies, isDependencyResolved, missingRequiredApps, readServerAppStatuses } from '../../composables/useDependencyCheck.js'\nimport { useLook } from '../../composables/useLook.js'\nimport { useSetupStatus } from '../../composables/useSetupStatus.js'\nimport { fieldsFromSchema } from '../../utils/schema.js'\n\n/** Id of the synthetic step shown while a required app is missing. */\nconst DEPENDENCY_STEP_ID = 'cn-setup-dependencies'\n\n/** The dataset value that means \"seed nothing\"; it never gets a Load button. */\nconst NONE_DATASET = 'none'\n\n/**\n * CnSetupWizard — abstract, manifest-driven first-time setup wizard (ADR-042).\n *\n * Wraps {@link CnWizardDialog} and renders a `manifest.setup.steps[]` array by\n * each step's `type`: `info` (note), `config-fields` (fields from a JSON Schema\n * via `fieldsFromSchema`, the same field shapes the admin pages use), `choice`\n * (`NcSelect` persisting to an app-config key), `run-action` (POST a privileged\n * server action), `summary` (recap), and `component` (a parent-provided\n * `#step-<id>` slot). It NEVER writes OpenRegister objects from the browser —\n * all persistence goes through the per-app setup contract\n * (`POST /apps/{appId}/api/setup/config` and `/api/setup/action/{actionId}`).\n *\n * Mount it gated by `CnAppRoot`'s setup phase, or open it on demand from\n * `CnAdminSettingsShell`.\n *\n * ```vue\n * <CnSetupWizard\n * :app-id=\"'procest'\"\n * :steps=\"manifest.setup.steps\"\n * @complete=\"onSetupComplete\"\n * @close=\"showWizard = false\" />\n * ```\n */\nexport default {\n\tname: 'CnSetupWizard',\n\n\tcomponents: {\n\t\tCnWizardDialog,\n\t\tCnChoiceCards,\n\t\tCnLeafDependencySettings,\n\t\tNcButton,\n\t\tNcNoteCard,\n\t\tNcSelect,\n\t\tNcTextField,\n\t\tNcCheckboxRadioSwitch,\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Consumer translation function, provided by CnAppRoot as\n\t\t * `cnTranslate: this.translate` (bound to the host app's id). Step field\n\t\t * labels come from schema property titles, authored in English as the\n\t\t * canonical source; the visible label is resolved through this function\n\t\t * so it follows the user's language. Defaults to identity when used\n\t\t * standalone (no CnAppRoot ancestor).\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t\t/**\n\t\t * The app manifest, provided by CnAppRoot. Read only for its\n\t\t * `dependencies` when the `dependencies` prop is empty.\n\t\t */\n\t\tcnManifest: { default: null },\n\t},\n\n\tprops: {\n\t\t/** The Nextcloud app id; used to build the `/apps/{appId}/api/setup/*` URLs. */\n\t\tappId: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** The `manifest.setup.steps` array to render. */\n\t\tsteps: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Dialog title. Overridden by `appName` when that prop is set. */\n\t\tdialogTitle: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Set up this app'),\n\t\t},\n\n\t\t/**\n\t\t * The app's human-readable display name. When set, the dialog title\n\t\t * becomes \"Set up {appName}\" instead of the generic `dialogTitle`\n\t\t * default — pass the same display name CnAppRoot already resolves\n\t\t * (`appDisplayName || manifest.name || appId`).\n\t\t */\n\t\tappName: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Final-step submit button label. */\n\t\tsubmitLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Finish'),\n\t\t},\n\n\t\t/** Cancel button label. */\n\t\tcancelLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Cancel'),\n\t\t},\n\n\t\t/** Next button label. */\n\t\tnextLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Next'),\n\t\t},\n\n\t\t/** Back button label. */\n\t\tbackLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Back'),\n\t\t},\n\n\t\t/** Run-action button label (shown before the first automatic run). */\n\t\trunLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Run'),\n\t\t},\n\n\t\t/** Run-action button label offered after a run already finished. */\n\t\trerunLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Run again'),\n\t\t},\n\n\t\t/** Label shown next to the spinner while a run-action step is running. */\n\t\trunningLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Loading…'),\n\t\t},\n\n\t\t/** Result-phase success text. */\n\t\tsuccessText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Setup complete.'),\n\t\t},\n\n\t\t/**\n\t\t * Whether the wizard can be dismissed before finishing (Cancel\n\t\t * button, ESC, backdrop click). Pass `false` when a REQUIRED step\n\t\t * is unmet and the host is gating its shell behind this wizard\n\t\t * (ADR-042) — an offered-but-non-functional Cancel would be\n\t\t * misleading. Optional-only setup stays cancellable (default).\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tcancellable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Ids of steps the SERVER already reports as done (e.g.\n\t\t * `useSetupStatus(...).steps` filtered to `done === true`), passed by\n\t\t * the host so a freshly (re)mounted wizard doesn't re-ask something\n\t\t * already persisted in an earlier session — this component's own\n\t\t * `localDone`/`choiceModel` only track the CURRENT session and reset\n\t\t * blank on every mount. Drives both the initial step (`initialStepId`)\n\t\t * and the summary page's done markers.\n\t\t *\n\t\t * @type {Array<string>}\n\t\t */\n\t\tcompletedStepIds: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * The app's dependencies, in the manifest's own shape (app-id strings\n\t\t * or `{ id, name?, required? }`). Falls back to the injected manifest's\n\t\t * `dependencies`. A missing REQUIRED one replaces every step with the\n\t\t * dependency list and disables Next; a missing optional one is listed\n\t\t * on the first step and does not block.\n\t\t *\n\t\t * @type {Array<string|object>}\n\t\t * @spec openspec/changes/setup-wizard-card-load-and-dependency-gate/specs/cn-setup-wizard/spec.md#requirement-the-wizard-checks-dependencies-before-any-step\n\t\t */\n\t\tdependencies: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Load button label on a dataset card (`loadAction`). */\n\t\tloadLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Load'),\n\t\t},\n\n\t\t/** Load button label once that dataset loaded in this session. */\n\t\treloadLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Load again'),\n\t\t},\n\t},\n\n\temits: ['complete', 'close', 'step-change', 'action-result'],\n\n\t/**\n\t * Live option lists come from the app's own setup status document, so a\n\t * step that offers a choice of things the SERVER ships never has to\n\t * restate them in the manifest. `useSetupStatus` caches per app id, so the\n\t * host's own status fetch (CnAppRoot's setup gate) is the one that runs —\n\t * this reads the same refs rather than asking again.\n\t *\n\t * Only called when a step actually declares `optionsSource`: a wizard with\n\t * none must not start making requests it never made before.\n\t *\n\t * @param {object} props The component props.\n\t * @return {object} `setupStatus` / `setupStatusLoading` for the option resolver.\n\t */\n\tsetup(props) {\n\t\tconst { isBoard } = useLook()\n\t\tconst wantsSource = (props.steps || []).some((s) => s && (s.optionsSource || s.visibleWhen))\n\t\tif (!wantsSource) {\n\t\t\treturn { isBoard }\n\t\t}\n\t\tconst { status, loading } = useSetupStatus(props.appId, { setup: { steps: props.steps } })\n\t\treturn { isBoard, setupStatus: status, setupStatusLoading: loading }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tchoiceModel: {},\n\t\t\tchoiceValues: {},\n\t\t\tconfigModel: {},\n\t\t\trunning: {},\n\t\t\tactionResult: {},\n\t\t\tlocalDone: {},\n\t\t\tuserTouched: {},\n\t\t\t// Per-card load state for `loadAction` steps, keyed by `loadKey()`.\n\t\t\tdatasetRunning: {},\n\t\t\tdatasetResult: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The title actually rendered by CnWizardDialog: \"Set up {appName}\"\n\t\t * when a display name was passed (the name through the host's\n\t\t * translate), otherwise whatever `dialogTitle` resolves to (a caller\n\t\t * override, or its own generic default). In the board look it is the\n\t\t * eyebrow's context and the current step's title is the heading.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedDialogTitle() {\n\t\t\treturn this.appName\n\t\t\t\t? t('nextcloud-vue', 'Set up {app}', { app: this.cnTranslate(this.appName) })\n\t\t\t\t: this.dialogTitle\n\t\t},\n\n\t\t/**\n\t\t * Every well-formed step, before `requires` is applied.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tdeclaredSteps() {\n\t\t\treturn (this.steps || []).filter((s) => s && s.id && s.type)\n\t\t},\n\n\t\t/**\n\t\t * Steps that declare `requires` on an app that is not installed and\n\t\t * enabled. They are not offered; the summary names them as skipped.\n\t\t *\n\t\t * @return {Array<{ step: object, missing: Array<string> }>}\n\t\t * @spec openspec/changes/setup-wizard-card-load-and-dependency-gate/specs/cn-setup-wizard/spec.md#requirement-the-wizard-checks-dependencies-before-any-step\n\t\t * @spec openspec/changes/optional-step-requires/specs/cn-setup-wizard/spec.md#requirement-a-step-whose-required-apps-are-absent-is-not-applicable\n\t\t */\n\t\tskippedSteps() {\n\t\t\treturn this.declaredSteps\n\t\t\t\t.filter((s) => Array.isArray(s.requires) && s.requires.length > 0)\n\t\t\t\t.map((step) => {\n\t\t\t\t\tconst known = Object.fromEntries(this.dependencyRows.map((d) => [d.id, d]))\n\t\t\t\t\tconst missing = missingRequiredApps(step.requires, this.serverAppStatuses)\n\t\t\t\t\t\t.map((row) => (known[row.id] || row).name || row.id)\n\t\t\t\t\treturn { step, missing }\n\t\t\t\t})\n\t\t\t\t.filter((entry) => entry.missing.length > 0)\n\t\t},\n\n\t\tsetupSteps() {\n\t\t\tconst skipped = new Set(this.skippedSteps.map((entry) => entry.step.id))\n\t\t\treturn this.declaredSteps.filter((s) => !skipped.has(s.id) && this.stepConditionHolds(s))\n\t\t},\n\n\t\t/**\n\t\t * Id of the first offered step: where the optional-apps note shows.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tfirstStepId() {\n\t\t\treturn this.setupSteps.length > 0 ? this.setupSteps[0].id : ''\n\t\t},\n\n\t\t/**\n\t\t * The dependency declarations to check: the prop, else the manifest's.\n\t\t *\n\t\t * @return {Array<string|object>}\n\t\t */\n\t\tdependencyEntries() {\n\t\t\tif (Array.isArray(this.dependencies) && this.dependencies.length > 0) {\n\t\t\t\treturn this.dependencies\n\t\t\t}\n\t\t\tconst fromManifest = this.cnManifest && this.cnManifest.dependencies\n\t\t\treturn Array.isArray(fromManifest) ? fromManifest : []\n\t\t},\n\n\t\t/**\n\t\t * The `dependency_statuses` initial state an app may inject from PHP.\n\t\t * Same source CnAppRoot reads; `{}` when the app injects none.\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tserverAppStatuses() {\n\t\t\treturn readServerAppStatuses(this.appId)\n\t\t},\n\n\t\t/**\n\t\t * The declared dependencies with their live status.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tdependencyRows() {\n\t\t\treturn checkDependencies(this.dependencyEntries, this.serverAppStatuses)\n\t\t},\n\n\t\t/** @return {Array<object>} Required dependencies that are missing or disabled. */\n\t\tmissingRequired() {\n\t\t\treturn this.dependencyRows.filter((d) => d.required && !isDependencyResolved(d))\n\t\t},\n\n\t\t/** @return {Array<object>} Optional dependencies that are missing or disabled. */\n\t\tmissingOptional() {\n\t\t\treturn this.dependencyRows.filter((d) => !d.required && !isDependencyResolved(d))\n\t\t},\n\n\t\t/**\n\t\t * Whether a required app is missing, so the wizard shows the\n\t\t * dependency list instead of its steps and refuses to continue.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/setup-wizard-card-load-and-dependency-gate/specs/cn-setup-wizard/spec.md#requirement-the-wizard-checks-dependencies-before-any-step\n\t\t */\n\t\tdependencyBlocked() {\n\t\t\treturn this.missingRequired.length > 0\n\t\t},\n\n\t\tdependencyStepSlot() {\n\t\t\treturn 'step-' + DEPENDENCY_STEP_ID\n\t\t},\n\n\t\tdependencySectionName() {\n\t\t\treturn t('nextcloud-vue', 'Apps this app needs')\n\t\t},\n\n\t\tdependencyBlockedText() {\n\t\t\treturn t('nextcloud-vue', 'Setup can start once these apps are installed and enabled: {apps}.', {\n\t\t\t\tapps: this.missingRequired.map((d) => d.name).join(', '),\n\t\t\t})\n\t\t},\n\n\t\toptionalMissingText() {\n\t\t\treturn t('nextcloud-vue', 'Optional, not installed: {apps}. You can add them later.', {\n\t\t\t\tapps: this.missingOptional.map((d) => d.name).join(', '),\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Whether the current user is a Nextcloud admin; only admins get the\n\t\t * install and enable buttons.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisAdmin() {\n\t\t\ttry {\n\t\t\t\tconst user = getCurrentUser()\n\t\t\t\treturn !!(user && user.isAdmin)\n\t\t\t} catch {\n\t\t\t\treturn false\n\t\t\t}\n\t\t},\n\n\t\twizardSteps() {\n\t\t\tif (this.dependencyBlocked) {\n\t\t\t\treturn [{\n\t\t\t\t\tid: DEPENDENCY_STEP_ID,\n\t\t\t\t\tlabel: t('nextcloud-vue', 'Required apps'),\n\t\t\t\t\toptional: false,\n\t\t\t\t}]\n\t\t\t}\n\t\t\treturn this.setupSteps.map((s) => ({\n\t\t\t\tid: s.id,\n\t\t\t\tlabel: this.stepTitle(s),\n\t\t\t\toptional: s.required !== true,\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Recap rows for the `summary` step — one per non-summary step, with\n\t\t * the value the user selected (choice label / config field values) and\n\t\t * a done marker (info steps always done; choice steps done when picked;\n\t\t * run-action steps done when the action succeeded).\n\t\t *\n\t\t * An on-demand step (`onDemand: true`) is the exception: it is done\n\t\t * only when its action succeeded in THIS session. Apps report such a\n\t\t * step as `done` on purpose so it never auto-runs (learniq's \"Remove\n\t\t * the example data\"), and reading that flag here ticked a removal\n\t\t * nobody ran. When it did not run it gets `notRun: true` and the value\n\t\t * \"Not run\" instead.\n\t\t *\n\t\t * @return {Array<{ id: string, title: string, value: string, done: boolean, notRun: boolean }>}\n\t\t */\n\t\tsummaryItems() {\n\t\t\tconst skipped = this.skippedSteps\n\t\t\t\t.filter((entry) => entry.step.type !== 'summary' && entry.step.type !== 'info')\n\t\t\t\t.map((entry) => ({\n\t\t\t\t\tid: entry.step.id,\n\t\t\t\t\ttitle: this.stepTitle(entry.step),\n\t\t\t\t\tvalue: t('nextcloud-vue', 'Skipped, needs {apps}', { apps: entry.missing.join(', ') }),\n\t\t\t\t\tdone: false,\n\t\t\t\t\tnotRun: true,\n\t\t\t\t}))\n\t\t\treturn this.setupSteps\n\t\t\t\t.filter((s) => s.type !== 'summary')\n\t\t\t\t.map((step) => {\n\t\t\t\t\tlet value = ''\n\t\t\t\t\tif (step.type === 'choice') {\n\t\t\t\t\t\tconst raw = this.choiceModel[step.id]\n\t\t\t\t\t\tif (Array.isArray(raw)) {\n\t\t\t\t\t\t\tvalue = raw.map((o) => this.choiceLabel(step, o)).join(', ')\n\t\t\t\t\t\t} else if (raw !== null && raw !== undefined && raw !== '') {\n\t\t\t\t\t\t\tvalue = this.choiceLabel(step, raw)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (step.type === 'config-fields') {\n\t\t\t\t\t\t// Only the fields the reader filled in: a row of empty\n\t\t\t\t\t\t// \"Name: , KvK: ,\" reads as broken, not as \"not set\".\n\t\t\t\t\t\tvalue = this.fieldsFor(step)\n\t\t\t\t\t\t\t.filter((f) => {\n\t\t\t\t\t\t\t\tconst v = this.configModel[f.key]\n\t\t\t\t\t\t\t\treturn v !== null && v !== undefined && String(v).trim() !== ''\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.map((f) => `${f.label}: ${this.configModel[f.key]}`)\n\t\t\t\t\t\t\t.join(', ') || t('nextcloud-vue', 'Not set')\n\t\t\t\t\t}\n\t\t\t\t\tif (this.isOnDemand(step)) {\n\t\t\t\t\t\tconst ran = this.ranThisSession(step.id)\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tid: step.id,\n\t\t\t\t\t\t\ttitle: this.stepTitle(step),\n\t\t\t\t\t\t\tvalue: ran ? '' : t('nextcloud-vue', 'Not run'),\n\t\t\t\t\t\t\tdone: ran,\n\t\t\t\t\t\t\tnotRun: !ran,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tlet done\n\t\t\t\t\tif (step.type === 'info') {\n\t\t\t\t\t\tdone = true\n\t\t\t\t\t} else if (step.type === 'choice') {\n\t\t\t\t\t\tdone = this.hasChoice(step) || this.isServerDone(step.id)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdone = this.isStepDone(step.id)\n\t\t\t\t\t}\n\t\t\t\t\treturn { id: step.id, title: this.stepTitle(step), value, done, notRun: false }\n\t\t\t\t})\n\t\t\t\t.concat(skipped)\n\t\t},\n\n\t\t/**\n\t\t * Id of the step the wizard should open on when RESUMING: the first\n\t\t * non-`info`/`summary` step not already done, per the CURRENT session's\n\t\t * local state OR the server-reported `completedStepIds` (a prior\n\t\t * session). Falls back to `''` (CnWizardDialog's own first-step default)\n\t\t * on a fresh setup and when every actionable step is already done.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tinitialStepId() {\n\t\t\t// Fresh setup — nothing done anywhere yet, so there is nothing to\n\t\t\t// resume past. Start at step one so a leading `info`/welcome step is\n\t\t\t// actually seen; resuming is only for returning sessions.\n\t\t\tif (this.completedStepIds.length === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\t// An on-demand step is never outstanding work, so it is never the\n\t\t\t// step to resume on.\n\t\t\tconst actionable = this.setupSteps.filter((s) => s.type !== 'info' && s.type !== 'summary' && !this.isOnDemand(s))\n\t\t\tconst firstUnmetIndex = actionable.findIndex((s) => {\n\t\t\t\tif (s.type === 'choice') {\n\t\t\t\t\treturn !(this.hasChoice(s) || this.isServerDone(s.id))\n\t\t\t\t}\n\t\t\t\treturn !this.isStepDone(s.id)\n\t\t\t})\n\t\t\t// Done is not the same as seen: an app that pre-satisfies a step at\n\t\t\t// install time (buildiq seeds templates from a repair step) reports it\n\t\t\t// done before anyone opens the wizard. Only finished work BEFORE the\n\t\t\t// outstanding step means there is something to resume past.\n\t\t\tif (firstUnmetIndex <= 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn actionable[firstUnmetIndex].id\n\t\t},\n\t},\n\n\tcreated() {\n\t\tfor (const step of this.setupSteps) {\n\t\t\tthis.applyDefault(step)\n\t\t}\n\t},\n\n\tmounted() {\n\t\t// Covers a RESUMED wizard opening straight onto its run-action step\n\t\t// (`initialStepId`) — `onStepChange` only fires on later navigation,\n\t\t// so a step already active at mount time needs its own trigger.\n\t\tthis.maybeAutoRunStep(this.initialStepId || (this.setupSteps[0] && this.setupSteps[0].id))\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Start a `run-action` step's action automatically the moment it\n\t\t * becomes current, instead of waiting for a manual \"Run\" click —\n\t\t * the step has nothing else for the user to decide by then (the\n\t\t * choice it acts on was already made on an earlier step), so\n\t\t * requiring a click before showing progress just adds a stall.\n\t\t * No-ops for any other step type, or one already run/running, and\n\t\t * never fires an on-demand step (`onDemand: true`): those run only\n\t\t * when the user clicks Run.\n\t\t *\n\t\t * @param {string} stepId The step id becoming current.\n\t\t * @return {void}\n\t\t */\n\t\tmaybeAutoRunStep(stepId) {\n\t\t\t// Nothing runs while a required app is missing: every action\n\t\t\t// would fail with an error that does not name the app.\n\t\t\tif (this.dependencyBlocked) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst step = this.setupSteps.find((s) => s.id === stepId)\n\t\t\tif (step && step.type === 'run-action' && !this.isOnDemand(step) && !this.isStepDone(step.id) && !this.running[step.id]) {\n\t\t\t\tthis.runAction(step)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The visible title for a step, resolved through the host app's\n\t\t * translation function.\n\t\t *\n\t\t * `manifest.setup.steps[].title` is authored in English as the\n\t\t * canonical source, exactly like the schema property titles this\n\t\t * component already routes through `cnTranslate` for field labels\n\t\t * (see `fieldsFromSchema` below). The step titles were the one\n\t\t * remaining place rendering the manifest string verbatim, so an\n\t\t * nl-locale user saw a wizard whose field labels were translated and\n\t\t * whose step headings were not.\n\t\t *\n\t\t * Falls back to the step id when a step declares no title, which is\n\t\t * the same fallback the raw bindings used.\n\t\t *\n\t\t * @param {object} step A `manifest.setup.steps[]` entry.\n\t\t * @return {string} The translated title, or the step id.\n\t\t */\n\t\tstepTitle(step) {\n\t\t\tconst raw = (step && step.title) || (step && step.id) || ''\n\t\t\treturn raw === '' ? '' : this.cnTranslate(raw)\n\t\t},\n\n\t\t/**\n\t\t * The visible body for a step, resolved through the host app's\n\t\t * translation function.\n\t\t *\n\t\t * 🔴 THIS USED TO RENDER THE MANIFEST STRING VERBATIM, IN FOUR PLACES.\n\t\t * `stepTitle()` was routed through `cnTranslate` and the body was not,\n\t\t * so a wizard on a Dutch instance showed a translated heading over an\n\t\t * English paragraph, with translated Cancel/Next buttons underneath.\n\t\t * That reads as a bug rather than as a missing string, and the app could\n\t\t * not fix it: decidiq shipped correct Dutch for its setup copy in\n\t\t * `l10n/nl.json`, and the component never asked for it.\n\t\t *\n\t\t * @param {object} step The step declaration.\n\t\t * @return {string} The translated body, or ''.\n\t\t */\n\t\tstepBody(step) {\n\t\t\tconst raw = (step && step.body) || ''\n\t\t\treturn raw === '' ? '' : this.cnTranslate(raw)\n\t\t},\n\n\t\t/**\n\t\t * The footnote under a choice step's options, from the step's `note`\n\t\t * (or `footnote`), translated like the body.\n\t\t *\n\t\t * @param {object} step The step declaration.\n\t\t * @return {string} The translated footnote, or ''.\n\t\t */\n\t\tstepNote(step) {\n\t\t\tconst raw = (step && (step.note || step.footnote)) || ''\n\t\t\treturn raw === '' ? '' : this.cnTranslate(raw)\n\t\t},\n\n\t\tstepSlot(step) {\n\t\t\treturn 'step-' + step.id\n\t\t},\n\n\t\thasCustomSlot(id) {\n\t\t\treturn !!this.$slots['step-' + id] || !!this.$slots['step-' + id]\n\t\t},\n\n\t\tfieldsFor(step) {\n\t\t\tif (step.schema && typeof step.schema === 'object') {\n\t\t\t\treturn fieldsFromSchema(step.schema, { translate: this.cnTranslate })\n\t\t\t}\n\t\t\t// Fallback: a plain text field per declared config key.\n\t\t\treturn (step.configKeys || []).map((key) => ({ key, label: key, widget: 'text' }))\n\t\t},\n\n\t\t/**\n\t\t * Options for a `choice` step. When the step declares `dependsOn`\n\t\t * (a parent step's configKey) + `optionsByParent`, the option list\n\t\t * is the entry keyed by the parent's currently-selected value — this\n\t\t * is the country → organisation-type cascade. Otherwise the static\n\t\t * `options` array.\n\t\t *\n\t\t * @param {object} step The choice step definition.\n\t\t * @return {Array} The option list to render.\n\t\t */\n\t\toptionsFor(step) {\n\t\t\tlet options = step.options || []\n\t\t\tif (step.optionsSource) {\n\t\t\t\toptions = this.sourcedOptions(step)\n\t\t\t} else if (step.dependsOn && step.optionsByParent) {\n\t\t\t\tconst parentValue = this.choiceValues[step.dependsOn]\n\t\t\t\toptions = (parentValue === null || parentValue === undefined || parentValue === '')\n\t\t\t\t\t? []\n\t\t\t\t\t: (step.optionsByParent[parentValue] || [])\n\t\t\t}\n\t\t\t// The LABEL is translated, the VALUE never is. `scalarChoice()` reads\n\t\t\t// `.value` and that is what reaches `POST /api/setup/config`, so a\n\t\t\t// translated label cannot change what gets stored — while an\n\t\t\t// untranslated one leaves the choice itself in English on a\n\t\t\t// localised instance, which is the half-translated screen this fix\n\t\t\t// exists to remove.\n\t\t\treturn options.map((option) => {\n\t\t\t\tif (!option || typeof option !== 'object' || !option.label) {\n\t\t\t\t\treturn option\n\t\t\t\t}\n\t\t\t\tconst translated = { ...option, label: this.cnTranslate(option.label) }\n\t\t\t\tif (option.description) {\n\t\t\t\t\ttranslated.description = this.cnTranslate(option.description)\n\t\t\t\t}\n\t\t\t\treturn translated\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Whether a step's `visibleWhen` holds: the value at that path in the\n\t\t * setup status (`\"flags.hasPayouts\"`, dots walk into objects) is truthy,\n\t\t * or equals `equals` when the step gives `{ path, equals }`. A step\n\t\t * without `visibleWhen` always shows; one with it stays hidden until the\n\t\t * status says so, also while the status loads (DqInstellen: the dwangsom\n\t\t * step only when the app pays penalties).\n\t\t *\n\t\t * @param {object} step The step declaration.\n\t\t * @return {boolean}\n\t\t */\n\t\tstepConditionHolds(step) {\n\t\t\tconst when = step && step.visibleWhen\n\t\t\tif (when === undefined || when === null || when === '') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst path = typeof when === 'string' ? when : when.path\n\t\t\tif (typeof path !== 'string' || path === '') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tlet node = this.setupStatus\n\t\t\tfor (const key of path.split('.')) {\n\t\t\t\tif (node === null || node === undefined || typeof node !== 'object') {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tnode = node[key]\n\t\t\t}\n\t\t\tif (typeof when === 'object' && Object.hasOwn(when, 'equals')) {\n\t\t\t\treturn node === when.equals\n\t\t\t}\n\t\t\treturn Boolean(node)\n\t\t},\n\n\t\t/**\n\t\t * The option list a `choice` step declares `optionsSource` for, read\n\t\t * from the setup status document the app already serves.\n\t\t *\n\t\t * `optionsSource` names a key in that document (dots walk into nested\n\t\t * objects), and the entries are normalised to the option shape: `id` is\n\t\t * accepted for `value`, `name` for `label`, and a bare `objectCount`\n\t\t * becomes the one stat a card shows. A server that already sends\n\t\t * `stats` keeps them.\n\t\t *\n\t\t * @param {object} step The choice step declaring `optionsSource`.\n\t\t * @return {Array<object>} The options, or [] while the status is loading.\n\t\t */\n\t\tsourcedOptions(step) {\n\t\t\tlet node = this.setupStatus\n\t\t\tfor (const key of String(step.optionsSource).split('.')) {\n\t\t\t\tif (node === null || node === undefined || typeof node !== 'object') {\n\t\t\t\t\treturn []\n\t\t\t\t}\n\t\t\t\tnode = node[key]\n\t\t\t}\n\t\t\tif (!Array.isArray(node)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn node\n\t\t\t\t.filter((entry) => entry && typeof entry === 'object')\n\t\t\t\t.map((entry) => {\n\t\t\t\t\tconst value = entry.value !== undefined ? entry.value : entry.id\n\t\t\t\t\tconst option = {\n\t\t\t\t\t\t...entry,\n\t\t\t\t\t\tvalue,\n\t\t\t\t\t\tlabel: entry.label || entry.name || String(value),\n\t\t\t\t\t}\n\t\t\t\t\tif (!option.stats && typeof entry.objectCount === 'number' && entry.objectCount > 0) {\n\t\t\t\t\t\toption.stats = [{ label: t('nextcloud-vue', 'Objects'), value: entry.objectCount }]\n\t\t\t\t\t}\n\t\t\t\t\treturn option\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 * Whether a choice step renders as CnChoiceCards (a card grid, or radio\n\t\t * rows with `display: \"rows\"`) rather than a dropdown.\n\t\t *\n\t\t * @param {object} step The choice step.\n\t\t * @return {boolean} True for `display: \"cards\"` and `display: \"rows\"`.\n\t\t */\n\t\tisCardChoice(step) {\n\t\t\treturn step.display === 'cards' || step.display === 'rows'\n\t\t},\n\n\t\t/**\n\t\t * Whether this step is still waiting for its live option list.\n\t\t *\n\t\t * @param {object} step The choice step.\n\t\t * @return {boolean} True while the status document is in flight.\n\t\t */\n\t\tisOptionsLoading(step) {\n\t\t\treturn !!step.optionsSource && this.setupStatusLoading === true\n\t\t},\n\n\t\t/**\n\t\t * The card grid's model: plain values, not the option objects NcSelect\n\t\t * binds. `scalarChoice` already reduces either shape to values, so both\n\t\t * renderers can share one `choiceModel` entry.\n\t\t *\n\t\t * @param {object} step The choice step.\n\t\t * @return {unknown} The selected value, or [] / null when nothing is picked.\n\t\t */\n\t\tcardModel(step) {\n\t\t\tconst value = this.scalarChoice(step)\n\t\t\tif (step.multiple === true) {\n\t\t\t\treturn Array.isArray(value) ? value : []\n\t\t\t}\n\t\t\treturn value === undefined ? null : value\n\t\t},\n\n\t\tisChoiceDisabled(step) {\n\t\t\tif (!step.dependsOn) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst parentValue = this.choiceValues[step.dependsOn]\n\t\t\treturn parentValue === null || parentValue === undefined || parentValue === ''\n\t\t},\n\n\t\tdependsOnHint(step) {\n\t\t\tconst parent = this.setupSteps.find((s) => s.configKey === step.dependsOn)\n\t\t\tconst label = parent ? (parent.title || parent.id) : step.dependsOn\n\t\t\treturn t('nextcloud-vue', 'Select \"{step}\" first.', { step: label })\n\t\t},\n\n\t\t/**\n\t\t * The visible label for one selected choice entry.\n\t\t *\n\t\t * A card grid stores plain VALUES where NcSelect stores option objects,\n\t\t * so the recap looked one up and printed the other: the summary read\n\t\t * \"municipality\" instead of \"Municipality\". Resolves a bare value back\n\t\t * to its option, and falls back to the value when the option list is\n\t\t * not (yet) available.\n\t\t *\n\t\t * @param {object} step The choice step.\n\t\t * @param {unknown} entry One selected entry: an option object or a value.\n\t\t * @return {string} The label to show.\n\t\t */\n\t\tchoiceLabel(step, entry) {\n\t\t\tif (entry && typeof entry === 'object' && entry.label) {\n\t\t\t\treturn entry.label\n\t\t\t}\n\t\t\tconst match = this.optionsFor(step).find((o) => o && String(o.value) === String(entry))\n\t\t\treturn (match && match.label) ? match.label : String(entry)\n\t\t},\n\n\t\thasChoice(step) {\n\t\t\tconst v = this.choiceModel[step.id]\n\t\t\tif (step.multiple === true) {\n\t\t\t\treturn Array.isArray(v) && v.length > 0\n\t\t\t}\n\t\t\treturn v !== null && v !== undefined && v !== ''\n\t\t},\n\n\t\tscalarChoice(step) {\n\t\t\tconst raw = this.choiceModel[step.id]\n\t\t\tif (step.multiple === true) {\n\t\t\t\treturn (raw || []).map((o) => (o && o.value !== undefined ? o.value : o))\n\t\t\t}\n\t\t\treturn raw && raw.value !== undefined ? raw.value : raw\n\t\t},\n\n\t\tonChoice(step, value) {\n\t\t\tthis.userTouched[step.id] = true\n\t\t\tthis.choiceModel[step.id] = value\n\t\t\tif (step.configKey) {\n\t\t\t\tthis.choiceValues[step.configKey] = this.scalarChoice(step)\n\t\t\t}\n\t\t\t// Reset any dependent child choices when the parent changes.\n\t\t\tfor (const child of this.setupSteps) {\n\t\t\t\tif (child.dependsOn && child.dependsOn === step.configKey) {\n\t\t\t\t\tthis.choiceModel[child.id] = child.multiple === true ? [] : null\n\t\t\t\t\tthis.userTouched[child.id] = false\n\t\t\t\t\tif (child.configKey) {\n\t\t\t\t\t\tthis.choiceValues[child.configKey] = ''\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Re-apply auto-suggestions for steps that derive a default from this one.\n\t\t\tfor (const dep of this.setupSteps) {\n\t\t\t\tif (dep.suggestFrom && dep.suggestFrom === step.configKey) {\n\t\t\t\t\tthis.applySuggestion(dep)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Preselect a `choice` step's `default` option. Skipped once the user\n\t\t * picked here, when a value is already chosen, and for a step the\n\t\t * server already completed (its stored value must not be overwritten\n\t\t * by a Next). A card step holds the plain value; a dropdown holds the\n\t\t * option object, or the value while the options are still loading.\n\t\t *\n\t\t * @param {object} step The choice step.\n\t\t * @return {void}\n\t\t */\n\t\tapplyDefault(step) {\n\t\t\tif (!step || step.type !== 'choice' || step.default === undefined || step.default === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.userTouched[step.id] === true || this.hasChoice(step) || this.isServerDone(step.id)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst wanted = step.multiple === true ? [].concat(step.default) : step.default\n\t\t\tif (this.isCardChoice(step)) {\n\t\t\t\tthis.choiceModel[step.id] = wanted\n\t\t\t} else {\n\t\t\t\tconst options = this.optionsFor(step)\n\t\t\t\tconst pick = (value) => options.find((o) => o.value === value) || value\n\t\t\t\tthis.choiceModel[step.id] = Array.isArray(wanted) ? wanted.map(pick) : pick(wanted)\n\t\t\t}\n\t\t\tif (step.configKey) {\n\t\t\t\tthis.choiceValues[step.configKey] = this.scalarChoice(step)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Auto-fill a `choice` step's suggested default from an earlier choice\n\t\t * (its `suggestFrom` configKey → `suggestMap[parentValue]`). No-op once\n\t\t * the user has manually picked here, so a suggestion never clobbers an\n\t\t * explicit choice.\n\t\t *\n\t\t * @param {object} step The dependent choice step.\n\t\t * @return {void}\n\t\t */\n\t\tapplySuggestion(step) {\n\t\t\tif (this.userTouched[step.id] === true) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst parentValue = this.choiceValues[step.suggestFrom]\n\t\t\tif (parentValue === null || parentValue === undefined || parentValue === '') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst wanted = (step.suggestMap || {})[parentValue]\n\t\t\tif (wanted === null || wanted === undefined) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst opt = this.optionsFor(step).find((o) => o.value === wanted) || { value: wanted, label: String(wanted) }\n\t\t\tthis.choiceModel[step.id] = opt\n\t\t\tif (step.configKey) {\n\t\t\t\tthis.choiceValues[step.configKey] = wanted\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * CnWizardDialog `validate` hook — intercepts Next/Submit to persist\n\t\t * the active step before advancing. `choice`/`config-fields` steps\n\t\t * POST their value via the setup contract; required `choice` steps\n\t\t * block advance until a value is picked; required `run-action` steps\n\t\t * block until the action has succeeded. Returns `true` to advance or\n\t\t * an error string to surface above the step and block navigation.\n\t\t *\n\t\t * @param {string} stepId The active step id.\n\t\t * @return {Promise<boolean|string>} True to advance, or an error message.\n\t\t */\n\t\tasync validateStep(stepId) {\n\t\t\tconst step = this.setupSteps.find((s) => s.id === stepId)\n\t\t\tif (!step) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tif (step.type === 'choice') {\n\t\t\t\tif (!this.hasChoice(step)) {\n\t\t\t\t\t// Already persisted server-side and the user only back-navigated\n\t\t\t\t\t// onto it — `choiceModel` is blank because it's session-local, not\n\t\t\t\t\t// because nothing is set. Don't force a re-pick, and don't re-POST.\n\t\t\t\t\tif (this.isServerDone(step.id)) {\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t\treturn step.required === true\n\t\t\t\t\t\t? t('nextcloud-vue', 'Please make a selection to continue.')\n\t\t\t\t\t\t: true\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tawait this.saveConfig({ [step.configKey]: this.scalarChoice(step) })\n\t\t\t\t\tthis.localDone[step.id] = true\n\t\t\t\t\treturn true\n\t\t\t\t} catch (err) {\n\t\t\t\t\treturn this.errorMessage(err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (step.type === 'config-fields') {\n\t\t\t\ttry {\n\t\t\t\t\tconst patch = {}\n\t\t\t\t\tfor (const field of this.fieldsFor(step)) {\n\t\t\t\t\t\tpatch[field.key] = this.configModel[field.key]\n\t\t\t\t\t}\n\t\t\t\t\tawait this.saveConfig(patch)\n\t\t\t\t\tthis.localDone[step.id] = true\n\t\t\t\t\treturn true\n\t\t\t\t} catch (err) {\n\t\t\t\t\treturn this.errorMessage(err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (step.type === 'run-action' && step.required === true && !this.isStepDone(step.id)) {\n\t\t\t\treturn t('nextcloud-vue', 'Please run this step before continuing.')\n\t\t\t}\n\t\t\treturn true\n\t\t},\n\n\t\t/**\n\t\t * Persist one or more app-config values via the setup contract.\n\t\t *\n\t\t * @param {object} patch `{ <configKey>: value }` to write.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync saveConfig(patch) {\n\t\t\tconst [{ default: axios }, { generateUrl }] = await Promise.all([\n\t\t\t\timport('@nextcloud/axios'),\n\t\t\t\timport('@nextcloud/router'),\n\t\t\t])\n\t\t\tawait axios.post(generateUrl(`/apps/${this.appId}/api/setup/config`), patch)\n\t\t},\n\n\t\tasync runAction(step) {\n\t\t\tthis.running[step.id] = true\n\t\t\tthis.actionResult[step.id] = null\n\t\t\ttry {\n\t\t\t\tconst [{ default: axios }, { generateUrl }] = await Promise.all([\n\t\t\t\t\timport('@nextcloud/axios'),\n\t\t\t\t\timport('@nextcloud/router'),\n\t\t\t\t])\n\t\t\t\tconst { data } = await axios.post(generateUrl(`/apps/${this.appId}/api/setup/action/${step.action}`))\n\t\t\t\tconst result = {\n\t\t\t\t\tsuccess: data && data.success !== false,\n\t\t\t\t\tmessage: (data && data.message) || t('nextcloud-vue', 'Done.'),\n\t\t\t\t}\n\t\t\t\tthis.actionResult[step.id] = result\n\t\t\t\tif (result.success) {\n\t\t\t\t\tthis.localDone[step.id] = true\n\t\t\t\t}\n\t\t\t\t/**\n\t\t\t\t * @event action-result Emitted when a `run-action` step finishes.\n\t\t\t\t * @type {{ stepId: string, action: string, success: boolean, message: string }}\n\t\t\t\t */\n\t\t\t\tthis.$emit('action-result', { stepId: step.id, action: step.action, ...result })\n\t\t\t} catch (err) {\n\t\t\t\tthis.actionResult[step.id] = { success: false, message: this.errorMessage(err) }\n\t\t\t\tthis.$emit('action-result', { stepId: step.id, action: step.action, success: false, message: this.errorMessage(err) })\n\t\t\t} finally {\n\t\t\t\tthis.running[step.id] = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Key for one card's load state.\n\t\t *\n\t\t * @param {object} step The choice step.\n\t\t * @param {object} option The card's option.\n\t\t * @return {string}\n\t\t */\n\t\tloadKey(step, option) {\n\t\t\treturn step.id + '::' + String(option && option.value)\n\t\t},\n\n\t\t/**\n\t\t * Whether a card gets a Load button: the step declares `loadAction`\n\t\t * and the option is not the \"seed nothing\" answer.\n\t\t *\n\t\t * @param {object} step The choice step.\n\t\t * @param {object} option The card's option.\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/setup-wizard-card-load-and-dependency-gate/specs/cn-setup-wizard/spec.md#requirement-a-dataset-card-loads-itself\n\t\t */\n\t\tisLoadableOption(step, option) {\n\t\t\treturn !!(step && step.loadAction) && !!option && String(option.value) !== NONE_DATASET\n\t\t},\n\n\t\tisDatasetLoading(step, option) {\n\t\t\treturn this.datasetRunning[this.loadKey(step, option)] === true\n\t\t},\n\n\t\tdatasetResultFor(step, option) {\n\t\t\treturn this.datasetResult[this.loadKey(step, option)] || null\n\t\t},\n\n\t\tloadButtonLabel(step, option) {\n\t\t\tif (this.isDatasetLoading(step, option)) {\n\t\t\t\treturn this.runningLabel\n\t\t\t}\n\t\t\tconst result = this.datasetResultFor(step, option)\n\t\t\treturn (result && result.success) ? this.reloadLabel : this.loadLabel\n\t\t},\n\n\t\tloadAriaLabel(option) {\n\t\t\treturn t('nextcloud-vue', 'Load {name}', { name: option.label })\n\t\t},\n\n\t\t/**\n\t\t * Load one dataset from its card: POST `{ dataset }` to the step's\n\t\t * `loadAction`, spinner on the button while it runs, the result on the\n\t\t * card after. A successful load also selects the card, so the summary\n\t\t * and the stored choice name what was loaded.\n\t\t *\n\t\t * @param {object} step The choice step declaring `loadAction`.\n\t\t * @param {object} option The card's option.\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/setup-wizard-card-load-and-dependency-gate/specs/cn-setup-wizard/spec.md#requirement-a-dataset-card-loads-itself\n\t\t */\n\t\tasync loadDataset(step, option) {\n\t\t\tif (!this.isLoadableOption(step, option) || this.isDatasetLoading(step, option)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst key = this.loadKey(step, option)\n\t\t\tthis.datasetRunning[key] = true\n\t\t\tthis.datasetResult[key] = null\n\t\t\tlet result\n\t\t\ttry {\n\t\t\t\tconst [{ default: axios }, { generateUrl }] = await Promise.all([\n\t\t\t\t\timport('@nextcloud/axios'),\n\t\t\t\t\timport('@nextcloud/router'),\n\t\t\t\t])\n\t\t\t\tconst { data } = await axios.post(\n\t\t\t\t\tgenerateUrl(`/apps/${this.appId}/api/setup/action/${step.loadAction}`),\n\t\t\t\t\t{ dataset: option.value },\n\t\t\t\t)\n\t\t\t\tresult = {\n\t\t\t\t\tsuccess: !!data && data.success !== false,\n\t\t\t\t\tmessage: (data && data.message) || t('nextcloud-vue', 'Loaded.'),\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tresult = { success: false, message: this.errorMessage(err) }\n\t\t\t} finally {\n\t\t\t\tthis.datasetRunning[key] = false\n\t\t\t}\n\t\t\tthis.datasetResult[key] = result\n\t\t\tif (result.success) {\n\t\t\t\tthis.selectLoadedOption(step, option)\n\t\t\t\tthis.localDone[step.id] = true\n\t\t\t}\n\t\t\tthis.$emit('action-result', {\n\t\t\t\tstepId: step.id,\n\t\t\t\taction: step.loadAction,\n\t\t\t\tdataset: option.value,\n\t\t\t\t...result,\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Select a card that just loaded, adding to (never replacing) the\n\t\t * selection of a `multiple` step.\n\t\t *\n\t\t * @param {object} step The choice step.\n\t\t * @param {object} option The loaded option.\n\t\t * @return {void}\n\t\t */\n\t\tselectLoadedOption(step, option) {\n\t\t\tif (step.multiple === true) {\n\t\t\t\tconst current = this.cardModel(step)\n\t\t\t\tif (current.some((v) => String(v) === String(option.value))) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.onChoice(step, current.concat([option.value]))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.onChoice(step, option.value)\n\t\t},\n\n\t\t/**\n\t\t * A required app was installed from the dependency list. A new app's\n\t\t * scripts and its `OC.appswebroots` entry exist only after a full page\n\t\t * load, and `useAppStatus` caches per page, so reload.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonDependencyInstalled() {\n\t\t\twindow.location.reload()\n\t\t},\n\n\t\t/**\n\t\t * Whether a step is marked on-demand (`onDemand: true`): it runs only\n\t\t * when the user asks, never automatically, and the summary ticks it\n\t\t * only when it ran in this session.\n\t\t *\n\t\t * @param {object} step The manifest step.\n\t\t * @return {boolean}\n\t\t */\n\t\tisOnDemand(step) {\n\t\t\treturn !!step && step.onDemand === true\n\t\t},\n\n\t\t/**\n\t\t * Whether a step's action succeeded in the CURRENT session, ignoring\n\t\t * whatever the server reports.\n\t\t *\n\t\t * @param {string} id Step id.\n\t\t * @return {boolean}\n\t\t */\n\t\tranThisSession(id) {\n\t\t\treturn !!(this.actionResult[id] && this.actionResult[id].success)\n\t\t},\n\n\t\tisStepDone(id) {\n\t\t\treturn this.isServerDone(id) || this.localDone[id] === true || (this.actionResult[id] && this.actionResult[id].success)\n\t\t},\n\n\t\t/**\n\t\t * Whether the server already reported this step done in a prior\n\t\t * session, via the `completedStepIds` prop.\n\t\t *\n\t\t * @param {string} id Step id.\n\t\t * @return {boolean}\n\t\t */\n\t\tisServerDone(id) {\n\t\t\treturn this.completedStepIds.includes(id)\n\t\t},\n\n\t\tonSubmit() {\n\t\t\tif (this.$refs.wizard && this.$refs.wizard.setResult) {\n\t\t\t\tthis.$refs.wizard.setResult({ success: true, message: this.successText })\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event complete Emitted when the last step is submitted (setup finished).\n\t\t\t */\n\t\t\tthis.$emit('complete')\n\t\t},\n\n\t\tonStepChange(payload) {\n\t\t\t// Pre-fill a suggested default when entering a step that derives one.\n\t\t\tconst step = this.setupSteps.find((s) => s.id === payload.stepId)\n\t\t\tif (step && step.suggestFrom) {\n\t\t\t\tthis.applySuggestion(step)\n\t\t\t}\n\t\t\t// A default seeded before a dropdown's options arrived is a bare\n\t\t\t// value; swap in its option object now the step opens.\n\t\t\tif (step && step.default !== undefined && this.userTouched[step.id] !== true && !this.isCardChoice(step)) {\n\t\t\t\tconst held = this.choiceModel[step.id]\n\t\t\t\tconst option = this.optionsFor(step).find((o) => o.value === held)\n\t\t\t\tif (option && held !== null && typeof held !== 'object') {\n\t\t\t\t\tthis.choiceModel[step.id] = option\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.maybeAutoRunStep(payload.stepId)\n\t\t\t/**\n\t\t\t * @event step-change Emitted when the active step changes.\n\t\t\t * @type {{ stepId: string, stepIndex: number, direction: string }}\n\t\t\t */\n\t\t\tthis.$emit('step-change', payload)\n\t\t},\n\n\t\tonClose() {\n\t\t\t/**\n\t\t\t * @event close Emitted when the dialog should close.\n\t\t\t */\n\t\t\tthis.$emit('close')\n\t\t},\n\n\t\terrorMessage(err) {\n\t\t\tconst data = err && err.response && err.response.data\n\t\t\tif (data && (data.message || data.error)) {\n\t\t\t\treturn data.message || data.error\n\t\t\t}\n\t\t\treturn (err && err.message) || t('nextcloud-vue', 'Something went wrong.')\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-setup-step {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-setup-step__intro {\n\tmargin: 0;\n\tfont-size: 15px;\n\tline-height: 1.5;\n}\n\n.cn-setup-step__note {\n\tmargin: 0;\n\tfont-size: 13px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-setup-step__nav {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\tgap: 8px;\n\tmargin-top: 8px;\n}\n\n.cn-setup-step__running {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tgap: 8px;\n\tpadding: 24px 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-setup-load-result {\n\tmargin: 0;\n\tfont-size: var(--font-size-small, 13px);\n}\n\n.cn-setup-load-result--success {\n\tcolor: var(--color-success-text, var(--color-success));\n}\n\n.cn-setup-load-result--error {\n\tcolor: var(--color-error-text, var(--color-error));\n}\n\n.cn-setup-field {\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-setup-summary {\n\tlist-style: none;\n\tpadding: 0;\n\tmargin: 0;\n}\n\n.cn-setup-summary__item {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 4px 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-setup-summary__item--done {\n\tcolor: var(--color-main-text);\n}\n\n.cn-setup-summary__mark {\n\tfont-weight: bold;\n\tcolor: var(--color-success);\n}\n\n.cn-setup-summary__label {\n\tflex: 0 0 auto;\n}\n\n.cn-setup-summary__value {\n\tmargin-inline-start: auto;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n/* An on-demand step that did not run says so quietly: it is a choice the\n user did not make, not a failure. */\n.cn-setup-summary__item--not-run .cn-setup-summary__value {\n\tfont-weight: normal;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_createBlock","_createVNode","_renderList","_withCtx","_createElementBlock","_renderSlot","_mergeProps","_createTextVNode","_toDisplayString","_Fragment","_openBlock","_normalizeClass","_createElementVNode"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnSetupWizard.vue3.js","sources":["../../../../src/components/CnSetupWizard/CnSetupWizard.vue?vue&type=template&id=dd2d6164&lang.js"],"sourcesContent":["<!-- SPDX-License-Identifier: EUPL-1.2 -->\n<!-- SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl> -->\n<template>\n\t<CnWizardDialog\n\t\tref=\"wizard\"\n\t\t:steps=\"wizardSteps\"\n\t\t:dialogTitle=\"resolvedDialogTitle\"\n\t\tstepHeading\n\t\t:submitLabel=\"submitLabel\"\n\t\t:cancelLabel=\"cancelLabel\"\n\t\t:nextLabel=\"nextLabel\"\n\t\t:backLabel=\"backLabel\"\n\t\t:successText=\"successText\"\n\t\t:validate=\"validateStep\"\n\t\t:cancellable=\"cancellable\"\n\t\t:initialStep=\"dependencyBlocked ? '' : initialStepId\"\n\t\t:nextDisabled=\"dependencyBlocked\"\n\t\t@stepChange=\"onStepChange\"\n\t\t@submit=\"onSubmit\"\n\t\t@close=\"onClose\">\n\t\t<!-- This `<template v-for>` defines dynamic SLOTS, not a rendered list.\n\t\t `@vue/compiler-sfc` DISCARDS a `:key` on a slot-defining\n\t\t `<template>` (verified against the generated `createSlots` call)\n\t\t and honours it only on the child, so the rule's advice is inverted\n\t\t here — moving the key up would throw it away. -->\n\t\t<!-- A required app is missing: this is the only step, and Next stays\n\t\t disabled until the app is there. -->\n\t\t<template v-if=\"dependencyBlocked\" #[dependencyStepSlot]>\n\t\t\t<div class=\"cn-setup-step\" data-step-type=\"dependencies\" data-testid=\"cn-setup-dependencies\">\n\t\t\t\t<NcNoteCard type=\"warning\">\n\t\t\t\t\t{{ dependencyBlockedText }}\n\t\t\t\t</NcNoteCard>\n\t\t\t\t<CnLeafDependencySettings\n\t\t\t\t\t:appId=\"appId\"\n\t\t\t\t\t:dependencies=\"dependencyEntries\"\n\t\t\t\t\t:isAdmin=\"isAdmin\"\n\t\t\t\t\t:sectionName=\"dependencySectionName\"\n\t\t\t\t\tsectionDescription=\"\"\n\t\t\t\t\t@installed=\"onDependencyInstalled\" />\n\t\t\t</div>\n\t\t</template>\n\t\t<!-- eslint-disable vue/no-v-for-template-key-on-child -->\n\t\t<template v-for=\"step in setupSteps\" #[stepSlot(step)]=\"scope\">\n\t\t\t<div :key=\"step.id\" class=\"cn-setup-step\" :data-step-type=\"step.type\">\n\t\t\t\t<!-- Optional apps that are missing never block, but the admin\n\t\t\t\t reads about them once, on the first step. -->\n\t\t\t\t<NcNoteCard\n\t\t\t\t\tv-if=\"step.id === firstStepId && missingOptional.length > 0\"\n\t\t\t\t\ttype=\"info\"\n\t\t\t\t\tdata-testid=\"cn-setup-optional-dependencies\">\n\t\t\t\t\t{{ optionalMissingText }}\n\t\t\t\t</NcNoteCard>\n\t\t\t\t<!-- @slot step-{id} Override a step's body (for `component` steps or\n\t\t\t\t any bespoke step). Scope: the CnWizardDialog step scope plus\n\t\t\t\t `{ step, runAction, saveConfig }`. -->\n\t\t\t\t<slot\n\t\t\t\t\tv-if=\"hasCustomSlot(step.id)\"\n\t\t\t\t\t:name=\"stepSlot(step)\"\n\t\t\t\t\tv-bind=\"scope\"\n\t\t\t\t\t:step=\"step\"\n\t\t\t\t\t:runAction=\"() => runAction(step)\"\n\t\t\t\t\t:saveConfig=\"(patch) => saveConfig(patch)\" />\n\n\t\t\t\t<!-- info -->\n\t\t\t\t<template v-else-if=\"step.type === 'info'\">\n\t\t\t\t\t<NcNoteCard type=\"info\" :heading=\"stepTitle(step)\">\n\t\t\t\t\t\t{{ stepBody(step) }}\n\t\t\t\t\t</NcNoteCard>\n\t\t\t\t</template>\n\n\t\t\t\t<!-- choice -->\n\t\t\t\t<template v-else-if=\"step.type === 'choice'\">\n\t\t\t\t\t<!-- The board sets the intro as a plain paragraph above the\n\t\t\t\t\t cards (DqInstellen); the Nextcloud look keeps the note. -->\n\t\t\t\t\t<p v-if=\"step.body && isBoard\" class=\"cn-setup-step__intro\" data-testid=\"cn-setup-step-intro\">\n\t\t\t\t\t\t{{ stepBody(step) }}\n\t\t\t\t\t</p>\n\t\t\t\t\t<NcNoteCard v-else-if=\"step.body\" type=\"info\">\n\t\t\t\t\t\t{{ stepBody(step) }}\n\t\t\t\t\t</NcNoteCard>\n\t\t\t\t\t<CnChoiceCards\n\t\t\t\t\t\tv-if=\"isCardChoice(step)\"\n\t\t\t\t\t\t:layout=\"step.display === 'rows' ? 'rows' : 'cards'\"\n\t\t\t\t\t\t:label=\"stepTitle(step)\"\n\t\t\t\t\t\t:options=\"optionsFor(step)\"\n\t\t\t\t\t\t:multiple=\"step.multiple === true\"\n\t\t\t\t\t\t:disabled=\"isChoiceDisabled(step)\"\n\t\t\t\t\t\t:loading=\"isOptionsLoading(step)\"\n\t\t\t\t\t\t:modelValue=\"cardModel(step)\"\n\t\t\t\t\t\t@update:modelValue=\"(v) => onChoice(step, v)\">\n\t\t\t\t\t\t<!-- `loadAction`: every dataset card except \"none\" loads\n\t\t\t\t\t\t itself, so no separate run-action step is needed. -->\n\t\t\t\t\t\t<template v-if=\"step.loadAction\" #option-actions=\"{ option }\">\n\t\t\t\t\t\t\t<template v-if=\"isLoadableOption(step, option)\">\n\t\t\t\t\t\t\t\t<NcButton\n\t\t\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t\t\t:disabled=\"isDatasetLoading(step, option)\"\n\t\t\t\t\t\t\t\t\t:aria-label=\"loadAriaLabel(option)\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-setup-load-dataset\"\n\t\t\t\t\t\t\t\t\t@click=\"loadDataset(step, option)\">\n\t\t\t\t\t\t\t\t\t<template v-if=\"isDatasetLoading(step, option)\" #icon>\n\t\t\t\t\t\t\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t\t\t{{ loadButtonLabel(step, option) }}\n\t\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t\t\t<p\n\t\t\t\t\t\t\t\t\tv-if=\"datasetResultFor(step, option)\"\n\t\t\t\t\t\t\t\t\trole=\"status\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-setup-load-result\"\n\t\t\t\t\t\t\t\t\t:class=\"datasetResultFor(step, option).success ? 'cn-setup-load-result--success' : 'cn-setup-load-result--error'\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-setup-load-result\">\n\t\t\t\t\t\t\t\t\t{{ datasetResultFor(step, option).message }}\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t</CnChoiceCards>\n\t\t\t\t\t<NcSelect\n\t\t\t\t\t\tv-else\n\t\t\t\t\t\t:inputLabel=\"stepTitle(step)\"\n\t\t\t\t\t\t:options=\"optionsFor(step)\"\n\t\t\t\t\t\t:multiple=\"step.multiple === true\"\n\t\t\t\t\t\t:disabled=\"isChoiceDisabled(step)\"\n\t\t\t\t\t\tlabel=\"label\"\n\t\t\t\t\t\t:modelValue=\"choiceModel[step.id]\"\n\t\t\t\t\t\t@update:modelValue=\"(v) => onChoice(step, v)\" />\n\t\t\t\t\t<NcNoteCard v-if=\"isChoiceDisabled(step)\" type=\"warning\">\n\t\t\t\t\t\t{{ dependsOnHint(step) }}\n\t\t\t\t\t</NcNoteCard>\n\t\t\t\t\t<!-- `noteStyle: \"card\"` sets the note as an info card (DqInstellen). -->\n\t\t\t\t\t<NcNoteCard\n\t\t\t\t\t\tv-if=\"stepNote(step) && step.noteStyle === 'card'\"\n\t\t\t\t\t\ttype=\"info\"\n\t\t\t\t\t\tclass=\"cn-setup-step__note-card\"\n\t\t\t\t\t\tdata-testid=\"cn-setup-step-note\">\n\t\t\t\t\t\t{{ stepNote(step) }}\n\t\t\t\t\t</NcNoteCard>\n\t\t\t\t\t<p v-else-if=\"stepNote(step)\" class=\"cn-setup-step__note\" data-testid=\"cn-setup-step-note\">\n\t\t\t\t\t\t{{ stepNote(step) }}\n\t\t\t\t\t</p>\n\t\t\t\t</template>\n\n\t\t\t\t<!-- config-fields -->\n\t\t\t\t<template v-else-if=\"step.type === 'config-fields'\">\n\t\t\t\t\t<!-- The step's intro, drawn like choice and run-action steps.\n\t\t\t\t\t It was left out here, so pipelinq's organisation step\n\t\t\t\t\t (\"Your organisation details…\") showed fields with no\n\t\t\t\t\t word about what they were for. -->\n\t\t\t\t\t<NcNoteCard v-if=\"step.body\" type=\"info\" data-testid=\"cn-setup-step-intro\">\n\t\t\t\t\t\t{{ stepBody(step) }}\n\t\t\t\t\t</NcNoteCard>\n\t\t\t\t\t<div\n\t\t\t\t\t\tv-for=\"field in fieldsFor(step)\"\n\t\t\t\t\t\t:key=\"field.key\"\n\t\t\t\t\t\tclass=\"cn-setup-field\">\n\t\t\t\t\t\t<NcCheckboxRadioSwitch\n\t\t\t\t\t\t\tv-if=\"field.widget === 'checkbox'\"\n\t\t\t\t\t\t\t:modelValue=\"!!configModel[field.key]\"\n\t\t\t\t\t\t\t@update:modelValue=\"(v) => configModel[field.key] = v\">\n\t\t\t\t\t\t\t{{ field.label }}\n\t\t\t\t\t\t</NcCheckboxRadioSwitch>\n\t\t\t\t\t\t<NcSelect\n\t\t\t\t\t\t\tv-else-if=\"field.widget === 'select'\"\n\t\t\t\t\t\t\t:inputLabel=\"field.label\"\n\t\t\t\t\t\t\t:options=\"field.enum || []\"\n\t\t\t\t\t\t\t:modelValue=\"configModel[field.key]\"\n\t\t\t\t\t\t\t@update:modelValue=\"(v) => configModel[field.key] = v\" />\n\t\t\t\t\t\t<NcTextField\n\t\t\t\t\t\t\tv-else\n\t\t\t\t\t\t\t:label=\"field.label\"\n\t\t\t\t\t\t\t:type=\"field.widget === 'number' ? 'number' : 'text'\"\n\t\t\t\t\t\t\t:modelValue=\"configModel[field.key] != null ? String(configModel[field.key]) : ''\"\n\t\t\t\t\t\t\t@update:modelValue=\"(v) => configModel[field.key] = v\" />\n\t\t\t\t\t</div>\n\t\t\t\t</template>\n\n\t\t\t\t<!-- run-action -->\n\t\t\t\t<template v-else-if=\"step.type === 'run-action'\">\n\t\t\t\t\t<NcNoteCard v-if=\"step.body\" type=\"info\">\n\t\t\t\t\t\t{{ stepBody(step) }}\n\t\t\t\t\t</NcNoteCard>\n\t\t\t\t\t<!-- The action starts automatically on entering this step\n\t\t\t\t\t (see `maybeAutoRunStep`), so the default state is the\n\t\t\t\t\t spinner, not an idle button. -->\n\t\t\t\t\t<div v-if=\"running[step.id]\" class=\"cn-setup-step__running\">\n\t\t\t\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t\t\t\t<span>{{ runningLabel }}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<template v-else>\n\t\t\t\t\t\t<NcNoteCard\n\t\t\t\t\t\t\tv-if=\"actionResult[step.id]\"\n\t\t\t\t\t\t\t:type=\"actionResult[step.id].success ? 'success' : 'error'\">\n\t\t\t\t\t\t\t{{ actionResult[step.id].message }}\n\t\t\t\t\t\t</NcNoteCard>\n\t\t\t\t\t\t<div class=\"cn-setup-step__nav\">\n\t\t\t\t\t\t\t<NcButton :variant=\"actionResult[step.id] ? 'secondary' : 'primary'\" @click=\"runAction(step)\">\n\t\t\t\t\t\t\t\t{{ actionResult[step.id] ? rerunLabel : runLabel }}\n\t\t\t\t\t\t\t</NcButton>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</template>\n\t\t\t\t</template>\n\n\t\t\t\t<!-- summary -->\n\t\t\t\t<template v-else-if=\"step.type === 'summary'\">\n\t\t\t\t\t<NcNoteCard v-if=\"step.body\" type=\"info\">\n\t\t\t\t\t\t{{ stepBody(step) }}\n\t\t\t\t\t</NcNoteCard>\n\t\t\t\t\t<ul class=\"cn-setup-summary\">\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tv-for=\"item in summaryItems\"\n\t\t\t\t\t\t\t:key=\"item.id\"\n\t\t\t\t\t\t\tclass=\"cn-setup-summary__item\"\n\t\t\t\t\t\t\t:class=\"{ 'cn-setup-summary__item--done': item.done, 'cn-setup-summary__item--not-run': item.notRun }\">\n\t\t\t\t\t\t\t<span class=\"cn-setup-summary__mark\">{{ item.done ? '✓' : '○' }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-setup-summary__label\">{{ item.title }}</span>\n\t\t\t\t\t\t\t<span v-if=\"item.value\" class=\"cn-setup-summary__value\">{{ item.value }}</span>\n\t\t\t\t\t\t</li>\n\t\t\t\t\t</ul>\n\t\t\t\t</template>\n\t\t\t</div>\n\t\t</template>\n\t\t<!-- eslint-enable vue/no-v-for-template-key-on-child -->\n\t</CnWizardDialog>\n</template>\n\n<script>\nimport { getCurrentUser } from '@nextcloud/auth'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcCheckboxRadioSwitch, NcLoadingIcon, NcNoteCard, NcSelect, NcTextField } from '@nextcloud/vue'\nimport CnChoiceCards from '../CnChoiceCards/CnChoiceCards.vue'\nimport CnLeafDependencySettings from '../CnLeafDependencySettings/CnLeafDependencySettings.vue'\nimport CnWizardDialog from '../CnWizardDialog/CnWizardDialog.vue'\nimport { checkDependencies, isDependencyResolved, missingRequiredApps, readServerAppStatuses } from '../../composables/useDependencyCheck.js'\nimport { useLook } from '../../composables/useLook.js'\nimport { useSetupStatus } from '../../composables/useSetupStatus.js'\nimport { fieldsFromSchema } from '../../utils/schema.js'\n\n/** Id of the synthetic step shown while a required app is missing. */\nconst DEPENDENCY_STEP_ID = 'cn-setup-dependencies'\n\n/** The dataset value that means \"seed nothing\"; it never gets a Load button. */\nconst NONE_DATASET = 'none'\n\n/**\n * CnSetupWizard — abstract, manifest-driven first-time setup wizard (ADR-042).\n *\n * Wraps {@link CnWizardDialog} and renders a `manifest.setup.steps[]` array by\n * each step's `type`: `info` (note), `config-fields` (fields from a JSON Schema\n * via `fieldsFromSchema`, the same field shapes the admin pages use), `choice`\n * (`NcSelect` persisting to an app-config key), `run-action` (POST a privileged\n * server action), `summary` (recap), and `component` (a parent-provided\n * `#step-<id>` slot). It NEVER writes OpenRegister objects from the browser —\n * all persistence goes through the per-app setup contract\n * (`POST /apps/{appId}/api/setup/config` and `/api/setup/action/{actionId}`).\n *\n * Mount it gated by `CnAppRoot`'s setup phase, or open it on demand from\n * `CnAdminSettingsShell`.\n *\n * ```vue\n * <CnSetupWizard\n * :app-id=\"'procest'\"\n * :steps=\"manifest.setup.steps\"\n * @complete=\"onSetupComplete\"\n * @close=\"showWizard = false\" />\n * ```\n */\nexport default {\n\tname: 'CnSetupWizard',\n\n\tcomponents: {\n\t\tCnWizardDialog,\n\t\tCnChoiceCards,\n\t\tCnLeafDependencySettings,\n\t\tNcButton,\n\t\tNcNoteCard,\n\t\tNcSelect,\n\t\tNcTextField,\n\t\tNcCheckboxRadioSwitch,\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Consumer translation function, provided by CnAppRoot as\n\t\t * `cnTranslate: this.translate` (bound to the host app's id). Step field\n\t\t * labels come from schema property titles, authored in English as the\n\t\t * canonical source; the visible label is resolved through this function\n\t\t * so it follows the user's language. Defaults to identity when used\n\t\t * standalone (no CnAppRoot ancestor).\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t\t/**\n\t\t * The app manifest, provided by CnAppRoot. Read only for its\n\t\t * `dependencies` when the `dependencies` prop is empty.\n\t\t */\n\t\tcnManifest: { default: null },\n\t},\n\n\tprops: {\n\t\t/** The Nextcloud app id; used to build the `/apps/{appId}/api/setup/*` URLs. */\n\t\tappId: {\n\t\t\ttype: String,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** The `manifest.setup.steps` array to render. */\n\t\tsteps: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Dialog title. Overridden by `appName` when that prop is set. */\n\t\tdialogTitle: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Set up this app'),\n\t\t},\n\n\t\t/**\n\t\t * The app's human-readable display name. When set, the dialog title\n\t\t * becomes \"Set up {appName}\" instead of the generic `dialogTitle`\n\t\t * default — pass the same display name CnAppRoot already resolves\n\t\t * (`appDisplayName || manifest.name || appId`).\n\t\t */\n\t\tappName: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Final-step submit button label. */\n\t\tsubmitLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Finish'),\n\t\t},\n\n\t\t/** Cancel button label. */\n\t\tcancelLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Cancel'),\n\t\t},\n\n\t\t/** Next button label. */\n\t\tnextLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Next'),\n\t\t},\n\n\t\t/** Back button label. */\n\t\tbackLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Back'),\n\t\t},\n\n\t\t/** Run-action button label (shown before the first automatic run). */\n\t\trunLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Run'),\n\t\t},\n\n\t\t/** Run-action button label offered after a run already finished. */\n\t\trerunLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Run again'),\n\t\t},\n\n\t\t/** Label shown next to the spinner while a run-action step is running. */\n\t\trunningLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Loading…'),\n\t\t},\n\n\t\t/** Result-phase success text. */\n\t\tsuccessText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Setup complete.'),\n\t\t},\n\n\t\t/**\n\t\t * Whether the wizard can be dismissed before finishing (Cancel\n\t\t * button, ESC, backdrop click). Pass `false` when a REQUIRED step\n\t\t * is unmet and the host is gating its shell behind this wizard\n\t\t * (ADR-042) — an offered-but-non-functional Cancel would be\n\t\t * misleading. Optional-only setup stays cancellable (default).\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tcancellable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Ids of steps the SERVER already reports as done (e.g.\n\t\t * `useSetupStatus(...).steps` filtered to `done === true`), passed by\n\t\t * the host so a freshly (re)mounted wizard doesn't re-ask something\n\t\t * already persisted in an earlier session — this component's own\n\t\t * `localDone`/`choiceModel` only track the CURRENT session and reset\n\t\t * blank on every mount. Drives both the initial step (`initialStepId`)\n\t\t * and the summary page's done markers.\n\t\t *\n\t\t * @type {Array<string>}\n\t\t */\n\t\tcompletedStepIds: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * The app's dependencies, in the manifest's own shape (app-id strings\n\t\t * or `{ id, name?, required? }`). Falls back to the injected manifest's\n\t\t * `dependencies`. A missing REQUIRED one replaces every step with the\n\t\t * dependency list and disables Next; a missing optional one is listed\n\t\t * on the first step and does not block.\n\t\t *\n\t\t * @type {Array<string|object>}\n\t\t * @spec openspec/changes/setup-wizard-card-load-and-dependency-gate/specs/cn-setup-wizard/spec.md#requirement-the-wizard-checks-dependencies-before-any-step\n\t\t */\n\t\tdependencies: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/** Load button label on a dataset card (`loadAction`). */\n\t\tloadLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Load'),\n\t\t},\n\n\t\t/** Load button label once that dataset loaded in this session. */\n\t\treloadLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Load again'),\n\t\t},\n\t},\n\n\temits: ['complete', 'close', 'step-change', 'action-result'],\n\n\t/**\n\t * Live option lists come from the app's own setup status document, so a\n\t * step that offers a choice of things the SERVER ships never has to\n\t * restate them in the manifest. `useSetupStatus` caches per app id, so the\n\t * host's own status fetch (CnAppRoot's setup gate) is the one that runs —\n\t * this reads the same refs rather than asking again.\n\t *\n\t * Only called when a step actually declares `optionsSource`: a wizard with\n\t * none must not start making requests it never made before.\n\t *\n\t * @param {object} props The component props.\n\t * @return {object} `setupStatus` / `setupStatusLoading` for the option resolver.\n\t */\n\tsetup(props) {\n\t\tconst { isBoard } = useLook()\n\t\tconst wantsSource = (props.steps || []).some((s) => s && (s.optionsSource || s.visibleWhen))\n\t\tif (!wantsSource) {\n\t\t\treturn { isBoard }\n\t\t}\n\t\tconst { status, loading } = useSetupStatus(props.appId, { setup: { steps: props.steps } })\n\t\treturn { isBoard, setupStatus: status, setupStatusLoading: loading }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tchoiceModel: {},\n\t\t\tchoiceValues: {},\n\t\t\tconfigModel: {},\n\t\t\trunning: {},\n\t\t\tactionResult: {},\n\t\t\tlocalDone: {},\n\t\t\tuserTouched: {},\n\t\t\t// Per-card load state for `loadAction` steps, keyed by `loadKey()`.\n\t\t\tdatasetRunning: {},\n\t\t\tdatasetResult: {},\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The title actually rendered by CnWizardDialog: \"Set up {appName}\"\n\t\t * when a display name was passed (the name through the host's\n\t\t * translate), otherwise whatever `dialogTitle` resolves to (a caller\n\t\t * override, or its own generic default). In the board look it is the\n\t\t * eyebrow's context and the current step's title is the heading.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tresolvedDialogTitle() {\n\t\t\treturn this.appName\n\t\t\t\t? t('nextcloud-vue', 'Set up {app}', { app: this.cnTranslate(this.appName) })\n\t\t\t\t: this.dialogTitle\n\t\t},\n\n\t\t/**\n\t\t * Every well-formed step, before `requires` is applied.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tdeclaredSteps() {\n\t\t\treturn (this.steps || []).filter((s) => s && s.id && s.type)\n\t\t},\n\n\t\t/**\n\t\t * Steps that declare `requires` on an app that is not installed and\n\t\t * enabled. They are not offered; the summary names them as skipped.\n\t\t *\n\t\t * @return {Array<{ step: object, missing: Array<string> }>}\n\t\t * @spec openspec/changes/setup-wizard-card-load-and-dependency-gate/specs/cn-setup-wizard/spec.md#requirement-the-wizard-checks-dependencies-before-any-step\n\t\t * @spec openspec/changes/optional-step-requires/specs/cn-setup-wizard/spec.md#requirement-a-step-whose-required-apps-are-absent-is-not-applicable\n\t\t */\n\t\tskippedSteps() {\n\t\t\treturn this.declaredSteps\n\t\t\t\t.filter((s) => Array.isArray(s.requires) && s.requires.length > 0)\n\t\t\t\t.map((step) => {\n\t\t\t\t\tconst known = Object.fromEntries(this.dependencyRows.map((d) => [d.id, d]))\n\t\t\t\t\tconst missing = missingRequiredApps(step.requires, this.serverAppStatuses)\n\t\t\t\t\t\t.map((row) => (known[row.id] || row).name || row.id)\n\t\t\t\t\treturn { step, missing }\n\t\t\t\t})\n\t\t\t\t.filter((entry) => entry.missing.length > 0)\n\t\t},\n\n\t\tsetupSteps() {\n\t\t\tconst skipped = new Set(this.skippedSteps.map((entry) => entry.step.id))\n\t\t\treturn this.declaredSteps.filter((s) => !skipped.has(s.id) && this.stepConditionHolds(s))\n\t\t},\n\n\t\t/**\n\t\t * Id of the first offered step: where the optional-apps note shows.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tfirstStepId() {\n\t\t\treturn this.setupSteps.length > 0 ? this.setupSteps[0].id : ''\n\t\t},\n\n\t\t/**\n\t\t * The dependency declarations to check: the prop, else the manifest's.\n\t\t *\n\t\t * @return {Array<string|object>}\n\t\t */\n\t\tdependencyEntries() {\n\t\t\tif (Array.isArray(this.dependencies) && this.dependencies.length > 0) {\n\t\t\t\treturn this.dependencies\n\t\t\t}\n\t\t\tconst fromManifest = this.cnManifest && this.cnManifest.dependencies\n\t\t\treturn Array.isArray(fromManifest) ? fromManifest : []\n\t\t},\n\n\t\t/**\n\t\t * The `dependency_statuses` initial state an app may inject from PHP.\n\t\t * Same source CnAppRoot reads; `{}` when the app injects none.\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\tserverAppStatuses() {\n\t\t\treturn readServerAppStatuses(this.appId)\n\t\t},\n\n\t\t/**\n\t\t * The declared dependencies with their live status.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t */\n\t\tdependencyRows() {\n\t\t\treturn checkDependencies(this.dependencyEntries, this.serverAppStatuses)\n\t\t},\n\n\t\t/** @return {Array<object>} Required dependencies that are missing or disabled. */\n\t\tmissingRequired() {\n\t\t\treturn this.dependencyRows.filter((d) => d.required && !isDependencyResolved(d))\n\t\t},\n\n\t\t/** @return {Array<object>} Optional dependencies that are missing or disabled. */\n\t\tmissingOptional() {\n\t\t\treturn this.dependencyRows.filter((d) => !d.required && !isDependencyResolved(d))\n\t\t},\n\n\t\t/**\n\t\t * Whether a required app is missing, so the wizard shows the\n\t\t * dependency list instead of its steps and refuses to continue.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/setup-wizard-card-load-and-dependency-gate/specs/cn-setup-wizard/spec.md#requirement-the-wizard-checks-dependencies-before-any-step\n\t\t */\n\t\tdependencyBlocked() {\n\t\t\treturn this.missingRequired.length > 0\n\t\t},\n\n\t\tdependencyStepSlot() {\n\t\t\treturn 'step-' + DEPENDENCY_STEP_ID\n\t\t},\n\n\t\tdependencySectionName() {\n\t\t\treturn t('nextcloud-vue', 'Apps this app needs')\n\t\t},\n\n\t\tdependencyBlockedText() {\n\t\t\treturn t('nextcloud-vue', 'Setup can start once these apps are installed and enabled: {apps}.', {\n\t\t\t\tapps: this.missingRequired.map((d) => d.name).join(', '),\n\t\t\t})\n\t\t},\n\n\t\toptionalMissingText() {\n\t\t\treturn t('nextcloud-vue', 'Optional, not installed: {apps}. You can add them later.', {\n\t\t\t\tapps: this.missingOptional.map((d) => d.name).join(', '),\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Whether the current user is a Nextcloud admin; only admins get the\n\t\t * install and enable buttons.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tisAdmin() {\n\t\t\ttry {\n\t\t\t\tconst user = getCurrentUser()\n\t\t\t\treturn !!(user && user.isAdmin)\n\t\t\t} catch {\n\t\t\t\treturn false\n\t\t\t}\n\t\t},\n\n\t\twizardSteps() {\n\t\t\tif (this.dependencyBlocked) {\n\t\t\t\treturn [{\n\t\t\t\t\tid: DEPENDENCY_STEP_ID,\n\t\t\t\t\tlabel: t('nextcloud-vue', 'Required apps'),\n\t\t\t\t\toptional: false,\n\t\t\t\t}]\n\t\t\t}\n\t\t\treturn this.setupSteps.map((s) => ({\n\t\t\t\tid: s.id,\n\t\t\t\tlabel: this.stepTitle(s),\n\t\t\t\toptional: s.required !== true,\n\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * Recap rows for the `summary` step — one per non-summary step, with\n\t\t * the value the user selected (choice label / config field values) and\n\t\t * a done marker (info steps always done; choice steps done when picked;\n\t\t * run-action steps done when the action succeeded).\n\t\t *\n\t\t * An on-demand step (`onDemand: true`) is the exception: it is done\n\t\t * only when its action succeeded in THIS session. Apps report such a\n\t\t * step as `done` on purpose so it never auto-runs (learniq's \"Remove\n\t\t * the example data\"), and reading that flag here ticked a removal\n\t\t * nobody ran. When it did not run it gets `notRun: true` and the value\n\t\t * \"Not run\" instead.\n\t\t *\n\t\t * @return {Array<{ id: string, title: string, value: string, done: boolean, notRun: boolean }>}\n\t\t */\n\t\tsummaryItems() {\n\t\t\tconst skipped = this.skippedSteps\n\t\t\t\t.filter((entry) => entry.step.type !== 'summary' && entry.step.type !== 'info')\n\t\t\t\t.map((entry) => ({\n\t\t\t\t\tid: entry.step.id,\n\t\t\t\t\ttitle: this.stepTitle(entry.step),\n\t\t\t\t\tvalue: t('nextcloud-vue', 'Skipped, needs {apps}', { apps: entry.missing.join(', ') }),\n\t\t\t\t\tdone: false,\n\t\t\t\t\tnotRun: true,\n\t\t\t\t}))\n\t\t\treturn this.setupSteps\n\t\t\t\t.filter((s) => s.type !== 'summary')\n\t\t\t\t.map((step) => {\n\t\t\t\t\tlet value = ''\n\t\t\t\t\tif (step.type === 'choice') {\n\t\t\t\t\t\tconst raw = this.choiceModel[step.id]\n\t\t\t\t\t\tif (Array.isArray(raw)) {\n\t\t\t\t\t\t\tvalue = raw.map((o) => this.choiceLabel(step, o)).join(', ')\n\t\t\t\t\t\t} else if (raw !== null && raw !== undefined && raw !== '') {\n\t\t\t\t\t\t\tvalue = this.choiceLabel(step, raw)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (step.type === 'config-fields') {\n\t\t\t\t\t\t// Only the fields the reader filled in: a row of empty\n\t\t\t\t\t\t// \"Name: , KvK: ,\" reads as broken, not as \"not set\".\n\t\t\t\t\t\tvalue = this.fieldsFor(step)\n\t\t\t\t\t\t\t.filter((f) => {\n\t\t\t\t\t\t\t\tconst v = this.configModel[f.key]\n\t\t\t\t\t\t\t\treturn v !== null && v !== undefined && String(v).trim() !== ''\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.map((f) => `${f.label}: ${this.configModel[f.key]}`)\n\t\t\t\t\t\t\t.join(', ') || t('nextcloud-vue', 'Not set')\n\t\t\t\t\t}\n\t\t\t\t\tif (this.isOnDemand(step)) {\n\t\t\t\t\t\tconst ran = this.ranThisSession(step.id)\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tid: step.id,\n\t\t\t\t\t\t\ttitle: this.stepTitle(step),\n\t\t\t\t\t\t\tvalue: ran ? '' : t('nextcloud-vue', 'Not run'),\n\t\t\t\t\t\t\tdone: ran,\n\t\t\t\t\t\t\tnotRun: !ran,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tlet done\n\t\t\t\t\tif (step.type === 'info') {\n\t\t\t\t\t\tdone = true\n\t\t\t\t\t} else if (step.type === 'choice') {\n\t\t\t\t\t\tdone = this.hasChoice(step) || this.isServerDone(step.id)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdone = this.isStepDone(step.id)\n\t\t\t\t\t}\n\t\t\t\t\treturn { id: step.id, title: this.stepTitle(step), value, done, notRun: false }\n\t\t\t\t})\n\t\t\t\t.concat(skipped)\n\t\t},\n\n\t\t/**\n\t\t * Id of the step the wizard should open on when RESUMING: the first\n\t\t * non-`info`/`summary` step not already done, per the CURRENT session's\n\t\t * local state OR the server-reported `completedStepIds` (a prior\n\t\t * session). Falls back to `''` (CnWizardDialog's own first-step default)\n\t\t * on a fresh setup and when every actionable step is already done.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tinitialStepId() {\n\t\t\t// Fresh setup — nothing done anywhere yet, so there is nothing to\n\t\t\t// resume past. Start at step one so a leading `info`/welcome step is\n\t\t\t// actually seen; resuming is only for returning sessions.\n\t\t\tif (this.completedStepIds.length === 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\t// An on-demand step is never outstanding work, so it is never the\n\t\t\t// step to resume on.\n\t\t\tconst actionable = this.setupSteps.filter((s) => s.type !== 'info' && s.type !== 'summary' && !this.isOnDemand(s))\n\t\t\tconst firstUnmetIndex = actionable.findIndex((s) => {\n\t\t\t\tif (s.type === 'choice') {\n\t\t\t\t\treturn !(this.hasChoice(s) || this.isServerDone(s.id))\n\t\t\t\t}\n\t\t\t\treturn !this.isStepDone(s.id)\n\t\t\t})\n\t\t\t// Done is not the same as seen: an app that pre-satisfies a step at\n\t\t\t// install time (buildiq seeds templates from a repair step) reports it\n\t\t\t// done before anyone opens the wizard. Only finished work BEFORE the\n\t\t\t// outstanding step means there is something to resume past.\n\t\t\tif (firstUnmetIndex <= 0) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn actionable[firstUnmetIndex].id\n\t\t},\n\t},\n\n\tcreated() {\n\t\tfor (const step of this.setupSteps) {\n\t\t\tthis.applyDefault(step)\n\t\t}\n\t},\n\n\tmounted() {\n\t\t// Covers a RESUMED wizard opening straight onto its run-action step\n\t\t// (`initialStepId`) — `onStepChange` only fires on later navigation,\n\t\t// so a step already active at mount time needs its own trigger.\n\t\tthis.maybeAutoRunStep(this.initialStepId || (this.setupSteps[0] && this.setupSteps[0].id))\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Start a `run-action` step's action automatically the moment it\n\t\t * becomes current, instead of waiting for a manual \"Run\" click —\n\t\t * the step has nothing else for the user to decide by then (the\n\t\t * choice it acts on was already made on an earlier step), so\n\t\t * requiring a click before showing progress just adds a stall.\n\t\t * No-ops for any other step type, or one already run/running, and\n\t\t * never fires an on-demand step (`onDemand: true`): those run only\n\t\t * when the user clicks Run.\n\t\t *\n\t\t * @param {string} stepId The step id becoming current.\n\t\t * @return {void}\n\t\t */\n\t\tmaybeAutoRunStep(stepId) {\n\t\t\t// Nothing runs while a required app is missing: every action\n\t\t\t// would fail with an error that does not name the app.\n\t\t\tif (this.dependencyBlocked) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst step = this.setupSteps.find((s) => s.id === stepId)\n\t\t\tif (step && step.type === 'run-action' && !this.isOnDemand(step) && !this.isStepDone(step.id) && !this.running[step.id]) {\n\t\t\t\tthis.runAction(step)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The visible title for a step, resolved through the host app's\n\t\t * translation function.\n\t\t *\n\t\t * `manifest.setup.steps[].title` is authored in English as the\n\t\t * canonical source, exactly like the schema property titles this\n\t\t * component already routes through `cnTranslate` for field labels\n\t\t * (see `fieldsFromSchema` below). The step titles were the one\n\t\t * remaining place rendering the manifest string verbatim, so an\n\t\t * nl-locale user saw a wizard whose field labels were translated and\n\t\t * whose step headings were not.\n\t\t *\n\t\t * Falls back to the step id when a step declares no title, which is\n\t\t * the same fallback the raw bindings used.\n\t\t *\n\t\t * @param {object} step A `manifest.setup.steps[]` entry.\n\t\t * @return {string} The translated title, or the step id.\n\t\t */\n\t\tstepTitle(step) {\n\t\t\tconst raw = (step && step.title) || (step && step.id) || ''\n\t\t\treturn raw === '' ? '' : this.cnTranslate(raw)\n\t\t},\n\n\t\t/**\n\t\t * The visible body for a step, resolved through the host app's\n\t\t * translation function.\n\t\t *\n\t\t * 🔴 THIS USED TO RENDER THE MANIFEST STRING VERBATIM, IN FOUR PLACES.\n\t\t * `stepTitle()` was routed through `cnTranslate` and the body was not,\n\t\t * so a wizard on a Dutch instance showed a translated heading over an\n\t\t * English paragraph, with translated Cancel/Next buttons underneath.\n\t\t * That reads as a bug rather than as a missing string, and the app could\n\t\t * not fix it: decidiq shipped correct Dutch for its setup copy in\n\t\t * `l10n/nl.json`, and the component never asked for it.\n\t\t *\n\t\t * @param {object} step The step declaration.\n\t\t * @return {string} The translated body, or ''.\n\t\t */\n\t\tstepBody(step) {\n\t\t\tconst raw = (step && step.body) || ''\n\t\t\treturn raw === '' ? '' : this.cnTranslate(raw)\n\t\t},\n\n\t\t/**\n\t\t * The footnote under a choice step's options, from the step's `note`\n\t\t * (or `footnote`), translated like the body.\n\t\t *\n\t\t * @param {object} step The step declaration.\n\t\t * @return {string} The translated footnote, or ''.\n\t\t */\n\t\tstepNote(step) {\n\t\t\tconst raw = (step && (step.note || step.footnote)) || ''\n\t\t\treturn raw === '' ? '' : this.cnTranslate(raw)\n\t\t},\n\n\t\tstepSlot(step) {\n\t\t\treturn 'step-' + step.id\n\t\t},\n\n\t\thasCustomSlot(id) {\n\t\t\treturn !!this.$slots['step-' + id] || !!this.$slots['step-' + id]\n\t\t},\n\n\t\tfieldsFor(step) {\n\t\t\tif (step.schema && typeof step.schema === 'object') {\n\t\t\t\treturn fieldsFromSchema(step.schema, { translate: this.cnTranslate })\n\t\t\t}\n\t\t\t// Fallback: a plain text field per declared config key.\n\t\t\treturn (step.configKeys || []).map((key) => ({ key, label: key, widget: 'text' }))\n\t\t},\n\n\t\t/**\n\t\t * Options for a `choice` step. When the step declares `dependsOn`\n\t\t * (a parent step's configKey) + `optionsByParent`, the option list\n\t\t * is the entry keyed by the parent's currently-selected value — this\n\t\t * is the country → organisation-type cascade. Otherwise the static\n\t\t * `options` array.\n\t\t *\n\t\t * @param {object} step The choice step definition.\n\t\t * @return {Array} The option list to render.\n\t\t */\n\t\toptionsFor(step) {\n\t\t\tlet options = step.options || []\n\t\t\tif (step.optionsSource) {\n\t\t\t\toptions = this.sourcedOptions(step)\n\t\t\t} else if (step.dependsOn && step.optionsByParent) {\n\t\t\t\tconst parentValue = this.choiceValues[step.dependsOn]\n\t\t\t\toptions = (parentValue === null || parentValue === undefined || parentValue === '')\n\t\t\t\t\t? []\n\t\t\t\t\t: (step.optionsByParent[parentValue] || [])\n\t\t\t}\n\t\t\t// The LABEL is translated, the VALUE never is. `scalarChoice()` reads\n\t\t\t// `.value` and that is what reaches `POST /api/setup/config`, so a\n\t\t\t// translated label cannot change what gets stored — while an\n\t\t\t// untranslated one leaves the choice itself in English on a\n\t\t\t// localised instance, which is the half-translated screen this fix\n\t\t\t// exists to remove.\n\t\t\treturn options.map((option) => {\n\t\t\t\tif (!option || typeof option !== 'object' || !option.label) {\n\t\t\t\t\treturn option\n\t\t\t\t}\n\t\t\t\tconst translated = { ...option, label: this.cnTranslate(option.label) }\n\t\t\t\tif (option.description) {\n\t\t\t\t\ttranslated.description = this.cnTranslate(option.description)\n\t\t\t\t}\n\t\t\t\treturn translated\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Whether a step's `visibleWhen` holds: the value at that path in the\n\t\t * setup status (`\"flags.hasPayouts\"`, dots walk into objects) is truthy,\n\t\t * or equals `equals` when the step gives `{ path, equals }`. A step\n\t\t * without `visibleWhen` always shows; one with it stays hidden until the\n\t\t * status says so, also while the status loads (DqInstellen: the dwangsom\n\t\t * step only when the app pays penalties).\n\t\t *\n\t\t * @param {object} step The step declaration.\n\t\t * @return {boolean}\n\t\t */\n\t\tstepConditionHolds(step) {\n\t\t\tconst when = step && step.visibleWhen\n\t\t\tif (when === undefined || when === null || when === '') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst path = typeof when === 'string' ? when : when.path\n\t\t\tif (typeof path !== 'string' || path === '') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tlet node = this.setupStatus\n\t\t\tfor (const key of path.split('.')) {\n\t\t\t\tif (node === null || node === undefined || typeof node !== 'object') {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tnode = node[key]\n\t\t\t}\n\t\t\tif (typeof when === 'object' && Object.hasOwn(when, 'equals')) {\n\t\t\t\treturn node === when.equals\n\t\t\t}\n\t\t\treturn Boolean(node)\n\t\t},\n\n\t\t/**\n\t\t * The option list a `choice` step declares `optionsSource` for, read\n\t\t * from the setup status document the app already serves.\n\t\t *\n\t\t * `optionsSource` names a key in that document (dots walk into nested\n\t\t * objects), and the entries are normalised to the option shape: `id` is\n\t\t * accepted for `value`, `name` for `label`, and a bare `objectCount`\n\t\t * becomes the one stat a card shows. A server that already sends\n\t\t * `stats` keeps them.\n\t\t *\n\t\t * @param {object} step The choice step declaring `optionsSource`.\n\t\t * @return {Array<object>} The options, or [] while the status is loading.\n\t\t */\n\t\tsourcedOptions(step) {\n\t\t\tlet node = this.setupStatus\n\t\t\tfor (const key of String(step.optionsSource).split('.')) {\n\t\t\t\tif (node === null || node === undefined || typeof node !== 'object') {\n\t\t\t\t\treturn []\n\t\t\t\t}\n\t\t\t\tnode = node[key]\n\t\t\t}\n\t\t\tif (!Array.isArray(node)) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn node\n\t\t\t\t.filter((entry) => entry && typeof entry === 'object')\n\t\t\t\t.map((entry) => {\n\t\t\t\t\tconst value = entry.value !== undefined ? entry.value : entry.id\n\t\t\t\t\tconst option = {\n\t\t\t\t\t\t...entry,\n\t\t\t\t\t\tvalue,\n\t\t\t\t\t\tlabel: entry.label || entry.name || String(value),\n\t\t\t\t\t}\n\t\t\t\t\tif (!option.stats && typeof entry.objectCount === 'number' && entry.objectCount > 0) {\n\t\t\t\t\t\toption.stats = [{ label: t('nextcloud-vue', 'Objects'), value: entry.objectCount }]\n\t\t\t\t\t}\n\t\t\t\t\treturn option\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 * Whether a choice step renders as CnChoiceCards (a card grid, or radio\n\t\t * rows with `display: \"rows\"`) rather than a dropdown.\n\t\t *\n\t\t * @param {object} step The choice step.\n\t\t * @return {boolean} True for `display: \"cards\"` and `display: \"rows\"`.\n\t\t */\n\t\tisCardChoice(step) {\n\t\t\treturn step.display === 'cards' || step.display === 'rows'\n\t\t},\n\n\t\t/**\n\t\t * Whether this step is still waiting for its live option list.\n\t\t *\n\t\t * @param {object} step The choice step.\n\t\t * @return {boolean} True while the status document is in flight.\n\t\t */\n\t\tisOptionsLoading(step) {\n\t\t\treturn !!step.optionsSource && this.setupStatusLoading === true\n\t\t},\n\n\t\t/**\n\t\t * The card grid's model: plain values, not the option objects NcSelect\n\t\t * binds. `scalarChoice` already reduces either shape to values, so both\n\t\t * renderers can share one `choiceModel` entry.\n\t\t *\n\t\t * @param {object} step The choice step.\n\t\t * @return {unknown} The selected value, or [] / null when nothing is picked.\n\t\t */\n\t\tcardModel(step) {\n\t\t\tconst value = this.scalarChoice(step)\n\t\t\tif (step.multiple === true) {\n\t\t\t\treturn Array.isArray(value) ? value : []\n\t\t\t}\n\t\t\treturn value === undefined ? null : value\n\t\t},\n\n\t\tisChoiceDisabled(step) {\n\t\t\tif (!step.dependsOn) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst parentValue = this.choiceValues[step.dependsOn]\n\t\t\treturn parentValue === null || parentValue === undefined || parentValue === ''\n\t\t},\n\n\t\tdependsOnHint(step) {\n\t\t\tconst parent = this.setupSteps.find((s) => s.configKey === step.dependsOn)\n\t\t\tconst label = parent ? (parent.title || parent.id) : step.dependsOn\n\t\t\treturn t('nextcloud-vue', 'Select \"{step}\" first.', { step: label })\n\t\t},\n\n\t\t/**\n\t\t * The visible label for one selected choice entry.\n\t\t *\n\t\t * A card grid stores plain VALUES where NcSelect stores option objects,\n\t\t * so the recap looked one up and printed the other: the summary read\n\t\t * \"municipality\" instead of \"Municipality\". Resolves a bare value back\n\t\t * to its option, and falls back to the value when the option list is\n\t\t * not (yet) available.\n\t\t *\n\t\t * @param {object} step The choice step.\n\t\t * @param {unknown} entry One selected entry: an option object or a value.\n\t\t * @return {string} The label to show.\n\t\t */\n\t\tchoiceLabel(step, entry) {\n\t\t\tif (entry && typeof entry === 'object' && entry.label) {\n\t\t\t\treturn entry.label\n\t\t\t}\n\t\t\tconst match = this.optionsFor(step).find((o) => o && String(o.value) === String(entry))\n\t\t\treturn (match && match.label) ? match.label : String(entry)\n\t\t},\n\n\t\thasChoice(step) {\n\t\t\tconst v = this.choiceModel[step.id]\n\t\t\tif (step.multiple === true) {\n\t\t\t\treturn Array.isArray(v) && v.length > 0\n\t\t\t}\n\t\t\treturn v !== null && v !== undefined && v !== ''\n\t\t},\n\n\t\tscalarChoice(step) {\n\t\t\tconst raw = this.choiceModel[step.id]\n\t\t\tif (step.multiple === true) {\n\t\t\t\treturn (raw || []).map((o) => (o && o.value !== undefined ? o.value : o))\n\t\t\t}\n\t\t\treturn raw && raw.value !== undefined ? raw.value : raw\n\t\t},\n\n\t\tonChoice(step, value) {\n\t\t\tthis.userTouched[step.id] = true\n\t\t\tthis.choiceModel[step.id] = value\n\t\t\tif (step.configKey) {\n\t\t\t\tthis.choiceValues[step.configKey] = this.scalarChoice(step)\n\t\t\t}\n\t\t\t// Reset any dependent child choices when the parent changes.\n\t\t\tfor (const child of this.setupSteps) {\n\t\t\t\tif (child.dependsOn && child.dependsOn === step.configKey) {\n\t\t\t\t\tthis.choiceModel[child.id] = child.multiple === true ? [] : null\n\t\t\t\t\tthis.userTouched[child.id] = false\n\t\t\t\t\tif (child.configKey) {\n\t\t\t\t\t\tthis.choiceValues[child.configKey] = ''\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Re-apply auto-suggestions for steps that derive a default from this one.\n\t\t\tfor (const dep of this.setupSteps) {\n\t\t\t\tif (dep.suggestFrom && dep.suggestFrom === step.configKey) {\n\t\t\t\t\tthis.applySuggestion(dep)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Preselect a `choice` step's `default` option. Skipped once the user\n\t\t * picked here, when a value is already chosen, and for a step the\n\t\t * server already completed (its stored value must not be overwritten\n\t\t * by a Next). A card step holds the plain value; a dropdown holds the\n\t\t * option object, or the value while the options are still loading.\n\t\t *\n\t\t * @param {object} step The choice step.\n\t\t * @return {void}\n\t\t */\n\t\tapplyDefault(step) {\n\t\t\tif (!step || step.type !== 'choice' || step.default === undefined || step.default === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.userTouched[step.id] === true || this.hasChoice(step) || this.isServerDone(step.id)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst wanted = step.multiple === true ? [].concat(step.default) : step.default\n\t\t\tif (this.isCardChoice(step)) {\n\t\t\t\tthis.choiceModel[step.id] = wanted\n\t\t\t} else {\n\t\t\t\tconst options = this.optionsFor(step)\n\t\t\t\tconst pick = (value) => options.find((o) => o.value === value) || value\n\t\t\t\tthis.choiceModel[step.id] = Array.isArray(wanted) ? wanted.map(pick) : pick(wanted)\n\t\t\t}\n\t\t\tif (step.configKey) {\n\t\t\t\tthis.choiceValues[step.configKey] = this.scalarChoice(step)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Auto-fill a `choice` step's suggested default from an earlier choice\n\t\t * (its `suggestFrom` configKey → `suggestMap[parentValue]`). No-op once\n\t\t * the user has manually picked here, so a suggestion never clobbers an\n\t\t * explicit choice.\n\t\t *\n\t\t * @param {object} step The dependent choice step.\n\t\t * @return {void}\n\t\t */\n\t\tapplySuggestion(step) {\n\t\t\tif (this.userTouched[step.id] === true) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst parentValue = this.choiceValues[step.suggestFrom]\n\t\t\tif (parentValue === null || parentValue === undefined || parentValue === '') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst wanted = (step.suggestMap || {})[parentValue]\n\t\t\tif (wanted === null || wanted === undefined) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst opt = this.optionsFor(step).find((o) => o.value === wanted) || { value: wanted, label: String(wanted) }\n\t\t\tthis.choiceModel[step.id] = opt\n\t\t\tif (step.configKey) {\n\t\t\t\tthis.choiceValues[step.configKey] = wanted\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * CnWizardDialog `validate` hook — intercepts Next/Submit to persist\n\t\t * the active step before advancing. `choice`/`config-fields` steps\n\t\t * POST their value via the setup contract; required `choice` steps\n\t\t * block advance until a value is picked; required `run-action` steps\n\t\t * block until the action has succeeded. Returns `true` to advance or\n\t\t * an error string to surface above the step and block navigation.\n\t\t *\n\t\t * @param {string} stepId The active step id.\n\t\t * @return {Promise<boolean|string>} True to advance, or an error message.\n\t\t */\n\t\tasync validateStep(stepId) {\n\t\t\tconst step = this.setupSteps.find((s) => s.id === stepId)\n\t\t\tif (!step) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tif (step.type === 'choice') {\n\t\t\t\tif (!this.hasChoice(step)) {\n\t\t\t\t\t// Already persisted server-side and the user only back-navigated\n\t\t\t\t\t// onto it — `choiceModel` is blank because it's session-local, not\n\t\t\t\t\t// because nothing is set. Don't force a re-pick, and don't re-POST.\n\t\t\t\t\tif (this.isServerDone(step.id)) {\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t\treturn step.required === true\n\t\t\t\t\t\t? t('nextcloud-vue', 'Please make a selection to continue.')\n\t\t\t\t\t\t: true\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tawait this.saveConfig({ [step.configKey]: this.scalarChoice(step) })\n\t\t\t\t\tthis.localDone[step.id] = true\n\t\t\t\t\treturn true\n\t\t\t\t} catch (err) {\n\t\t\t\t\treturn this.errorMessage(err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (step.type === 'config-fields') {\n\t\t\t\ttry {\n\t\t\t\t\tconst patch = {}\n\t\t\t\t\tfor (const field of this.fieldsFor(step)) {\n\t\t\t\t\t\tpatch[field.key] = this.configModel[field.key]\n\t\t\t\t\t}\n\t\t\t\t\tawait this.saveConfig(patch)\n\t\t\t\t\tthis.localDone[step.id] = true\n\t\t\t\t\treturn true\n\t\t\t\t} catch (err) {\n\t\t\t\t\treturn this.errorMessage(err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (step.type === 'run-action' && step.required === true && !this.isStepDone(step.id)) {\n\t\t\t\treturn t('nextcloud-vue', 'Please run this step before continuing.')\n\t\t\t}\n\t\t\treturn true\n\t\t},\n\n\t\t/**\n\t\t * Persist one or more app-config values via the setup contract.\n\t\t *\n\t\t * @param {object} patch `{ <configKey>: value }` to write.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync saveConfig(patch) {\n\t\t\tconst [{ default: axios }, { generateUrl }] = await Promise.all([\n\t\t\t\timport('@nextcloud/axios'),\n\t\t\t\timport('@nextcloud/router'),\n\t\t\t])\n\t\t\tawait axios.post(generateUrl(`/apps/${this.appId}/api/setup/config`), patch)\n\t\t},\n\n\t\tasync runAction(step) {\n\t\t\tthis.running[step.id] = true\n\t\t\tthis.actionResult[step.id] = null\n\t\t\ttry {\n\t\t\t\tconst [{ default: axios }, { generateUrl }] = await Promise.all([\n\t\t\t\t\timport('@nextcloud/axios'),\n\t\t\t\t\timport('@nextcloud/router'),\n\t\t\t\t])\n\t\t\t\tconst { data } = await axios.post(generateUrl(`/apps/${this.appId}/api/setup/action/${step.action}`))\n\t\t\t\tconst result = {\n\t\t\t\t\tsuccess: data && data.success !== false,\n\t\t\t\t\tmessage: (data && data.message) || t('nextcloud-vue', 'Done.'),\n\t\t\t\t}\n\t\t\t\tthis.actionResult[step.id] = result\n\t\t\t\tif (result.success) {\n\t\t\t\t\tthis.localDone[step.id] = true\n\t\t\t\t}\n\t\t\t\t/**\n\t\t\t\t * @event action-result Emitted when a `run-action` step finishes.\n\t\t\t\t * @type {{ stepId: string, action: string, success: boolean, message: string }}\n\t\t\t\t */\n\t\t\t\tthis.$emit('action-result', { stepId: step.id, action: step.action, ...result })\n\t\t\t} catch (err) {\n\t\t\t\tthis.actionResult[step.id] = { success: false, message: this.errorMessage(err) }\n\t\t\t\tthis.$emit('action-result', { stepId: step.id, action: step.action, success: false, message: this.errorMessage(err) })\n\t\t\t} finally {\n\t\t\t\tthis.running[step.id] = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Key for one card's load state.\n\t\t *\n\t\t * @param {object} step The choice step.\n\t\t * @param {object} option The card's option.\n\t\t * @return {string}\n\t\t */\n\t\tloadKey(step, option) {\n\t\t\treturn step.id + '::' + String(option && option.value)\n\t\t},\n\n\t\t/**\n\t\t * Whether a card gets a Load button: the step declares `loadAction`\n\t\t * and the option is not the \"seed nothing\" answer.\n\t\t *\n\t\t * @param {object} step The choice step.\n\t\t * @param {object} option The card's option.\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/setup-wizard-card-load-and-dependency-gate/specs/cn-setup-wizard/spec.md#requirement-a-dataset-card-loads-itself\n\t\t */\n\t\tisLoadableOption(step, option) {\n\t\t\treturn !!(step && step.loadAction) && !!option && String(option.value) !== NONE_DATASET\n\t\t},\n\n\t\tisDatasetLoading(step, option) {\n\t\t\treturn this.datasetRunning[this.loadKey(step, option)] === true\n\t\t},\n\n\t\tdatasetResultFor(step, option) {\n\t\t\treturn this.datasetResult[this.loadKey(step, option)] || null\n\t\t},\n\n\t\tloadButtonLabel(step, option) {\n\t\t\tif (this.isDatasetLoading(step, option)) {\n\t\t\t\treturn this.runningLabel\n\t\t\t}\n\t\t\tconst result = this.datasetResultFor(step, option)\n\t\t\treturn (result && result.success) ? this.reloadLabel : this.loadLabel\n\t\t},\n\n\t\tloadAriaLabel(option) {\n\t\t\treturn t('nextcloud-vue', 'Load {name}', { name: option.label })\n\t\t},\n\n\t\t/**\n\t\t * Load one dataset from its card: POST `{ dataset }` to the step's\n\t\t * `loadAction`, spinner on the button while it runs, the result on the\n\t\t * card after. A successful load also selects the card, so the summary\n\t\t * and the stored choice name what was loaded.\n\t\t *\n\t\t * @param {object} step The choice step declaring `loadAction`.\n\t\t * @param {object} option The card's option.\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/setup-wizard-card-load-and-dependency-gate/specs/cn-setup-wizard/spec.md#requirement-a-dataset-card-loads-itself\n\t\t */\n\t\tasync loadDataset(step, option) {\n\t\t\tif (!this.isLoadableOption(step, option) || this.isDatasetLoading(step, option)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst key = this.loadKey(step, option)\n\t\t\tthis.datasetRunning[key] = true\n\t\t\tthis.datasetResult[key] = null\n\t\t\tlet result\n\t\t\ttry {\n\t\t\t\tconst [{ default: axios }, { generateUrl }] = await Promise.all([\n\t\t\t\t\timport('@nextcloud/axios'),\n\t\t\t\t\timport('@nextcloud/router'),\n\t\t\t\t])\n\t\t\t\tconst { data } = await axios.post(\n\t\t\t\t\tgenerateUrl(`/apps/${this.appId}/api/setup/action/${step.loadAction}`),\n\t\t\t\t\t{ dataset: option.value },\n\t\t\t\t)\n\t\t\t\tresult = {\n\t\t\t\t\tsuccess: !!data && data.success !== false,\n\t\t\t\t\tmessage: (data && data.message) || t('nextcloud-vue', 'Loaded.'),\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tresult = { success: false, message: this.errorMessage(err) }\n\t\t\t} finally {\n\t\t\t\tthis.datasetRunning[key] = false\n\t\t\t}\n\t\t\tthis.datasetResult[key] = result\n\t\t\tif (result.success) {\n\t\t\t\tthis.selectLoadedOption(step, option)\n\t\t\t\tthis.localDone[step.id] = true\n\t\t\t}\n\t\t\tthis.$emit('action-result', {\n\t\t\t\tstepId: step.id,\n\t\t\t\taction: step.loadAction,\n\t\t\t\tdataset: option.value,\n\t\t\t\t...result,\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Select a card that just loaded, adding to (never replacing) the\n\t\t * selection of a `multiple` step.\n\t\t *\n\t\t * @param {object} step The choice step.\n\t\t * @param {object} option The loaded option.\n\t\t * @return {void}\n\t\t */\n\t\tselectLoadedOption(step, option) {\n\t\t\tif (step.multiple === true) {\n\t\t\t\tconst current = this.cardModel(step)\n\t\t\t\tif (current.some((v) => String(v) === String(option.value))) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.onChoice(step, current.concat([option.value]))\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.onChoice(step, option.value)\n\t\t},\n\n\t\t/**\n\t\t * A required app was installed from the dependency list. A new app's\n\t\t * scripts and its `OC.appswebroots` entry exist only after a full page\n\t\t * load, and `useAppStatus` caches per page, so reload.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonDependencyInstalled() {\n\t\t\twindow.location.reload()\n\t\t},\n\n\t\t/**\n\t\t * Whether a step is marked on-demand (`onDemand: true`): it runs only\n\t\t * when the user asks, never automatically, and the summary ticks it\n\t\t * only when it ran in this session.\n\t\t *\n\t\t * @param {object} step The manifest step.\n\t\t * @return {boolean}\n\t\t */\n\t\tisOnDemand(step) {\n\t\t\treturn !!step && step.onDemand === true\n\t\t},\n\n\t\t/**\n\t\t * Whether a step's action succeeded in the CURRENT session, ignoring\n\t\t * whatever the server reports.\n\t\t *\n\t\t * @param {string} id Step id.\n\t\t * @return {boolean}\n\t\t */\n\t\tranThisSession(id) {\n\t\t\treturn !!(this.actionResult[id] && this.actionResult[id].success)\n\t\t},\n\n\t\tisStepDone(id) {\n\t\t\treturn this.isServerDone(id) || this.localDone[id] === true || (this.actionResult[id] && this.actionResult[id].success)\n\t\t},\n\n\t\t/**\n\t\t * Whether the server already reported this step done in a prior\n\t\t * session, via the `completedStepIds` prop.\n\t\t *\n\t\t * @param {string} id Step id.\n\t\t * @return {boolean}\n\t\t */\n\t\tisServerDone(id) {\n\t\t\treturn this.completedStepIds.includes(id)\n\t\t},\n\n\t\tonSubmit() {\n\t\t\tif (this.$refs.wizard && this.$refs.wizard.setResult) {\n\t\t\t\tthis.$refs.wizard.setResult({ success: true, message: this.successText })\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event complete Emitted when the last step is submitted (setup finished).\n\t\t\t */\n\t\t\tthis.$emit('complete')\n\t\t},\n\n\t\tonStepChange(payload) {\n\t\t\t// Pre-fill a suggested default when entering a step that derives one.\n\t\t\tconst step = this.setupSteps.find((s) => s.id === payload.stepId)\n\t\t\tif (step && step.suggestFrom) {\n\t\t\t\tthis.applySuggestion(step)\n\t\t\t}\n\t\t\t// A default seeded before a dropdown's options arrived is a bare\n\t\t\t// value; swap in its option object now the step opens.\n\t\t\tif (step && step.default !== undefined && this.userTouched[step.id] !== true && !this.isCardChoice(step)) {\n\t\t\t\tconst held = this.choiceModel[step.id]\n\t\t\t\tconst option = this.optionsFor(step).find((o) => o.value === held)\n\t\t\t\tif (option && held !== null && typeof held !== 'object') {\n\t\t\t\t\tthis.choiceModel[step.id] = option\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.maybeAutoRunStep(payload.stepId)\n\t\t\t/**\n\t\t\t * @event step-change Emitted when the active step changes.\n\t\t\t * @type {{ stepId: string, stepIndex: number, direction: string }}\n\t\t\t */\n\t\t\tthis.$emit('step-change', payload)\n\t\t},\n\n\t\tonClose() {\n\t\t\t/**\n\t\t\t * @event close Emitted when the dialog should close.\n\t\t\t */\n\t\t\tthis.$emit('close')\n\t\t},\n\n\t\terrorMessage(err) {\n\t\t\tconst data = err && err.response && err.response.data\n\t\t\tif (data && (data.message || data.error)) {\n\t\t\t\treturn data.message || data.error\n\t\t\t}\n\t\t\treturn (err && err.message) || t('nextcloud-vue', 'Something went wrong.')\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-setup-step {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n\n.cn-setup-step__intro {\n\tmargin: 0;\n\tfont-size: 15px;\n\tline-height: 1.5;\n}\n\n.cn-setup-step__note {\n\tmargin: 0;\n\tfont-size: 13px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-setup-step__nav {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\tgap: 8px;\n\tmargin-top: 8px;\n}\n\n.cn-setup-step__running {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tgap: 8px;\n\tpadding: 24px 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-setup-load-result {\n\tmargin: 0;\n\tfont-size: var(--font-size-small, 13px);\n}\n\n.cn-setup-load-result--success {\n\tcolor: var(--color-success-text, var(--color-success));\n}\n\n.cn-setup-load-result--error {\n\tcolor: var(--color-error-text, var(--color-error));\n}\n\n.cn-setup-field {\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-setup-summary {\n\tlist-style: none;\n\tpadding: 0;\n\tmargin: 0;\n}\n\n.cn-setup-summary__item {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tpadding: 4px 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-setup-summary__item--done {\n\tcolor: var(--color-main-text);\n}\n\n.cn-setup-summary__mark {\n\tfont-weight: bold;\n\tcolor: var(--color-success);\n}\n\n.cn-setup-summary__label {\n\tflex: 0 0 auto;\n}\n\n.cn-setup-summary__value {\n\tmargin-inline-start: auto;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n/* An on-demand step that did not run says so quietly: it is a choice the\n user did not make, not a failure. */\n.cn-setup-summary__item--not-run .cn-setup-summary__value {\n\tfont-weight: normal;\n\tcolor: var(--color-text-maxcontrast);\n}\n</style>\n"],"names":["_createBlock","_createVNode","_renderList","_withCtx","_createElementBlock","_renderSlot","_mergeProps","_createTextVNode","_toDisplayString","_Fragment","_openBlock","_normalizeClass","_createElementVNode"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnTabs.vue2.js","sources":["../../../../src/components/CnTabs/CnTabs.vue"],"sourcesContent":["<template>\n\t<div class=\"cn-tabs\" :class=\"[{ 'cn-tabs--card': card, 'cn-tabs--segmented': variant === 'segmented', 'cn-tabs--underline': variant === 'underline', 'cn-tabs--board': boardStrip }, boardClass]\">\n\t\t<div class=\"cn-tabs__bar\">\n\t\t\t<div\n\t\t\t\tclass=\"cn-tabs__strip\"\n\t\t\t\t:class=\"{ 'cn-tabs__strip--more-start': moreStart, 'cn-tabs__strip--more-end': moreEnd }\">\n\t\t\t\t<div\n\t\t\t\t\tref=\"nav\"\n\t\t\t\t\tclass=\"cn-tabs__nav\"\n\t\t\t\t\t:class=\"{ 'cn-tabs__nav--justified': justified }\"\n\t\t\t\t\trole=\"tablist\"\n\t\t\t\t\t:aria-label=\"ariaLabel || null\"\n\t\t\t\t\t@keydown=\"onNavKeydown\"\n\t\t\t\t\t@wheel=\"onNavWheel\"\n\t\t\t\t\t@scroll=\"measureOverflow\">\n\t\t\t\t\t<button\n\t\t\t\t\t\tv-for=\"tab in visibleTabs\"\n\t\t\t\t\t\t:id=\"tab.tabId\"\n\t\t\t\t\t\t:key=\"tab.uid\"\n\t\t\t\t\t\tref=\"navButtons\"\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\trole=\"tab\"\n\t\t\t\t\t\tclass=\"cn-tabs__nav-item\"\n\t\t\t\t\t\t:class=\"{ 'cn-tabs__nav-item--active': isActive(tab.uid) }\"\n\t\t\t\t\t\t:aria-selected=\"isActive(tab.uid) ? 'true' : 'false'\"\n\t\t\t\t\t\t:aria-controls=\"tab.panelId\"\n\t\t\t\t\t\t:tabindex=\"isActive(tab.uid) ? 0 : -1\"\n\t\t\t\t\t\t:disabled=\"tab.disabled || null\"\n\t\t\t\t\t\t@click=\"tab.onActivate()\">\n\t\t\t\t\t\t<!-- The space before the count is load-bearing: it keeps\n\t\t\t\t\t\t the tab's accessible name \"Documents 5\" rather than\n\t\t\t\t\t\t \"Documents5\". The margin separates the two for the eye\n\t\t\t\t\t\t only, so keep both tags on one line. -->\n\t\t\t\t\t\t<component :is=\"tab.titleRender\" /> <span\n\t\t\t\t\t\t\tv-if=\"hasCount(tab)\"\n\t\t\t\t\t\t\tclass=\"cn-tabs__count\"\n\t\t\t\t\t\t\tdata-testid=\"cn-tabs-count\">{{ tab.count }}</span>\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t\t<!-- Mouse-only affordances: keyboard users already have the arrow\n\t\t\t\t keys, and the tabs themselves stay in the accessibility tree. -->\n\t\t\t\t<button\n\t\t\t\t\tv-if=\"moreStart\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-tabs__scroll cn-tabs__scroll--start\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\t@click=\"scrollStrip(-1)\">\n\t\t\t\t\t<ChevronLeft :size=\"20\" />\n\t\t\t\t</button>\n\t\t\t\t<button\n\t\t\t\t\tv-if=\"moreEnd\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-tabs__scroll cn-tabs__scroll--end\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\t@click=\"scrollStrip(1)\">\n\t\t\t\t\t<ChevronRight :size=\"20\" />\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<!-- The tabs that did not make the strip. OUTSIDE the tablist, like\n\t\t\t `#nav-end`: a menu button nested in it would be announced as\n\t\t\t one more tab. A tab picked here joins the strip while it is\n\t\t\t selected, so the open panel always has a real tab to be\n\t\t\t labelled by. -->\n\t\t\t<div v-if=\"overflowTabs.length > 0\" class=\"cn-tabs__more\">\n\t\t\t\t<NcActions\n\t\t\t\t\t:forceMenu=\"true\"\n\t\t\t\t\t:forceName=\"true\"\n\t\t\t\t\t:menuName=\"moreLabel\"\n\t\t\t\t\t:aria-label=\"moreAriaLabel\"\n\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\tdata-testid=\"cn-tabs-more\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<ChevronDown :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\tv-for=\"tab in overflowTabs\"\n\t\t\t\t\t\t:key=\"tab.uid\"\n\t\t\t\t\t\t:disabled=\"tab.disabled || null\"\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\tdata-testid=\"cn-tabs-more-item\"\n\t\t\t\t\t\t@click=\"onOverflowPick(tab)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<span class=\"cn-tabs__more-spacer\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ overflowLabel(tab) }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</NcActions>\n\t\t\t</div>\n\t\t\t<div v-if=\"$slots['nav-end']\" class=\"cn-tabs__nav-end\">\n\t\t\t\t<!-- @slot nav-end Rendered at the right-hand end of the tab bar, deliberately OUTSIDE the `role=\"tablist\"` element. A widget Actions menu belongs beside the strip, not inside it: anything nested in the tablist is announced as one of the tabs, so a screen-reader user counting six tabs would hear seven. -->\n\t\t\t\t<!-- @binding {number} active-index Index of the currently selected tab. -->\n\t\t\t\t<slot name=\"nav-end\" :activeIndex=\"activeIndex\" />\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"cn-tabs__content\" :class=\"contentClass\">\n\t\t\t<!-- @slot The CnTab children. Anything else is rendered into the panel area untouched. -->\n\t\t\t<slot />\n\t\t</div>\n\t</div>\n</template>\n\n<script>\n/**\n * CnTabs — a generic tab strip.\n *\n * Neither `@nextcloud/vue@9` nor this library previously shipped one: the only\n * tab component in either is `NcAppSidebarTab`, which is meaningless outside an\n * `NcAppSidebar`. Apps that needed a plain tab strip in a page, a dialog or a\n * detail view were on `bootstrap-vue@2` — a Vue-2-only package with no Vue 3\n * release (`bootstrap-vue-next` is a different package with a different API) —\n * and pulling a second UI framework in alongside `@nextcloud/vue` cuts against\n * the fleet rule that apps render with Nextcloud components. So zaakafhandelapp\n * wrote a local `Tabs`/`Tab` pair for eight views and opencatalogi and\n * openregister flagged the same gap. This is that pair, lifted and hardened.\n *\n * ```vue\n * <CnTabs aria-label=\"Case details\" justified>\n * <CnTab title=\"Documents\">…</CnTab>\n * <CnTab title=\"Tasks\" :active=\"showTasks\" @click=\"onTasks\">…</CnTab>\n * <CnTab>\n * <template #title><CnIcon name=\"close\" /> Draft</template>\n * …\n * </CnTab>\n * </CnTabs>\n * ```\n *\n * ## Putting controls beside the strip\n *\n * `#nav-end` fills the right-hand end of the tab bar. `CnTabsWidget` uses it to\n * carry the active child's Actions menu, so one menu serves every tab instead\n * of each panel drawing its own header:\n *\n * ```vue\n * <CnTabs aria-label=\"Case details\">\n * <template #nav-end><CnActionsMenu :title=\"activeTitle\" /></template>\n * <CnTab title=\"Notes\">…</CnTab>\n * </CnTabs>\n * ```\n *\n * The slot renders OUTSIDE the `role=\"tablist\"` element on purpose. See the\n * comment on it in the template.\n *\n * ## Migrating from `bootstrap-vue`\n *\n * `<BTabs>`/`<BTab>` map across directly for the subset apps actually used:\n * `content-class`, `justified` and `card` on the strip; `title`, the `#title`\n * slot, `active` and `@click` on a tab. Anything else (`lazy`, `no-fade`,\n * `pills`, `<b-tab disabled>`'s tooltip behaviour) is not reimplemented.\n *\n * ## How registration works\n *\n * Children register themselves on mount and hand back a *render function* for\n * their title rather than a plain string. That is what lets a child's `#title`\n * slot be rendered inside the parent's nav strip, and it keeps a computed title\n * reactive: the parent invokes it inside its own render effect, so a title\n * change re-renders the nav.\n *\n * Mount order is document order for both static children and `v-for`-generated\n * ones, so the nav strip matches the source. A tab that unmounts (a closable\n * tab) hands the selection to its neighbour.\n *\n * ## Accessibility\n *\n * Implements the WAI-ARIA tabs pattern: `role=\"tablist\"` / `role=\"tab\"` /\n * `role=\"tabpanel\"`, `aria-selected`, `aria-controls` ↔ `aria-labelledby`\n * wiring, a roving `tabindex` (only the selected tab is in the tab order), and\n * Left/Right/Home/End keyboard navigation within the strip. Pass `aria-label`\n * (or `ariaLabel`) so screen-reader users hear what the strip is for.\n *\n * ## Counts and the \"More\" menu\n *\n * A `CnTab` with `count` shows that number after its title. A `CnTab` with\n * `overflow` is listed under a \"More\" menu beside the strip instead of in it:\n *\n * ```vue\n * <CnTabs aria-label=\"Case details\">\n * <CnTab title=\"Documents\" :count=\"5\">…</CnTab>\n * <CnTab title=\"Archiving\" overflow>…</CnTab>\n * </CnTabs>\n * ```\n *\n * Picking a tab from the menu selects it and shows it in the strip for as long\n * as it is selected. With no overflow tab the menu does not render, and the\n * strip is exactly what it was before these props existed.\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActions } from '@nextcloud/vue'\nimport { computed, defineComponent, nextTick, onBeforeUnmount, onMounted, onUpdated, provide, reactive, ref, watch } from 'vue'\nimport ChevronDown from 'vue-material-design-icons/ChevronDown.vue'\nimport ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'\nimport ChevronRight from 'vue-material-design-icons/ChevronRight.vue'\nimport { useLook } from '../../composables/useLook.js'\nimport { CN_TABS_INJECTION_KEY } from './tabsKey.js'\n\nexport default defineComponent({\n\tname: 'CnTabs',\n\n\tcomponents: {\n\t\tChevronDown,\n\t\tChevronLeft,\n\t\tChevronRight,\n\t\tNcActionButton,\n\t\tNcActions,\n\t},\n\n\tprops: {\n\t\t/** Extra class applied to the panel container (bootstrap-vue's `content-class`). */\n\t\tcontentClass: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Stretch the nav items to fill the strip (bootstrap-vue's `justified`). */\n\t\tjustified: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Card-style chrome (border + padding) around the panel area. */\n\t\tcard: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * How the strip is drawn: `line` (the default: folder tabs joined to\n\t\t * the panel below), `segmented` (a compact pill switch, for a view\n\t\t * switch such as \"My work / My team\") or `underline` (plain labels on\n\t\t * a rule, the open one underlined; board LpBeheerAlgemeen). An\n\t\t * `underline` strip wraps instead of scrolling. Roles and keyboard\n\t\t * behaviour are the same in all three.\n\t\t *\n\t\t * @type {'line'|'segmented'|'underline'}\n\t\t * @spec openspec/changes/admin-settings-underline-tabs/specs/settings-board-look/spec.md#requirement-the-admin-shell-can-draw-an-underline-tab-strip\n\t\t */\n\t\tvariant: {\n\t\t\ttype: String,\n\t\t\tdefault: 'line',\n\t\t\tvalidator: (v) => ['line', 'segmented', 'underline'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Accessible name for the tab strip, applied to the `role=\"tablist\"`\n\t\t * element. Screen readers announce it when focus enters the strip.\n\t\t */\n\t\tariaLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * The look the strip is drawn in (`nextcloud` or `board`). Empty takes\n\t\t * the look the app provides. In the board look a `line` strip draws the\n\t\t * screens' folder tabs and never moves a tab under \"More\": the strip\n\t\t * scrolls instead.\n\t\t *\n\t\t * @spec openspec/changes/screens-detail-page-parity/specs/detail-page-board-look/spec.md#requirement-folder-tabs-take-the-board-strip\n\t\t */\n\t\tlook: { // eslint-disable-line vue/no-unused-properties -- read through useLook(props) in setup\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Leave the `overflow` tabs to the host's `#nav-end` menu instead of a\n\t\t * \"More\" menu of the strip's own. The strip then draws only the tabs\n\t\t * that are not `overflow`, and an overflow tab that is selected joins\n\t\t * it just before the last of them (board DqZaakBesluiten: Overview,\n\t\t * Files, Contact, Tasks, Decisions, History). The host lists the\n\t\t * overflow tabs and selects one through its `CnTab`'s `active`.\n\t\t *\n\t\t * @spec openspec/changes/board-tabs-overflow-in-actions/specs/board-tabs-overflow-in-actions/spec.md#requirement-board-tabs-list-their-overflow-in-the-actions-menu\n\t\t */\n\t\toverflowInNavEnd: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Name of the menu that lists the `overflow` tabs. */\n\t\tmoreLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'More'),\n\t\t},\n\t},\n\n\temits: [\n\t\t/** The selected tab changed. Payload: the new active tab's index. */\n\t\t'update:activeIndex',\n\t],\n\n\tsetup(props, { emit }) {\n\t\tconst { isBoard, lookClass: boardClass } = useLook(props)\n\t\t// The board strip: a `line` strip in the board look.\n\t\tconst boardStrip = computed(() => isBoard.value && props.variant === 'line')\n\t\t// Registered children, in mount order.\n\t\tconst tabs = reactive([])\n\t\tconst activeUid = ref(null)\n\t\tconst navButtons = ref([])\n\t\tconst nav = ref(null)\n\t\t// Whether the selection was chosen (a tab declared `active`, or the\n\t\t// user picked one) rather than defaulted to the first tab registered.\n\t\tlet selectionChosen = false\n\n\t\t/**\n\t\t * Register a child tab. The first child to register wins the initial\n\t\t * selection unless a later one declares itself `active`. Disabled tabs\n\t\t * never take the initial selection.\n\t\t *\n\t\t * @param {object} tab Child descriptor.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction register(tab) {\n\t\t\ttabs.push(tab)\n\t\t\tif (tab.disabled) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (tab.active) {\n\t\t\t\tactiveUid.value = tab.uid\n\t\t\t\tselectionChosen = true\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (activeUid.value === null) {\n\t\t\t\tactiveUid.value = tab.uid\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// A tab under \"More\" only held the default selection because it\n\t\t\t// registered first. The first tab that is actually in the strip\n\t\t\t// takes it over, unless somebody chose.\n\t\t\tif (!selectionChosen && !tab.overflow) {\n\t\t\t\tconst current = tabs.find((candidate) => candidate.uid === activeUid.value)\n\t\t\t\tif (current && current.overflow) {\n\t\t\t\t\tactiveUid.value = tab.uid\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Drop a child that is unmounting, moving the selection to its nearest\n\t\t * remaining neighbour when it was the active one. Closable tabs rely on\n\t\t * this; without it the strip would render with nothing selected.\n\t\t *\n\t\t * @param {number} uid The child's instance uid.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction unregister(uid) {\n\t\t\tconst index = tabs.findIndex((tab) => tab.uid === uid)\n\t\t\tif (index !== -1) {\n\t\t\t\ttabs.splice(index, 1)\n\t\t\t}\n\t\t\tif (activeUid.value === uid) {\n\t\t\t\tconst neighbour = tabs[index] || tabs[index - 1] || tabs[0] || null\n\t\t\t\tactiveUid.value = neighbour ? neighbour.uid : null\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Make a child the visible one.\n\t\t *\n\t\t * @param {number} uid The child's instance uid.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction select(uid) {\n\t\t\tselectionChosen = true\n\t\t\tif (activeUid.value === uid) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tactiveUid.value = uid\n\t\t\temit('update:activeIndex', tabs.findIndex((tab) => tab.uid === uid))\n\t\t}\n\n\t\t/**\n\t\t * Whether a child is the visible one.\n\t\t *\n\t\t * @param {number} uid The child's instance uid.\n\t\t *\n\t\t * @return {boolean} True when this tab's panel should render.\n\t\t */\n\t\tfunction isActive(uid) {\n\t\t\treturn activeUid.value === uid\n\t\t}\n\n\t\tconst activeIndex = computed(() => tabs.findIndex((tab) => tab.uid === activeUid.value))\n\n\t\t/**\n\t\t * The tabs drawn in the strip: every tab that is not `overflow`, plus\n\t\t * an overflow tab for as long as it is the selected one.\n\t\t *\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow\n\t\t */\n\t\tconst visibleTabs = computed(() => {\n\t\t\tif (props.overflowInNavEnd) {\n\t\t\t\treturn navEndStrip()\n\t\t\t}\n\t\t\treturn tabs.filter((tab) => boardStrip.value || !tab.overflow || tab.uid === activeUid.value)\n\t\t})\n\n\t\t/**\n\t\t * The strip under `overflowInNavEnd`: the tabs that are not\n\t\t * `overflow`, with a selected overflow tab placed before the last.\n\t\t *\n\t\t * @return {object[]} The tabs to draw, in order.\n\t\t * @spec openspec/changes/board-tabs-overflow-in-actions/specs/board-tabs-overflow-in-actions/spec.md#requirement-an-opened-overflow-tab-joins-the-strip-before-the-last-tab\n\t\t */\n\t\tfunction navEndStrip() {\n\t\t\tconst pinned = tabs.filter((tab) => !tab.overflow)\n\t\t\tconst opened = tabs.find((tab) => tab.overflow && tab.uid === activeUid.value)\n\t\t\tif (!opened) {\n\t\t\t\treturn pinned\n\t\t\t}\n\t\t\tconst at = Math.max(0, pinned.length - 1)\n\t\t\treturn [...pinned.slice(0, at), opened, ...pinned.slice(at)]\n\t\t}\n\n\t\t/**\n\t\t * The tabs listed under \"More\": overflow tabs that are not selected.\n\t\t *\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow\n\t\t */\n\t\tconst overflowTabs = computed(() => (boardStrip.value || props.overflowInNavEnd ? [] : tabs.filter((tab) => tab.overflow && tab.uid !== activeUid.value)))\n\n\t\t/**\n\t\t * Whether a tab declares a count. `0` is a count; null and '' are not.\n\t\t *\n\t\t * @param {object} tab Child descriptor.\n\t\t *\n\t\t * @return {boolean} True when the count renders.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow\n\t\t */\n\t\tfunction hasCount(tab) {\n\t\t\treturn tab.count !== null && tab.count !== undefined && tab.count !== ''\n\t\t}\n\n\t\t/**\n\t\t * A \"More\" entry's text: the tab's title, and its count when it has one.\n\t\t *\n\t\t * @param {object} tab Child descriptor.\n\t\t *\n\t\t * @return {string} The menu label.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow\n\t\t */\n\t\tfunction overflowLabel(tab) {\n\t\t\treturn hasCount(tab) ? `${tab.label} (${tab.count})` : tab.label\n\t\t}\n\n\t\t/**\n\t\t * Select a tab picked from \"More\" and move focus to it in the strip,\n\t\t * so a keyboard user lands on the tab they chose rather than on a menu\n\t\t * button that no longer lists it.\n\t\t *\n\t\t * @param {object} tab Child descriptor.\n\t\t *\n\t\t * @return {void}\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow\n\t\t */\n\t\tfunction onOverflowPick(tab) {\n\t\t\ttab.onActivate()\n\t\t\tnextTick(() => {\n\t\t\t\tconst button = nav.value?.querySelector(`#${tab.tabId}`)\n\t\t\t\tif (button && typeof button.focus === 'function') {\n\t\t\t\t\tbutton.focus()\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\n\t\tconst moreAriaLabel = computed(() => t('nextcloud-vue', 'More tabs'))\n\n\t\t/**\n\t\t * Move the selection and DOM focus to a tab by index, skipping disabled\n\t\t * ones. Focus has to move with the selection: in the WAI-ARIA tabs\n\t\t * pattern only the selected tab is in the tab order, so leaving focus on\n\t\t * a now-`tabindex=\"-1\"` button strands the keyboard user.\n\t\t *\n\t\t * @param {number} index Target index (clamped to the enabled tabs).\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction focusTab(index) {\n\t\t\t// The strip's own tabs only: the arrow keys walk what is drawn, and\n\t\t\t// the \"More\" menu has its own keyboard handling.\n\t\t\tconst enabled = visibleTabs.value.filter((tab) => !tab.disabled)\n\t\t\tif (enabled.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst wrapped = ((index % enabled.length) + enabled.length) % enabled.length\n\t\t\tconst target = enabled[wrapped]\n\t\t\tconst position = visibleTabs.value.indexOf(target)\n\t\t\tselect(target.uid)\n\t\t\tconst button = navButtons.value?.[position]\n\t\t\tif (button && typeof button.focus === 'function') {\n\t\t\t\tbutton.focus()\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Left/Right/Home/End navigation inside the strip.\n\t\t *\n\t\t * @param {KeyboardEvent} event The keydown event.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction onNavKeydown(event) {\n\t\t\tconst enabled = visibleTabs.value.filter((tab) => !tab.disabled)\n\t\t\tconst current = enabled.findIndex((tab) => tab.uid === activeUid.value)\n\t\t\tlet next = null\n\n\t\t\tif (event.key === 'ArrowRight') {\n\t\t\t\tnext = current + 1\n\t\t\t} else if (event.key === 'ArrowLeft') {\n\t\t\t\tnext = current - 1\n\t\t\t} else if (event.key === 'Home') {\n\t\t\t\tnext = 0\n\t\t\t} else if (event.key === 'End') {\n\t\t\t\tnext = enabled.length - 1\n\t\t\t}\n\n\t\t\tif (next === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tfocusTab(next)\n\t\t}\n\n\t\t/**\n\t\t * Scroll the strip so the selected tab is wholly in view.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction revealActiveTab() {\n\t\t\tconst strip = nav.value\n\t\t\tconst button = strip?.querySelector('[role=\"tab\"][aria-selected=\"true\"]')\n\t\t\tif (!strip || !button) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst stripRect = strip.getBoundingClientRect()\n\t\t\tconst rect = button.getBoundingClientRect()\n\t\t\tif (rect.left < stripRect.left) {\n\t\t\t\tstrip.scrollLeft -= stripRect.left - rect.left\n\t\t\t} else if (rect.right > stripRect.right) {\n\t\t\t\tstrip.scrollLeft += rect.right - stripRect.right\n\t\t\t}\n\t\t}\n\n\t\twatch(activeUid, revealActiveTab, { flush: 'post' })\n\n\t\t// Whether tabs are hidden past the start or end edge of the strip.\n\t\tconst moreStart = ref(false)\n\t\tconst moreEnd = ref(false)\n\n\t\t/**\n\t\t * Recompute which edges hide tabs; 1px of tolerance absorbs sub-pixel rounding.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction measureOverflow() {\n\t\t\tconst strip = nav.value\n\t\t\tif (!strip) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tmoreStart.value = strip.scrollLeft > 1\n\t\t\tmoreEnd.value = strip.scrollWidth - strip.clientWidth - strip.scrollLeft > 1\n\t\t}\n\n\t\tlet resizeObserver = null\n\n\t\tonMounted(() => {\n\t\t\tmeasureOverflow()\n\t\t\tif (typeof ResizeObserver === 'undefined' || !nav.value) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tresizeObserver = new ResizeObserver(measureOverflow)\n\t\t\tresizeObserver.observe(nav.value)\n\t\t})\n\n\t\t// A title change or a tab added can tip the strip into overflowing without resizing it.\n\t\tonUpdated(measureOverflow)\n\n\t\tonBeforeUnmount(() => {\n\t\t\tresizeObserver?.disconnect()\n\t\t\tresizeObserver = null\n\t\t})\n\n\t\t/**\n\t\t * Scroll the strip most of a viewport towards one edge.\n\t\t *\n\t\t * @param {number} direction -1 towards the start, 1 towards the end.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction scrollStrip(direction) {\n\t\t\tconst strip = nav.value\n\t\t\tif (!strip) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tstrip.scrollLeft += direction * Math.max(1, Math.round(strip.clientWidth * 0.6))\n\t\t}\n\n\t\t/**\n\t\t * Turn a vertical wheel into a horizontal scroll while the strip overflows.\n\t\t *\n\t\t * @param {WheelEvent} event The wheel event.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction onNavWheel(event) {\n\t\t\tconst strip = event.currentTarget\n\t\t\tif (strip.scrollWidth <= strip.clientWidth || event.deltaX !== 0 || event.deltaY === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tstrip.scrollLeft += event.deltaY\n\t\t}\n\n\t\tprovide(CN_TABS_INJECTION_KEY, { register, unregister, select, isActive })\n\n\t\treturn {\n\t\t\tboardStrip,\n\t\t\tboardClass,\n\t\t\ttabs,\n\t\t\tvisibleTabs,\n\t\t\toverflowTabs,\n\t\t\thasCount,\n\t\t\toverflowLabel,\n\t\t\tonOverflowPick,\n\t\t\tmoreAriaLabel,\n\t\t\tisActive,\n\t\t\tactiveIndex,\n\t\t\tnav,\n\t\t\tnavButtons,\n\t\t\tmoreStart,\n\t\t\tmoreEnd,\n\t\t\tmeasureOverflow,\n\t\t\tscrollStrip,\n\t\t\tonNavKeydown,\n\t\t\tonNavWheel,\n\t\t}\n\t},\n})\n</script>\n\n<style scoped>\n/* The bar owns the rule under the strip so a `#nav-end` surface sits ON the\n line rather than above a line that stops where the last tab does. With no\n nav-end content the nav is the bar's only child and the result is pixel\n identical to the rule living on the nav itself. */\n.cn-tabs__bar {\n\t/* Top-aligned so the `#nav-end` control sits on the tab row, not below it,\n\t should the two sides ever differ in height. */\n\talign-items: flex-start;\n\tborder-bottom: 1px solid var(--color-border);\n\tdisplay: flex;\n\tgap: 8px;\n}\n\n/* The strip holds the scrolling nav and the edge affordances drawn over it. */\n.cn-tabs__strip {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n\tposition: relative;\n}\n\n.cn-tabs__nav {\n\tdisplay: flex;\n\tgap: 4px;\n\t/* One row that scrolls, never a wrap: in a narrow sidebar a nine-tab strip\n\t wrapped into a stack taller than the panel under it. The scrollbar is\n\t hidden because it would sit between the tabs and the bar's rule and break\n\t the open tab's join with its panel; a fade and a chevron on the clipped\n\t edge say there is more, and the strip follows the wheel. */\n\tflex-wrap: nowrap;\n\toverflow-x: auto;\n\toverflow-y: hidden;\n\tscroll-behavior: smooth;\n\tscrollbar-width: none;\n\t/* Overhang the bar's 1px rule so the open tab's own bottom edge, drawn in\n\t the panel's colour, covers it and the tab joins its panel. */\n\tmargin-bottom: -1px;\n}\n\n.cn-tabs__nav::-webkit-scrollbar {\n\tdisplay: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n\t.cn-tabs__nav {\n\t\tscroll-behavior: auto;\n\t}\n}\n\n/* A fade over the clipped edge, only on the side that hides tabs. Stops a\n pixel above the bar's rule so the rule keeps its full colour.\n\n z-index 2 so the fades outrank a lifted tab: CnTabsWidget puts the open tab\n at 1, and a tie goes to DOM order, which would draw that tab over the start\n fade (it precedes the tabs) while the end fade still covered it. */\n.cn-tabs__strip::before,\n.cn-tabs__strip::after {\n\tcontent: '';\n\tdisplay: none;\n\tinset-block: 0 1px;\n\tpointer-events: none;\n\tposition: absolute;\n\twidth: 56px;\n\tz-index: 2;\n}\n\n.cn-tabs__strip::before {\n\tbackground: linear-gradient(to right, var(--color-main-background) 30%, transparent);\n\tinset-inline-start: 0;\n}\n\n.cn-tabs__strip::after {\n\tbackground: linear-gradient(to left, var(--color-main-background) 30%, transparent);\n\tinset-inline-end: 0;\n}\n\n.cn-tabs__strip--more-start::before,\n.cn-tabs__strip--more-end::after {\n\tdisplay: block;\n}\n\n/* Doubled selector for the same reason as `.cn-tabs__nav .cn-tabs__nav-item`:\n Nextcloud's own `button` rule would otherwise give this a border, a fill\n and a margin. */\n.cn-tabs__strip .cn-tabs__scroll {\n\talign-items: center;\n\tbackground: none;\n\tborder: 0;\n\tborder-radius: 0;\n\tcolor: var(--color-main-text);\n\tcursor: pointer;\n\tdisplay: flex;\n\t/* Pushed down a little so the chevron sits level with the tab labels. */\n\tinset-block: 2px 0;\n\tjustify-content: center;\n\tmargin: 0;\n\tmin-height: 0;\n\tpadding: 0;\n\tposition: absolute;\n\twidth: 28px;\n\tz-index: 3;\n}\n\n/* Nextcloud's `button:hover` rule would otherwise fill the chevron. */\n.cn-tabs__strip .cn-tabs__scroll:hover,\n.cn-tabs__strip .cn-tabs__scroll:focus {\n\tbackground: none;\n}\n\n.cn-tabs__scroll--start {\n\tinset-inline-start: 0;\n}\n\n.cn-tabs__scroll--end {\n\tinset-inline-end: 0;\n}\n\n/* The count behind a tab's title. Full-contrast text on the page background,\n so it reads on the idle tab's darker fill and on the open tab alike. */\n.cn-tabs__count {\n\tbackground-color: var(--color-main-background);\n\tborder: 1px solid var(--color-border-dark);\n\tborder-radius: var(--border-radius-pill, 100px);\n\tcolor: var(--color-main-text);\n\tdisplay: inline-block;\n\tfont-size: 0.85em;\n\tline-height: 1.4;\n\tmargin-inline-start: 6px;\n\tmin-width: 1.4em;\n\tpadding: 0 6px;\n\ttext-align: center;\n}\n\n.cn-tabs__more {\n\talign-items: center;\n\tdisplay: flex;\n\tflex: 0 0 auto;\n}\n\n/* Keeps a \"More\" entry's text on the menu's text column without drawing an icon. */\n.cn-tabs__more-spacer {\n\tdisplay: inline-block;\n\twidth: 20px;\n}\n\n.cn-tabs__nav-end {\n\talign-items: center;\n\tdisplay: flex;\n\tflex: 0 0 auto;\n\tgap: 4px;\n\t/* No min-height. This used to hard-code 38px to mirror the old nav item's\n\t vertical box, but the folder-tab restyle changed that box to 33px, and\n\t because the bar is `align-items: flex-start` the taller nav-end then drove\n\t the BAR's height. The bar's bottom rule sank 6px below the tab row, so the\n\t open tab no longer met the panel it is drawn as joined to. Letting the\n\t control size to its own content keeps the rule on the tab row whatever the\n\t nav item's box becomes, so the two cannot drift apart again. */\n\tmin-height: 0;\n}\n\n/* Folder tabs, not underlined labels. An underline is the weakest possible\n affordance on a strip that already carries icons: with nine tabs on a dossiq\n case the row read as a sentence of links, and only the 2px rule said which\n one was open. Giving the inactive tabs their own darker surface makes the\n strip legible as a control at a glance, and the active tab, sharing the\n panel's background and punching a hole in the bar's rule, reads as the sheet\n in front. */\n.cn-tabs__nav-item {\n\tbackground-color: var(--color-background-dark);\n\t/* `--color-border-dark`, not `--color-border`: the latter is the same\n\t #ededed as `--color-background-dark` in the stock light theme, so a tab\n\t drawn with it has no visible edge against its own fill. */\n\tborder: 1px solid var(--color-border-dark);\n\tborder-bottom: none;\n\tborder-radius: var(--border-radius-large, 8px) var(--border-radius-large, 8px) 0 0;\n\t/* Full-contrast label, not `--color-text-maxcontrast`. On the old\n\t transparent strip maxcontrast sat on white at 5.3:1; on this darker\n\t surface the same pair measures 4.55:1, which clears WCAG AA by 0.05 and\n\t would not survive a themed instance shifting either token. A real tab\n\t strip carries its selection in the surface and the weight anyway, so\n\t nothing is lost by giving every label the readable colour. */\n\tcolor: var(--color-main-text);\n\tcursor: pointer;\n\tfont-weight: normal;\n\tpadding: 8px 12px;\n\twhite-space: nowrap;\n\t/* Match the `#nav-end` control's own height. The bar is a flex row, so\n\t whichever side is taller sets the bar's height and therefore where its\n\t bottom rule lands. When the control was the taller one the rule sat below\n\t the tab row and showed as a line under the open tab, breaking the join to\n\t the panel. Sizing both sides the same is what keeps the rule ON the tab\n\t row; `inline-flex` centres the label inside the taller box. */\n\talign-items: center;\n\tbox-sizing: border-box;\n\tdisplay: inline-flex;\n\tmin-height: var(--default-clickable-area, 34px);\n}\n\n/* Nextcloud's server stylesheet gives every plain `button` a 3px bottom margin\n with a selector that outscores the scoped rule above, which floated the tabs\n above the bar's rule inside a real page. The parent class wins on\n specificity rather than on `!important` or source order. */\n.cn-tabs__nav .cn-tabs__nav-item {\n\tmargin-block-end: 0;\n}\n\n.cn-tabs__nav--justified .cn-tabs__nav-item {\n\tflex: 1 1 0;\n}\n\n.cn-tabs__nav-item:hover,\n.cn-tabs__nav-item:focus-visible {\n\tbackground-color: var(--color-background-hover);\n\tcolor: var(--color-main-text);\n}\n\n/* Inside the box: the scrolling strip clips anything drawn outside a tab. */\n.cn-tabs__nav-item:focus-visible {\n\toutline-offset: -2px;\n}\n\n.cn-tabs__nav-item[disabled] {\n\tcursor: default;\n\topacity: 0.5;\n}\n\n.cn-tabs__nav-item[disabled]:hover {\n\tbackground-color: var(--color-background-dark);\n\tcolor: var(--color-main-text);\n}\n\n/* The open tab: the panel's own background, and a bottom border painted in\n that same background so it erases the bar's rule underneath and the tab\n joins the sheet below it. The primary-coloured top edge is what carries the\n selection at a glance, and it survives forced-colours mode, which drops\n background colours but keeps borders. */\n.cn-tabs__nav-item--active {\n\tbackground-color: var(--color-main-background);\n\tborder-bottom: 1px solid var(--color-main-background);\n\tborder-top: 2px solid var(--color-primary-element);\n\tcolor: var(--color-main-text);\n\tfont-weight: bold;\n\t/* Keep the text baseline identical to an inactive tab despite the 1px\n\t thicker top border, so the strip does not jog when the selection moves. */\n\tpadding-top: 7px;\n}\n\n.cn-tabs__content {\n\tpadding-top: 12px;\n}\n\n.cn-tabs--card .cn-tabs__content {\n\tborder: 1px solid var(--color-border);\n\tborder-top: none;\n\tpadding: 12px;\n}\n\n/* Segmented variant: the strip is a pill switch that stands on its own, so\n the bar's rule and the tab-joins-panel treatment are both dropped. */\n.cn-tabs--segmented .cn-tabs__bar {\n\tborder-bottom: none;\n}\n\n.cn-tabs--segmented .cn-tabs__strip {\n\tflex: 0 1 auto;\n}\n\n.cn-tabs--segmented .cn-tabs__nav {\n\tgap: 4px;\n\tmargin-bottom: 0;\n\tpadding: 4px;\n\tborder-radius: var(--border-radius-container, var(--border-radius-large, 10px));\n\tbackground-color: var(--color-background-dark);\n}\n\n.cn-tabs--segmented .cn-tabs__nav-item {\n\tborder: 1px solid transparent;\n\tborder-radius: var(--border-radius-element, var(--border-radius-large, 8px));\n\tbackground-color: transparent;\n\tfont-weight: 600;\n\tpadding: 6px 16px;\n}\n\n.cn-tabs--segmented .cn-tabs__nav-item:hover {\n\tbackground-color: var(--color-background-hover);\n}\n\n/* The open segment is the raised one. Its border survives forced-colours\n mode, which drops the background and the shadow. */\n.cn-tabs--segmented .cn-tabs__nav-item--active {\n\tborder: 1px solid var(--color-border-dark);\n\tbackground-color: var(--color-main-background);\n\tbox-shadow: 0 1px 2px var(--color-box-shadow);\n\tfont-weight: 600;\n\tpadding-top: 6px;\n}\n\n/* Underline variant (board LpBeheerAlgemeen): plain labels on the bar's rule,\n the open one carried by a 3px primary underline that sits on that rule. The\n strip wraps onto a second row rather than scrolling. */\n.cn-tabs--underline .cn-tabs__nav {\n\tflex-wrap: wrap;\n\toverflow: visible;\n}\n\n.cn-tabs--underline .cn-tabs__nav-item {\n\tbackground-color: transparent;\n\tborder: 0;\n\tborder-bottom: 3px solid transparent;\n\tborder-radius: 0;\n\tcolor: var(--cn-board-text-soft, var(--color-main-text));\n\tfont-size: 14px;\n\tfont-weight: 500;\n\tgap: 8px;\n\tmin-height: 44px;\n\tpadding: 0 9px;\n}\n\n.cn-tabs--underline .cn-tabs__nav-item:hover,\n.cn-tabs--underline .cn-tabs__nav-item:focus-visible {\n\tbackground-color: transparent;\n\tborder-bottom-color: var(--color-border-dark);\n\tcolor: var(--color-main-text);\n}\n\n.cn-tabs--underline .cn-tabs__nav-item[disabled]:hover {\n\tbackground-color: transparent;\n\tborder-bottom-color: transparent;\n}\n\n.cn-tabs--underline .cn-tabs__nav-item--active,\n.cn-tabs--underline .cn-tabs__nav-item--active:hover,\n.cn-tabs--underline .cn-tabs__nav-item--active:focus-visible {\n\tborder-bottom: 3px solid var(--color-primary-element);\n\tcolor: var(--color-main-text);\n\tfont-weight: 700;\n\tpadding-top: 0;\n}\n\n.cn-tabs--underline .cn-tabs__count {\n\tmargin-inline-start: 0;\n}\n\n.cn-tabs--underline .cn-tabs__content {\n\tpadding-top: 20px;\n}\n</style>\n"],"names":["t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnTabs.vue2.js","sources":["../../../../src/components/CnTabs/CnTabs.vue"],"sourcesContent":["<template>\n\t<div class=\"cn-tabs\" :class=\"[{ 'cn-tabs--card': card, 'cn-tabs--segmented': variant === 'segmented', 'cn-tabs--underline': variant === 'underline', 'cn-tabs--board': boardStrip }, boardClass]\">\n\t\t<div class=\"cn-tabs__bar\">\n\t\t\t<div\n\t\t\t\tclass=\"cn-tabs__strip\"\n\t\t\t\t:class=\"{ 'cn-tabs__strip--more-start': moreStart, 'cn-tabs__strip--more-end': moreEnd }\">\n\t\t\t\t<div\n\t\t\t\t\tref=\"nav\"\n\t\t\t\t\tclass=\"cn-tabs__nav\"\n\t\t\t\t\t:class=\"{ 'cn-tabs__nav--justified': justified }\"\n\t\t\t\t\trole=\"tablist\"\n\t\t\t\t\t:aria-label=\"ariaLabel || null\"\n\t\t\t\t\t@keydown=\"onNavKeydown\"\n\t\t\t\t\t@wheel=\"onNavWheel\"\n\t\t\t\t\t@scroll=\"measureOverflow\">\n\t\t\t\t\t<button\n\t\t\t\t\t\tv-for=\"tab in visibleTabs\"\n\t\t\t\t\t\t:id=\"tab.tabId\"\n\t\t\t\t\t\t:key=\"tab.uid\"\n\t\t\t\t\t\tref=\"navButtons\"\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\trole=\"tab\"\n\t\t\t\t\t\tclass=\"cn-tabs__nav-item\"\n\t\t\t\t\t\t:class=\"{ 'cn-tabs__nav-item--active': isActive(tab.uid) }\"\n\t\t\t\t\t\t:aria-selected=\"isActive(tab.uid) ? 'true' : 'false'\"\n\t\t\t\t\t\t:aria-controls=\"tab.panelId\"\n\t\t\t\t\t\t:tabindex=\"isActive(tab.uid) ? 0 : -1\"\n\t\t\t\t\t\t:disabled=\"tab.disabled || null\"\n\t\t\t\t\t\t@click=\"tab.onActivate()\">\n\t\t\t\t\t\t<!-- The space before the count is load-bearing: it keeps\n\t\t\t\t\t\t the tab's accessible name \"Documents 5\" rather than\n\t\t\t\t\t\t \"Documents5\". The margin separates the two for the eye\n\t\t\t\t\t\t only, so keep both tags on one line. -->\n\t\t\t\t\t\t<component :is=\"tab.titleRender\" /> <span\n\t\t\t\t\t\t\tv-if=\"hasCount(tab)\"\n\t\t\t\t\t\t\tclass=\"cn-tabs__count\"\n\t\t\t\t\t\t\tdata-testid=\"cn-tabs-count\">{{ tab.count }}</span>\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t\t<!-- Mouse-only affordances: keyboard users already have the arrow\n\t\t\t\t keys, and the tabs themselves stay in the accessibility tree. -->\n\t\t\t\t<button\n\t\t\t\t\tv-if=\"moreStart\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-tabs__scroll cn-tabs__scroll--start\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\t@click=\"scrollStrip(-1)\">\n\t\t\t\t\t<ChevronLeft :size=\"20\" />\n\t\t\t\t</button>\n\t\t\t\t<button\n\t\t\t\t\tv-if=\"moreEnd\"\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"cn-tabs__scroll cn-tabs__scroll--end\"\n\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\ttabindex=\"-1\"\n\t\t\t\t\t@click=\"scrollStrip(1)\">\n\t\t\t\t\t<ChevronRight :size=\"20\" />\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t<!-- The tabs that did not make the strip. OUTSIDE the tablist, like\n\t\t\t `#nav-end`: a menu button nested in it would be announced as\n\t\t\t one more tab. A tab picked here joins the strip while it is\n\t\t\t selected, so the open panel always has a real tab to be\n\t\t\t labelled by. -->\n\t\t\t<div v-if=\"overflowTabs.length > 0\" class=\"cn-tabs__more\">\n\t\t\t\t<NcActions\n\t\t\t\t\t:forceMenu=\"true\"\n\t\t\t\t\t:forceName=\"true\"\n\t\t\t\t\t:menuName=\"moreLabel\"\n\t\t\t\t\t:aria-label=\"moreAriaLabel\"\n\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\tdata-testid=\"cn-tabs-more\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<ChevronDown :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t<NcActionButton\n\t\t\t\t\t\tv-for=\"tab in overflowTabs\"\n\t\t\t\t\t\t:key=\"tab.uid\"\n\t\t\t\t\t\t:disabled=\"tab.disabled || null\"\n\t\t\t\t\t\t:closeAfterClick=\"true\"\n\t\t\t\t\t\tdata-testid=\"cn-tabs-more-item\"\n\t\t\t\t\t\t@click=\"onOverflowPick(tab)\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<span class=\"cn-tabs__more-spacer\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ overflowLabel(tab) }}\n\t\t\t\t\t</NcActionButton>\n\t\t\t\t</NcActions>\n\t\t\t</div>\n\t\t\t<div v-if=\"$slots['nav-end']\" class=\"cn-tabs__nav-end\">\n\t\t\t\t<!-- @slot nav-end Rendered at the right-hand end of the tab bar, deliberately OUTSIDE the `role=\"tablist\"` element. A widget Actions menu belongs beside the strip, not inside it: anything nested in the tablist is announced as one of the tabs, so a screen-reader user counting six tabs would hear seven. -->\n\t\t\t\t<!-- @binding {number} active-index Index of the currently selected tab. -->\n\t\t\t\t<slot name=\"nav-end\" :activeIndex=\"activeIndex\" />\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"cn-tabs__content\" :class=\"contentClass\">\n\t\t\t<!-- @slot The CnTab children. Anything else is rendered into the panel area untouched. -->\n\t\t\t<slot />\n\t\t</div>\n\t</div>\n</template>\n\n<script>\n/**\n * CnTabs — a generic tab strip.\n *\n * Neither `@nextcloud/vue@9` nor this library previously shipped one: the only\n * tab component in either is `NcAppSidebarTab`, which is meaningless outside an\n * `NcAppSidebar`. Apps that needed a plain tab strip in a page, a dialog or a\n * detail view were on `bootstrap-vue@2` — a Vue-2-only package with no Vue 3\n * release (`bootstrap-vue-next` is a different package with a different API) —\n * and pulling a second UI framework in alongside `@nextcloud/vue` cuts against\n * the fleet rule that apps render with Nextcloud components. So zaakafhandelapp\n * wrote a local `Tabs`/`Tab` pair for eight views and opencatalogi and\n * openregister flagged the same gap. This is that pair, lifted and hardened.\n *\n * ```vue\n * <CnTabs aria-label=\"Case details\" justified>\n * <CnTab title=\"Documents\">…</CnTab>\n * <CnTab title=\"Tasks\" :active=\"showTasks\" @click=\"onTasks\">…</CnTab>\n * <CnTab>\n * <template #title><CnIcon name=\"close\" /> Draft</template>\n * …\n * </CnTab>\n * </CnTabs>\n * ```\n *\n * ## Putting controls beside the strip\n *\n * `#nav-end` fills the right-hand end of the tab bar. `CnTabsWidget` uses it to\n * carry the active child's Actions menu, so one menu serves every tab instead\n * of each panel drawing its own header:\n *\n * ```vue\n * <CnTabs aria-label=\"Case details\">\n * <template #nav-end><CnActionsMenu :title=\"activeTitle\" /></template>\n * <CnTab title=\"Notes\">…</CnTab>\n * </CnTabs>\n * ```\n *\n * The slot renders OUTSIDE the `role=\"tablist\"` element on purpose. See the\n * comment on it in the template.\n *\n * ## Migrating from `bootstrap-vue`\n *\n * `<BTabs>`/`<BTab>` map across directly for the subset apps actually used:\n * `content-class`, `justified` and `card` on the strip; `title`, the `#title`\n * slot, `active` and `@click` on a tab. Anything else (`lazy`, `no-fade`,\n * `pills`, `<b-tab disabled>`'s tooltip behaviour) is not reimplemented.\n *\n * ## How registration works\n *\n * Children register themselves on mount and hand back a *render function* for\n * their title rather than a plain string. That is what lets a child's `#title`\n * slot be rendered inside the parent's nav strip, and it keeps a computed title\n * reactive: the parent invokes it inside its own render effect, so a title\n * change re-renders the nav.\n *\n * Mount order is document order for both static children and `v-for`-generated\n * ones, so the nav strip matches the source. A tab that unmounts (a closable\n * tab) hands the selection to its neighbour.\n *\n * ## Accessibility\n *\n * Implements the WAI-ARIA tabs pattern: `role=\"tablist\"` / `role=\"tab\"` /\n * `role=\"tabpanel\"`, `aria-selected`, `aria-controls` ↔ `aria-labelledby`\n * wiring, a roving `tabindex` (only the selected tab is in the tab order), and\n * Left/Right/Home/End keyboard navigation within the strip. Pass `aria-label`\n * (or `ariaLabel`) so screen-reader users hear what the strip is for.\n *\n * ## Counts and the \"More\" menu\n *\n * A `CnTab` with `count` shows that number after its title. A `CnTab` with\n * `overflow` is listed under a \"More\" menu beside the strip instead of in it:\n *\n * ```vue\n * <CnTabs aria-label=\"Case details\">\n * <CnTab title=\"Documents\" :count=\"5\">…</CnTab>\n * <CnTab title=\"Archiving\" overflow>…</CnTab>\n * </CnTabs>\n * ```\n *\n * Picking a tab from the menu selects it and shows it in the strip for as long\n * as it is selected. With no overflow tab the menu does not render, and the\n * strip is exactly what it was before these props existed.\n */\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActions } from '@nextcloud/vue'\nimport { computed, defineComponent, nextTick, onBeforeUnmount, onMounted, onUpdated, provide, reactive, ref, watch } from 'vue'\nimport ChevronDown from 'vue-material-design-icons/ChevronDown.vue'\nimport ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'\nimport ChevronRight from 'vue-material-design-icons/ChevronRight.vue'\nimport { useLook } from '../../composables/useLook.js'\nimport { CN_TABS_INJECTION_KEY } from './tabsKey.js'\n\nexport default defineComponent({\n\tname: 'CnTabs',\n\n\tcomponents: {\n\t\tChevronDown,\n\t\tChevronLeft,\n\t\tChevronRight,\n\t\tNcActionButton,\n\t\tNcActions,\n\t},\n\n\tprops: {\n\t\t/** Extra class applied to the panel container (bootstrap-vue's `content-class`). */\n\t\tcontentClass: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Stretch the nav items to fill the strip (bootstrap-vue's `justified`). */\n\t\tjustified: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Card-style chrome (border + padding) around the panel area. */\n\t\tcard: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/**\n\t\t * How the strip is drawn: `line` (the default: folder tabs joined to\n\t\t * the panel below), `segmented` (a compact pill switch, for a view\n\t\t * switch such as \"My work / My team\") or `underline` (plain labels on\n\t\t * a rule, the open one underlined; board LpBeheerAlgemeen). An\n\t\t * `underline` strip wraps instead of scrolling. Roles and keyboard\n\t\t * behaviour are the same in all three.\n\t\t *\n\t\t * @type {'line'|'segmented'|'underline'}\n\t\t * @spec openspec/changes/admin-settings-underline-tabs/specs/settings-board-look/spec.md#requirement-the-admin-shell-can-draw-an-underline-tab-strip\n\t\t */\n\t\tvariant: {\n\t\t\ttype: String,\n\t\t\tdefault: 'line',\n\t\t\tvalidator: (v) => ['line', 'segmented', 'underline'].includes(v),\n\t\t},\n\n\t\t/**\n\t\t * Accessible name for the tab strip, applied to the `role=\"tablist\"`\n\t\t * element. Screen readers announce it when focus enters the strip.\n\t\t */\n\t\tariaLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * The look the strip is drawn in (`nextcloud` or `board`). Empty takes\n\t\t * the look the app provides. In the board look a `line` strip draws the\n\t\t * screens' folder tabs and never moves a tab under \"More\": the strip\n\t\t * scrolls instead.\n\t\t *\n\t\t * @spec openspec/changes/screens-detail-page-parity/specs/detail-page-board-look/spec.md#requirement-folder-tabs-take-the-board-strip\n\t\t */\n\t\tlook: { // eslint-disable-line vue/no-unused-properties -- read through useLook(props) in setup\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Leave the `overflow` tabs to the host's `#nav-end` menu instead of a\n\t\t * \"More\" menu of the strip's own. The strip then draws only the tabs\n\t\t * that are not `overflow`, and an overflow tab that is selected joins\n\t\t * it just before the last of them (board DqZaakBesluiten: Overview,\n\t\t * Files, Contact, Tasks, Decisions, History). The host lists the\n\t\t * overflow tabs and selects one through its `CnTab`'s `active`.\n\t\t *\n\t\t * @spec openspec/changes/board-tabs-overflow-in-actions/specs/board-tabs-overflow-in-actions/spec.md#requirement-board-tabs-list-their-overflow-in-the-actions-menu\n\t\t */\n\t\toverflowInNavEnd: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\n\t\t/** Name of the menu that lists the `overflow` tabs. */\n\t\tmoreLabel: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'More'),\n\t\t},\n\t},\n\n\temits: [\n\t\t/** The selected tab changed. Payload: the new active tab's index. */\n\t\t'update:activeIndex',\n\t],\n\n\tsetup(props, { emit }) {\n\t\tconst { isBoard, lookClass: boardClass } = useLook(props)\n\t\t// The board strip: a `line` strip in the board look.\n\t\tconst boardStrip = computed(() => isBoard.value && props.variant === 'line')\n\t\t// Registered children, in mount order.\n\t\tconst tabs = reactive([])\n\t\tconst activeUid = ref(null)\n\t\tconst navButtons = ref([])\n\t\tconst nav = ref(null)\n\t\t// Whether the selection was chosen (a tab declared `active`, or the\n\t\t// user picked one) rather than defaulted to the first tab registered.\n\t\tlet selectionChosen = false\n\n\t\t/**\n\t\t * Register a child tab. The first child to register wins the initial\n\t\t * selection unless a later one declares itself `active`. Disabled tabs\n\t\t * never take the initial selection.\n\t\t *\n\t\t * @param {object} tab Child descriptor.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction register(tab) {\n\t\t\ttabs.push(tab)\n\t\t\tif (tab.disabled) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (tab.active) {\n\t\t\t\tactiveUid.value = tab.uid\n\t\t\t\tselectionChosen = true\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (activeUid.value === null) {\n\t\t\t\tactiveUid.value = tab.uid\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// A tab under \"More\" only held the default selection because it\n\t\t\t// registered first. The first tab that is actually in the strip\n\t\t\t// takes it over, unless somebody chose.\n\t\t\tif (!selectionChosen && !tab.overflow) {\n\t\t\t\tconst current = tabs.find((candidate) => candidate.uid === activeUid.value)\n\t\t\t\tif (current && current.overflow) {\n\t\t\t\t\tactiveUid.value = tab.uid\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Drop a child that is unmounting, moving the selection to its nearest\n\t\t * remaining neighbour when it was the active one. Closable tabs rely on\n\t\t * this; without it the strip would render with nothing selected.\n\t\t *\n\t\t * @param {number} uid The child's instance uid.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction unregister(uid) {\n\t\t\tconst index = tabs.findIndex((tab) => tab.uid === uid)\n\t\t\tif (index !== -1) {\n\t\t\t\ttabs.splice(index, 1)\n\t\t\t}\n\t\t\tif (activeUid.value === uid) {\n\t\t\t\tconst neighbour = tabs[index] || tabs[index - 1] || tabs[0] || null\n\t\t\t\tactiveUid.value = neighbour ? neighbour.uid : null\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Make a child the visible one.\n\t\t *\n\t\t * @param {number} uid The child's instance uid.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction select(uid) {\n\t\t\tselectionChosen = true\n\t\t\tif (activeUid.value === uid) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tactiveUid.value = uid\n\t\t\temit('update:activeIndex', tabs.findIndex((tab) => tab.uid === uid))\n\t\t}\n\n\t\t/**\n\t\t * Whether a child is the visible one.\n\t\t *\n\t\t * @param {number} uid The child's instance uid.\n\t\t *\n\t\t * @return {boolean} True when this tab's panel should render.\n\t\t */\n\t\tfunction isActive(uid) {\n\t\t\treturn activeUid.value === uid\n\t\t}\n\n\t\tconst activeIndex = computed(() => tabs.findIndex((tab) => tab.uid === activeUid.value))\n\n\t\t/**\n\t\t * The tabs drawn in the strip: every tab that is not `overflow`, plus\n\t\t * an overflow tab for as long as it is the selected one.\n\t\t *\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow\n\t\t */\n\t\tconst visibleTabs = computed(() => {\n\t\t\tif (props.overflowInNavEnd) {\n\t\t\t\treturn navEndStrip()\n\t\t\t}\n\t\t\treturn tabs.filter((tab) => boardStrip.value || !tab.overflow || tab.uid === activeUid.value)\n\t\t})\n\n\t\t/**\n\t\t * The strip under `overflowInNavEnd`: the tabs that are not\n\t\t * `overflow`, with a selected overflow tab placed before the last.\n\t\t *\n\t\t * @return {object[]} The tabs to draw, in order.\n\t\t * @spec openspec/changes/board-tabs-overflow-in-actions/specs/board-tabs-overflow-in-actions/spec.md#requirement-an-opened-overflow-tab-joins-the-strip-before-the-last-tab\n\t\t */\n\t\tfunction navEndStrip() {\n\t\t\tconst pinned = tabs.filter((tab) => !tab.overflow)\n\t\t\tconst opened = tabs.find((tab) => tab.overflow && tab.uid === activeUid.value)\n\t\t\tif (!opened) {\n\t\t\t\treturn pinned\n\t\t\t}\n\t\t\tconst at = Math.max(0, pinned.length - 1)\n\t\t\treturn [...pinned.slice(0, at), opened, ...pinned.slice(at)]\n\t\t}\n\n\t\t/**\n\t\t * The tabs listed under \"More\": overflow tabs that are not selected.\n\t\t *\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow\n\t\t */\n\t\tconst overflowTabs = computed(() => (boardStrip.value || props.overflowInNavEnd ? [] : tabs.filter((tab) => tab.overflow && tab.uid !== activeUid.value)))\n\n\t\t/**\n\t\t * Whether a tab declares a count. `0` is a count; null and '' are not.\n\t\t *\n\t\t * @param {object} tab Child descriptor.\n\t\t *\n\t\t * @return {boolean} True when the count renders.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow\n\t\t */\n\t\tfunction hasCount(tab) {\n\t\t\treturn tab.count !== null && tab.count !== undefined && tab.count !== ''\n\t\t}\n\n\t\t/**\n\t\t * A \"More\" entry's text: the tab's title, and its count when it has one.\n\t\t *\n\t\t * @param {object} tab Child descriptor.\n\t\t *\n\t\t * @return {string} The menu label.\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow\n\t\t */\n\t\tfunction overflowLabel(tab) {\n\t\t\treturn hasCount(tab) ? `${tab.label} (${tab.count})` : tab.label\n\t\t}\n\n\t\t/**\n\t\t * Select a tab picked from \"More\" and move focus to it in the strip,\n\t\t * so a keyboard user lands on the tab they chose rather than on a menu\n\t\t * button that no longer lists it.\n\t\t *\n\t\t * @param {object} tab Child descriptor.\n\t\t *\n\t\t * @return {void}\n\t\t * @spec openspec/changes/detail-action-model-and-case-surfaces/specs/detail-action-model/spec.md#requirement-tab-counts-and-overflow\n\t\t */\n\t\tfunction onOverflowPick(tab) {\n\t\t\ttab.onActivate()\n\t\t\tnextTick(() => {\n\t\t\t\tconst button = nav.value?.querySelector(`#${tab.tabId}`)\n\t\t\t\tif (button && typeof button.focus === 'function') {\n\t\t\t\t\tbutton.focus()\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\n\t\tconst moreAriaLabel = computed(() => t('nextcloud-vue', 'More tabs'))\n\n\t\t/**\n\t\t * Move the selection and DOM focus to a tab by index, skipping disabled\n\t\t * ones. Focus has to move with the selection: in the WAI-ARIA tabs\n\t\t * pattern only the selected tab is in the tab order, so leaving focus on\n\t\t * a now-`tabindex=\"-1\"` button strands the keyboard user.\n\t\t *\n\t\t * @param {number} index Target index (clamped to the enabled tabs).\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction focusTab(index) {\n\t\t\t// The strip's own tabs only: the arrow keys walk what is drawn, and\n\t\t\t// the \"More\" menu has its own keyboard handling.\n\t\t\tconst enabled = visibleTabs.value.filter((tab) => !tab.disabled)\n\t\t\tif (enabled.length === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst wrapped = ((index % enabled.length) + enabled.length) % enabled.length\n\t\t\tconst target = enabled[wrapped]\n\t\t\tconst position = visibleTabs.value.indexOf(target)\n\t\t\tselect(target.uid)\n\t\t\tconst button = navButtons.value?.[position]\n\t\t\tif (button && typeof button.focus === 'function') {\n\t\t\t\tbutton.focus()\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Left/Right/Home/End navigation inside the strip.\n\t\t *\n\t\t * @param {KeyboardEvent} event The keydown event.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction onNavKeydown(event) {\n\t\t\tconst enabled = visibleTabs.value.filter((tab) => !tab.disabled)\n\t\t\tconst current = enabled.findIndex((tab) => tab.uid === activeUid.value)\n\t\t\tlet next = null\n\n\t\t\tif (event.key === 'ArrowRight') {\n\t\t\t\tnext = current + 1\n\t\t\t} else if (event.key === 'ArrowLeft') {\n\t\t\t\tnext = current - 1\n\t\t\t} else if (event.key === 'Home') {\n\t\t\t\tnext = 0\n\t\t\t} else if (event.key === 'End') {\n\t\t\t\tnext = enabled.length - 1\n\t\t\t}\n\n\t\t\tif (next === null) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tfocusTab(next)\n\t\t}\n\n\t\t/**\n\t\t * Scroll the strip so the selected tab is wholly in view.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction revealActiveTab() {\n\t\t\tconst strip = nav.value\n\t\t\tconst button = strip?.querySelector('[role=\"tab\"][aria-selected=\"true\"]')\n\t\t\tif (!strip || !button) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst stripRect = strip.getBoundingClientRect()\n\t\t\tconst rect = button.getBoundingClientRect()\n\t\t\tif (rect.left < stripRect.left) {\n\t\t\t\tstrip.scrollLeft -= stripRect.left - rect.left\n\t\t\t} else if (rect.right > stripRect.right) {\n\t\t\t\tstrip.scrollLeft += rect.right - stripRect.right\n\t\t\t}\n\t\t}\n\n\t\twatch(activeUid, revealActiveTab, { flush: 'post' })\n\n\t\t// Whether tabs are hidden past the start or end edge of the strip.\n\t\tconst moreStart = ref(false)\n\t\tconst moreEnd = ref(false)\n\n\t\t/**\n\t\t * Recompute which edges hide tabs; 1px of tolerance absorbs sub-pixel rounding.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction measureOverflow() {\n\t\t\tconst strip = nav.value\n\t\t\tif (!strip) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tmoreStart.value = strip.scrollLeft > 1\n\t\t\tmoreEnd.value = strip.scrollWidth - strip.clientWidth - strip.scrollLeft > 1\n\t\t}\n\n\t\tlet resizeObserver = null\n\n\t\tonMounted(() => {\n\t\t\tmeasureOverflow()\n\t\t\tif (typeof ResizeObserver === 'undefined' || !nav.value) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tresizeObserver = new ResizeObserver(measureOverflow)\n\t\t\tresizeObserver.observe(nav.value)\n\t\t})\n\n\t\t// A title change or a tab added can tip the strip into overflowing without resizing it.\n\t\tonUpdated(measureOverflow)\n\n\t\tonBeforeUnmount(() => {\n\t\t\tresizeObserver?.disconnect()\n\t\t\tresizeObserver = null\n\t\t})\n\n\t\t/**\n\t\t * Scroll the strip most of a viewport towards one edge.\n\t\t *\n\t\t * @param {number} direction -1 towards the start, 1 towards the end.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction scrollStrip(direction) {\n\t\t\tconst strip = nav.value\n\t\t\tif (!strip) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tstrip.scrollLeft += direction * Math.max(1, Math.round(strip.clientWidth * 0.6))\n\t\t}\n\n\t\t/**\n\t\t * Turn a vertical wheel into a horizontal scroll while the strip overflows.\n\t\t *\n\t\t * @param {WheelEvent} event The wheel event.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction onNavWheel(event) {\n\t\t\tconst strip = event.currentTarget\n\t\t\tif (strip.scrollWidth <= strip.clientWidth || event.deltaX !== 0 || event.deltaY === 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tevent.preventDefault()\n\t\t\tstrip.scrollLeft += event.deltaY\n\t\t}\n\n\t\tprovide(CN_TABS_INJECTION_KEY, { register, unregister, select, isActive })\n\n\t\treturn {\n\t\t\tboardStrip,\n\t\t\tboardClass,\n\t\t\ttabs,\n\t\t\tvisibleTabs,\n\t\t\toverflowTabs,\n\t\t\thasCount,\n\t\t\toverflowLabel,\n\t\t\tonOverflowPick,\n\t\t\tmoreAriaLabel,\n\t\t\tisActive,\n\t\t\tactiveIndex,\n\t\t\tnav,\n\t\t\tnavButtons,\n\t\t\tmoreStart,\n\t\t\tmoreEnd,\n\t\t\tmeasureOverflow,\n\t\t\tscrollStrip,\n\t\t\tonNavKeydown,\n\t\t\tonNavWheel,\n\t\t}\n\t},\n})\n</script>\n\n<style scoped>\n/* The bar owns the rule under the strip so a `#nav-end` surface sits ON the\n line rather than above a line that stops where the last tab does. With no\n nav-end content the nav is the bar's only child and the result is pixel\n identical to the rule living on the nav itself. */\n.cn-tabs__bar {\n\t/* Top-aligned so the `#nav-end` control sits on the tab row, not below it,\n\t should the two sides ever differ in height. */\n\talign-items: flex-start;\n\tborder-bottom: 1px solid var(--color-border);\n\tdisplay: flex;\n\tgap: 8px;\n}\n\n/* The strip holds the scrolling nav and the edge affordances drawn over it. */\n.cn-tabs__strip {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n\tposition: relative;\n}\n\n.cn-tabs__nav {\n\tdisplay: flex;\n\tgap: 4px;\n\t/* One row that scrolls, never a wrap: in a narrow sidebar a nine-tab strip\n\t wrapped into a stack taller than the panel under it. The scrollbar is\n\t hidden because it would sit between the tabs and the bar's rule and break\n\t the open tab's join with its panel; a fade and a chevron on the clipped\n\t edge say there is more, and the strip follows the wheel. */\n\tflex-wrap: nowrap;\n\toverflow-x: auto;\n\toverflow-y: hidden;\n\tscroll-behavior: smooth;\n\tscrollbar-width: none;\n\t/* Overhang the bar's 1px rule so the open tab's own bottom edge, drawn in\n\t the panel's colour, covers it and the tab joins its panel. */\n\tmargin-bottom: -1px;\n}\n\n.cn-tabs__nav::-webkit-scrollbar {\n\tdisplay: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n\t.cn-tabs__nav {\n\t\tscroll-behavior: auto;\n\t}\n}\n\n/* A fade over the clipped edge, only on the side that hides tabs. Stops a\n pixel above the bar's rule so the rule keeps its full colour.\n\n z-index 2 so the fades outrank a lifted tab: CnTabsWidget puts the open tab\n at 1, and a tie goes to DOM order, which would draw that tab over the start\n fade (it precedes the tabs) while the end fade still covered it. */\n.cn-tabs__strip::before,\n.cn-tabs__strip::after {\n\tcontent: '';\n\tdisplay: none;\n\tinset-block: 0 1px;\n\tpointer-events: none;\n\tposition: absolute;\n\twidth: 56px;\n\tz-index: 2;\n}\n\n.cn-tabs__strip::before {\n\tbackground: linear-gradient(to right, var(--color-main-background) 30%, transparent);\n\tinset-inline-start: 0;\n}\n\n.cn-tabs__strip::after {\n\tbackground: linear-gradient(to left, var(--color-main-background) 30%, transparent);\n\tinset-inline-end: 0;\n}\n\n.cn-tabs__strip--more-start::before,\n.cn-tabs__strip--more-end::after {\n\tdisplay: block;\n}\n\n/* Doubled selector for the same reason as `.cn-tabs__nav .cn-tabs__nav-item`:\n Nextcloud's own `button` rule would otherwise give this a border, a fill\n and a margin. */\n.cn-tabs__strip .cn-tabs__scroll {\n\talign-items: center;\n\tbackground: none;\n\tborder: 0;\n\tborder-radius: 0;\n\tcolor: var(--color-main-text);\n\tcursor: pointer;\n\tdisplay: flex;\n\t/* Pushed down a little so the chevron sits level with the tab labels. */\n\tinset-block: 2px 0;\n\tjustify-content: center;\n\tmargin: 0;\n\tmin-height: 0;\n\tpadding: 0;\n\tposition: absolute;\n\twidth: 28px;\n\tz-index: 3;\n}\n\n/* Nextcloud's `button:hover` rule would otherwise fill the chevron. */\n.cn-tabs__strip .cn-tabs__scroll:hover,\n.cn-tabs__strip .cn-tabs__scroll:focus {\n\tbackground: none;\n}\n\n.cn-tabs__scroll--start {\n\tinset-inline-start: 0;\n}\n\n.cn-tabs__scroll--end {\n\tinset-inline-end: 0;\n}\n\n/* The count behind a tab's title. Full-contrast text on the page background,\n so it reads on the idle tab's darker fill and on the open tab alike. */\n.cn-tabs__count {\n\tbackground-color: var(--color-main-background);\n\tborder: 1px solid var(--color-border-dark);\n\tborder-radius: var(--border-radius-pill, 100px);\n\tcolor: var(--color-main-text);\n\tdisplay: inline-block;\n\tfont-size: 0.85em;\n\tline-height: 1.4;\n\tmargin-inline-start: 6px;\n\tmin-width: 1.4em;\n\tpadding: 0 6px;\n\ttext-align: center;\n}\n\n.cn-tabs__more {\n\talign-items: center;\n\tdisplay: flex;\n\tflex: 0 0 auto;\n}\n\n/* Keeps a \"More\" entry's text on the menu's text column without drawing an icon. */\n.cn-tabs__more-spacer {\n\tdisplay: inline-block;\n\twidth: 20px;\n}\n\n.cn-tabs__nav-end {\n\talign-items: center;\n\tdisplay: flex;\n\tflex: 0 0 auto;\n\tgap: 4px;\n\t/* No min-height. This used to hard-code 38px to mirror the old nav item's\n\t vertical box, but the folder-tab restyle changed that box to 33px, and\n\t because the bar is `align-items: flex-start` the taller nav-end then drove\n\t the BAR's height. The bar's bottom rule sank 6px below the tab row, so the\n\t open tab no longer met the panel it is drawn as joined to. Letting the\n\t control size to its own content keeps the rule on the tab row whatever the\n\t nav item's box becomes, so the two cannot drift apart again. */\n\tmin-height: 0;\n}\n\n/* Folder tabs, not underlined labels. An underline is the weakest possible\n affordance on a strip that already carries icons: with nine tabs on a dossiq\n case the row read as a sentence of links, and only the 2px rule said which\n one was open. Giving the inactive tabs their own darker surface makes the\n strip legible as a control at a glance, and the active tab, sharing the\n panel's background and punching a hole in the bar's rule, reads as the sheet\n in front. */\n.cn-tabs__nav-item {\n\tbackground-color: var(--color-background-dark);\n\t/* `--color-border-dark`, not `--color-border`: the latter is the same\n\t #ededed as `--color-background-dark` in the stock light theme, so a tab\n\t drawn with it has no visible edge against its own fill. */\n\tborder: 1px solid var(--color-border-dark);\n\tborder-bottom: none;\n\tborder-radius: var(--border-radius-large, 8px) var(--border-radius-large, 8px) 0 0;\n\t/* Full-contrast label, not `--color-text-maxcontrast`. On the old\n\t transparent strip maxcontrast sat on white at 5.3:1; on this darker\n\t surface the same pair measures 4.55:1, which clears WCAG AA by 0.05 and\n\t would not survive a themed instance shifting either token. A real tab\n\t strip carries its selection in the surface and the weight anyway, so\n\t nothing is lost by giving every label the readable colour. */\n\tcolor: var(--color-main-text);\n\tcursor: pointer;\n\tfont-weight: normal;\n\tpadding: 8px 12px;\n\twhite-space: nowrap;\n\t/* Match the `#nav-end` control's own height. The bar is a flex row, so\n\t whichever side is taller sets the bar's height and therefore where its\n\t bottom rule lands. When the control was the taller one the rule sat below\n\t the tab row and showed as a line under the open tab, breaking the join to\n\t the panel. Sizing both sides the same is what keeps the rule ON the tab\n\t row; `inline-flex` centres the label inside the taller box. */\n\talign-items: center;\n\tbox-sizing: border-box;\n\tdisplay: inline-flex;\n\tmin-height: var(--default-clickable-area, 34px);\n}\n\n/* Nextcloud's server stylesheet gives every plain `button` a 3px bottom margin\n with a selector that outscores the scoped rule above, which floated the tabs\n above the bar's rule inside a real page. The parent class wins on\n specificity rather than on `!important` or source order. */\n.cn-tabs__nav .cn-tabs__nav-item {\n\tmargin-block-end: 0;\n}\n\n.cn-tabs__nav--justified .cn-tabs__nav-item {\n\tflex: 1 1 0;\n}\n\n.cn-tabs__nav-item:hover,\n.cn-tabs__nav-item:focus-visible {\n\tbackground-color: var(--color-background-hover);\n\tcolor: var(--color-main-text);\n}\n\n/* Inside the box: the scrolling strip clips anything drawn outside a tab. */\n.cn-tabs__nav-item:focus-visible {\n\toutline-offset: -2px;\n}\n\n.cn-tabs__nav-item[disabled] {\n\tcursor: default;\n\topacity: 0.5;\n}\n\n.cn-tabs__nav-item[disabled]:hover {\n\tbackground-color: var(--color-background-dark);\n\tcolor: var(--color-main-text);\n}\n\n/* The open tab: the panel's own background, and a bottom border painted in\n that same background so it erases the bar's rule underneath and the tab\n joins the sheet below it. The primary-coloured top edge is what carries the\n selection at a glance, and it survives forced-colours mode, which drops\n background colours but keeps borders. */\n.cn-tabs__nav-item--active {\n\tbackground-color: var(--color-main-background);\n\tborder-bottom: 1px solid var(--color-main-background);\n\tborder-top: 2px solid var(--color-primary-element);\n\tcolor: var(--color-main-text);\n\tfont-weight: bold;\n\t/* Keep the text baseline identical to an inactive tab despite the 1px\n\t thicker top border, so the strip does not jog when the selection moves. */\n\tpadding-top: 7px;\n}\n\n.cn-tabs__content {\n\tpadding-top: 12px;\n}\n\n.cn-tabs--card .cn-tabs__content {\n\tborder: 1px solid var(--color-border);\n\tborder-top: none;\n\tpadding: 12px;\n}\n\n/* Segmented variant: the strip is a pill switch that stands on its own, so\n the bar's rule and the tab-joins-panel treatment are both dropped. */\n.cn-tabs--segmented .cn-tabs__bar {\n\tborder-bottom: none;\n}\n\n.cn-tabs--segmented .cn-tabs__strip {\n\tflex: 0 1 auto;\n}\n\n.cn-tabs--segmented .cn-tabs__nav {\n\tgap: 4px;\n\tmargin-bottom: 0;\n\tpadding: 4px;\n\tborder-radius: var(--border-radius-container, var(--border-radius-large, 10px));\n\tbackground-color: var(--color-background-dark);\n}\n\n.cn-tabs--segmented .cn-tabs__nav-item {\n\tborder: 1px solid transparent;\n\tborder-radius: var(--border-radius-element, var(--border-radius-large, 8px));\n\tbackground-color: transparent;\n\tfont-weight: 600;\n\tpadding: 6px 16px;\n}\n\n.cn-tabs--segmented .cn-tabs__nav-item:hover {\n\tbackground-color: var(--color-background-hover);\n}\n\n/* The open segment is the raised one. Its border survives forced-colours\n mode, which drops the background and the shadow. */\n.cn-tabs--segmented .cn-tabs__nav-item--active {\n\tborder: 1px solid var(--color-border-dark);\n\tbackground-color: var(--color-main-background);\n\tbox-shadow: 0 1px 2px var(--color-box-shadow);\n\tfont-weight: 600;\n\tpadding-top: 6px;\n}\n\n/* Underline variant (board LpBeheerAlgemeen): plain labels on the bar's rule,\n the open one carried by a 3px primary underline that sits on that rule. The\n strip wraps onto a second row rather than scrolling. */\n.cn-tabs--underline .cn-tabs__nav {\n\tflex-wrap: wrap;\n\toverflow: visible;\n}\n\n/* `margin: 0`: a tab is a <button>, and Nextcloud's core stylesheet gives\n every plain button a 3px margin, which put the tabs 7px apart and 3px low\n (DqInstellingen: 4px apart, on the strip's top). The nldesign theme sets a\n button's weight, corner and border width with `!important` through its\n button tokens, which drew every tab bold with a 1px underline; the tab\n hands its own values to those tokens. */\n.cn-tabs--underline .cn-tabs__nav-item {\n\t--nldesign-component-button-font-weight: 500;\n\t--nldesign-component-button-border-radius: 0;\n\t--nldesign-component-button-border-width: 0 0 3px;\n\tbackground-color: transparent;\n\tborder: 0;\n\tborder-bottom: 3px solid transparent;\n\tborder-radius: 0;\n\tcolor: var(--cn-board-text-soft, var(--color-main-text));\n\tfont-size: 14px;\n\tfont-weight: 500;\n\tgap: 8px;\n\tmargin: 0;\n\tmin-height: 44px;\n\tpadding: 0 9px;\n}\n\n.cn-tabs--underline .cn-tabs__nav-item:hover,\n.cn-tabs--underline .cn-tabs__nav-item:focus-visible {\n\tbackground-color: transparent;\n\tborder-bottom-color: var(--color-border-dark);\n\tcolor: var(--color-main-text);\n}\n\n.cn-tabs--underline .cn-tabs__nav-item[disabled]:hover {\n\tbackground-color: transparent;\n\tborder-bottom-color: transparent;\n}\n\n.cn-tabs--underline .cn-tabs__nav-item--active,\n.cn-tabs--underline .cn-tabs__nav-item--active:hover,\n.cn-tabs--underline .cn-tabs__nav-item--active:focus-visible {\n\t--nldesign-component-button-font-weight: 700;\n\tborder-bottom: 3px solid var(--color-primary-element);\n\tcolor: var(--color-main-text);\n\tfont-weight: 700;\n\tpadding-top: 0;\n}\n\n.cn-tabs--underline .cn-tabs__count {\n\tmargin-inline-start: 0;\n}\n\n.cn-tabs--underline .cn-tabs__content {\n\tpadding-top: 20px;\n}\n</style>\n"],"names":["t"],"mappings":"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"}
|