@conduction/nextcloud-vue 2.99.0 → 2.101.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  2. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +6 -1
  3. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  4. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +2 -1
  5. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  6. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
  7. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js +22 -1
  8. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  9. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +26 -16
  10. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  11. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  12. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +51 -7
  13. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  14. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +104 -73
  15. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  16. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +25 -8
  17. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  18. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +5 -3
  19. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  20. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  21. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +11 -8
  22. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  23. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +13 -4
  24. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  25. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue.js +9 -0
  26. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue.js.map +1 -0
  27. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue2.js +139 -0
  28. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue2.js.map +1 -0
  29. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue3.js +76 -0
  30. package/dist/esm/components/CnExportsWidget/CnExportsWidget.vue3.js.map +1 -0
  31. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  32. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +54 -3
  33. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  34. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +16 -2
  35. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  36. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  37. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +160 -2
  38. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  39. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +206 -94
  40. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  41. package/dist/esm/components/CnFilesBrowser/filesReview.js +61 -0
  42. package/dist/esm/components/CnFilesBrowser/filesReview.js.map +1 -0
  43. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue.js +1 -1
  44. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js +42 -7
  45. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js.map +1 -1
  46. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js +121 -57
  47. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js.map +1 -1
  48. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue2.js +10 -0
  49. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue2.js.map +1 -1
  50. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue3.js +2 -1
  51. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue3.js.map +1 -1
  52. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue.js +9 -0
  53. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue.js.map +1 -0
  54. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js +279 -0
  55. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue2.js.map +1 -0
  56. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js +122 -0
  57. package/dist/esm/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue3.js.map +1 -0
  58. package/dist/esm/components/CnGroupedCardsWidget/index.js +29 -0
  59. package/dist/esm/components/CnGroupedCardsWidget/index.js.map +1 -0
  60. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +387 -78
  61. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  62. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +317 -209
  63. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  64. package/dist/esm/components/CnIndexPage/useNamedSource.js +4 -1
  65. package/dist/esm/components/CnIndexPage/useNamedSource.js.map +1 -1
  66. package/dist/esm/components/CnMapWidget/CnMapWidget.vue.js +1 -1
  67. package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js +15 -0
  68. package/dist/esm/components/CnMapWidget/CnMapWidget.vue2.js.map +1 -1
  69. package/dist/esm/components/CnMapWidget/CnMapWidget.vue3.js.map +1 -1
  70. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
  71. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +14 -1
  72. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  73. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +14 -6
  74. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  75. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
  76. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +10 -0
  77. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  78. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +6 -1
  79. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  80. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue.js +1 -1
  81. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js +91 -4
  82. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue2.js.map +1 -1
  83. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js +63 -38
  84. package/dist/esm/components/CnOrderedListField/CnOrderedListField.vue3.js.map +1 -1
  85. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  86. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +68 -7
  87. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  88. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +256 -240
  89. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  90. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js +9 -0
  91. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue.js.map +1 -0
  92. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js +175 -0
  93. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue2.js.map +1 -0
  94. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js +77 -0
  95. package/dist/esm/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue3.js.map +1 -0
  96. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue.js +1 -1
  97. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue2.js +38 -9
  98. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue2.js.map +1 -1
  99. package/dist/esm/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue3.js.map +1 -1
  100. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
  101. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +51 -4
  102. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  103. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +40 -25
  104. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  105. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +4 -0
  106. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
  107. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  108. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  109. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue.js +1 -1
  110. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js +7 -7
  111. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
  112. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
  113. package/dist/esm/composables/indexSources.js +167 -20
  114. package/dist/esm/composables/indexSources.js.map +1 -1
  115. package/dist/esm/composables/useEndpointSource.js +28 -6
  116. package/dist/esm/composables/useEndpointSource.js.map +1 -1
  117. package/dist/esm/index.js +1 -0
  118. package/dist/esm/index.js.map +1 -1
  119. package/dist/esm/l10n/en.json.js +43 -1
  120. package/dist/esm/l10n/en.json.js.map +1 -1
  121. package/dist/esm/l10n/nl.json.js +41 -1
  122. package/dist/esm/l10n/nl.json.js.map +1 -1
  123. package/dist/esm/library-style.js +1 -1
  124. package/dist/esm/nextcloud-vue.css +1101 -396
  125. package/dist/esm/utils/languageMapText.js +50 -0
  126. package/dist/esm/utils/languageMapText.js.map +1 -0
  127. package/dist/esm/utils/libraryWidgetKeys.js +3 -0
  128. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  129. package/dist/esm/utils/mapList.js +71 -0
  130. package/dist/esm/utils/mapList.js.map +1 -0
  131. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  132. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  133. package/dist/nextcloud-vue.cjs.js +9996 -7378
  134. package/dist/nextcloud-vue.cjs.js.map +2 -2
  135. package/dist/nextcloud-vue.css +1101 -396
  136. package/l10n/en.json +43 -1
  137. package/l10n/nl.json +41 -1
  138. package/package.json +1 -1
  139. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +7 -1
  140. package/src/components/CnChoiceCards/CnChoiceCards.md +39 -0
  141. package/src/components/CnChoiceCards/CnChoiceCards.vue +93 -16
  142. package/src/components/CnDashboardPage/CnDashboardPage.vue +89 -20
  143. package/src/components/CnDetailPage/CnDetailPage.vue +28 -9
  144. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +13 -4
  145. package/src/components/CnExportsWidget/CnExportsWidget.vue +233 -0
  146. package/src/components/CnExportsWidget/index.js +7 -0
  147. package/src/components/CnFactsWidget/CnFactsWidget.vue +65 -3
  148. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +324 -0
  149. package/src/components/CnFilesBrowser/filesReview.js +57 -0
  150. package/src/components/CnFlowsPage/CnFlowBoard.vue +77 -2
  151. package/src/components/CnFlowsPage/CnFlowEditorPage.vue +11 -0
  152. package/src/components/CnGroupedCardsWidget/CnGroupedCardsWidget.vue +433 -0
  153. package/src/components/CnGroupedCardsWidget/index.js +28 -0
  154. package/src/components/CnIndexPage/CnIndexPage.vue +376 -11
  155. package/src/components/CnIndexPage/useNamedSource.js +4 -1
  156. package/src/components/CnMapWidget/CnMapWidget.vue +37 -0
  157. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +29 -1
  158. package/src/components/CnObjectSidebar/CnFilesTab.vue +15 -0
  159. package/src/components/CnOrderedListField/CnOrderedListField.vue +151 -5
  160. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +114 -7
  161. package/src/components/CnSavedViewAlertWidget/CnSavedViewAlertWidget.vue +272 -0
  162. package/src/components/CnSavedViewAlertWidget/index.js +7 -0
  163. package/src/components/CnSettingsFieldsSection/CnSettingsFieldsSection.vue +38 -9
  164. package/src/components/CnSetupWizard/CnSetupWizard.vue +72 -5
  165. package/src/components/CnWidgetGrid/builtInWidgets.js +4 -0
  166. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  167. package/src/components/CnWizardDialog/CnWizardDialog.vue +7 -7
  168. package/src/components/index.js +3 -0
  169. package/src/composables/indexSources.js +166 -19
  170. package/src/composables/useEndpointSource.js +28 -6
  171. package/src/css/detail-page.css +15 -0
  172. package/src/css/dialog.css +48 -0
  173. package/src/css/index-page.css +117 -0
  174. package/src/css/look-board.css +127 -1
  175. package/src/index.js +1 -0
  176. package/src/schemas/app-manifest-v2.schema.json +55 -1
  177. package/src/utils/languageMapText.js +46 -0
  178. package/src/utils/libraryWidgetKeys.js +3 -0
  179. package/src/utils/mapList.js +67 -0
  180. package/src/utils/validateManifestV2.compiled.js +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"CnWizardDialog.vue2.js","sources":["../../../../src/components/CnWizardDialog/CnWizardDialog.vue"],"sourcesContent":["<template>\n\t<CnDialog\n\t\t:look=\"look\"\n\t\t:width=\"width\"\n\t\tdefaultWidth=\"wizard\"\n\t\t:eyebrow=\"boardEyebrow\"\n\t\t:subtitle=\"subtitle\"\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: an ordered list with the current step marked.\n\t\t\t A finished step is a button when allowJumpBack is true. -->\n\t\t\t<CnStepper\n\t\t\t\t:steps=\"steps\"\n\t\t\t\t:currentIndex=\"currentIndex\"\n\t\t\t\t:allowJumpBack=\"allowJumpBack\"\n\t\t\t\t:look=\"look\"\n\t\t\t\t@jump=\"jumpTo\" />\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<template v-if=\"isBoardLook\" #icon>\n\t\t\t\t\t\t<ChevronLeft :size=\"20\" />\n\t\t\t\t\t</template>\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:alignment=\"isBoardLook && !isLast ? 'center-reverse' : 'center'\"\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\t<template v-else-if=\"isBoardLook\">\n\t\t\t\t\t\t\t<ChevronRight v-if=\"!isLast\" :size=\"20\" />\n\t\t\t\t\t\t\t<component :is=\"submitIcon || Check\" v-else :size=\"20\" />\n\t\t\t\t\t\t</template>\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</CnDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\nimport Check from 'vue-material-design-icons/Check.vue'\nimport ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'\nimport ChevronRight from 'vue-material-design-icons/ChevronRight.vue'\nimport CnDialog from '../CnDialog/CnDialog.vue'\nimport CnStepper from '../CnStepper/CnStepper.vue'\nimport { dialogBoardMixin } from '../../mixins/dialogBoard.js'\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: { CnDialog, CnStepper, ChevronLeft, ChevronRight, NcButton, NcNoteCard, NcLoadingIcon },\n\n\tmixins: [dialogBoardMixin],\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/**\n\t\t * The context in the board look's eyebrow (\"<context>, step N of M\").\n\t\t * Empty uses the dialog title.\n\t\t */\n\t\teyebrowContext: { type: String, default: '' },\n\t\t/**\n\t\t * The eyebrow sentence, with `{context}`, `{step}` and `{total}`\n\t\t * placeholders, so it can be translated.\n\t\t */\n\t\tstepEyebrow: { type: String, default: () => t('nextcloud-vue', '{context}, step {step} of {total}') },\n\t\t/**\n\t\t * Icon component on the finishing button of the last step (board look).\n\t\t * Empty draws a check.\n\t\t *\n\t\t * @type {object|Function|null}\n\t\t */\n\t\tsubmitIcon: { type: [Object, Function], default: null },\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 board look's eyebrow, \"<context>, step N of M\", updated on every step\n\t\t * change. Empty in the Nextcloud look and in the result phase.\n\t\t *\n\t\t * @return {string} The eyebrow.\n\t\t */\n\t\tboardEyebrow() {\n\t\t\tif (!this.isBoardLook || this.result !== null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.stepEyebrow\n\t\t\t\t.replace('{context}', this.eyebrowContext || this.eyebrow || this.dialogTitle)\n\t\t\t\t.replace('{step}', String(this.currentIndex + 1))\n\t\t\t\t.replace('{total}', String(this.steps.length))\n\t\t},\n\n\t\t/** @return {object} The check icon, the default finishing icon. */\n\t\tCheck() {\n\t\t\treturn Check\n\t\t},\n\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__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":["CnDialog","CnStepper","t"],"mappings":"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"}
