@conduction/nextcloud-vue 2.62.0 → 2.63.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/CnAdminActionCard/CnAdminActionCard.vue.js +10 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue.js.map +1 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue2.js +189 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue2.js.map +1 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue3.js +72 -0
- package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue3.js.map +1 -0
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +271 -32
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +99 -9
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +39 -18
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +53 -37
- package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -2
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js +10 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js.map +1 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +293 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +189 -0
- package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -0
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +216 -8
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +118 -50
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +2 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js +3 -1
- package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js.map +1 -1
- package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js +1 -1
- package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +234 -9
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +96 -57
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +51 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +5 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/selfModeActions.js +3 -3
- package/dist/esm/components/CnIndexPage/selfModeActions.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +36 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js +121 -12
- package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js.map +1 -1
- package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js +3 -1
- package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +326 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +112 -26
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js +10 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +337 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js +102 -0
- package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js +34 -0
- package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js.map +1 -0
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +131 -8
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js +40 -38
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +8 -6
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js +19 -0
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js +1 -1
- package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
- package/dist/esm/composables/useDependencyCheck.js +65 -0
- package/dist/esm/composables/useDependencyCheck.js.map +1 -0
- package/dist/esm/composables/useWalkthrough.js +218 -6
- package/dist/esm/composables/useWalkthrough.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 +43 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +43 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +449 -0
- package/dist/esm/store/useObjectStore.js +8 -0
- package/dist/esm/store/useObjectStore.js.map +1 -1
- package/dist/esm/utils/columnFilters.js +311 -0
- package/dist/esm/utils/columnFilters.js.map +1 -0
- package/dist/esm/utils/groupAutocomplete.js +123 -0
- package/dist/esm/utils/groupAutocomplete.js.map +1 -0
- package/dist/esm/utils/libraryWidgetKeys.js +2 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/pickerOptions.js +198 -0
- package/dist/esm/utils/pickerOptions.js.map +1 -0
- package/dist/esm/utils/schema.js +83 -7
- package/dist/esm/utils/schema.js.map +1 -1
- package/dist/esm/utils/timelineEvents.js +174 -0
- package/dist/esm/utils/timelineEvents.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/esm/utils/walkthroughSignals.js +88 -0
- package/dist/esm/utils/walkthroughSignals.js.map +1 -0
- package/dist/nextcloud-vue.cjs.js +9369 -5193
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +449 -0
- package/l10n/en.json +43 -0
- package/l10n/nl.json +43 -0
- package/package.json +1 -1
- package/src/components/CnAdminActionCard/CnAdminActionCard.vue +234 -0
- package/src/components/CnAdminActionCard/index.js +4 -0
- package/src/components/CnAppNav/CnAppNav.vue +271 -32
- package/src/components/CnAppRoot/CnAppRoot.vue +129 -15
- package/src/components/CnChoiceCards/CnChoiceCards.vue +56 -31
- package/src/components/CnDashboardPage/CnDashboardPage.vue +2 -2
- package/src/components/CnDataTable/CnColumnFilterPopover.vue +524 -0
- package/src/components/CnDataTable/CnDataTable.md +3 -0
- package/src/components/CnDataTable/CnDataTable.vue +260 -4
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +2 -2
- package/src/components/CnFieldHelper/CnFieldHelper.vue +4 -2
- package/src/components/CnFormDialog/CnFormDialog.vue +290 -31
- package/src/components/CnIndexPage/CnIndexPage.vue +55 -2
- package/src/components/CnIndexPage/selfModeActions.js +3 -3
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +39 -1
- package/src/components/CnResourceSelect/CnResourceSelect.vue +123 -12
- package/src/components/CnSetupWizard/CnSetupWizard.vue +392 -3
- package/src/components/CnTimelineWidget/CnTimelineWidget.vue +477 -0
- package/src/components/CnTimelineWidget/dashboardRegistration.js +32 -0
- package/src/components/CnTimelineWidget/index.js +4 -0
- package/src/components/CnWalkthrough/CnWalkthrough.vue +143 -10
- package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
- package/src/components/CnWizardDialog/CnWizardDialog.vue +20 -1
- package/src/components/index.js +2 -0
- package/src/composables/useDependencyCheck.js +61 -0
- package/src/composables/useWalkthrough.js +217 -5
- package/src/css/dashboard.css +49 -0
- package/src/css/detail-page.css +19 -0
- package/src/css/table.css +81 -0
- package/src/index.js +2 -0
- package/src/schemas/app-manifest-v2.schema.json +23 -1
- package/src/store/useObjectStore.js +8 -0
- package/src/utils/columnFilters.js +324 -0
- package/src/utils/groupAutocomplete.js +119 -0
- package/src/utils/libraryWidgetKeys.js +2 -0
- package/src/utils/pickerOptions.js +194 -0
- package/src/utils/schema.js +83 -7
- package/src/utils/timelineEvents.js +171 -0
- package/src/utils/validateManifestV2.compiled.js +2 -2
- package/src/utils/walkthroughSignals.js +94 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnWizardDialog.vue2.js","sources":["../../../../src/components/CnWizardDialog/CnWizardDialog.vue"],"sourcesContent":["<template>\n\t<NcDialog\n\t\t:name=\"dialogTitle\"\n\t\tsize=\"large\"\n\t\t:open=\"dialogOpen\"\n\t\t:noClose=\"loading || !cancellable\"\n\t\tdata-testid=\"cn-modal\"\n\t\tdata-testid-modal=\"cn-wizard-dialog\"\n\t\t@update:open=\"dialogOpen = $event\"\n\t\t@closing=\"onClose\">\n\t\t<!-- Result phase: rendered after a successful `submit()`. -->\n\t\t<div v-if=\"result !== null\"\n\t\t\tclass=\"cn-wizard-dialog__result\"\n\t\t\tdata-testid-phase=\"result\">\n\t\t\t<NcNoteCard v-if=\"result.success\" type=\"success\">\n\t\t\t\t{{ result.message || successText }}\n\t\t\t</NcNoteCard>\n\t\t\t<NcNoteCard v-else-if=\"result.error\" type=\"error\">\n\t\t\t\t{{ result.error }}\n\t\t\t</NcNoteCard>\n\t\t\t<!-- @slot result-extra Optional extra content (e.g. batch\n\t\t\t progress poller, summary table) rendered below the\n\t\t\t success/error banner. Scope: { result }. -->\n\t\t\t<slot name=\"result-extra\" :result=\"result\" />\n\t\t</div>\n\n\t\t<!-- Wizard phase: progress bar + current step body. -->\n\t\t<div v-else\n\t\t\tclass=\"cn-wizard-dialog__wizard\"\n\t\t\tdata-testid-phase=\"form\">\n\t\t\t<!-- Progress indicator. Click jumps back to a completed\n\t\t\t step when allowJumpBack is true. -->\n\t\t\t<ol class=\"cn-wizard-dialog__progress\" role=\"tablist\">\n\t\t\t\t<li v-for=\"(step, idx) in steps\"\n\t\t\t\t\t:key=\"step.id\"\n\t\t\t\t\tclass=\"cn-wizard-dialog__progress-item\"\n\t\t\t\t\t:class=\"{\n\t\t\t\t\t\t'cn-wizard-dialog__progress-item--active': idx === currentIndex,\n\t\t\t\t\t\t'cn-wizard-dialog__progress-item--done': idx < currentIndex,\n\t\t\t\t\t\t'cn-wizard-dialog__progress-item--clickable': allowJumpBack && idx < currentIndex,\n\t\t\t\t\t}\"\n\t\t\t\t\trole=\"tab\"\n\t\t\t\t\t:aria-selected=\"idx === currentIndex\"\n\t\t\t\t\t@click=\"allowJumpBack && idx < currentIndex ? jumpTo(step.id) : null\">\n\t\t\t\t\t<span class=\"cn-wizard-dialog__progress-dot\">\n\t\t\t\t\t\t<span v-if=\"idx < currentIndex\" class=\"cn-wizard-dialog__progress-check\" aria-hidden=\"true\">✓</span>\n\t\t\t\t\t\t<span v-else>{{ idx + 1 }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t<span class=\"cn-wizard-dialog__progress-label\">{{ step.label }}</span>\n\t\t\t\t\t<span v-if=\"idx < steps.length - 1\"\n\t\t\t\t\t\tclass=\"cn-wizard-dialog__progress-connector\"\n\t\t\t\t\t\t:class=\"{ 'cn-wizard-dialog__progress-connector--done': idx < currentIndex }\"\n\t\t\t\t\t\taria-hidden=\"true\" />\n\t\t\t\t</li>\n\t\t\t</ol>\n\n\t\t\t<!-- Validation error from the current step's validate() result. -->\n\t\t\t<NcNoteCard v-if=\"validationError\" type=\"error\">\n\t\t\t\t{{ validationError }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<!-- Current step body — a dynamic slot named by step id. -->\n\t\t\t<div class=\"cn-wizard-dialog__step-body\" :data-step-id=\"currentStep.id\">\n\t\t\t\t<!-- @slot step-{id} Per-step body. Scope: { next,\n\t\t\t\t back, jumpTo, submit, currentStep, stepIndex,\n\t\t\t\t totalSteps, stepData, setStepData, isFirst,\n\t\t\t\t isLast }. -->\n\t\t\t\t<slot\n\t\t\t\t\t:name=\"'step-' + currentStep.id\"\n\t\t\t\t\t:next=\"next\"\n\t\t\t\t\t:back=\"back\"\n\t\t\t\t\t:jumpTo=\"jumpTo\"\n\t\t\t\t\t:submit=\"submit\"\n\t\t\t\t\t:currentStep=\"currentStep\"\n\t\t\t\t\t:stepIndex=\"currentIndex\"\n\t\t\t\t\t:totalSteps=\"steps.length\"\n\t\t\t\t\t:stepData=\"stepData\"\n\t\t\t\t\t:setStepData=\"setStepData\"\n\t\t\t\t\t:isFirst=\"isFirst\"\n\t\t\t\t\t:isLast=\"isLast\" />\n\t\t\t</div>\n\t\t</div>\n\n\t\t<template #actions>\n\t\t\t<!-- Result phase actions: just Close. -->\n\t\t\t<NcButton v-if=\"result !== null\" @click=\"onClose\">\n\t\t\t\t{{ closeLabel }}\n\t\t\t</NcButton>\n\n\t\t\t<!-- Wizard phase actions: Cancel + Back + Next/Submit.\n\t\t\t The testids exist because these buttons are the only way\n\t\t\t through a wizard and their LABELS are translated: an e2e that\n\t\t\t clicks \"Next\" passes on an English instance and times out on a\n\t\t\t Dutch one, reporting a broken dialog rather than a missing\n\t\t\t string. -->\n\t\t\t<template v-else>\n\t\t\t\t<NcButton v-if=\"cancellable\" data-testid=\"cn-wizard-cancel\" @click=\"onClose\">\n\t\t\t\t\t{{ cancelLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton v-if=\"!isFirst\"\n\t\t\t\t\tdata-testid=\"cn-wizard-back\"\n\t\t\t\t\t:disabled=\"loading\"\n\t\t\t\t\t@click=\"back\">\n\t\t\t\t\t{{ backLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton variant=\"primary\"\n\t\t\t\t\tdata-testid=\"cn-wizard-next\"\n\t\t\t\t\t:disabled=\"loading\"\n\t\t\t\t\t@click=\"isLast ? submit() : next()\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<NcLoadingIcon v-if=\"loading\" :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ isLast ? submitLabel : nextLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</template>\n\t\t</template>\n\t</NcDialog>\n</template>\n\n<script>\nimport { NcButton, NcDialog, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\n\n/**\n * CnWizardDialog — Multi-step modal with per-step slots,\n * back/next navigation, optional per-step validation, and a single\n * result phase.\n *\n * Each step is declared in the `steps[]` prop (`{ id, label,\n * optional?, icon? }`) and rendered via a `#step-{id}` named slot\n * with the navigation API exposed through the slot scope. The\n * wizard owns the progress indicator, the back/next chrome, the\n * loading lifecycle, and the result-phase banner — the consumer\n * owns the per-step body + the data collection logic.\n *\n * Per-step validation is opt-in via the `validate` prop:\n *\n * ```js\n * async function validate(stepId, stepData) {\n * if (stepId === 'audience' && !stepData.cohort) {\n * return 'Pick a cohort first.'\n * }\n * return true\n * }\n * ```\n *\n * Returning `true` (or any truthy non-string) advances; returning a\n * string surfaces it as an error banner above the step body and\n * blocks the navigation.\n *\n * The result phase is entered by the consumer calling\n * `setResult({ success?, message?, error? })` after `@submit` fires.\n *\n * Use [`CnFormDialog`](../CnFormDialog/) for single-step forms.\n * Use [`CnExportWizard`](../CnExportWizard/) for the specific\n * scope+format+delivery export-trigger shape.\n *\n * ```vue\n * <CnWizardDialog\n * ref=\"wizard\"\n * dialog-title=\"Bulk enrol\"\n * :steps=\"[\n * { id: 'audience', label: 'Audience' },\n * { id: 'course', label: 'Course' },\n * { id: 'confirm', label: 'Confirm' },\n * ]\"\n * :validate=\"validateStep\"\n * @submit=\"onSubmit\"\n * @close=\"show = false\">\n * <template #step-audience=\"{ next, stepData, setStepData }\">\n * <CohortPicker :value=\"stepData.cohort\" @input=\"v => setStepData({ cohort: v })\" />\n * </template>\n * <template #step-course=\"{ next, back, stepData, setStepData }\">\n * <CoursePicker :value=\"stepData.course\" @input=\"v => setStepData({ course: v })\" />\n * </template>\n * <template #step-confirm=\"{ back, submit, stepData }\">\n * <p>Enrol {{ stepData.audience.cohort.size }} learners into {{ stepData.course.course.name }}?</p>\n * </template>\n * </CnWizardDialog>\n * ```\n */\nexport default {\n\tname: 'CnWizardDialog',\n\tcomponents: { NcDialog, NcButton, NcNoteCard, NcLoadingIcon },\n\tprops: {\n\t\t/**\n\t\t * Step declarations. Order is significant — the wizard\n\t\t * navigates linearly between adjacent steps unless the\n\t\t * consumer calls `jumpTo()` from a slot scope.\n\t\t *\n\t\t * @type {Array<{ id: string, label: string, optional?: boolean, icon?: string }>}\n\t\t */\n\t\tsteps: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t\tvalidator: (v) => Array.isArray(v) && v.length > 0\n\t\t\t\t&& v.every((s) => s && typeof s.id === 'string' && typeof s.label === 'string'),\n\t\t},\n\n\t\t/**\n\t\t * Dialog title shown in the NcDialog header.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdialogTitle: {\n\t\t\ttype: String,\n\t\t\tdefault: 'Wizard',\n\t\t},\n\n\t\t/**\n\t\t * Step id to start on. Defaults to the first step in `steps[]`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tinitialStep: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Per-step validator. Async function returning `true` to\n\t\t * advance, a string to surface as an error banner + block\n\t\t * navigation, or `false` to block silently. Called from\n\t\t * `next()` and `submit()`. When omitted, navigation always\n\t\t * proceeds.\n\t\t *\n\t\t * @type {(stepId: string, stepData: object) => Promise<boolean|string>}\n\t\t */\n\t\tvalidate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether the progress indicator allows jumping back to a\n\t\t * completed step via click. Forward jumps are NEVER allowed\n\t\t * via the progress indicator (consumer-side `jumpTo` is\n\t\t * unrestricted).\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tallowJumpBack: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Seed values for `stepData` (the cross-step object passed\n\t\t * to every slot scope). Useful for prefilling on edit-style\n\t\t * wizards.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tdefaults: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Cancel-button label (wizard phase). */\n\t\tcancelLabel: { type: String, default: 'Cancel' },\n\t\t/** Back-button label. */\n\t\tbackLabel: { type: String, default: 'Back' },\n\t\t/** Next-button label (intermediate steps). */\n\t\tnextLabel: { type: String, default: 'Next' },\n\t\t/** Submit-button label (last step). */\n\t\tsubmitLabel: { type: String, default: 'Submit' },\n\t\t/** Close-button label (result phase). */\n\t\tcloseLabel: { type: String, default: 'Close' },\n\t\t/** Success banner default text when result.message is empty. */\n\t\tsuccessText: { type: String, default: 'Done.' },\n\t\t/**\n\t\t * Whether the dialog can be dismissed (Cancel button, ESC, backdrop\n\t\t * click) before reaching the result phase. `false` hides Cancel and\n\t\t * disables all other close affordances — use for a step that must\n\t\t * be completed, not merely skipped. The result-phase Close button is\n\t\t * unaffected (a completed wizard is always dismissible).\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\t},\n\n\temits: ['close', 'step-change', 'submit'],\n\tdata() {\n\t\tconst initialIndex = this.resolveInitialIndex()\n\t\treturn {\n\t\t\tcurrentIndex: initialIndex,\n\t\t\tstepData: { ...this.defaults },\n\t\t\tloading: false,\n\t\t\tresult: null,\n\t\t\tvalidationError: '',\n\t\t\t/**\n\t\t\t * Drives NcDialog's `open` prop directly. NcDialog's own `open`\n\t\t\t * prop defaults to `true` and is NOT self-managing on close — its\n\t\t\t * internal `showModal` flag resets back to `true` ~300ms after\n\t\t\t * any close (animation bookkeeping for next time it's reopened\n\t\t\t * via the prop), so without an explicit `false` here the dialog\n\t\t\t * reopens itself right after Cancel/ESC/backdrop-click.\n\t\t\t */\n\t\t\tdialogOpen: true,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The currently-active step object from `steps[]`.\n\t\t *\n\t\t * @return {object} The current step definition.\n\t\t */\n\t\tcurrentStep() {\n\t\t\treturn this.steps[this.currentIndex] || this.steps[0]\n\t\t},\n\n\t\t/**\n\t\t * Whether the current step is the first one.\n\t\t *\n\t\t * @return {boolean} True when on the first step.\n\t\t */\n\t\tisFirst() {\n\t\t\treturn this.currentIndex === 0\n\t\t},\n\n\t\t/**\n\t\t * Whether the current step is the last one (drives the\n\t\t * Next → Submit label flip).\n\t\t *\n\t\t * @return {boolean} True when on the last step.\n\t\t */\n\t\tisLast() {\n\t\t\treturn this.currentIndex === this.steps.length - 1\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Resolve the index of `initialStep` (falling back to 0 when\n\t\t * unset / not found).\n\t\t *\n\t\t * @return {number} Step index in the range [0, steps.length).\n\t\t */\n\t\tresolveInitialIndex() {\n\t\t\tif (!this.initialStep) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tconst idx = this.steps.findIndex((s) => s.id === this.initialStep)\n\t\t\treturn idx >= 0 ? idx : 0\n\t\t},\n\n\t\t/**\n\t\t * Merge a partial step-data object onto the shared state.\n\t\t * Each slot scope receives this so consumers can write their\n\t\t * step's data without owning a `data()` block in their slot\n\t\t * component.\n\t\t *\n\t\t * @param {object} partial Fields to merge.\n\t\t * @return {void}\n\t\t */\n\t\tsetStepData(partial) {\n\t\t\tthis.stepData = { ...this.stepData, ...partial }\n\t\t},\n\n\t\t/**\n\t\t * Run the consumer's `validate` prop (if provided) for the\n\t\t * current step. Returns true on pass; sets `validationError`\n\t\t * and returns false on fail.\n\t\t *\n\t\t * @return {Promise<boolean>} Whether validation passed.\n\t\t */\n\t\tasync runValidation() {\n\t\t\tthis.validationError = ''\n\t\t\tif (typeof this.validate !== 'function') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst outcome = await this.validate(this.currentStep.id, this.stepData)\n\t\t\t\tif (outcome === true) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\tif (typeof outcome === 'string') {\n\t\t\t\t\tthis.validationError = outcome\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn Boolean(outcome)\n\t\t\t} catch (e) {\n\t\t\t\tthis.validationError = e && e.message ? e.message : 'Validation failed.'\n\t\t\t\treturn false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Advance to the next step after validation. Emits\n\t\t * `@step-change` with the new step id.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync next() {\n\t\t\tif (this.isLast) {\n\t\t\t\treturn this.submit()\n\t\t\t}\n\t\t\tconst ok = await this.runValidation()\n\t\t\tif (!ok) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.currentIndex += 1\n\t\t\t/**\n\t\t\t * @event step-change Emitted on every step navigation\n\t\t\t * (next / back / jumpTo / submit).\n\t\t\t * @type {{ stepId: string, stepIndex: number, direction: 'next'|'back'|'jump' }}\n\t\t\t */\n\t\t\tthis.$emit('step-change', {\n\t\t\t\tstepId: this.currentStep.id,\n\t\t\t\tstepIndex: this.currentIndex,\n\t\t\t\tdirection: 'next',\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Step back to the previous step. No validation runs on\n\t\t * back navigation — consumers can reach back without\n\t\t * losing the current step's draft.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tback() {\n\t\t\tif (this.isFirst) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.validationError = ''\n\t\t\tthis.currentIndex -= 1\n\t\t\tthis.$emit('step-change', {\n\t\t\t\tstepId: this.currentStep.id,\n\t\t\t\tstepIndex: this.currentIndex,\n\t\t\t\tdirection: 'back',\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Jump to an arbitrary step by id. Validation is skipped\n\t\t * (the consumer is responsible for re-running it before\n\t\t * `submit()` if it matters).\n\t\t *\n\t\t * @param {string} stepId Target step id.\n\t\t * @return {void}\n\t\t */\n\t\tjumpTo(stepId) {\n\t\t\tconst idx = this.steps.findIndex((s) => s.id === stepId)\n\t\t\tif (idx < 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.validationError = ''\n\t\t\tthis.currentIndex = idx\n\t\t\tthis.$emit('step-change', {\n\t\t\t\tstepId: this.currentStep.id,\n\t\t\t\tstepIndex: this.currentIndex,\n\t\t\t\tdirection: 'jump',\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Submit the wizard. Runs the current step's validation;\n\t\t * on pass flips `loading` on and emits `@submit` with the\n\t\t * accumulated `stepData`. The parent is responsible for\n\t\t * calling `setResult()` to flip into the result phase.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync submit() {\n\t\t\tconst ok = await this.runValidation()\n\t\t\tif (!ok) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\t/**\n\t\t\t * @event submit Emitted when the user reaches the final\n\t\t\t * step and clicks Submit (or `submit()` is called\n\t\t\t * programmatically from a slot scope). Payload is the\n\t\t\t * accumulated `stepData`.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('submit', { ...this.stepData })\n\t\t},\n\n\t\t/**\n\t\t * Public method called by the parent to switch the wizard\n\t\t * into the result phase.\n\t\t *\n\t\t * @param {object} result `{ success?, message?, error? }`.\n\t\t * @return {void}\n\t\t */\n\t\tsetResult(result) {\n\t\t\tthis.result = result || { success: true }\n\t\t\tthis.loading = false\n\t\t},\n\n\t\t/**\n\t\t * Public method for a NON-terminal submit failure: stops the\n\t\t * loading spinner and surfaces `message` as the error banner above\n\t\t * the (still-editable) current step, WITHOUT entering the result\n\t\t * phase — so the user can correct the input and resubmit. Use this\n\t\t * instead of `setResult({ error })` when the failure is recoverable\n\t\t * (e.g. a taken slug, a validation conflict).\n\t\t *\n\t\t * @param {string} message The error to show above the step body.\n\t\t * @return {void}\n\t\t */\n\t\tsetError(message) {\n\t\t\tthis.loading = false\n\t\t\tthis.validationError = message || ''\n\t\t},\n\n\t\t/**\n\t\t * Close-button handler. Resets local state so the next open\n\t\t * starts fresh and emits `@close`.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonClose() {\n\t\t\tthis.result = null\n\t\t\tthis.loading = false\n\t\t\tthis.validationError = ''\n\t\t\tthis.currentIndex = this.resolveInitialIndex()\n\t\t\tthis.stepData = { ...this.defaults }\n\t\t\tthis.dialogOpen = false\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\t},\n}\n</script>\n\n<style scoped>\n.cn-wizard-dialog__progress {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tjustify-content: center;\n\tgap: 0;\n\tpadding: 8px 0 4px;\n\tmargin: 0 0 20px;\n\tlist-style: none;\n}\n\n.cn-wizard-dialog__progress-item {\n\tposition: relative;\n\tflex: 1 1 0;\n\tmin-width: 0;\n\tmax-width: 180px;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 6px;\n\ttext-align: center;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8125rem;\n}\n\n.cn-wizard-dialog__progress-item--clickable {\n\tcursor: pointer;\n}\n\n/* Marker circle */\n.cn-wizard-dialog__progress-dot {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 34px;\n\theight: 34px;\n\tborder-radius: 50%;\n\tborder: 2px solid var(--color-border-dark);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n\tfont-weight: 600;\n\ttransition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;\n\tz-index: 1;\n}\n\n.cn-wizard-dialog__progress-check {\n\tfont-size: 16px;\n\tline-height: 1;\n}\n\n.cn-wizard-dialog__progress-label {\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n/* Connector line between this marker and the next, centered on the marker. */\n.cn-wizard-dialog__progress-connector {\n\tposition: absolute;\n\ttop: 17px;\n\tleft: calc(50% + 21px);\n\tright: calc(-50% + 21px);\n\theight: 2px;\n\tbackground: var(--color-border-dark);\n}\n\n.cn-wizard-dialog__progress-connector--done {\n\tbackground: var(--color-primary-element);\n}\n\n/* States */\n.cn-wizard-dialog__progress-item--active .cn-wizard-dialog__progress-dot {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n}\n\n.cn-wizard-dialog__progress-item--active .cn-wizard-dialog__progress-label {\n\tcolor: var(--color-main-text);\n\tfont-weight: 600;\n}\n\n.cn-wizard-dialog__progress-item--done .cn-wizard-dialog__progress-dot {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n}\n\n.cn-wizard-dialog__progress-item--done .cn-wizard-dialog__progress-label {\n\tcolor: var(--color-main-text);\n}\n\n.cn-wizard-dialog__progress-item--clickable:hover .cn-wizard-dialog__progress-label {\n\tcolor: var(--color-primary-element);\n}\n\n.cn-wizard-dialog__step-body {\n\tmin-height: 200px;\n}\n\n.cn-wizard-dialog__result {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n</style>\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnWizardDialog.vue2.js","sources":["../../../../src/components/CnWizardDialog/CnWizardDialog.vue"],"sourcesContent":["<template>\n\t<NcDialog\n\t\t:name=\"dialogTitle\"\n\t\tsize=\"large\"\n\t\t:open=\"dialogOpen\"\n\t\t:noClose=\"loading || !cancellable\"\n\t\tdata-testid=\"cn-modal\"\n\t\tdata-testid-modal=\"cn-wizard-dialog\"\n\t\t@update:open=\"dialogOpen = $event\"\n\t\t@closing=\"onClose\">\n\t\t<!-- Result phase: rendered after a successful `submit()`. -->\n\t\t<div v-if=\"result !== null\"\n\t\t\tclass=\"cn-wizard-dialog__result\"\n\t\t\tdata-testid-phase=\"result\">\n\t\t\t<NcNoteCard v-if=\"result.success\" type=\"success\">\n\t\t\t\t{{ result.message || successText }}\n\t\t\t</NcNoteCard>\n\t\t\t<NcNoteCard v-else-if=\"result.error\" type=\"error\">\n\t\t\t\t{{ result.error }}\n\t\t\t</NcNoteCard>\n\t\t\t<!-- @slot result-extra Optional extra content (e.g. batch\n\t\t\t progress poller, summary table) rendered below the\n\t\t\t success/error banner. Scope: { result }. -->\n\t\t\t<slot name=\"result-extra\" :result=\"result\" />\n\t\t</div>\n\n\t\t<!-- Wizard phase: progress bar + current step body. -->\n\t\t<div v-else\n\t\t\tclass=\"cn-wizard-dialog__wizard\"\n\t\t\tdata-testid-phase=\"form\">\n\t\t\t<!-- Progress indicator. Click jumps back to a completed\n\t\t\t step when allowJumpBack is true. -->\n\t\t\t<ol class=\"cn-wizard-dialog__progress\" role=\"tablist\">\n\t\t\t\t<li v-for=\"(step, idx) in steps\"\n\t\t\t\t\t:key=\"step.id\"\n\t\t\t\t\tclass=\"cn-wizard-dialog__progress-item\"\n\t\t\t\t\t:class=\"{\n\t\t\t\t\t\t'cn-wizard-dialog__progress-item--active': idx === currentIndex,\n\t\t\t\t\t\t'cn-wizard-dialog__progress-item--done': idx < currentIndex,\n\t\t\t\t\t\t'cn-wizard-dialog__progress-item--clickable': allowJumpBack && idx < currentIndex,\n\t\t\t\t\t}\"\n\t\t\t\t\trole=\"tab\"\n\t\t\t\t\t:aria-selected=\"idx === currentIndex\"\n\t\t\t\t\t@click=\"allowJumpBack && idx < currentIndex ? jumpTo(step.id) : null\">\n\t\t\t\t\t<span class=\"cn-wizard-dialog__progress-dot\">\n\t\t\t\t\t\t<span v-if=\"idx < currentIndex\" class=\"cn-wizard-dialog__progress-check\" aria-hidden=\"true\">✓</span>\n\t\t\t\t\t\t<span v-else>{{ idx + 1 }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t<span class=\"cn-wizard-dialog__progress-label\">{{ step.label }}</span>\n\t\t\t\t\t<span v-if=\"idx < steps.length - 1\"\n\t\t\t\t\t\tclass=\"cn-wizard-dialog__progress-connector\"\n\t\t\t\t\t\t:class=\"{ 'cn-wizard-dialog__progress-connector--done': idx < currentIndex }\"\n\t\t\t\t\t\taria-hidden=\"true\" />\n\t\t\t\t</li>\n\t\t\t</ol>\n\n\t\t\t<!-- Validation error from the current step's validate() result. -->\n\t\t\t<NcNoteCard v-if=\"validationError\" type=\"error\">\n\t\t\t\t{{ validationError }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<!-- Current step body — a dynamic slot named by step id. -->\n\t\t\t<div class=\"cn-wizard-dialog__step-body\" :data-step-id=\"currentStep.id\">\n\t\t\t\t<!-- @slot step-{id} Per-step body. Scope: { next,\n\t\t\t\t back, jumpTo, submit, currentStep, stepIndex,\n\t\t\t\t totalSteps, stepData, setStepData, isFirst,\n\t\t\t\t isLast }. -->\n\t\t\t\t<slot\n\t\t\t\t\t:name=\"'step-' + currentStep.id\"\n\t\t\t\t\t:next=\"next\"\n\t\t\t\t\t:back=\"back\"\n\t\t\t\t\t:jumpTo=\"jumpTo\"\n\t\t\t\t\t:submit=\"submit\"\n\t\t\t\t\t:currentStep=\"currentStep\"\n\t\t\t\t\t:stepIndex=\"currentIndex\"\n\t\t\t\t\t:totalSteps=\"steps.length\"\n\t\t\t\t\t:stepData=\"stepData\"\n\t\t\t\t\t:setStepData=\"setStepData\"\n\t\t\t\t\t:isFirst=\"isFirst\"\n\t\t\t\t\t:isLast=\"isLast\" />\n\t\t\t</div>\n\t\t</div>\n\n\t\t<template #actions>\n\t\t\t<!-- Result phase actions: just Close. -->\n\t\t\t<NcButton v-if=\"result !== null\" @click=\"onClose\">\n\t\t\t\t{{ closeLabel }}\n\t\t\t</NcButton>\n\n\t\t\t<!-- Wizard phase actions: Cancel + Back + Next/Submit.\n\t\t\t The testids exist because these buttons are the only way\n\t\t\t through a wizard and their LABELS are translated: an e2e that\n\t\t\t clicks \"Next\" passes on an English instance and times out on a\n\t\t\t Dutch one, reporting a broken dialog rather than a missing\n\t\t\t string. -->\n\t\t\t<template v-else>\n\t\t\t\t<NcButton v-if=\"cancellable\" data-testid=\"cn-wizard-cancel\" @click=\"onClose\">\n\t\t\t\t\t{{ cancelLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton v-if=\"!isFirst\"\n\t\t\t\t\tdata-testid=\"cn-wizard-back\"\n\t\t\t\t\t:disabled=\"loading\"\n\t\t\t\t\t@click=\"back\">\n\t\t\t\t\t{{ backLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton variant=\"primary\"\n\t\t\t\t\tdata-testid=\"cn-wizard-next\"\n\t\t\t\t\t:disabled=\"loading || nextDisabled\"\n\t\t\t\t\t@click=\"isLast ? submit() : next()\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<NcLoadingIcon v-if=\"loading\" :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ isLast ? submitLabel : nextLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</template>\n\t\t</template>\n\t</NcDialog>\n</template>\n\n<script>\nimport { NcButton, NcDialog, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\n\n/**\n * CnWizardDialog — Multi-step modal with per-step slots,\n * back/next navigation, optional per-step validation, and a single\n * result phase.\n *\n * Each step is declared in the `steps[]` prop (`{ id, label,\n * optional?, icon? }`) and rendered via a `#step-{id}` named slot\n * with the navigation API exposed through the slot scope. The\n * wizard owns the progress indicator, the back/next chrome, the\n * loading lifecycle, and the result-phase banner — the consumer\n * owns the per-step body + the data collection logic.\n *\n * Per-step validation is opt-in via the `validate` prop:\n *\n * ```js\n * async function validate(stepId, stepData) {\n * if (stepId === 'audience' && !stepData.cohort) {\n * return 'Pick a cohort first.'\n * }\n * return true\n * }\n * ```\n *\n * Returning `true` (or any truthy non-string) advances; returning a\n * string surfaces it as an error banner above the step body and\n * blocks the navigation.\n *\n * The result phase is entered by the consumer calling\n * `setResult({ success?, message?, error? })` after `@submit` fires.\n *\n * Use [`CnFormDialog`](../CnFormDialog/) for single-step forms.\n * Use [`CnExportWizard`](../CnExportWizard/) for the specific\n * scope+format+delivery export-trigger shape.\n *\n * ```vue\n * <CnWizardDialog\n * ref=\"wizard\"\n * dialog-title=\"Bulk enrol\"\n * :steps=\"[\n * { id: 'audience', label: 'Audience' },\n * { id: 'course', label: 'Course' },\n * { id: 'confirm', label: 'Confirm' },\n * ]\"\n * :validate=\"validateStep\"\n * @submit=\"onSubmit\"\n * @close=\"show = false\">\n * <template #step-audience=\"{ next, stepData, setStepData }\">\n * <CohortPicker :value=\"stepData.cohort\" @input=\"v => setStepData({ cohort: v })\" />\n * </template>\n * <template #step-course=\"{ next, back, stepData, setStepData }\">\n * <CoursePicker :value=\"stepData.course\" @input=\"v => setStepData({ course: v })\" />\n * </template>\n * <template #step-confirm=\"{ back, submit, stepData }\">\n * <p>Enrol {{ stepData.audience.cohort.size }} learners into {{ stepData.course.course.name }}?</p>\n * </template>\n * </CnWizardDialog>\n * ```\n */\nexport default {\n\tname: 'CnWizardDialog',\n\tcomponents: { NcDialog, NcButton, NcNoteCard, NcLoadingIcon },\n\tprops: {\n\t\t/**\n\t\t * Step declarations. Order is significant — the wizard\n\t\t * navigates linearly between adjacent steps unless the\n\t\t * consumer calls `jumpTo()` from a slot scope.\n\t\t *\n\t\t * @type {Array<{ id: string, label: string, optional?: boolean, icon?: string }>}\n\t\t */\n\t\tsteps: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t\tvalidator: (v) => Array.isArray(v) && v.length > 0\n\t\t\t\t&& v.every((s) => s && typeof s.id === 'string' && typeof s.label === 'string'),\n\t\t},\n\n\t\t/**\n\t\t * Dialog title shown in the NcDialog header.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdialogTitle: {\n\t\t\ttype: String,\n\t\t\tdefault: 'Wizard',\n\t\t},\n\n\t\t/**\n\t\t * Step id to start on. Defaults to the first step in `steps[]`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tinitialStep: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Per-step validator. Async function returning `true` to\n\t\t * advance, a string to surface as an error banner + block\n\t\t * navigation, or `false` to block silently. Called from\n\t\t * `next()` and `submit()`. When omitted, navigation always\n\t\t * proceeds.\n\t\t *\n\t\t * @type {(stepId: string, stepData: object) => Promise<boolean|string>}\n\t\t */\n\t\tvalidate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether the progress indicator allows jumping back to a\n\t\t * completed step via click. Forward jumps are NEVER allowed\n\t\t * via the progress indicator (consumer-side `jumpTo` is\n\t\t * unrestricted).\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tallowJumpBack: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Seed values for `stepData` (the cross-step object passed\n\t\t * to every slot scope). Useful for prefilling on edit-style\n\t\t * wizards.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tdefaults: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Cancel-button label (wizard phase). */\n\t\tcancelLabel: { type: String, default: 'Cancel' },\n\t\t/** Back-button label. */\n\t\tbackLabel: { type: String, default: 'Back' },\n\t\t/** Next-button label (intermediate steps). */\n\t\tnextLabel: { type: String, default: 'Next' },\n\t\t/** Submit-button label (last step). */\n\t\tsubmitLabel: { type: String, default: 'Submit' },\n\t\t/** Close-button label (result phase). */\n\t\tcloseLabel: { type: String, default: 'Close' },\n\t\t/** Success banner default text when result.message is empty. */\n\t\tsuccessText: { type: String, default: 'Done.' },\n\t\t/**\n\t\t * Whether the dialog can be dismissed (Cancel button, ESC, backdrop\n\t\t * click) before reaching the result phase. `false` hides Cancel and\n\t\t * disables all other close affordances — use for a step that must\n\t\t * be completed, not merely skipped. The result-phase Close button is\n\t\t * unaffected (a completed wizard is always dismissible).\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 * Disable the Next / Submit button, whatever the step's `validate`\n\t\t * would say. For a host that knows the wizard cannot go on yet, such as\n\t\t * `CnSetupWizard` while a required app is missing.\n\t\t *\n\t\t * @type {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\tnextDisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\temits: ['close', 'step-change', 'submit'],\n\tdata() {\n\t\tconst initialIndex = this.resolveInitialIndex()\n\t\treturn {\n\t\t\tcurrentIndex: initialIndex,\n\t\t\tstepData: { ...this.defaults },\n\t\t\tloading: false,\n\t\t\tresult: null,\n\t\t\tvalidationError: '',\n\t\t\t/**\n\t\t\t * Drives NcDialog's `open` prop directly. NcDialog's own `open`\n\t\t\t * prop defaults to `true` and is NOT self-managing on close — its\n\t\t\t * internal `showModal` flag resets back to `true` ~300ms after\n\t\t\t * any close (animation bookkeeping for next time it's reopened\n\t\t\t * via the prop), so without an explicit `false` here the dialog\n\t\t\t * reopens itself right after Cancel/ESC/backdrop-click.\n\t\t\t */\n\t\t\tdialogOpen: true,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The currently-active step object from `steps[]`.\n\t\t *\n\t\t * @return {object} The current step definition.\n\t\t */\n\t\tcurrentStep() {\n\t\t\treturn this.steps[this.currentIndex] || this.steps[0]\n\t\t},\n\n\t\t/**\n\t\t * Whether the current step is the first one.\n\t\t *\n\t\t * @return {boolean} True when on the first step.\n\t\t */\n\t\tisFirst() {\n\t\t\treturn this.currentIndex === 0\n\t\t},\n\n\t\t/**\n\t\t * Whether the current step is the last one (drives the\n\t\t * Next → Submit label flip).\n\t\t *\n\t\t * @return {boolean} True when on the last step.\n\t\t */\n\t\tisLast() {\n\t\t\treturn this.currentIndex === this.steps.length - 1\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Resolve the index of `initialStep` (falling back to 0 when\n\t\t * unset / not found).\n\t\t *\n\t\t * @return {number} Step index in the range [0, steps.length).\n\t\t */\n\t\tresolveInitialIndex() {\n\t\t\tif (!this.initialStep) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tconst idx = this.steps.findIndex((s) => s.id === this.initialStep)\n\t\t\treturn idx >= 0 ? idx : 0\n\t\t},\n\n\t\t/**\n\t\t * Merge a partial step-data object onto the shared state.\n\t\t * Each slot scope receives this so consumers can write their\n\t\t * step's data without owning a `data()` block in their slot\n\t\t * component.\n\t\t *\n\t\t * @param {object} partial Fields to merge.\n\t\t * @return {void}\n\t\t */\n\t\tsetStepData(partial) {\n\t\t\tthis.stepData = { ...this.stepData, ...partial }\n\t\t},\n\n\t\t/**\n\t\t * Run the consumer's `validate` prop (if provided) for the\n\t\t * current step. Returns true on pass; sets `validationError`\n\t\t * and returns false on fail.\n\t\t *\n\t\t * @return {Promise<boolean>} Whether validation passed.\n\t\t */\n\t\tasync runValidation() {\n\t\t\tthis.validationError = ''\n\t\t\tif (typeof this.validate !== 'function') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst outcome = await this.validate(this.currentStep.id, this.stepData)\n\t\t\t\tif (outcome === true) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\tif (typeof outcome === 'string') {\n\t\t\t\t\tthis.validationError = outcome\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn Boolean(outcome)\n\t\t\t} catch (e) {\n\t\t\t\tthis.validationError = e && e.message ? e.message : 'Validation failed.'\n\t\t\t\treturn false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Advance to the next step after validation. Emits\n\t\t * `@step-change` with the new step id.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync next() {\n\t\t\tif (this.nextDisabled) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.isLast) {\n\t\t\t\treturn this.submit()\n\t\t\t}\n\t\t\tconst ok = await this.runValidation()\n\t\t\tif (!ok) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.currentIndex += 1\n\t\t\t/**\n\t\t\t * @event step-change Emitted on every step navigation\n\t\t\t * (next / back / jumpTo / submit).\n\t\t\t * @type {{ stepId: string, stepIndex: number, direction: 'next'|'back'|'jump' }}\n\t\t\t */\n\t\t\tthis.$emit('step-change', {\n\t\t\t\tstepId: this.currentStep.id,\n\t\t\t\tstepIndex: this.currentIndex,\n\t\t\t\tdirection: 'next',\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Step back to the previous step. No validation runs on\n\t\t * back navigation — consumers can reach back without\n\t\t * losing the current step's draft.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tback() {\n\t\t\tif (this.isFirst) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.validationError = ''\n\t\t\tthis.currentIndex -= 1\n\t\t\tthis.$emit('step-change', {\n\t\t\t\tstepId: this.currentStep.id,\n\t\t\t\tstepIndex: this.currentIndex,\n\t\t\t\tdirection: 'back',\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Jump to an arbitrary step by id. Validation is skipped\n\t\t * (the consumer is responsible for re-running it before\n\t\t * `submit()` if it matters).\n\t\t *\n\t\t * @param {string} stepId Target step id.\n\t\t * @return {void}\n\t\t */\n\t\tjumpTo(stepId) {\n\t\t\tconst idx = this.steps.findIndex((s) => s.id === stepId)\n\t\t\tif (idx < 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.validationError = ''\n\t\t\tthis.currentIndex = idx\n\t\t\tthis.$emit('step-change', {\n\t\t\t\tstepId: this.currentStep.id,\n\t\t\t\tstepIndex: this.currentIndex,\n\t\t\t\tdirection: 'jump',\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Submit the wizard. Runs the current step's validation;\n\t\t * on pass flips `loading` on and emits `@submit` with the\n\t\t * accumulated `stepData`. The parent is responsible for\n\t\t * calling `setResult()` to flip into the result phase.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync submit() {\n\t\t\tif (this.nextDisabled) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst ok = await this.runValidation()\n\t\t\tif (!ok) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\t/**\n\t\t\t * @event submit Emitted when the user reaches the final\n\t\t\t * step and clicks Submit (or `submit()` is called\n\t\t\t * programmatically from a slot scope). Payload is the\n\t\t\t * accumulated `stepData`.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('submit', { ...this.stepData })\n\t\t},\n\n\t\t/**\n\t\t * Public method called by the parent to switch the wizard\n\t\t * into the result phase.\n\t\t *\n\t\t * @param {object} result `{ success?, message?, error? }`.\n\t\t * @return {void}\n\t\t */\n\t\tsetResult(result) {\n\t\t\tthis.result = result || { success: true }\n\t\t\tthis.loading = false\n\t\t},\n\n\t\t/**\n\t\t * Public method for a NON-terminal submit failure: stops the\n\t\t * loading spinner and surfaces `message` as the error banner above\n\t\t * the (still-editable) current step, WITHOUT entering the result\n\t\t * phase — so the user can correct the input and resubmit. Use this\n\t\t * instead of `setResult({ error })` when the failure is recoverable\n\t\t * (e.g. a taken slug, a validation conflict).\n\t\t *\n\t\t * @param {string} message The error to show above the step body.\n\t\t * @return {void}\n\t\t */\n\t\tsetError(message) {\n\t\t\tthis.loading = false\n\t\t\tthis.validationError = message || ''\n\t\t},\n\n\t\t/**\n\t\t * Close-button handler. Resets local state so the next open\n\t\t * starts fresh and emits `@close`.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonClose() {\n\t\t\tthis.result = null\n\t\t\tthis.loading = false\n\t\t\tthis.validationError = ''\n\t\t\tthis.currentIndex = this.resolveInitialIndex()\n\t\t\tthis.stepData = { ...this.defaults }\n\t\t\tthis.dialogOpen = false\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\t},\n}\n</script>\n\n<style scoped>\n.cn-wizard-dialog__progress {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tjustify-content: center;\n\tgap: 0;\n\tpadding: 8px 0 4px;\n\tmargin: 0 0 20px;\n\tlist-style: none;\n}\n\n.cn-wizard-dialog__progress-item {\n\tposition: relative;\n\tflex: 1 1 0;\n\tmin-width: 0;\n\tmax-width: 180px;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 6px;\n\ttext-align: center;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8125rem;\n}\n\n.cn-wizard-dialog__progress-item--clickable {\n\tcursor: pointer;\n}\n\n/* Marker circle */\n.cn-wizard-dialog__progress-dot {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 34px;\n\theight: 34px;\n\tborder-radius: 50%;\n\tborder: 2px solid var(--color-border-dark);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n\tfont-weight: 600;\n\ttransition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;\n\tz-index: 1;\n}\n\n.cn-wizard-dialog__progress-check {\n\tfont-size: 16px;\n\tline-height: 1;\n}\n\n.cn-wizard-dialog__progress-label {\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n/* Connector line between this marker and the next, centered on the marker. */\n.cn-wizard-dialog__progress-connector {\n\tposition: absolute;\n\ttop: 17px;\n\tleft: calc(50% + 21px);\n\tright: calc(-50% + 21px);\n\theight: 2px;\n\tbackground: var(--color-border-dark);\n}\n\n.cn-wizard-dialog__progress-connector--done {\n\tbackground: var(--color-primary-element);\n}\n\n/* States */\n.cn-wizard-dialog__progress-item--active .cn-wizard-dialog__progress-dot {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n}\n\n.cn-wizard-dialog__progress-item--active .cn-wizard-dialog__progress-label {\n\tcolor: var(--color-main-text);\n\tfont-weight: 600;\n}\n\n.cn-wizard-dialog__progress-item--done .cn-wizard-dialog__progress-dot {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n}\n\n.cn-wizard-dialog__progress-item--done .cn-wizard-dialog__progress-label {\n\tcolor: var(--color-main-text);\n}\n\n.cn-wizard-dialog__progress-item--clickable:hover .cn-wizard-dialog__progress-label {\n\tcolor: var(--color-primary-element);\n}\n\n.cn-wizard-dialog__step-body {\n\tmin-height: 200px;\n}\n\n.cn-wizard-dialog__result {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n</style>\n"],"names":[],"mappings":"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"}
|
|
@@ -81,7 +81,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
81
81
|
createVNode(_component_NcButton, {
|
|
82
82
|
variant: "primary",
|
|
83
83
|
"data-testid": "cn-wizard-next",
|
|
84
|
-
disabled: $data.loading,
|
|
84
|
+
disabled: $data.loading || $props.nextDisabled,
|
|
85
85
|
onClick: _cache[0] || (_cache[0] = $event => ($options.isLast ? $options.submit() : $options.next()))
|
|
86
86
|
}, {
|
|
87
87
|
icon: withCtx(() => [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnWizardDialog.vue3.js","sources":["../../../../src/components/CnWizardDialog/CnWizardDialog.vue?vue&type=template&id=27d51a62&lang.js"],"sourcesContent":["<template>\n\t<NcDialog\n\t\t:name=\"dialogTitle\"\n\t\tsize=\"large\"\n\t\t:open=\"dialogOpen\"\n\t\t:noClose=\"loading || !cancellable\"\n\t\tdata-testid=\"cn-modal\"\n\t\tdata-testid-modal=\"cn-wizard-dialog\"\n\t\t@update:open=\"dialogOpen = $event\"\n\t\t@closing=\"onClose\">\n\t\t<!-- Result phase: rendered after a successful `submit()`. -->\n\t\t<div v-if=\"result !== null\"\n\t\t\tclass=\"cn-wizard-dialog__result\"\n\t\t\tdata-testid-phase=\"result\">\n\t\t\t<NcNoteCard v-if=\"result.success\" type=\"success\">\n\t\t\t\t{{ result.message || successText }}\n\t\t\t</NcNoteCard>\n\t\t\t<NcNoteCard v-else-if=\"result.error\" type=\"error\">\n\t\t\t\t{{ result.error }}\n\t\t\t</NcNoteCard>\n\t\t\t<!-- @slot result-extra Optional extra content (e.g. batch\n\t\t\t progress poller, summary table) rendered below the\n\t\t\t success/error banner. Scope: { result }. -->\n\t\t\t<slot name=\"result-extra\" :result=\"result\" />\n\t\t</div>\n\n\t\t<!-- Wizard phase: progress bar + current step body. -->\n\t\t<div v-else\n\t\t\tclass=\"cn-wizard-dialog__wizard\"\n\t\t\tdata-testid-phase=\"form\">\n\t\t\t<!-- Progress indicator. Click jumps back to a completed\n\t\t\t step when allowJumpBack is true. -->\n\t\t\t<ol class=\"cn-wizard-dialog__progress\" role=\"tablist\">\n\t\t\t\t<li v-for=\"(step, idx) in steps\"\n\t\t\t\t\t:key=\"step.id\"\n\t\t\t\t\tclass=\"cn-wizard-dialog__progress-item\"\n\t\t\t\t\t:class=\"{\n\t\t\t\t\t\t'cn-wizard-dialog__progress-item--active': idx === currentIndex,\n\t\t\t\t\t\t'cn-wizard-dialog__progress-item--done': idx < currentIndex,\n\t\t\t\t\t\t'cn-wizard-dialog__progress-item--clickable': allowJumpBack && idx < currentIndex,\n\t\t\t\t\t}\"\n\t\t\t\t\trole=\"tab\"\n\t\t\t\t\t:aria-selected=\"idx === currentIndex\"\n\t\t\t\t\t@click=\"allowJumpBack && idx < currentIndex ? jumpTo(step.id) : null\">\n\t\t\t\t\t<span class=\"cn-wizard-dialog__progress-dot\">\n\t\t\t\t\t\t<span v-if=\"idx < currentIndex\" class=\"cn-wizard-dialog__progress-check\" aria-hidden=\"true\">✓</span>\n\t\t\t\t\t\t<span v-else>{{ idx + 1 }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t<span class=\"cn-wizard-dialog__progress-label\">{{ step.label }}</span>\n\t\t\t\t\t<span v-if=\"idx < steps.length - 1\"\n\t\t\t\t\t\tclass=\"cn-wizard-dialog__progress-connector\"\n\t\t\t\t\t\t:class=\"{ 'cn-wizard-dialog__progress-connector--done': idx < currentIndex }\"\n\t\t\t\t\t\taria-hidden=\"true\" />\n\t\t\t\t</li>\n\t\t\t</ol>\n\n\t\t\t<!-- Validation error from the current step's validate() result. -->\n\t\t\t<NcNoteCard v-if=\"validationError\" type=\"error\">\n\t\t\t\t{{ validationError }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<!-- Current step body — a dynamic slot named by step id. -->\n\t\t\t<div class=\"cn-wizard-dialog__step-body\" :data-step-id=\"currentStep.id\">\n\t\t\t\t<!-- @slot step-{id} Per-step body. Scope: { next,\n\t\t\t\t back, jumpTo, submit, currentStep, stepIndex,\n\t\t\t\t totalSteps, stepData, setStepData, isFirst,\n\t\t\t\t isLast }. -->\n\t\t\t\t<slot\n\t\t\t\t\t:name=\"'step-' + currentStep.id\"\n\t\t\t\t\t:next=\"next\"\n\t\t\t\t\t:back=\"back\"\n\t\t\t\t\t:jumpTo=\"jumpTo\"\n\t\t\t\t\t:submit=\"submit\"\n\t\t\t\t\t:currentStep=\"currentStep\"\n\t\t\t\t\t:stepIndex=\"currentIndex\"\n\t\t\t\t\t:totalSteps=\"steps.length\"\n\t\t\t\t\t:stepData=\"stepData\"\n\t\t\t\t\t:setStepData=\"setStepData\"\n\t\t\t\t\t:isFirst=\"isFirst\"\n\t\t\t\t\t:isLast=\"isLast\" />\n\t\t\t</div>\n\t\t</div>\n\n\t\t<template #actions>\n\t\t\t<!-- Result phase actions: just Close. -->\n\t\t\t<NcButton v-if=\"result !== null\" @click=\"onClose\">\n\t\t\t\t{{ closeLabel }}\n\t\t\t</NcButton>\n\n\t\t\t<!-- Wizard phase actions: Cancel + Back + Next/Submit.\n\t\t\t The testids exist because these buttons are the only way\n\t\t\t through a wizard and their LABELS are translated: an e2e that\n\t\t\t clicks \"Next\" passes on an English instance and times out on a\n\t\t\t Dutch one, reporting a broken dialog rather than a missing\n\t\t\t string. -->\n\t\t\t<template v-else>\n\t\t\t\t<NcButton v-if=\"cancellable\" data-testid=\"cn-wizard-cancel\" @click=\"onClose\">\n\t\t\t\t\t{{ cancelLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton v-if=\"!isFirst\"\n\t\t\t\t\tdata-testid=\"cn-wizard-back\"\n\t\t\t\t\t:disabled=\"loading\"\n\t\t\t\t\t@click=\"back\">\n\t\t\t\t\t{{ backLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton variant=\"primary\"\n\t\t\t\t\tdata-testid=\"cn-wizard-next\"\n\t\t\t\t\t:disabled=\"loading\"\n\t\t\t\t\t@click=\"isLast ? submit() : next()\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<NcLoadingIcon v-if=\"loading\" :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ isLast ? submitLabel : nextLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</template>\n\t\t</template>\n\t</NcDialog>\n</template>\n\n<script>\nimport { NcButton, NcDialog, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\n\n/**\n * CnWizardDialog — Multi-step modal with per-step slots,\n * back/next navigation, optional per-step validation, and a single\n * result phase.\n *\n * Each step is declared in the `steps[]` prop (`{ id, label,\n * optional?, icon? }`) and rendered via a `#step-{id}` named slot\n * with the navigation API exposed through the slot scope. The\n * wizard owns the progress indicator, the back/next chrome, the\n * loading lifecycle, and the result-phase banner — the consumer\n * owns the per-step body + the data collection logic.\n *\n * Per-step validation is opt-in via the `validate` prop:\n *\n * ```js\n * async function validate(stepId, stepData) {\n * if (stepId === 'audience' && !stepData.cohort) {\n * return 'Pick a cohort first.'\n * }\n * return true\n * }\n * ```\n *\n * Returning `true` (or any truthy non-string) advances; returning a\n * string surfaces it as an error banner above the step body and\n * blocks the navigation.\n *\n * The result phase is entered by the consumer calling\n * `setResult({ success?, message?, error? })` after `@submit` fires.\n *\n * Use [`CnFormDialog`](../CnFormDialog/) for single-step forms.\n * Use [`CnExportWizard`](../CnExportWizard/) for the specific\n * scope+format+delivery export-trigger shape.\n *\n * ```vue\n * <CnWizardDialog\n * ref=\"wizard\"\n * dialog-title=\"Bulk enrol\"\n * :steps=\"[\n * { id: 'audience', label: 'Audience' },\n * { id: 'course', label: 'Course' },\n * { id: 'confirm', label: 'Confirm' },\n * ]\"\n * :validate=\"validateStep\"\n * @submit=\"onSubmit\"\n * @close=\"show = false\">\n * <template #step-audience=\"{ next, stepData, setStepData }\">\n * <CohortPicker :value=\"stepData.cohort\" @input=\"v => setStepData({ cohort: v })\" />\n * </template>\n * <template #step-course=\"{ next, back, stepData, setStepData }\">\n * <CoursePicker :value=\"stepData.course\" @input=\"v => setStepData({ course: v })\" />\n * </template>\n * <template #step-confirm=\"{ back, submit, stepData }\">\n * <p>Enrol {{ stepData.audience.cohort.size }} learners into {{ stepData.course.course.name }}?</p>\n * </template>\n * </CnWizardDialog>\n * ```\n */\nexport default {\n\tname: 'CnWizardDialog',\n\tcomponents: { NcDialog, NcButton, NcNoteCard, NcLoadingIcon },\n\tprops: {\n\t\t/**\n\t\t * Step declarations. Order is significant — the wizard\n\t\t * navigates linearly between adjacent steps unless the\n\t\t * consumer calls `jumpTo()` from a slot scope.\n\t\t *\n\t\t * @type {Array<{ id: string, label: string, optional?: boolean, icon?: string }>}\n\t\t */\n\t\tsteps: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t\tvalidator: (v) => Array.isArray(v) && v.length > 0\n\t\t\t\t&& v.every((s) => s && typeof s.id === 'string' && typeof s.label === 'string'),\n\t\t},\n\n\t\t/**\n\t\t * Dialog title shown in the NcDialog header.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdialogTitle: {\n\t\t\ttype: String,\n\t\t\tdefault: 'Wizard',\n\t\t},\n\n\t\t/**\n\t\t * Step id to start on. Defaults to the first step in `steps[]`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tinitialStep: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Per-step validator. Async function returning `true` to\n\t\t * advance, a string to surface as an error banner + block\n\t\t * navigation, or `false` to block silently. Called from\n\t\t * `next()` and `submit()`. When omitted, navigation always\n\t\t * proceeds.\n\t\t *\n\t\t * @type {(stepId: string, stepData: object) => Promise<boolean|string>}\n\t\t */\n\t\tvalidate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether the progress indicator allows jumping back to a\n\t\t * completed step via click. Forward jumps are NEVER allowed\n\t\t * via the progress indicator (consumer-side `jumpTo` is\n\t\t * unrestricted).\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tallowJumpBack: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Seed values for `stepData` (the cross-step object passed\n\t\t * to every slot scope). Useful for prefilling on edit-style\n\t\t * wizards.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tdefaults: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Cancel-button label (wizard phase). */\n\t\tcancelLabel: { type: String, default: 'Cancel' },\n\t\t/** Back-button label. */\n\t\tbackLabel: { type: String, default: 'Back' },\n\t\t/** Next-button label (intermediate steps). */\n\t\tnextLabel: { type: String, default: 'Next' },\n\t\t/** Submit-button label (last step). */\n\t\tsubmitLabel: { type: String, default: 'Submit' },\n\t\t/** Close-button label (result phase). */\n\t\tcloseLabel: { type: String, default: 'Close' },\n\t\t/** Success banner default text when result.message is empty. */\n\t\tsuccessText: { type: String, default: 'Done.' },\n\t\t/**\n\t\t * Whether the dialog can be dismissed (Cancel button, ESC, backdrop\n\t\t * click) before reaching the result phase. `false` hides Cancel and\n\t\t * disables all other close affordances — use for a step that must\n\t\t * be completed, not merely skipped. The result-phase Close button is\n\t\t * unaffected (a completed wizard is always dismissible).\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\t},\n\n\temits: ['close', 'step-change', 'submit'],\n\tdata() {\n\t\tconst initialIndex = this.resolveInitialIndex()\n\t\treturn {\n\t\t\tcurrentIndex: initialIndex,\n\t\t\tstepData: { ...this.defaults },\n\t\t\tloading: false,\n\t\t\tresult: null,\n\t\t\tvalidationError: '',\n\t\t\t/**\n\t\t\t * Drives NcDialog's `open` prop directly. NcDialog's own `open`\n\t\t\t * prop defaults to `true` and is NOT self-managing on close — its\n\t\t\t * internal `showModal` flag resets back to `true` ~300ms after\n\t\t\t * any close (animation bookkeeping for next time it's reopened\n\t\t\t * via the prop), so without an explicit `false` here the dialog\n\t\t\t * reopens itself right after Cancel/ESC/backdrop-click.\n\t\t\t */\n\t\t\tdialogOpen: true,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The currently-active step object from `steps[]`.\n\t\t *\n\t\t * @return {object} The current step definition.\n\t\t */\n\t\tcurrentStep() {\n\t\t\treturn this.steps[this.currentIndex] || this.steps[0]\n\t\t},\n\n\t\t/**\n\t\t * Whether the current step is the first one.\n\t\t *\n\t\t * @return {boolean} True when on the first step.\n\t\t */\n\t\tisFirst() {\n\t\t\treturn this.currentIndex === 0\n\t\t},\n\n\t\t/**\n\t\t * Whether the current step is the last one (drives the\n\t\t * Next → Submit label flip).\n\t\t *\n\t\t * @return {boolean} True when on the last step.\n\t\t */\n\t\tisLast() {\n\t\t\treturn this.currentIndex === this.steps.length - 1\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Resolve the index of `initialStep` (falling back to 0 when\n\t\t * unset / not found).\n\t\t *\n\t\t * @return {number} Step index in the range [0, steps.length).\n\t\t */\n\t\tresolveInitialIndex() {\n\t\t\tif (!this.initialStep) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tconst idx = this.steps.findIndex((s) => s.id === this.initialStep)\n\t\t\treturn idx >= 0 ? idx : 0\n\t\t},\n\n\t\t/**\n\t\t * Merge a partial step-data object onto the shared state.\n\t\t * Each slot scope receives this so consumers can write their\n\t\t * step's data without owning a `data()` block in their slot\n\t\t * component.\n\t\t *\n\t\t * @param {object} partial Fields to merge.\n\t\t * @return {void}\n\t\t */\n\t\tsetStepData(partial) {\n\t\t\tthis.stepData = { ...this.stepData, ...partial }\n\t\t},\n\n\t\t/**\n\t\t * Run the consumer's `validate` prop (if provided) for the\n\t\t * current step. Returns true on pass; sets `validationError`\n\t\t * and returns false on fail.\n\t\t *\n\t\t * @return {Promise<boolean>} Whether validation passed.\n\t\t */\n\t\tasync runValidation() {\n\t\t\tthis.validationError = ''\n\t\t\tif (typeof this.validate !== 'function') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst outcome = await this.validate(this.currentStep.id, this.stepData)\n\t\t\t\tif (outcome === true) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\tif (typeof outcome === 'string') {\n\t\t\t\t\tthis.validationError = outcome\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn Boolean(outcome)\n\t\t\t} catch (e) {\n\t\t\t\tthis.validationError = e && e.message ? e.message : 'Validation failed.'\n\t\t\t\treturn false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Advance to the next step after validation. Emits\n\t\t * `@step-change` with the new step id.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync next() {\n\t\t\tif (this.isLast) {\n\t\t\t\treturn this.submit()\n\t\t\t}\n\t\t\tconst ok = await this.runValidation()\n\t\t\tif (!ok) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.currentIndex += 1\n\t\t\t/**\n\t\t\t * @event step-change Emitted on every step navigation\n\t\t\t * (next / back / jumpTo / submit).\n\t\t\t * @type {{ stepId: string, stepIndex: number, direction: 'next'|'back'|'jump' }}\n\t\t\t */\n\t\t\tthis.$emit('step-change', {\n\t\t\t\tstepId: this.currentStep.id,\n\t\t\t\tstepIndex: this.currentIndex,\n\t\t\t\tdirection: 'next',\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Step back to the previous step. No validation runs on\n\t\t * back navigation — consumers can reach back without\n\t\t * losing the current step's draft.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tback() {\n\t\t\tif (this.isFirst) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.validationError = ''\n\t\t\tthis.currentIndex -= 1\n\t\t\tthis.$emit('step-change', {\n\t\t\t\tstepId: this.currentStep.id,\n\t\t\t\tstepIndex: this.currentIndex,\n\t\t\t\tdirection: 'back',\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Jump to an arbitrary step by id. Validation is skipped\n\t\t * (the consumer is responsible for re-running it before\n\t\t * `submit()` if it matters).\n\t\t *\n\t\t * @param {string} stepId Target step id.\n\t\t * @return {void}\n\t\t */\n\t\tjumpTo(stepId) {\n\t\t\tconst idx = this.steps.findIndex((s) => s.id === stepId)\n\t\t\tif (idx < 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.validationError = ''\n\t\t\tthis.currentIndex = idx\n\t\t\tthis.$emit('step-change', {\n\t\t\t\tstepId: this.currentStep.id,\n\t\t\t\tstepIndex: this.currentIndex,\n\t\t\t\tdirection: 'jump',\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Submit the wizard. Runs the current step's validation;\n\t\t * on pass flips `loading` on and emits `@submit` with the\n\t\t * accumulated `stepData`. The parent is responsible for\n\t\t * calling `setResult()` to flip into the result phase.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync submit() {\n\t\t\tconst ok = await this.runValidation()\n\t\t\tif (!ok) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\t/**\n\t\t\t * @event submit Emitted when the user reaches the final\n\t\t\t * step and clicks Submit (or `submit()` is called\n\t\t\t * programmatically from a slot scope). Payload is the\n\t\t\t * accumulated `stepData`.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('submit', { ...this.stepData })\n\t\t},\n\n\t\t/**\n\t\t * Public method called by the parent to switch the wizard\n\t\t * into the result phase.\n\t\t *\n\t\t * @param {object} result `{ success?, message?, error? }`.\n\t\t * @return {void}\n\t\t */\n\t\tsetResult(result) {\n\t\t\tthis.result = result || { success: true }\n\t\t\tthis.loading = false\n\t\t},\n\n\t\t/**\n\t\t * Public method for a NON-terminal submit failure: stops the\n\t\t * loading spinner and surfaces `message` as the error banner above\n\t\t * the (still-editable) current step, WITHOUT entering the result\n\t\t * phase — so the user can correct the input and resubmit. Use this\n\t\t * instead of `setResult({ error })` when the failure is recoverable\n\t\t * (e.g. a taken slug, a validation conflict).\n\t\t *\n\t\t * @param {string} message The error to show above the step body.\n\t\t * @return {void}\n\t\t */\n\t\tsetError(message) {\n\t\t\tthis.loading = false\n\t\t\tthis.validationError = message || ''\n\t\t},\n\n\t\t/**\n\t\t * Close-button handler. Resets local state so the next open\n\t\t * starts fresh and emits `@close`.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonClose() {\n\t\t\tthis.result = null\n\t\t\tthis.loading = false\n\t\t\tthis.validationError = ''\n\t\t\tthis.currentIndex = this.resolveInitialIndex()\n\t\t\tthis.stepData = { ...this.defaults }\n\t\t\tthis.dialogOpen = false\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\t},\n}\n</script>\n\n<style scoped>\n.cn-wizard-dialog__progress {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tjustify-content: center;\n\tgap: 0;\n\tpadding: 8px 0 4px;\n\tmargin: 0 0 20px;\n\tlist-style: none;\n}\n\n.cn-wizard-dialog__progress-item {\n\tposition: relative;\n\tflex: 1 1 0;\n\tmin-width: 0;\n\tmax-width: 180px;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 6px;\n\ttext-align: center;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8125rem;\n}\n\n.cn-wizard-dialog__progress-item--clickable {\n\tcursor: pointer;\n}\n\n/* Marker circle */\n.cn-wizard-dialog__progress-dot {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 34px;\n\theight: 34px;\n\tborder-radius: 50%;\n\tborder: 2px solid var(--color-border-dark);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n\tfont-weight: 600;\n\ttransition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;\n\tz-index: 1;\n}\n\n.cn-wizard-dialog__progress-check {\n\tfont-size: 16px;\n\tline-height: 1;\n}\n\n.cn-wizard-dialog__progress-label {\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n/* Connector line between this marker and the next, centered on the marker. */\n.cn-wizard-dialog__progress-connector {\n\tposition: absolute;\n\ttop: 17px;\n\tleft: calc(50% + 21px);\n\tright: calc(-50% + 21px);\n\theight: 2px;\n\tbackground: var(--color-border-dark);\n}\n\n.cn-wizard-dialog__progress-connector--done {\n\tbackground: var(--color-primary-element);\n}\n\n/* States */\n.cn-wizard-dialog__progress-item--active .cn-wizard-dialog__progress-dot {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n}\n\n.cn-wizard-dialog__progress-item--active .cn-wizard-dialog__progress-label {\n\tcolor: var(--color-main-text);\n\tfont-weight: 600;\n}\n\n.cn-wizard-dialog__progress-item--done .cn-wizard-dialog__progress-dot {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n}\n\n.cn-wizard-dialog__progress-item--done .cn-wizard-dialog__progress-label {\n\tcolor: var(--color-main-text);\n}\n\n.cn-wizard-dialog__progress-item--clickable:hover .cn-wizard-dialog__progress-label {\n\tcolor: var(--color-primary-element);\n}\n\n.cn-wizard-dialog__step-body {\n\tmin-height: 200px;\n}\n\n.cn-wizard-dialog__result {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n</style>\n"],"names":["_createBlock","_createElementBlock","_Fragment","_createVNode","_createTextVNode","_toDisplayString","_openBlock","_renderSlot","_createElementVNode","_renderList","_normalizeClass"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnWizardDialog.vue3.js","sources":["../../../../src/components/CnWizardDialog/CnWizardDialog.vue?vue&type=template&id=27d51a62&lang.js"],"sourcesContent":["<template>\n\t<NcDialog\n\t\t:name=\"dialogTitle\"\n\t\tsize=\"large\"\n\t\t:open=\"dialogOpen\"\n\t\t:noClose=\"loading || !cancellable\"\n\t\tdata-testid=\"cn-modal\"\n\t\tdata-testid-modal=\"cn-wizard-dialog\"\n\t\t@update:open=\"dialogOpen = $event\"\n\t\t@closing=\"onClose\">\n\t\t<!-- Result phase: rendered after a successful `submit()`. -->\n\t\t<div v-if=\"result !== null\"\n\t\t\tclass=\"cn-wizard-dialog__result\"\n\t\t\tdata-testid-phase=\"result\">\n\t\t\t<NcNoteCard v-if=\"result.success\" type=\"success\">\n\t\t\t\t{{ result.message || successText }}\n\t\t\t</NcNoteCard>\n\t\t\t<NcNoteCard v-else-if=\"result.error\" type=\"error\">\n\t\t\t\t{{ result.error }}\n\t\t\t</NcNoteCard>\n\t\t\t<!-- @slot result-extra Optional extra content (e.g. batch\n\t\t\t progress poller, summary table) rendered below the\n\t\t\t success/error banner. Scope: { result }. -->\n\t\t\t<slot name=\"result-extra\" :result=\"result\" />\n\t\t</div>\n\n\t\t<!-- Wizard phase: progress bar + current step body. -->\n\t\t<div v-else\n\t\t\tclass=\"cn-wizard-dialog__wizard\"\n\t\t\tdata-testid-phase=\"form\">\n\t\t\t<!-- Progress indicator. Click jumps back to a completed\n\t\t\t step when allowJumpBack is true. -->\n\t\t\t<ol class=\"cn-wizard-dialog__progress\" role=\"tablist\">\n\t\t\t\t<li v-for=\"(step, idx) in steps\"\n\t\t\t\t\t:key=\"step.id\"\n\t\t\t\t\tclass=\"cn-wizard-dialog__progress-item\"\n\t\t\t\t\t:class=\"{\n\t\t\t\t\t\t'cn-wizard-dialog__progress-item--active': idx === currentIndex,\n\t\t\t\t\t\t'cn-wizard-dialog__progress-item--done': idx < currentIndex,\n\t\t\t\t\t\t'cn-wizard-dialog__progress-item--clickable': allowJumpBack && idx < currentIndex,\n\t\t\t\t\t}\"\n\t\t\t\t\trole=\"tab\"\n\t\t\t\t\t:aria-selected=\"idx === currentIndex\"\n\t\t\t\t\t@click=\"allowJumpBack && idx < currentIndex ? jumpTo(step.id) : null\">\n\t\t\t\t\t<span class=\"cn-wizard-dialog__progress-dot\">\n\t\t\t\t\t\t<span v-if=\"idx < currentIndex\" class=\"cn-wizard-dialog__progress-check\" aria-hidden=\"true\">✓</span>\n\t\t\t\t\t\t<span v-else>{{ idx + 1 }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t<span class=\"cn-wizard-dialog__progress-label\">{{ step.label }}</span>\n\t\t\t\t\t<span v-if=\"idx < steps.length - 1\"\n\t\t\t\t\t\tclass=\"cn-wizard-dialog__progress-connector\"\n\t\t\t\t\t\t:class=\"{ 'cn-wizard-dialog__progress-connector--done': idx < currentIndex }\"\n\t\t\t\t\t\taria-hidden=\"true\" />\n\t\t\t\t</li>\n\t\t\t</ol>\n\n\t\t\t<!-- Validation error from the current step's validate() result. -->\n\t\t\t<NcNoteCard v-if=\"validationError\" type=\"error\">\n\t\t\t\t{{ validationError }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<!-- Current step body — a dynamic slot named by step id. -->\n\t\t\t<div class=\"cn-wizard-dialog__step-body\" :data-step-id=\"currentStep.id\">\n\t\t\t\t<!-- @slot step-{id} Per-step body. Scope: { next,\n\t\t\t\t back, jumpTo, submit, currentStep, stepIndex,\n\t\t\t\t totalSteps, stepData, setStepData, isFirst,\n\t\t\t\t isLast }. -->\n\t\t\t\t<slot\n\t\t\t\t\t:name=\"'step-' + currentStep.id\"\n\t\t\t\t\t:next=\"next\"\n\t\t\t\t\t:back=\"back\"\n\t\t\t\t\t:jumpTo=\"jumpTo\"\n\t\t\t\t\t:submit=\"submit\"\n\t\t\t\t\t:currentStep=\"currentStep\"\n\t\t\t\t\t:stepIndex=\"currentIndex\"\n\t\t\t\t\t:totalSteps=\"steps.length\"\n\t\t\t\t\t:stepData=\"stepData\"\n\t\t\t\t\t:setStepData=\"setStepData\"\n\t\t\t\t\t:isFirst=\"isFirst\"\n\t\t\t\t\t:isLast=\"isLast\" />\n\t\t\t</div>\n\t\t</div>\n\n\t\t<template #actions>\n\t\t\t<!-- Result phase actions: just Close. -->\n\t\t\t<NcButton v-if=\"result !== null\" @click=\"onClose\">\n\t\t\t\t{{ closeLabel }}\n\t\t\t</NcButton>\n\n\t\t\t<!-- Wizard phase actions: Cancel + Back + Next/Submit.\n\t\t\t The testids exist because these buttons are the only way\n\t\t\t through a wizard and their LABELS are translated: an e2e that\n\t\t\t clicks \"Next\" passes on an English instance and times out on a\n\t\t\t Dutch one, reporting a broken dialog rather than a missing\n\t\t\t string. -->\n\t\t\t<template v-else>\n\t\t\t\t<NcButton v-if=\"cancellable\" data-testid=\"cn-wizard-cancel\" @click=\"onClose\">\n\t\t\t\t\t{{ cancelLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton v-if=\"!isFirst\"\n\t\t\t\t\tdata-testid=\"cn-wizard-back\"\n\t\t\t\t\t:disabled=\"loading\"\n\t\t\t\t\t@click=\"back\">\n\t\t\t\t\t{{ backLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton variant=\"primary\"\n\t\t\t\t\tdata-testid=\"cn-wizard-next\"\n\t\t\t\t\t:disabled=\"loading || nextDisabled\"\n\t\t\t\t\t@click=\"isLast ? submit() : next()\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<NcLoadingIcon v-if=\"loading\" :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ isLast ? submitLabel : nextLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</template>\n\t\t</template>\n\t</NcDialog>\n</template>\n\n<script>\nimport { NcButton, NcDialog, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\n\n/**\n * CnWizardDialog — Multi-step modal with per-step slots,\n * back/next navigation, optional per-step validation, and a single\n * result phase.\n *\n * Each step is declared in the `steps[]` prop (`{ id, label,\n * optional?, icon? }`) and rendered via a `#step-{id}` named slot\n * with the navigation API exposed through the slot scope. The\n * wizard owns the progress indicator, the back/next chrome, the\n * loading lifecycle, and the result-phase banner — the consumer\n * owns the per-step body + the data collection logic.\n *\n * Per-step validation is opt-in via the `validate` prop:\n *\n * ```js\n * async function validate(stepId, stepData) {\n * if (stepId === 'audience' && !stepData.cohort) {\n * return 'Pick a cohort first.'\n * }\n * return true\n * }\n * ```\n *\n * Returning `true` (or any truthy non-string) advances; returning a\n * string surfaces it as an error banner above the step body and\n * blocks the navigation.\n *\n * The result phase is entered by the consumer calling\n * `setResult({ success?, message?, error? })` after `@submit` fires.\n *\n * Use [`CnFormDialog`](../CnFormDialog/) for single-step forms.\n * Use [`CnExportWizard`](../CnExportWizard/) for the specific\n * scope+format+delivery export-trigger shape.\n *\n * ```vue\n * <CnWizardDialog\n * ref=\"wizard\"\n * dialog-title=\"Bulk enrol\"\n * :steps=\"[\n * { id: 'audience', label: 'Audience' },\n * { id: 'course', label: 'Course' },\n * { id: 'confirm', label: 'Confirm' },\n * ]\"\n * :validate=\"validateStep\"\n * @submit=\"onSubmit\"\n * @close=\"show = false\">\n * <template #step-audience=\"{ next, stepData, setStepData }\">\n * <CohortPicker :value=\"stepData.cohort\" @input=\"v => setStepData({ cohort: v })\" />\n * </template>\n * <template #step-course=\"{ next, back, stepData, setStepData }\">\n * <CoursePicker :value=\"stepData.course\" @input=\"v => setStepData({ course: v })\" />\n * </template>\n * <template #step-confirm=\"{ back, submit, stepData }\">\n * <p>Enrol {{ stepData.audience.cohort.size }} learners into {{ stepData.course.course.name }}?</p>\n * </template>\n * </CnWizardDialog>\n * ```\n */\nexport default {\n\tname: 'CnWizardDialog',\n\tcomponents: { NcDialog, NcButton, NcNoteCard, NcLoadingIcon },\n\tprops: {\n\t\t/**\n\t\t * Step declarations. Order is significant — the wizard\n\t\t * navigates linearly between adjacent steps unless the\n\t\t * consumer calls `jumpTo()` from a slot scope.\n\t\t *\n\t\t * @type {Array<{ id: string, label: string, optional?: boolean, icon?: string }>}\n\t\t */\n\t\tsteps: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t\tvalidator: (v) => Array.isArray(v) && v.length > 0\n\t\t\t\t&& v.every((s) => s && typeof s.id === 'string' && typeof s.label === 'string'),\n\t\t},\n\n\t\t/**\n\t\t * Dialog title shown in the NcDialog header.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdialogTitle: {\n\t\t\ttype: String,\n\t\t\tdefault: 'Wizard',\n\t\t},\n\n\t\t/**\n\t\t * Step id to start on. Defaults to the first step in `steps[]`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tinitialStep: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Per-step validator. Async function returning `true` to\n\t\t * advance, a string to surface as an error banner + block\n\t\t * navigation, or `false` to block silently. Called from\n\t\t * `next()` and `submit()`. When omitted, navigation always\n\t\t * proceeds.\n\t\t *\n\t\t * @type {(stepId: string, stepData: object) => Promise<boolean|string>}\n\t\t */\n\t\tvalidate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether the progress indicator allows jumping back to a\n\t\t * completed step via click. Forward jumps are NEVER allowed\n\t\t * via the progress indicator (consumer-side `jumpTo` is\n\t\t * unrestricted).\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tallowJumpBack: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Seed values for `stepData` (the cross-step object passed\n\t\t * to every slot scope). Useful for prefilling on edit-style\n\t\t * wizards.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tdefaults: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Cancel-button label (wizard phase). */\n\t\tcancelLabel: { type: String, default: 'Cancel' },\n\t\t/** Back-button label. */\n\t\tbackLabel: { type: String, default: 'Back' },\n\t\t/** Next-button label (intermediate steps). */\n\t\tnextLabel: { type: String, default: 'Next' },\n\t\t/** Submit-button label (last step). */\n\t\tsubmitLabel: { type: String, default: 'Submit' },\n\t\t/** Close-button label (result phase). */\n\t\tcloseLabel: { type: String, default: 'Close' },\n\t\t/** Success banner default text when result.message is empty. */\n\t\tsuccessText: { type: String, default: 'Done.' },\n\t\t/**\n\t\t * Whether the dialog can be dismissed (Cancel button, ESC, backdrop\n\t\t * click) before reaching the result phase. `false` hides Cancel and\n\t\t * disables all other close affordances — use for a step that must\n\t\t * be completed, not merely skipped. The result-phase Close button is\n\t\t * unaffected (a completed wizard is always dismissible).\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 * Disable the Next / Submit button, whatever the step's `validate`\n\t\t * would say. For a host that knows the wizard cannot go on yet, such as\n\t\t * `CnSetupWizard` while a required app is missing.\n\t\t *\n\t\t * @type {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\tnextDisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\temits: ['close', 'step-change', 'submit'],\n\tdata() {\n\t\tconst initialIndex = this.resolveInitialIndex()\n\t\treturn {\n\t\t\tcurrentIndex: initialIndex,\n\t\t\tstepData: { ...this.defaults },\n\t\t\tloading: false,\n\t\t\tresult: null,\n\t\t\tvalidationError: '',\n\t\t\t/**\n\t\t\t * Drives NcDialog's `open` prop directly. NcDialog's own `open`\n\t\t\t * prop defaults to `true` and is NOT self-managing on close — its\n\t\t\t * internal `showModal` flag resets back to `true` ~300ms after\n\t\t\t * any close (animation bookkeeping for next time it's reopened\n\t\t\t * via the prop), so without an explicit `false` here the dialog\n\t\t\t * reopens itself right after Cancel/ESC/backdrop-click.\n\t\t\t */\n\t\t\tdialogOpen: true,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The currently-active step object from `steps[]`.\n\t\t *\n\t\t * @return {object} The current step definition.\n\t\t */\n\t\tcurrentStep() {\n\t\t\treturn this.steps[this.currentIndex] || this.steps[0]\n\t\t},\n\n\t\t/**\n\t\t * Whether the current step is the first one.\n\t\t *\n\t\t * @return {boolean} True when on the first step.\n\t\t */\n\t\tisFirst() {\n\t\t\treturn this.currentIndex === 0\n\t\t},\n\n\t\t/**\n\t\t * Whether the current step is the last one (drives the\n\t\t * Next → Submit label flip).\n\t\t *\n\t\t * @return {boolean} True when on the last step.\n\t\t */\n\t\tisLast() {\n\t\t\treturn this.currentIndex === this.steps.length - 1\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Resolve the index of `initialStep` (falling back to 0 when\n\t\t * unset / not found).\n\t\t *\n\t\t * @return {number} Step index in the range [0, steps.length).\n\t\t */\n\t\tresolveInitialIndex() {\n\t\t\tif (!this.initialStep) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tconst idx = this.steps.findIndex((s) => s.id === this.initialStep)\n\t\t\treturn idx >= 0 ? idx : 0\n\t\t},\n\n\t\t/**\n\t\t * Merge a partial step-data object onto the shared state.\n\t\t * Each slot scope receives this so consumers can write their\n\t\t * step's data without owning a `data()` block in their slot\n\t\t * component.\n\t\t *\n\t\t * @param {object} partial Fields to merge.\n\t\t * @return {void}\n\t\t */\n\t\tsetStepData(partial) {\n\t\t\tthis.stepData = { ...this.stepData, ...partial }\n\t\t},\n\n\t\t/**\n\t\t * Run the consumer's `validate` prop (if provided) for the\n\t\t * current step. Returns true on pass; sets `validationError`\n\t\t * and returns false on fail.\n\t\t *\n\t\t * @return {Promise<boolean>} Whether validation passed.\n\t\t */\n\t\tasync runValidation() {\n\t\t\tthis.validationError = ''\n\t\t\tif (typeof this.validate !== 'function') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst outcome = await this.validate(this.currentStep.id, this.stepData)\n\t\t\t\tif (outcome === true) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\tif (typeof outcome === 'string') {\n\t\t\t\t\tthis.validationError = outcome\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn Boolean(outcome)\n\t\t\t} catch (e) {\n\t\t\t\tthis.validationError = e && e.message ? e.message : 'Validation failed.'\n\t\t\t\treturn false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Advance to the next step after validation. Emits\n\t\t * `@step-change` with the new step id.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync next() {\n\t\t\tif (this.nextDisabled) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.isLast) {\n\t\t\t\treturn this.submit()\n\t\t\t}\n\t\t\tconst ok = await this.runValidation()\n\t\t\tif (!ok) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.currentIndex += 1\n\t\t\t/**\n\t\t\t * @event step-change Emitted on every step navigation\n\t\t\t * (next / back / jumpTo / submit).\n\t\t\t * @type {{ stepId: string, stepIndex: number, direction: 'next'|'back'|'jump' }}\n\t\t\t */\n\t\t\tthis.$emit('step-change', {\n\t\t\t\tstepId: this.currentStep.id,\n\t\t\t\tstepIndex: this.currentIndex,\n\t\t\t\tdirection: 'next',\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Step back to the previous step. No validation runs on\n\t\t * back navigation — consumers can reach back without\n\t\t * losing the current step's draft.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tback() {\n\t\t\tif (this.isFirst) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.validationError = ''\n\t\t\tthis.currentIndex -= 1\n\t\t\tthis.$emit('step-change', {\n\t\t\t\tstepId: this.currentStep.id,\n\t\t\t\tstepIndex: this.currentIndex,\n\t\t\t\tdirection: 'back',\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Jump to an arbitrary step by id. Validation is skipped\n\t\t * (the consumer is responsible for re-running it before\n\t\t * `submit()` if it matters).\n\t\t *\n\t\t * @param {string} stepId Target step id.\n\t\t * @return {void}\n\t\t */\n\t\tjumpTo(stepId) {\n\t\t\tconst idx = this.steps.findIndex((s) => s.id === stepId)\n\t\t\tif (idx < 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.validationError = ''\n\t\t\tthis.currentIndex = idx\n\t\t\tthis.$emit('step-change', {\n\t\t\t\tstepId: this.currentStep.id,\n\t\t\t\tstepIndex: this.currentIndex,\n\t\t\t\tdirection: 'jump',\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Submit the wizard. Runs the current step's validation;\n\t\t * on pass flips `loading` on and emits `@submit` with the\n\t\t * accumulated `stepData`. The parent is responsible for\n\t\t * calling `setResult()` to flip into the result phase.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync submit() {\n\t\t\tif (this.nextDisabled) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst ok = await this.runValidation()\n\t\t\tif (!ok) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\t/**\n\t\t\t * @event submit Emitted when the user reaches the final\n\t\t\t * step and clicks Submit (or `submit()` is called\n\t\t\t * programmatically from a slot scope). Payload is the\n\t\t\t * accumulated `stepData`.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('submit', { ...this.stepData })\n\t\t},\n\n\t\t/**\n\t\t * Public method called by the parent to switch the wizard\n\t\t * into the result phase.\n\t\t *\n\t\t * @param {object} result `{ success?, message?, error? }`.\n\t\t * @return {void}\n\t\t */\n\t\tsetResult(result) {\n\t\t\tthis.result = result || { success: true }\n\t\t\tthis.loading = false\n\t\t},\n\n\t\t/**\n\t\t * Public method for a NON-terminal submit failure: stops the\n\t\t * loading spinner and surfaces `message` as the error banner above\n\t\t * the (still-editable) current step, WITHOUT entering the result\n\t\t * phase — so the user can correct the input and resubmit. Use this\n\t\t * instead of `setResult({ error })` when the failure is recoverable\n\t\t * (e.g. a taken slug, a validation conflict).\n\t\t *\n\t\t * @param {string} message The error to show above the step body.\n\t\t * @return {void}\n\t\t */\n\t\tsetError(message) {\n\t\t\tthis.loading = false\n\t\t\tthis.validationError = message || ''\n\t\t},\n\n\t\t/**\n\t\t * Close-button handler. Resets local state so the next open\n\t\t * starts fresh and emits `@close`.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonClose() {\n\t\t\tthis.result = null\n\t\t\tthis.loading = false\n\t\t\tthis.validationError = ''\n\t\t\tthis.currentIndex = this.resolveInitialIndex()\n\t\t\tthis.stepData = { ...this.defaults }\n\t\t\tthis.dialogOpen = false\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\t},\n}\n</script>\n\n<style scoped>\n.cn-wizard-dialog__progress {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tjustify-content: center;\n\tgap: 0;\n\tpadding: 8px 0 4px;\n\tmargin: 0 0 20px;\n\tlist-style: none;\n}\n\n.cn-wizard-dialog__progress-item {\n\tposition: relative;\n\tflex: 1 1 0;\n\tmin-width: 0;\n\tmax-width: 180px;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 6px;\n\ttext-align: center;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8125rem;\n}\n\n.cn-wizard-dialog__progress-item--clickable {\n\tcursor: pointer;\n}\n\n/* Marker circle */\n.cn-wizard-dialog__progress-dot {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 34px;\n\theight: 34px;\n\tborder-radius: 50%;\n\tborder: 2px solid var(--color-border-dark);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n\tfont-weight: 600;\n\ttransition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;\n\tz-index: 1;\n}\n\n.cn-wizard-dialog__progress-check {\n\tfont-size: 16px;\n\tline-height: 1;\n}\n\n.cn-wizard-dialog__progress-label {\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n/* Connector line between this marker and the next, centered on the marker. */\n.cn-wizard-dialog__progress-connector {\n\tposition: absolute;\n\ttop: 17px;\n\tleft: calc(50% + 21px);\n\tright: calc(-50% + 21px);\n\theight: 2px;\n\tbackground: var(--color-border-dark);\n}\n\n.cn-wizard-dialog__progress-connector--done {\n\tbackground: var(--color-primary-element);\n}\n\n/* States */\n.cn-wizard-dialog__progress-item--active .cn-wizard-dialog__progress-dot {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n}\n\n.cn-wizard-dialog__progress-item--active .cn-wizard-dialog__progress-label {\n\tcolor: var(--color-main-text);\n\tfont-weight: 600;\n}\n\n.cn-wizard-dialog__progress-item--done .cn-wizard-dialog__progress-dot {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n}\n\n.cn-wizard-dialog__progress-item--done .cn-wizard-dialog__progress-label {\n\tcolor: var(--color-main-text);\n}\n\n.cn-wizard-dialog__progress-item--clickable:hover .cn-wizard-dialog__progress-label {\n\tcolor: var(--color-primary-element);\n}\n\n.cn-wizard-dialog__step-body {\n\tmin-height: 200px;\n}\n\n.cn-wizard-dialog__result {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n</style>\n"],"names":["_createBlock","_createElementBlock","_Fragment","_createVNode","_createTextVNode","_toDisplayString","_openBlock","_renderSlot","_createElementVNode","_renderList","_normalizeClass"],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useAppStatus } from './useAppStatus.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
5
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resolve `manifest.dependencies` entries to their live status.
|
|
11
|
+
*
|
|
12
|
+
* Accepts the manifest's own shape: an app-id string (a REQUIRED dependency)
|
|
13
|
+
* or `{ id, name?, required? }` where `required` defaults to true. Each entry
|
|
14
|
+
* becomes `{ id, name, required, installed, enabled }`.
|
|
15
|
+
*
|
|
16
|
+
* `serverStatuses` is the optional `dependency_statuses` initial state an app
|
|
17
|
+
* injects from PHP, keyed by app id: `{ installed, enabled }`. It wins over
|
|
18
|
+
* the browser heuristic, because only the server can tell "installed but
|
|
19
|
+
* disabled" from "not installed". Without it, `useAppStatus` answers, which
|
|
20
|
+
* is the same resolution `CnAppRoot` uses for its dependency-missing phase.
|
|
21
|
+
*
|
|
22
|
+
* @param {Array<string|{id: string, name?: string, required?: boolean}>} entries The declared dependencies.
|
|
23
|
+
* @param {object} [serverStatuses] Server-reported statuses keyed by app id.
|
|
24
|
+
* @return {Array<{id: string, name: string, required: boolean, installed: boolean, enabled: boolean}>} One row per valid entry.
|
|
25
|
+
*
|
|
26
|
+
* @spec openspec/changes/setup-wizard-card-load-and-dependency-gate/specs/cn-setup-wizard/spec.md#requirement-the-wizard-checks-dependencies-before-any-step
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* const missing = checkDependencies(manifest.dependencies)
|
|
30
|
+
* .filter((d) => d.required && !(d.installed && d.enabled))
|
|
31
|
+
*/
|
|
32
|
+
function checkDependencies(entries, serverStatuses = {}) {
|
|
33
|
+
const list = Array.isArray(entries) ? entries : [];
|
|
34
|
+
const server = (serverStatuses && typeof serverStatuses === 'object') ? serverStatuses : {};
|
|
35
|
+
return list
|
|
36
|
+
.map((entry) => {
|
|
37
|
+
const isObject = entry !== null && typeof entry === 'object';
|
|
38
|
+
const id = isObject ? entry.id : entry;
|
|
39
|
+
if (typeof id !== 'string' || id === '') {
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
const required = isObject ? entry.required !== false : true;
|
|
43
|
+
const name = (isObject && entry.name) || id;
|
|
44
|
+
const reported = server[id];
|
|
45
|
+
if (reported !== undefined && reported !== null) {
|
|
46
|
+
return { id, name, required, installed: !!reported.installed, enabled: !!reported.enabled }
|
|
47
|
+
}
|
|
48
|
+
const status = useAppStatus(id);
|
|
49
|
+
return { id, name, required, installed: !!status.installed.value, enabled: !!status.enabled.value }
|
|
50
|
+
})
|
|
51
|
+
.filter((row) => row !== null)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Whether a resolved dependency row is usable: installed AND enabled.
|
|
56
|
+
*
|
|
57
|
+
* @param {{installed: boolean, enabled: boolean}} row A row from `checkDependencies`.
|
|
58
|
+
* @return {boolean} True when the app can be used.
|
|
59
|
+
*/
|
|
60
|
+
function isDependencyResolved(row) {
|
|
61
|
+
return !!row && row.installed === true && row.enabled === true
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { checkDependencies, isDependencyResolved };
|
|
65
|
+
//# sourceMappingURL=useDependencyCheck.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDependencyCheck.js","sources":["../../../src/composables/useDependencyCheck.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n */\n\nimport { useAppStatus } from './useAppStatus.js'\n\n/**\n * Resolve `manifest.dependencies` entries to their live status.\n *\n * Accepts the manifest's own shape: an app-id string (a REQUIRED dependency)\n * or `{ id, name?, required? }` where `required` defaults to true. Each entry\n * becomes `{ id, name, required, installed, enabled }`.\n *\n * `serverStatuses` is the optional `dependency_statuses` initial state an app\n * injects from PHP, keyed by app id: `{ installed, enabled }`. It wins over\n * the browser heuristic, because only the server can tell \"installed but\n * disabled\" from \"not installed\". Without it, `useAppStatus` answers, which\n * is the same resolution `CnAppRoot` uses for its dependency-missing phase.\n *\n * @param {Array<string|{id: string, name?: string, required?: boolean}>} entries The declared dependencies.\n * @param {object} [serverStatuses] Server-reported statuses keyed by app id.\n * @return {Array<{id: string, name: string, required: boolean, installed: boolean, enabled: boolean}>} One row per valid entry.\n *\n * @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 *\n * @example\n * const missing = checkDependencies(manifest.dependencies)\n * .filter((d) => d.required && !(d.installed && d.enabled))\n */\nexport function checkDependencies(entries, serverStatuses = {}) {\n\tconst list = Array.isArray(entries) ? entries : []\n\tconst server = (serverStatuses && typeof serverStatuses === 'object') ? serverStatuses : {}\n\treturn list\n\t\t.map((entry) => {\n\t\t\tconst isObject = entry !== null && typeof entry === 'object'\n\t\t\tconst id = isObject ? entry.id : entry\n\t\t\tif (typeof id !== 'string' || id === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst required = isObject ? entry.required !== false : true\n\t\t\tconst name = (isObject && entry.name) || id\n\t\t\tconst reported = server[id]\n\t\t\tif (reported !== undefined && reported !== null) {\n\t\t\t\treturn { id, name, required, installed: !!reported.installed, enabled: !!reported.enabled }\n\t\t\t}\n\t\t\tconst status = useAppStatus(id)\n\t\t\treturn { id, name, required, installed: !!status.installed.value, enabled: !!status.enabled.value }\n\t\t})\n\t\t.filter((row) => row !== null)\n}\n\n/**\n * Whether a resolved dependency row is usable: installed AND enabled.\n *\n * @param {{installed: boolean, enabled: boolean}} row A row from `checkDependencies`.\n * @return {boolean} True when the app can be used.\n */\nexport function isDependencyResolved(row) {\n\treturn !!row && row.installed === true && row.enabled === true\n}\n"],"names":[],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,iBAAiB,CAAC,OAAO,EAAE,cAAc,GAAG,EAAE,EAAE;AAChE,CAAC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,GAAG,GAAE;AACnD,CAAC,MAAM,MAAM,GAAG,CAAC,cAAc,IAAI,OAAO,cAAc,KAAK,QAAQ,IAAI,cAAc,GAAG,GAAE;AAC5F,CAAC,OAAO,IAAI;AACZ,GAAG,GAAG,CAAC,CAAC,KAAK,KAAK;AAClB,GAAG,MAAM,QAAQ,GAAG,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,SAAQ;AAC/D,GAAG,MAAM,EAAE,GAAG,QAAQ,GAAG,KAAK,CAAC,EAAE,GAAG,MAAK;AACzC,GAAG,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,KAAK,EAAE,EAAE;AAC5C,IAAI,OAAO,IAAI;AACf,IAAI;AACJ,GAAG,MAAM,QAAQ,GAAG,QAAQ,GAAG,KAAK,CAAC,QAAQ,KAAK,KAAK,GAAG,KAAI;AAC9D,GAAG,MAAM,IAAI,GAAG,CAAC,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,GAAE;AAC9C,GAAG,MAAM,QAAQ,GAAG,MAAM,CAAC,EAAE,EAAC;AAC9B,GAAG,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,IAAI,EAAE;AACpD,IAAI,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC,QAAQ,CAAC,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE;AAC/F,IAAI;AACJ,GAAG,MAAM,MAAM,GAAG,YAAY,CAAC,EAAE,EAAC;AAClC,GAAG,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE;AACtG,GAAG,CAAC;AACJ,GAAG,MAAM,CAAC,CAAC,GAAG,KAAK,GAAG,KAAK,IAAI,CAAC;AAChC,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,oBAAoB,CAAC,GAAG,EAAE;AAC1C,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,SAAS,KAAK,IAAI,IAAI,GAAG,CAAC,OAAO,KAAK,IAAI;AAC/D;;;;"}
|
|
@@ -200,6 +200,162 @@ function persistWalkthroughSeenVersion(appId, configKey, version, options = {})
|
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
+
/**
|
|
204
|
+
* localStorage key prefix for the per-browser mirror of tour progress.
|
|
205
|
+
*/
|
|
206
|
+
const WALKTHROUGH_PROGRESS_STORAGE_PREFIX = 'cn-walkthrough-progress:';
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* The preference key that holds tour progress, next to the completion key.
|
|
210
|
+
*
|
|
211
|
+
* @param {string} configKey `manifest.walkthrough.completionConfigKey` (may be empty).
|
|
212
|
+
* @return {string} `<configKey>-progress`, or '' when no key is declared.
|
|
213
|
+
*/
|
|
214
|
+
function walkthroughProgressKey(configKey) {
|
|
215
|
+
return configKey ? configKey + '-progress' : ''
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Normalise stored progress (an object, or the JSON string a preference
|
|
220
|
+
* holds) into `{ tourId, stepId, index, version }`, or null.
|
|
221
|
+
*
|
|
222
|
+
* @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
|
|
223
|
+
* @param {unknown} value The stored value.
|
|
224
|
+
* @return {{tourId: string, stepId: string, index: number, version: string}|null} The progress.
|
|
225
|
+
*/
|
|
226
|
+
function normaliseWalkthroughProgress(value) {
|
|
227
|
+
let data = value;
|
|
228
|
+
if (typeof data === 'string') {
|
|
229
|
+
if (data === '') {
|
|
230
|
+
return null
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
data = JSON.parse(data);
|
|
234
|
+
} catch {
|
|
235
|
+
return null
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (!isPlainObject(data) || typeof data.tourId !== 'string' || data.tourId === '') {
|
|
239
|
+
return null
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
tourId: data.tourId,
|
|
243
|
+
stepId: typeof data.stepId === 'string' ? data.stepId : '',
|
|
244
|
+
index: Number.isInteger(data.index) && data.index >= 0 ? data.index : 0,
|
|
245
|
+
version: typeof data.version === 'string' ? data.version : '',
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Read the per-browser mirror of the tour progress. Synchronous.
|
|
251
|
+
*
|
|
252
|
+
* @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
|
|
253
|
+
* @param {string} appId The Nextcloud app id.
|
|
254
|
+
* @param {Storage} [storage] Injectable storage backend.
|
|
255
|
+
* @return {object|null} The progress, or null.
|
|
256
|
+
*/
|
|
257
|
+
function readLocalWalkthroughProgress(appId, storage) {
|
|
258
|
+
const s = resolveStorage(storage);
|
|
259
|
+
if (!s) {
|
|
260
|
+
return null
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
return normaliseWalkthroughProgress(s.getItem(WALKTHROUGH_PROGRESS_STORAGE_PREFIX + appId))
|
|
264
|
+
} catch {
|
|
265
|
+
return null
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Load the user's tour progress: the per-user preference
|
|
271
|
+
* `<completionConfigKey>-progress` when declared, else the local mirror.
|
|
272
|
+
*
|
|
273
|
+
* @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
|
|
274
|
+
* @param {string} appId The Nextcloud app id.
|
|
275
|
+
* @param {string} configKey `manifest.walkthrough.completionConfigKey` (may be empty).
|
|
276
|
+
* @param {object} [options] `{ http, storage }` injection points for tests.
|
|
277
|
+
* @return {Promise<object|null>} The progress, or null.
|
|
278
|
+
*/
|
|
279
|
+
async function loadWalkthroughProgress(appId, configKey, options = {}) {
|
|
280
|
+
const local = readLocalWalkthroughProgress(appId, options.storage);
|
|
281
|
+
const key = walkthroughProgressKey(configKey);
|
|
282
|
+
if (!key) {
|
|
283
|
+
return local
|
|
284
|
+
}
|
|
285
|
+
const http = options.http || axios__default;
|
|
286
|
+
try {
|
|
287
|
+
const { data } = await http.get(walkthroughPreferenceUrl(appId, key));
|
|
288
|
+
if (!isPlainObject(data) || !('value' in data)) {
|
|
289
|
+
return local
|
|
290
|
+
}
|
|
291
|
+
return normaliseWalkthroughProgress(data.value) || local
|
|
292
|
+
} catch {
|
|
293
|
+
return local
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Persist the user's tour progress, or clear it with `null`. Writes the
|
|
299
|
+
* local mirror and, with a completion key, the per-user preference. Never
|
|
300
|
+
* throws.
|
|
301
|
+
*
|
|
302
|
+
* @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
|
|
303
|
+
* @param {string} appId The Nextcloud app id.
|
|
304
|
+
* @param {string} configKey `manifest.walkthrough.completionConfigKey` (may be empty).
|
|
305
|
+
* @param {object|null} progress `{ tourId, stepId, index, version }`, or null to clear.
|
|
306
|
+
* @param {object} [options] `{ http, storage }` injection points for tests.
|
|
307
|
+
* @return {Promise<boolean>} True when the server preference was written.
|
|
308
|
+
*/
|
|
309
|
+
function persistWalkthroughProgress(appId, configKey, progress, options = {}) {
|
|
310
|
+
const value = progress ? normaliseWalkthroughProgress(progress) : null;
|
|
311
|
+
const raw = value ? JSON.stringify(value) : '';
|
|
312
|
+
const s = resolveStorage(options.storage);
|
|
313
|
+
if (s) {
|
|
314
|
+
try {
|
|
315
|
+
if (raw) {
|
|
316
|
+
s.setItem(WALKTHROUGH_PROGRESS_STORAGE_PREFIX + appId, raw);
|
|
317
|
+
} else {
|
|
318
|
+
s.removeItem(WALKTHROUGH_PROGRESS_STORAGE_PREFIX + appId);
|
|
319
|
+
}
|
|
320
|
+
} catch {
|
|
321
|
+
/* quota / private mode: best-effort */
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const key = walkthroughProgressKey(configKey);
|
|
325
|
+
if (!key) {
|
|
326
|
+
return Promise.resolve(false)
|
|
327
|
+
}
|
|
328
|
+
const http = options.http || axios__default;
|
|
329
|
+
try {
|
|
330
|
+
return Promise.resolve(http.put(walkthroughPreferenceUrl(appId, key), { value: raw }))
|
|
331
|
+
.then(() => true)
|
|
332
|
+
.catch(() => false)
|
|
333
|
+
} catch {
|
|
334
|
+
return Promise.resolve(false)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Whether an `object-created` signal satisfies a step's `advanceOn`.
|
|
340
|
+
*
|
|
341
|
+
* The signal's own `register` / `schema` (the slugs the creating surface
|
|
342
|
+
* knows) are compared first; an object's `@self` values are numeric ids on
|
|
343
|
+
* OpenRegister and only match a step that also uses ids.
|
|
344
|
+
*
|
|
345
|
+
* @param {object} advanceOn The step's `advanceOn`.
|
|
346
|
+
* @param {object} signal `{ register?, schema?, object? }`.
|
|
347
|
+
* @return {boolean} True when it matches.
|
|
348
|
+
*/
|
|
349
|
+
function objectCreatedMatches(advanceOn, signal) {
|
|
350
|
+
const obj = signal.object || {};
|
|
351
|
+
const self = obj['@self'] || {};
|
|
352
|
+
const same = (want, candidates) => !want || candidates
|
|
353
|
+
.filter((c) => c !== null && c !== undefined && c !== '')
|
|
354
|
+
.some((c) => String(c).toLowerCase() === String(want).toLowerCase());
|
|
355
|
+
return same(advanceOn.register, [signal.register, obj.register, self.register])
|
|
356
|
+
&& same(advanceOn.schema, [signal.schema, obj.schema, self.schema])
|
|
357
|
+
}
|
|
358
|
+
|
|
203
359
|
/**
|
|
204
360
|
* Compare two semver-ish strings. Missing / unparseable parts sort as 0.
|
|
205
361
|
*
|
|
@@ -277,6 +433,9 @@ function useWalkthrough(appId, manifest, options = {}) {
|
|
|
277
433
|
const currentIndex = ref(0);
|
|
278
434
|
const context = ref({});
|
|
279
435
|
const running = ref(false);
|
|
436
|
+
// True while the tour is hidden by ESC or a backdrop click. The tour
|
|
437
|
+
// and its step stay, so it continues where it was.
|
|
438
|
+
const paused = ref(false);
|
|
280
439
|
// True while a user-initiated replay is active (the "Restart tutorial"
|
|
281
440
|
// entry). A replay shows the FULL tour regardless of the persisted
|
|
282
441
|
// seen-version — otherwise a returning user, whose seenVersion already
|
|
@@ -480,6 +639,7 @@ function useWalkthrough(appId, manifest, options = {}) {
|
|
|
480
639
|
*/
|
|
481
640
|
function start(tourId, startIndex = 0) {
|
|
482
641
|
replaying.value = false;
|
|
642
|
+
paused.value = false;
|
|
483
643
|
activeTourId.value = tourId;
|
|
484
644
|
currentIndex.value = startIndex;
|
|
485
645
|
context.value = {};
|
|
@@ -504,9 +664,59 @@ function useWalkthrough(appId, manifest, options = {}) {
|
|
|
504
664
|
*/
|
|
505
665
|
function dismiss() {
|
|
506
666
|
running.value = false;
|
|
667
|
+
paused.value = false;
|
|
507
668
|
activeTourId.value = null;
|
|
508
669
|
replaying.value = false;
|
|
509
670
|
}
|
|
671
|
+
/**
|
|
672
|
+
* Hide the active tour without ending it (ESC, a click on the dim).
|
|
673
|
+
* The tour and step are kept, so `resumePaused()` continues there.
|
|
674
|
+
*
|
|
675
|
+
* @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
|
|
676
|
+
* @return {void}
|
|
677
|
+
*/
|
|
678
|
+
function pause() {
|
|
679
|
+
if (!activeTourId.value) {
|
|
680
|
+
return
|
|
681
|
+
}
|
|
682
|
+
running.value = false;
|
|
683
|
+
paused.value = true;
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Show a paused tour again at the step it was on.
|
|
687
|
+
*
|
|
688
|
+
* @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
|
|
689
|
+
* @return {boolean} True when there was a paused tour to show.
|
|
690
|
+
*/
|
|
691
|
+
function resumePaused() {
|
|
692
|
+
if (!paused.value || !activeTourId.value) {
|
|
693
|
+
return false
|
|
694
|
+
}
|
|
695
|
+
paused.value = false;
|
|
696
|
+
running.value = true;
|
|
697
|
+
return true
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Start a tour at a remembered step ("continue where you left off").
|
|
701
|
+
* Runs in replay mode so a returning user sees the full tour, and
|
|
702
|
+
* falls back to the first step when the step no longer exists.
|
|
703
|
+
*
|
|
704
|
+
* @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
|
|
705
|
+
* @param {string} tourId The tour id.
|
|
706
|
+
* @param {string} [stepId] The step to continue at.
|
|
707
|
+
* @return {boolean} True when the tour exists and started.
|
|
708
|
+
*/
|
|
709
|
+
function resumeAt(tourId, stepId) {
|
|
710
|
+
const tour = tours.find((t) => t.id === tourId);
|
|
711
|
+
if (!tour) {
|
|
712
|
+
return false
|
|
713
|
+
}
|
|
714
|
+
replaying.value = true;
|
|
715
|
+
const idx = composeSteps(tour).findIndex((s) => s.id === stepId);
|
|
716
|
+
start(tourId, Math.max(0, idx));
|
|
717
|
+
replaying.value = true;
|
|
718
|
+
return true
|
|
719
|
+
}
|
|
510
720
|
/**
|
|
511
721
|
* Mark the active tour complete: stop running and invoke the
|
|
512
722
|
* `onComplete(appVersion)` persistence callback so the seen version is
|
|
@@ -516,6 +726,7 @@ function useWalkthrough(appId, manifest, options = {}) {
|
|
|
516
726
|
*/
|
|
517
727
|
function complete() {
|
|
518
728
|
running.value = false;
|
|
729
|
+
paused.value = false;
|
|
519
730
|
activeTourId.value = null;
|
|
520
731
|
replaying.value = false;
|
|
521
732
|
if (typeof options.onComplete === 'function') {
|
|
@@ -543,12 +754,9 @@ function useWalkthrough(appId, manifest, options = {}) {
|
|
|
543
754
|
runCapture(step, { params: signal.params || {} });
|
|
544
755
|
}
|
|
545
756
|
} else if (signal.kind === 'object-created' && a.type === 'object-created') {
|
|
546
|
-
|
|
547
|
-
const reg = obj.register ?? obj['@self']?.register;
|
|
548
|
-
const sch = obj.schema ?? obj['@self']?.schema;
|
|
549
|
-
match = (!a.register || reg === a.register) && (!a.schema || sch === a.schema);
|
|
757
|
+
match = objectCreatedMatches(a, signal);
|
|
550
758
|
if (match) {
|
|
551
|
-
runCapture(step, { object:
|
|
759
|
+
runCapture(step, { object: signal.object || {} });
|
|
552
760
|
}
|
|
553
761
|
} else if (signal.kind === 'element' && a.type === 'element-appears') {
|
|
554
762
|
match = true;
|
|
@@ -576,9 +784,13 @@ function useWalkthrough(appId, manifest, options = {}) {
|
|
|
576
784
|
isLast,
|
|
577
785
|
context,
|
|
578
786
|
running,
|
|
787
|
+
paused,
|
|
579
788
|
replaying,
|
|
580
789
|
start,
|
|
581
790
|
restart,
|
|
791
|
+
pause,
|
|
792
|
+
resumePaused,
|
|
793
|
+
resumeAt,
|
|
582
794
|
next,
|
|
583
795
|
back,
|
|
584
796
|
skip,
|
|
@@ -604,5 +816,5 @@ function useWalkthrough(appId, manifest, options = {}) {
|
|
|
604
816
|
return entry
|
|
605
817
|
}
|
|
606
818
|
|
|
607
|
-
export { WALKTHROUGH_SEEN_STORAGE_PREFIX, compareSemver, interpolateTokens, loadWalkthroughSeenVersion, normaliseSeenVersion, persistWalkthroughSeenVersion, readLocalWalkthroughSeenVersion, useWalkthrough, walkthroughPreferenceUrl };
|
|
819
|
+
export { WALKTHROUGH_PROGRESS_STORAGE_PREFIX, WALKTHROUGH_SEEN_STORAGE_PREFIX, compareSemver, interpolateTokens, loadWalkthroughProgress, loadWalkthroughSeenVersion, normaliseSeenVersion, normaliseWalkthroughProgress, persistWalkthroughProgress, persistWalkthroughSeenVersion, readLocalWalkthroughProgress, readLocalWalkthroughSeenVersion, useWalkthrough, walkthroughPreferenceUrl, walkthroughProgressKey };
|
|
608
820
|
//# sourceMappingURL=useWalkthrough.js.map
|