1
+ {"version":3,"file":"CnWizardDialog.vue2.js","sources":["../../../../src/components/CnWizardDialog/CnWizardDialog.vue"],"sourcesContent":["<template>\n\t<CnDialog\n\t\t:look=\"look\"\n\t\t:width=\"width\"\n\t\tdefaultWidth=\"wizard\"\n\t\t:eyebrow=\"boardEyebrow\"\n\t\t:subtitle=\"subtitle\"\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: an ordered list with the current step marked.\n\t\t\t A finished step is a button when allowJumpBack is true. -->\n\t\t\t<CnStepper\n\t\t\t\t:steps=\"steps\"\n\t\t\t\t:currentIndex=\"currentIndex\"\n\t\t\t\t:allowJumpBack=\"allowJumpBack\"\n\t\t\t\t:look=\"look\"\n\t\t\t\t@jump=\"jumpTo\" />\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<template v-if=\"isBoardLook\" #icon>\n\t\t\t\t\t\t<ChevronLeft :size=\"20\" />\n\t\t\t\t\t</template>\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:alignment=\"isBoardLook && !isLast ? 'center-reverse' : 'center'\"\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\t<template v-else-if=\"isBoardLook\">\n\t\t\t\t\t\t\t<ChevronRight v-if=\"!isLast\" :size=\"20\" />\n\t\t\t\t\t\t\t<component :is=\"submitIcon || Check\" v-else :size=\"20\" />\n\t\t\t\t\t\t</template>\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</CnDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\nimport Check from 'vue-material-design-icons/Check.vue'\nimport ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'\nimport ChevronRight from 'vue-material-design-icons/ChevronRight.vue'\nimport CnDialog from '../CnDialog/CnDialog.vue'\nimport CnStepper from '../CnStepper/CnStepper.vue'\nimport { dialogBoardMixin } from '../../mixins/dialogBoard.js'\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: { CnDialog, CnStepper, ChevronLeft, ChevronRight, NcButton, NcNoteCard, NcLoadingIcon },\n\n\tmixins: [dialogBoardMixin],\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: () => t('nextcloud-vue', '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/**\n\t\t * The context in the board look's eyebrow (\"<context>, step N of M\").\n\t\t * Empty uses the dialog title.\n\t\t */\n\t\teyebrowContext: { type: String, default: '' },\n\t\t/**\n\t\t * The eyebrow sentence, with `{context}`, `{step}` and `{total}`\n\t\t * placeholders, so it can be translated.\n\t\t */\n\t\tstepEyebrow: { type: String, default: () => t('nextcloud-vue', '{context}, step {step} of {total}') },\n\t\t/**\n\t\t * Icon component on the finishing button of the last step (board look).\n\t\t * Empty draws a check.\n\t\t *\n\t\t * @type {object|Function|null}\n\t\t */\n\t\tsubmitIcon: { type: [Object, Function], default: null },\n\n\t\t/** Cancel-button label (wizard phase). */\n\t\tcancelLabel: { type: String, default: () => t('nextcloud-vue', 'Cancel') },\n\t\t/** Back-button label. */\n\t\tbackLabel: { type: String, default: () => t('nextcloud-vue', 'Back') },\n\t\t/** Next-button label (intermediate steps). */\n\t\tnextLabel: { type: String, default: () => t('nextcloud-vue', 'Next') },\n\t\t/** Submit-button label (last step). */\n\t\tsubmitLabel: { type: String, default: () => t('nextcloud-vue', 'Submit') },\n\t\t/** Close-button label (result phase). */\n\t\tcloseLabel: { type: String, default: () => t('nextcloud-vue', 'Close') },\n\t\t/** Success banner default text when result.message is empty. */\n\t\tsuccessText: { type: String, default: () => t('nextcloud-vue', '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 board look's eyebrow, \"<context>, step N of M\", updated on every step\n\t\t * change. Empty in the Nextcloud look and in the result phase.\n\t\t *\n\t\t * @return {string} The eyebrow.\n\t\t */\n\t\tboardEyebrow() {\n\t\t\tif (!this.isBoardLook || this.result !== null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.stepEyebrow\n\t\t\t\t.replace('{context}', this.eyebrowContext || this.eyebrow || this.dialogTitle)\n\t\t\t\t.replace('{step}', String(this.currentIndex + 1))\n\t\t\t\t.replace('{total}', String(this.steps.length))\n\t\t},\n\n\t\t/** @return {object} The check icon, the default finishing icon. */\n\t\tCheck() {\n\t\t\treturn Check\n\t\t},\n\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__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":["CnDialog","CnStepper","t"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CnWizardDialog.vue3.js","sources":["../../../../src/components/CnWizardDialog/CnWizardDialog.vue?vue&type=template&id=4a9bf6d1&lang.js"],"sourcesContent":["<template>\n\t<CnDialog\n\t\t:look=\"look\"\n\t\t:width=\"width\"\n\t\tdefaultWidth=\"wizard\"\n\t\t:eyebrow=\"boardEyebrow\"\n\t\t:subtitle=\"subtitle\"\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: an ordered list with the current step marked.\n\t\t\t A finished step is a button when allowJumpBack is true. -->\n\t\t\t<CnStepper\n\t\t\t\t:steps=\"steps\"\n\t\t\t\t:currentIndex=\"currentIndex\"\n\t\t\t\t:allowJumpBack=\"allowJumpBack\"\n\t\t\t\t:look=\"look\"\n\t\t\t\t@jump=\"jumpTo\" />\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<template v-if=\"isBoardLook\" #icon>\n\t\t\t\t\t\t<ChevronLeft :size=\"20\" />\n\t\t\t\t\t</template>\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:alignment=\"isBoardLook && !isLast ? 'center-reverse' : 'center'\"\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\t<template v-else-if=\"isBoardLook\">\n\t\t\t\t\t\t\t<ChevronRight v-if=\"!isLast\" :size=\"20\" />\n\t\t\t\t\t\t\t<component :is=\"submitIcon || Check\" v-else :size=\"20\" />\n\t\t\t\t\t\t</template>\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</CnDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\nimport Check from 'vue-material-design-icons/Check.vue'\nimport ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'\nimport ChevronRight from 'vue-material-design-icons/ChevronRight.vue'\nimport CnDialog from '../CnDialog/CnDialog.vue'\nimport CnStepper from '../CnStepper/CnStepper.vue'\nimport { dialogBoardMixin } from '../../mixins/dialogBoard.js'\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: { CnDialog, CnStepper, ChevronLeft, ChevronRight, NcButton, NcNoteCard, NcLoadingIcon },\n\n\tmixins: [dialogBoardMixin],\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/**\n\t\t * The context in the board look's eyebrow (\"<context>, step N of M\").\n\t\t * Empty uses the dialog title.\n\t\t */\n\t\teyebrowContext: { type: String, default: '' },\n\t\t/**\n\t\t * The eyebrow sentence, with `{context}`, `{step}` and `{total}`\n\t\t * placeholders, so it can be translated.\n\t\t */\n\t\tstepEyebrow: { type: String, default: () => t('nextcloud-vue', '{context}, step {step} of {total}') },\n\t\t/**\n\t\t * Icon component on the finishing button of the last step (board look).\n\t\t * Empty draws a check.\n\t\t *\n\t\t * @type {object|Function|null}\n\t\t */\n\t\tsubmitIcon: { type: [Object, Function], default: null },\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 board look's eyebrow, \"<context>, step N of M\", updated on every step\n\t\t * change. Empty in the Nextcloud look and in the result phase.\n\t\t *\n\t\t * @return {string} The eyebrow.\n\t\t */\n\t\tboardEyebrow() {\n\t\t\tif (!this.isBoardLook || this.result !== null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.stepEyebrow\n\t\t\t\t.replace('{context}', this.eyebrowContext || this.eyebrow || this.dialogTitle)\n\t\t\t\t.replace('{step}', String(this.currentIndex + 1))\n\t\t\t\t.replace('{total}', String(this.steps.length))\n\t\t},\n\n\t\t/** @return {object} The check icon, the default finishing icon. */\n\t\tCheck() {\n\t\t\treturn Check\n\t\t},\n\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__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","_createTextVNode","_createVNode","_resolveDynamicComponent","_toDisplayString","_openBlock","_renderSlot","_createElementVNode"],"mappings":"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"}
1
+ {"version":3,"file":"CnWizardDialog.vue3.js","sources":["../../../../src/components/CnWizardDialog/CnWizardDialog.vue?vue&type=template&id=547e7819&lang.js"],"sourcesContent":["<template>\n\t<CnDialog\n\t\t:look=\"look\"\n\t\t:width=\"width\"\n\t\tdefaultWidth=\"wizard\"\n\t\t:eyebrow=\"boardEyebrow\"\n\t\t:subtitle=\"subtitle\"\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: an ordered list with the current step marked.\n\t\t\t A finished step is a button when allowJumpBack is true. -->\n\t\t\t<CnStepper\n\t\t\t\t:steps=\"steps\"\n\t\t\t\t:currentIndex=\"currentIndex\"\n\t\t\t\t:allowJumpBack=\"allowJumpBack\"\n\t\t\t\t:look=\"look\"\n\t\t\t\t@jump=\"jumpTo\" />\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<template v-if=\"isBoardLook\" #icon>\n\t\t\t\t\t\t<ChevronLeft :size=\"20\" />\n\t\t\t\t\t</template>\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:alignment=\"isBoardLook && !isLast ? 'center-reverse' : 'center'\"\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\t<template v-else-if=\"isBoardLook\">\n\t\t\t\t\t\t\t<ChevronRight v-if=\"!isLast\" :size=\"20\" />\n\t\t\t\t\t\t\t<component :is=\"submitIcon || Check\" v-else :size=\"20\" />\n\t\t\t\t\t\t</template>\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</CnDialog>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\nimport Check from 'vue-material-design-icons/Check.vue'\nimport ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'\nimport ChevronRight from 'vue-material-design-icons/ChevronRight.vue'\nimport CnDialog from '../CnDialog/CnDialog.vue'\nimport CnStepper from '../CnStepper/CnStepper.vue'\nimport { dialogBoardMixin } from '../../mixins/dialogBoard.js'\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: { CnDialog, CnStepper, ChevronLeft, ChevronRight, NcButton, NcNoteCard, NcLoadingIcon },\n\n\tmixins: [dialogBoardMixin],\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: () => t('nextcloud-vue', '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/**\n\t\t * The context in the board look's eyebrow (\"<context>, step N of M\").\n\t\t * Empty uses the dialog title.\n\t\t */\n\t\teyebrowContext: { type: String, default: '' },\n\t\t/**\n\t\t * The eyebrow sentence, with `{context}`, `{step}` and `{total}`\n\t\t * placeholders, so it can be translated.\n\t\t */\n\t\tstepEyebrow: { type: String, default: () => t('nextcloud-vue', '{context}, step {step} of {total}') },\n\t\t/**\n\t\t * Icon component on the finishing button of the last step (board look).\n\t\t * Empty draws a check.\n\t\t *\n\t\t * @type {object|Function|null}\n\t\t */\n\t\tsubmitIcon: { type: [Object, Function], default: null },\n\n\t\t/** Cancel-button label (wizard phase). */\n\t\tcancelLabel: { type: String, default: () => t('nextcloud-vue', 'Cancel') },\n\t\t/** Back-button label. */\n\t\tbackLabel: { type: String, default: () => t('nextcloud-vue', 'Back') },\n\t\t/** Next-button label (intermediate steps). */\n\t\tnextLabel: { type: String, default: () => t('nextcloud-vue', 'Next') },\n\t\t/** Submit-button label (last step). */\n\t\tsubmitLabel: { type: String, default: () => t('nextcloud-vue', 'Submit') },\n\t\t/** Close-button label (result phase). */\n\t\tcloseLabel: { type: String, default: () => t('nextcloud-vue', 'Close') },\n\t\t/** Success banner default text when result.message is empty. */\n\t\tsuccessText: { type: String, default: () => t('nextcloud-vue', '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 board look's eyebrow, \"<context>, step N of M\", updated on every step\n\t\t * change. Empty in the Nextcloud look and in the result phase.\n\t\t *\n\t\t * @return {string} The eyebrow.\n\t\t */\n\t\tboardEyebrow() {\n\t\t\tif (!this.isBoardLook || this.result !== null) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn this.stepEyebrow\n\t\t\t\t.replace('{context}', this.eyebrowContext || this.eyebrow || this.dialogTitle)\n\t\t\t\t.replace('{step}', String(this.currentIndex + 1))\n\t\t\t\t.replace('{total}', String(this.steps.length))\n\t\t},\n\n\t\t/** @return {object} The check icon, the default finishing icon. */\n\t\tCheck() {\n\t\t\treturn Check\n\t\t},\n\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__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","_createTextVNode","_createVNode","_resolveDynamicComponent","_toDisplayString","_openBlock","_renderSlot","_createElementVNode"],"mappings":"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"}
@@ -6,6 +6,8 @@ import HandBackRight from 'vue-material-design-icons/HandBackRight.vue';
6
6
  import LightningBolt from 'vue-material-design-icons/LightningBolt.vue';
7
7
  import Pencil from 'vue-material-design-icons/Pencil.vue';
8
8
  import { formatRelativeDate } from '../utils/boardDate.js';
9
+ import { fetchReferenceObjects } from '../utils/boardReferenceColumns.js';
10
+ import { fillTemplate } from '../utils/fillTemplate.js';
9
11
  import { flowRunStatuses, FLOW_RUN_ERROR_STATUSES } from '../utils/flowRunStatus.js';
10
12
  import { fetchGroupMembers, fetchStatuses, fetchPresence } from '../utils/nextcloudDirectory.js';
11
13
  import { readPath } from '../utils/readPath.js';
@@ -649,6 +651,10 @@ function readRowsPath(body, path) {
649
651
  * requested: page-config keys must never become query parameters. The list
650
652
  * stays empty and one console error names the page.
651
653
  *
654
+ * A quick filter with `showCount` is counted with `count(config)`: the same
655
+ * request with that tab's filter, its total read at `sourceConfig.totalPath`
656
+ * (`total` by default), else the number of rows the answer holds.
657
+ *
652
658
  * @return {object} The source adapter.
653
659
  * @spec openspec/changes/index-endpoint-source/specs/index-page/spec.md#requirement-an-index-page-can-list-rows-from-an-app-endpoint
654
660
  */
@@ -657,6 +663,36 @@ function endpointSource() {
657
663
  let seq = 0;
658
664
  let warned = false;
659
665
 
666
+ /**
667
+ * The query parameters for a config: every key but the url and the paths,
668
+ * empty values left out, a list joined by commas.
669
+ *
670
+ * @param {object} rest The config without `url`, `rowsPath` and `totalPath`.
671
+ * @return {object} The parameters.
672
+ */
673
+ const paramsOf = (rest) => {
674
+ const params = {};
675
+ for (const [key, value] of Object.entries(rest)) {
676
+ if (value === undefined || value === null || value === '') {
677
+ continue
678
+ }
679
+ params[key] = Array.isArray(value) ? value.join(',') : (typeof value === 'object' ? JSON.stringify(value) : value);
680
+ }
681
+ return params
682
+ };
683
+
684
+ /**
685
+ * Whether a config can be requested: it names a url and is not the page
686
+ * config handed over in its place.
687
+ *
688
+ * @param {object} config The config.
689
+ * @return {boolean}
690
+ */
691
+ const requestable = (config) => {
692
+ const url = config && config.url;
693
+ return typeof url === 'string' && url !== '' && !Object.hasOwn(config, 'entitySource')
694
+ };
695
+
660
696
  return {
661
697
  /**
662
698
  * Fetch the rows.
@@ -665,10 +701,9 @@ function endpointSource() {
665
701
  * @return {Promise<void>} Resolves when loaded; a failed request leaves no rows and rethrows.
666
702
  */
667
703
  load: async (config) => {
668
- const { url, rowsPath, ...rest } = config || {};
704
+ const { url, rowsPath, totalPath, ...rest } = config || {};
669
705
  const mine = ++seq;
670
- const pageConfig = Boolean(config && Object.hasOwn(config, 'entitySource'));
671
- if (typeof url !== 'string' || url === '' || pageConfig) {
706
+ if (!requestable(config)) {
672
707
  state.rows = [];
673
708
  if (!warned) {
674
709
  warned = true;
@@ -678,13 +713,7 @@ function endpointSource() {
678
713
  }
679
714
  return
680
715
  }
681
- const params = {};
682
- for (const [key, value] of Object.entries(rest)) {
683
- if (value === undefined || value === null || value === '') {
684
- continue
685
- }
686
- params[key] = Array.isArray(value) ? value.join(',') : (typeof value === 'object' ? JSON.stringify(value) : value);
687
- }
716
+ const params = paramsOf(rest);
688
717
  state.loading = true;
689
718
  try {
690
719
  const { default: axios } = await import('@nextcloud/axios');
@@ -706,6 +735,29 @@ function endpointSource() {
706
735
  }
707
736
  },
708
737
 
738
+ /**
739
+ * Count the rows for a config without touching the rows on screen, for
740
+ * a quick-filter tab with `showCount`.
741
+ *
742
+ * @param {object} config The page's source config with the tab's filter merged in.
743
+ * @return {Promise<number>} The total, or NaN when nothing can be requested.
744
+ */
745
+ count: async (config) => {
746
+ if (!requestable(config)) {
747
+ return Number.NaN
748
+ }
749
+ const { url, rowsPath, totalPath, ...rest } = config;
750
+ const { default: axios } = await import('@nextcloud/axios');
751
+ const res = await axios.get(generateUrl(url), { params: paramsOf(rest) });
752
+ const body = res ? res.data : null;
753
+ const total = body && typeof body === 'object' && !Array.isArray(body) ? readRowsPath(body, totalPath || 'total') : undefined;
754
+ if (typeof total === 'number' && Number.isFinite(total)) {
755
+ return total
756
+ }
757
+ const rows = Array.isArray(body) ? body : readRowsPath(body, rowsPath || 'results');
758
+ return Array.isArray(rows) ? rows.length : Number.NaN
759
+ },
760
+
709
761
  /**
710
762
  * @return {Array<object>} The rows the endpoint returned.
711
763
  */
@@ -738,39 +790,102 @@ function fillRowTemplate(template, row) {
738
790
  * and its count (DqZoekresultaten).
739
791
  *
740
792
  * `sourceConfig.kinds` lists what is searched:
741
- * `{ id, label, register, schema, titleField?, subtitleField?, statusField?, href?, limit? }`
793
+ * `{ id, label, register, schema, titleField?, subtitleField?, statusField?, statusResolve?, linkedField?, linkedTemplate?, href?, limit? }`
742
794
  * for objects (OpenRegister's `_search`, so the reader sees what they may
743
795
  * read), or `{ id, label, files: true, limit? }` for the reader's files
744
796
  * (Nextcloud's unified search). The term is `sourceConfig.term`, else the
745
797
  * address's `?q=` (or `sourceConfig.termQuery`). A kind that fails shows no
746
798
  * rows and no count; the others still answer.
747
799
  *
800
+ * The kind's `label` goes through the app's translate (the "Soort" cell and
801
+ * the quick filters). `statusResolve: { register?, schema, labelField? }`
802
+ * reads `statusField` as a reference and shows the referenced object's label
803
+ * (`labelField`, else `title`, then `name`); the status column is a pill,
804
+ * coloured by `sourceConfig.statusColorMap`. `linkedTemplate` (a field
805
+ * template, "{case.title}") or `linkedField` (a path) fills the "Linked"
806
+ * column, drawn when a kind declares one; `sourceConfig.linkedLabel` names it.
807
+ * The page's title and count line can name the term as `{term}`, and the
808
+ * count is the active kind's total. Kind, link and status are filterable.
809
+ *
748
810
  * @param {object} [config] The page's source config.
811
+ * @param {object} [context] The page's helpers.
812
+ * @param {Function} [context.translate] The app's translate.
749
813
  * @return {object} The source adapter.
750
814
  * @spec openspec/changes/index-search-source/specs/index-page/spec.md#requirement-an-index-page-can-search-several-schemas-and-files
751
815
  */
752
- function searchSource(config = {}) {
816
+ function searchSource(config = {}, context = {}) {
817
+ const translate$1 = typeof context.translate === 'function' ? context.translate : (text) => text;
753
818
  const kinds = (Array.isArray(config && config.kinds) ? config.kinds : [])
754
- .filter((kind) => kind && typeof kind.id === 'string' && kind.id !== '' && (kind.files === true || (kind.register && kind.schema)));
755
- const state = reactive({ rows: [], totals: {}, loading: false, kind: 'all', term: '' });
819
+ .filter((kind) => kind && typeof kind.id === 'string' && kind.id !== '' && (kind.files === true || (kind.register && kind.schema)))
820
+ .map((kind) => ({ ...kind, label: translate$1(String(kind.label || kind.id)) }));
821
+ const hasLinked = kinds.some((kind) => kind.linkedTemplate || kind.linkedField);
822
+ const state = reactive({ rows: [], totals: {}, loading: false, kind: 'all', term: '', filters: {} });
756
823
  let seq = 0;
757
824
 
825
+ /**
826
+ * The labels of the objects a kind's status points at, by id: the
827
+ * referenced schema read once per search.
828
+ *
829
+ * @param {object} kind The kind with `statusResolve`.
830
+ * @return {Promise<Map<string, string>>} Labels by id; empty when it cannot be read.
831
+ */
832
+ const statusLabels = async (kind) => {
833
+ const resolve = kind.statusResolve;
834
+ if (!resolve || !resolve.schema) {
835
+ return new Map()
836
+ }
837
+ const objects = await fetchReferenceObjects({ register: resolve.register || kind.register, schema: resolve.schema }).catch(() => []);
838
+ const labels = new Map();
839
+ for (const object of objects) {
840
+ const id = object.id || object.uuid || (object['@self'] && object['@self'].id);
841
+ const label = readPath(object, resolve.labelField || 'title') ?? object.name;
842
+ if (id !== undefined && label !== undefined && label !== null) {
843
+ labels.set(String(id), String(label));
844
+ }
845
+ }
846
+ return labels
847
+ };
848
+
849
+ /**
850
+ * What a row shows in the Linked column.
851
+ *
852
+ * @param {object} kind The row's kind.
853
+ * @param {object} object The found object.
854
+ * @return {string}
855
+ */
856
+ const linkedOf = (kind, object) => {
857
+ if (kind.linkedTemplate) {
858
+ return fillTemplate(translate$1(kind.linkedTemplate), object)
859
+ }
860
+ if (kind.linkedField) {
861
+ const value = readPath(object, kind.linkedField);
862
+ return value === undefined || value === null ? '' : String(value)
863
+ }
864
+ return ''
865
+ };
866
+
758
867
  const searchObjects = async (axios, kind, term) => {
759
868
  const url = generateUrl('/apps/openregister/api/objects/{register}/{schema}', { register: kind.register, schema: kind.schema });
760
869
  const res = await axios.get(url, { params: { _search: term, _limit: kind.limit || 20 } });
761
870
  const body = (res && res.data) || {};
762
871
  const results = Array.isArray(body.results) ? body.results : (Array.isArray(body) ? body : []);
872
+ const labels = kind.statusResolve && kind.statusField ? await statusLabels(kind) : null;
763
873
  return {
764
874
  total: typeof body.total === 'number' ? body.total : results.length,
765
875
  rows: results.map((object) => {
766
876
  const id = object.id || object.uuid || (object['@self'] && object['@self'].id);
877
+ const rawStatus = kind.statusField ? readPath(object, kind.statusField) : undefined;
878
+ const status = rawStatus === undefined || rawStatus === null
879
+ ? ''
880
+ : (labels ? (labels.get(String(rawStatus)) || '') : String(rawStatus));
767
881
  return {
768
882
  id: `${kind.id}:${id}`,
769
883
  kind: kind.id,
770
884
  kindLabel: kind.label,
771
885
  title: String(readPath(object, kind.titleField || 'title') ?? object.name ?? id ?? ''),
772
886
  subtitle: kind.subtitleField ? String(readPath(object, kind.subtitleField) ?? '') : '',
773
- status: kind.statusField ? String(readPath(object, kind.statusField) ?? '') : '',
887
+ status,
888
+ linked: linkedOf(kind, object),
774
889
  href: kind.href ? generateUrl(fillRowTemplate(kind.href, { ...object, id })) : '',
775
890
  object,
776
891
  }
@@ -793,6 +908,7 @@ function searchSource(config = {}) {
793
908
  title: entry.title || '',
794
909
  subtitle: entry.subline || '',
795
910
  status: '',
911
+ linked: '',
796
912
  href: entry.resourceUrl || '',
797
913
  })),
798
914
  }
@@ -807,6 +923,17 @@ function searchSource(config = {}) {
807
923
  */
808
924
  load: async (cfg) => {
809
925
  state.kind = cfg && typeof cfg.kind === 'string' ? cfg.kind : 'all';
926
+ // The header filters on Kind, Linked and Status narrow the answer
927
+ // already fetched; they do not search again.
928
+ const filters = {};
929
+ for (const key of ['kindLabel', 'linked', 'status']) {
930
+ const value = cfg && cfg[key];
931
+ const values = (Array.isArray(value) ? value : (value === undefined || value === null || value === '' ? [] : [value])).map(String);
932
+ if (values.length > 0) {
933
+ filters[key] = values;
934
+ }
935
+ }
936
+ state.filters = filters;
810
937
  const query = typeof window !== 'undefined' ? new URLSearchParams(window.location.search) : null;
811
938
  const term = String((cfg && cfg.term) || (query && query.get((cfg && cfg.termQuery) || 'q')) || '').trim();
812
939
  const mine = ++seq;
@@ -838,7 +965,20 @@ function searchSource(config = {}) {
838
965
  /**
839
966
  * @return {Array<object>} The results of the active kind, or all of them.
840
967
  */
841
- rows: () => (state.kind === 'all' ? state.rows : state.rows.filter((row) => row.kind === state.kind)),
968
+ rows: () => (state.kind === 'all' ? state.rows : state.rows.filter((row) => row.kind === state.kind))
969
+ .filter((row) => Object.entries(state.filters).every(([key, values]) => values.includes(String(row[key])))),
970
+
971
+ /**
972
+ * @return {string} The term searched for, for a `{term}` in the title or count line.
973
+ */
974
+ term: () => state.term,
975
+
976
+ /**
977
+ * @return {number} The active kind's total, or every kind's together on All.
978
+ */
979
+ total: () => (state.kind === 'all'
980
+ ? Object.values(state.totals).reduce((sum, value) => sum + value, 0)
981
+ : (state.totals[state.kind] || 0)),
842
982
 
843
983
  /**
844
984
  * @return {boolean} Whether a search is in flight.
@@ -847,8 +987,14 @@ function searchSource(config = {}) {
847
987
 
848
988
  columns: [
849
989
  { key: 'title', label: translate('nextcloud-vue', 'Result'), secondary: 'subtitle' },
850
- { key: 'kindLabel', label: translate('nextcloud-vue', 'Kind') },
851
- { key: 'status', label: translate('nextcloud-vue', 'Status') },
990
+ { key: 'kindLabel', label: translate('nextcloud-vue', 'Kind'), enum: kinds.map((kind) => kind.label) },
991
+ ...(hasLinked ? [{ key: 'linked', label: config.linkedLabel ? translate$1(config.linkedLabel) : translate('nextcloud-vue', 'Linked') }] : []),
992
+ {
993
+ key: 'status',
994
+ label: translate('nextcloud-vue', 'Status'),
995
+ widget: 'badge',
996
+ widgetProps: { colorMap: (config && config.statusColorMap) || {} },
997
+ },
852
998
  ],
853
999
 
854
1000
  quickFilters: kinds.length > 0
@@ -1119,10 +1265,11 @@ const indexSources = {
1119
1265
  *
1120
1266
  * @param {string} name The source name from `config.source`.
1121
1267
  * @param {object} [config] The page's source config, for a source that builds its tabs from it.
1268
+ * @param {object} [context] The page's helpers a source may use, such as `translate` (the app's catalogue).
1122
1269
  *
1123
1270
  * @return {object|null} The adapter, or null when the name is not registered.
1124
1271
  */
1125
- function resolveIndexSource(name, config) {
1272
+ function resolveIndexSource(name, config, context) {
1126
1273
  const key = String(name || '');
1127
1274
  if (!key) {
1128
1275
  return null
@@ -1135,7 +1282,7 @@ function resolveIndexSource(name, config) {
1135
1282
  return null
1136
1283
  }
1137
1284
 
1138
- return factory(config || {})
1285
+ return factory(config || {}, context || {})
1139
1286
  }
1140
1287
 
1141
1288
  /**