@conduction/nextcloud-vue 2.61.0 → 2.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue.js +10 -0
  2. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue.js.map +1 -0
  3. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue2.js +189 -0
  4. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue2.js.map +1 -0
  5. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue3.js +72 -0
  6. package/dist/esm/components/CnAdminActionCard/CnAdminActionCard.vue3.js.map +1 -0
  7. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +394 -27
  8. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  9. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  10. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +99 -9
  11. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  12. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +39 -18
  13. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  14. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js +79 -2
  15. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue2.js.map +1 -1
  16. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js +38 -25
  17. package/dist/esm/components/CnBannerWidget/CnBannerWidget.vue3.js.map +1 -1
  18. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js +21 -1
  19. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue2.js.map +1 -1
  20. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js +4 -1
  21. package/dist/esm/components/CnBrandStripe/CnBrandStripe.vue3.js.map +1 -1
  22. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  23. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +53 -37
  24. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  25. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +30 -6
  26. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  27. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -2
  28. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  29. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js +10 -0
  30. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js.map +1 -0
  31. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +293 -0
  32. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -0
  33. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +189 -0
  34. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -0
  35. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +256 -23
  36. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  37. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +119 -51
  38. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  39. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  40. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +2 -1
  41. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  42. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js +3 -1
  43. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js.map +1 -1
  44. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js +1 -1
  45. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js.map +1 -1
  46. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +234 -9
  47. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  48. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +96 -57
  49. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  50. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +51 -2
  51. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  52. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +5 -1
  53. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  54. package/dist/esm/components/CnIndexPage/selfModeActions.js +3 -3
  55. package/dist/esm/components/CnIndexPage/selfModeActions.js.map +1 -1
  56. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue2.js.map +1 -1
  57. package/dist/esm/components/CnLinkCardsPage/CnLinkCardsPage.vue3.js.map +1 -1
  58. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +36 -1
  59. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  60. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -1
  61. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  62. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +53 -0
  63. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  64. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +22 -5
  65. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  66. package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js.map +1 -1
  67. package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js.map +1 -1
  68. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js +121 -12
  69. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js.map +1 -1
  70. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js +3 -1
  71. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js.map +1 -1
  72. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +326 -1
  73. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  74. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +112 -26
  75. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  76. package/dist/esm/components/CnStorePage/CnStorePage.vue2.js.map +1 -1
  77. package/dist/esm/components/CnStorePage/CnStorePage.vue3.js.map +1 -1
  78. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js +10 -0
  79. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js.map +1 -0
  80. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +337 -0
  81. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -0
  82. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js +102 -0
  83. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -0
  84. package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js +34 -0
  85. package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js.map +1 -0
  86. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +131 -8
  87. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
  88. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js +40 -38
  89. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
  90. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +8 -6
  91. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
  92. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  93. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  94. package/dist/esm/components/CnWikiPage/CnWikiPage.vue2.js.map +1 -1
  95. package/dist/esm/components/CnWikiPage/CnWikiPage.vue3.js.map +1 -1
  96. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js +19 -0
  97. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
  98. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js +1 -1
  99. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
  100. package/dist/esm/composables/useDependencyCheck.js +65 -0
  101. package/dist/esm/composables/useDependencyCheck.js.map +1 -0
  102. package/dist/esm/composables/useWalkthrough.js +218 -6
  103. package/dist/esm/composables/useWalkthrough.js.map +1 -1
  104. package/dist/esm/index.js +2 -0
  105. package/dist/esm/index.js.map +1 -1
  106. package/dist/esm/l10n/en.json.js +48 -0
  107. package/dist/esm/l10n/en.json.js.map +1 -1
  108. package/dist/esm/l10n/nl.json.js +48 -0
  109. package/dist/esm/l10n/nl.json.js.map +1 -1
  110. package/dist/esm/nextcloud-vue.css +557 -9
  111. package/dist/esm/store/useObjectStore.js +8 -0
  112. package/dist/esm/store/useObjectStore.js.map +1 -1
  113. package/dist/esm/utils/columnFilters.js +311 -0
  114. package/dist/esm/utils/columnFilters.js.map +1 -0
  115. package/dist/esm/utils/groupAutocomplete.js +123 -0
  116. package/dist/esm/utils/groupAutocomplete.js.map +1 -0
  117. package/dist/esm/utils/libraryWidgetKeys.js +2 -0
  118. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  119. package/dist/esm/utils/pickerOptions.js +198 -0
  120. package/dist/esm/utils/pickerOptions.js.map +1 -0
  121. package/dist/esm/utils/schema.js +83 -7
  122. package/dist/esm/utils/schema.js.map +1 -1
  123. package/dist/esm/utils/timelineEvents.js +174 -0
  124. package/dist/esm/utils/timelineEvents.js.map +1 -0
  125. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  126. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  127. package/dist/esm/utils/walkthroughSignals.js +88 -0
  128. package/dist/esm/utils/walkthroughSignals.js.map +1 -0
  129. package/dist/nextcloud-vue.cjs.js +9779 -5236
  130. package/dist/nextcloud-vue.cjs.js.map +2 -2
  131. package/dist/nextcloud-vue.css +557 -9
  132. package/l10n/en.json +48 -0
  133. package/l10n/nl.json +48 -0
  134. package/package.json +3 -2
  135. package/scripts/manifest-schema-hash.js +41 -0
  136. package/scripts/record-manifest-schema-hash.js +52 -0
  137. package/src/components/CnAdminActionCard/CnAdminActionCard.vue +234 -0
  138. package/src/components/CnAdminActionCard/index.js +4 -0
  139. package/src/components/CnAppNav/CnAppNav.vue +394 -27
  140. package/src/components/CnAppRoot/CnAppRoot.vue +129 -15
  141. package/src/components/CnBannerWidget/CnBannerWidget.vue +113 -2
  142. package/src/components/CnBrandStripe/CnBrandStripe.md +16 -0
  143. package/src/components/CnBrandStripe/CnBrandStripe.vue +58 -5
  144. package/src/components/CnChoiceCards/CnChoiceCards.vue +56 -31
  145. package/src/components/CnDashboardPage/CnDashboardPage.vue +32 -8
  146. package/src/components/CnDataTable/CnColumnFilterPopover.vue +524 -0
  147. package/src/components/CnDataTable/CnDataTable.md +4 -1
  148. package/src/components/CnDataTable/CnDataTable.vue +302 -21
  149. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +2 -2
  150. package/src/components/CnFieldHelper/CnFieldHelper.vue +4 -2
  151. package/src/components/CnFormDialog/CnFormDialog.vue +290 -31
  152. package/src/components/CnIndexPage/CnIndexPage.vue +55 -2
  153. package/src/components/CnIndexPage/selfModeActions.js +3 -3
  154. package/src/components/CnLinkCardsPage/CnLinkCardsPage.vue +35 -2
  155. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +39 -1
  156. package/src/components/CnObjectSidebar/CnFilesTab.vue +65 -0
  157. package/src/components/CnReportsPage/CnReportsPage.vue +7 -0
  158. package/src/components/CnResourceSelect/CnResourceSelect.vue +123 -12
  159. package/src/components/CnSetupWizard/CnSetupWizard.vue +392 -3
  160. package/src/components/CnStorePage/CnStorePage.vue +7 -0
  161. package/src/components/CnTimelineWidget/CnTimelineWidget.vue +477 -0
  162. package/src/components/CnTimelineWidget/dashboardRegistration.js +32 -0
  163. package/src/components/CnTimelineWidget/index.js +4 -0
  164. package/src/components/CnWalkthrough/CnWalkthrough.vue +143 -10
  165. package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
  166. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  167. package/src/components/CnWikiPage/CnWikiPage.vue +7 -0
  168. package/src/components/CnWizardDialog/CnWizardDialog.vue +20 -1
  169. package/src/components/index.js +2 -0
  170. package/src/composables/useDependencyCheck.js +61 -0
  171. package/src/composables/useWalkthrough.js +217 -5
  172. package/src/css/dashboard.css +49 -0
  173. package/src/css/detail-page.css +19 -0
  174. package/src/css/table.css +82 -2
  175. package/src/index.js +2 -0
  176. package/src/public/index.js +5 -0
  177. package/src/schemas/app-manifest-v2.schema.json +23 -1
  178. package/src/store/useObjectStore.js +8 -0
  179. package/src/utils/columnFilters.js +324 -0
  180. package/src/utils/groupAutocomplete.js +119 -0
  181. package/src/utils/libraryWidgetKeys.js +2 -0
  182. package/src/utils/pickerOptions.js +194 -0
  183. package/src/utils/schema.js +83 -7
  184. package/src/utils/timelineEvents.js +171 -0
  185. package/src/utils/validateManifestV2.compiled.js +2 -2
  186. package/src/utils/walkthroughSignals.js +94 -0
@@ -1 +1 @@
1
- {"version":3,"file":"CnWikiPage.vue3.js","sources":["../../../../src/components/CnWikiPage/CnWikiPage.vue?vue&type=template&id=25a3d3cf&lang.js"],"sourcesContent":["<!--\n CnWikiPage — Manifest-driven markdown article surface.\n\n Renders one wiki / docs / knowledge-base article declared in\n `pages[].config` for `type: \"wiki\"` pages. Closes the gap that\n forces every consumer's docs / help / article-detail route onto\n `type: \"custom\"`.\n\n The page reads its content from props rather than fetching itself.\n Following the precedent set by CnFilesPage and CnFormPage, the\n consumer (or a wiki-aware wrapper) populates `article` (object)\n and `tree` (array) — typically from a `useDetailView()` /\n list-call against the manifest-declared register/schema. This\n keeps the lib zero-dep on a particular OpenRegister fetcher\n while still making the manifest config the source of truth for\n field names.\n\n Markdown rendering is delegated to the `cnRenderMarkdown` composable\n which configures `marked` with `{ gfm: true, breaks: false }` —\n GitHub-flavoured markdown, no auto-`<br>` on newlines.\n\n Slots:\n - `#header` — overrides the default header. Scope `{ title, article }`.\n - `#sidebar` — overrides the default tree sidebar. Scope `{ tree, onClick }`.\n - `#body` — overrides the default markdown body. Scope `{ article, html }`.\n - `#empty` — replaces the empty-state when `article` is null.\n\n Events:\n - `@tree-click` — `node` whenever a sidebar tree node is clicked.\n - `@error` — error object if markdown parsing throws.\n\n Spec: REQ-MWPT-* (manifest-wiki-page-type).\n-->\n<template>\n\t<div class=\"cn-wiki-page\" data-testid=\"cn-wiki-page\" :class=\"{ 'cn-wiki-page--has-sidebar': hasSidebar }\">\n\t\t<!--\n\t\t\t@slot sidebar\n\t\t\t@description Replaces the default sidebar tree. Scoped props `{ tree, onClick }` —\n\t\t\t`onClick(node)` re-emits `@tree-click` with the node.\n\t\t-->\n\t\t<slot\n\t\t\tv-if=\"hasSidebar\"\n\t\t\tname=\"sidebar\"\n\t\t\t:tree=\"tree\"\n\t\t\t:onClick=\"onTreeClick\">\n\t\t\t<nav class=\"cn-wiki-page__sidebar\">\n\t\t\t\t<ul class=\"cn-wiki-page__tree\">\n\t\t\t\t\t<CnWikiTreeNode\n\t\t\t\t\t\tv-for=\"node in tree\"\n\t\t\t\t\t\t:key=\"treeKey(node)\"\n\t\t\t\t\t\t:node=\"node\"\n\t\t\t\t\t\t:titleField=\"effectiveSidebarTitleField\"\n\t\t\t\t\t\t:treeField=\"treeField\"\n\t\t\t\t\t\t@click=\"onTreeClick\" />\n\t\t\t\t</ul>\n\t\t\t</nav>\n\t\t</slot>\n\n\t\t<div class=\"cn-wiki-page__main\">\n\t\t\t<!--\n\t\t\t\t@slot header\n\t\t\t\t@description Replaces the default page header. Scoped props `{ title, article }`.\n\t\t\t-->\n\t\t\t<slot\n\t\t\t\tname=\"header\"\n\t\t\t\t:title=\"resolvedTitle\"\n\t\t\t\t:article=\"article\">\n\t\t\t\t<header v-if=\"resolvedTitle\" class=\"cn-wiki-page__header\">\n\t\t\t\t\t<h1 class=\"cn-wiki-page__title\">\n\t\t\t\t\t\t{{ resolvedTitle }}\n\t\t\t\t\t</h1>\n\t\t\t\t</header>\n\t\t\t</slot>\n\n\t\t\t<!--\n\t\t\t\t@slot empty\n\t\t\t\t@description Replaces the empty-state shown when `article` is null/undefined.\n\t\t\t-->\n\t\t\t<slot v-if=\"!article\" name=\"empty\">\n\t\t\t\t<NcEmptyContent\n\t\t\t\t\t:name=\"emptyText\"\n\t\t\t\t\t:description=\"emptyDescription\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<FileDocumentOutline :size=\"64\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcEmptyContent>\n\t\t\t</slot>\n\n\t\t\t<!--\n\t\t\t\t@slot body\n\t\t\t\t@description Replaces the default markdown body. Scoped props `{ article, html }` —\n\t\t\t\t`html` is the pre-parsed markdown so a custom renderer can wrap it.\n\t\t\t-->\n\t\t\t<slot\n\t\t\t\tv-else\n\t\t\t\tname=\"body\"\n\t\t\t\t:article=\"article\"\n\t\t\t\t:html=\"renderedBody\">\n\t\t\t\t<div\n\t\t\t\t\tv-if=\"renderedBody\"\n\t\t\t\t\tclass=\"cn-wiki-page__body\"\n\t\t\t\t\tv-html=\"renderedBody\" /><!-- eslint-disable-line vue/no-v-html -->\n\t\t\t\t<NcEmptyContent\n\t\t\t\t\tv-else\n\t\t\t\t\t:name=\"emptyBodyText\"\n\t\t\t\t\t:description=\"emptyBodyDescription\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<FileDocumentOutline :size=\"64\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcEmptyContent>\n\t\t\t</slot>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcEmptyContent } from '@nextcloud/vue'\nimport FileDocumentOutline from 'vue-material-design-icons/FileDocumentOutline.vue'\nimport CnWikiTreeNode from './CnWikiTreeNode.vue'\nimport { cnRenderMarkdown } from '../../composables/cnRenderMarkdown.js'\n\n/**\n * CnWikiPage — Manifest-driven markdown article surface.\n *\n * Reads `article` (object) and `tree` (array) as props rather than\n * fetching itself; consumers populate these from their own data\n * store or a wrapper composable. The manifest config still drives\n * which fields/registers are involved (via `contentField`,\n * `titleField`, `sidebarSchema`, …) so the validator stays\n * meaningful even though the lib does not own the fetch.\n *\n * Basic usage:\n *\n * <CnWikiPage\n * :article=\"article\"\n * :register=\"'pipelinq'\"\n * :schema=\"'article'\"\n * :content-field=\"'body'\" />\n *\n * With sidebar tree:\n *\n * <CnWikiPage\n * :article=\"article\"\n * :tree=\"categoryTree\"\n * :sidebar-schema=\"'category'\"\n *\n * @tree-click=\"onCategoryClick\" />\n */\nexport default {\n\tname: 'CnWikiPage',\n\n\tcomponents: {\n\t\tNcEmptyContent,\n\t\tFileDocumentOutline,\n\t\tCnWikiTreeNode,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The article record to render. When null/undefined the\n\t\t * component shows its empty-state. The shape is consumer-defined;\n\t\t * the component reads only `article[contentField]` and\n\t\t * `article[titleField]`.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tarticle: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The (optional) sidebar tree. Each node:\n\t\t * `{ id, [titleField], children?: [...] }`. Empty array hides\n\t\t * the sidebar entirely.\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\ttree: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * OpenRegister register slug. Informational at this layer\n\t\t * (the consumer does the fetch); declared as a prop so the\n\t\t * manifest-driven prop forwarder in CnPageRenderer surfaces it\n\t\t * for child components / debugging.\n\t\t */\n\t\tregister: { // eslint-disable-line vue/no-unused-properties -- informational at this layer; CnPageRenderer forwards it from the manifest for child components.\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * OpenRegister schema slug. Informational at this layer —\n\t\t * see `register` above.\n\t\t */\n\t\tschema: { // eslint-disable-line vue/no-unused-properties -- informational at this layer; CnPageRenderer forwards it from the manifest for child components.\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Property on `article` holding the markdown body. Defaults\n\t\t * to `'body'`.\n\t\t */\n\t\tcontentField: {\n\t\t\ttype: String,\n\t\t\tdefault: 'body',\n\t\t},\n\n\t\t/**\n\t\t * Property on `article` holding the page title. Defaults\n\t\t * to `'title'`.\n\t\t */\n\t\ttitleField: {\n\t\t\ttype: String,\n\t\t\tdefault: 'title',\n\t\t},\n\n\t\t/**\n\t\t * `$route.params` key holding the article id. Informational at\n\t\t * this layer; declared so the consumer's data store can wire\n\t\t * the same default the validator and renderer both reference.\n\t\t */\n\t\tidParam: { // eslint-disable-line vue/no-unused-properties -- informational at this layer; CnPageRenderer forwards it from the manifest for child components.\n\t\t\ttype: String,\n\t\t\tdefault: 'id',\n\t\t},\n\n\t\t/**\n\t\t * Schema slug for the sidebar tree. When set AND `tree` is\n\t\t * non-empty the sidebar renders. Unset suppresses the sidebar\n\t\t * regardless of `tree` content.\n\t\t */\n\t\tsidebarSchema: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Register slug for the sidebar tree. Defaults to `register`\n\t\t * when unset (the common case).\n\t\t */\n\t\tsidebarRegister: { // eslint-disable-line vue/no-unused-properties -- informational at this layer; CnPageRenderer forwards it from the manifest for child components.\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Property on each sidebar node holding its child array.\n\t\t * Defaults to `'children'`.\n\t\t */\n\t\ttreeField: {\n\t\t\ttype: String,\n\t\t\tdefault: 'children',\n\t\t},\n\n\t\t/**\n\t\t * Property on each sidebar node holding its label. Defaults\n\t\t * to `titleField` (so `'title'` unless overridden).\n\t\t */\n\t\tsidebarTitleField: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Empty-state heading when `article` is null. */\n\t\temptyText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Article not found'),\n\t\t},\n\n\t\t/** Empty-state description when `article` is null. */\n\t\temptyDescription: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'The requested article could not be found.'),\n\t\t},\n\n\t\t/** Empty-body heading when the article has no `contentField`. */\n\t\temptyBodyText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No content'),\n\t\t},\n\n\t\t/** Empty-body description when the article has no `contentField`. */\n\t\temptyBodyDescription: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'This article has no content yet.'),\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Emitted when a sidebar tree node is clicked. The payload is\n\t\t * the clicked node object (the same shape the consumer placed\n\t\t * in `tree`).\n\t\t */\n\t\t'tree-click',\n\t\t/**\n\t\t * Emitted when markdown parsing throws. Rare — `marked` and\n\t\t * the helper are defensive — but the contract is explicit so\n\t\t * consumers can log / surface a banner.\n\t\t */\n\t\t'error',\n\t],\n\n\tcomputed: {\n\t\t/**\n\t\t * Effective sidebar-title field — falls back to the article\n\t\t * title field when unset so consumers can leave it off in\n\t\t * the simple case.\n\t\t */\n\t\teffectiveSidebarTitleField() {\n\t\t\treturn this.sidebarTitleField || this.titleField\n\t\t},\n\n\t\t/**\n\t\t * Whether the sidebar should render. Both a non-empty tree\n\t\t * AND an explicit `sidebarSchema` are required — the schema\n\t\t * acts as the manifest-author's \"yes, I want a sidebar\" signal,\n\t\t * matching the spec's REQ-MWPT-5.\n\t\t */\n\t\thasSidebar() {\n\t\t\treturn Boolean(this.sidebarSchema) && Array.isArray(this.tree) && this.tree.length > 0\n\t\t},\n\n\t\t/** Article title resolved through `titleField`. */\n\t\tresolvedTitle() {\n\t\t\tif (!this.article) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst value = this.article[this.titleField]\n\t\t\treturn typeof value === 'string' ? value : ''\n\t\t},\n\n\t\t/**\n\t\t * Article body parsed to HTML through `cnRenderMarkdown`.\n\t\t * Empty string when the article is null or has no body.\n\t\t */\n\t\trenderedBody() {\n\t\t\tif (!this.article) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst source = this.article[this.contentField]\n\t\t\ttry {\n\t\t\t\treturn cnRenderMarkdown(source)\n\t\t\t} catch (error) {\n\t\t\t\tthis.$emit('error', error)\n\t\t\t\treturn ''\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Re-emit `@tree-click` with the clicked node.\n\t\t *\n\t\t * @param {object} node The tree node the user clicked.\n\t\t */\n\t\tonTreeClick(node) {\n\t\t\tthis.$emit('tree-click', node)\n\t\t},\n\n\t\t/**\n\t\t * Stable v-for key for tree nodes — prefer `id`, fall back\n\t\t * to the title field, finally to JSON-stringified shape.\n\t\t *\n\t\t * @param {object} node Tree node.\n\t\t * @return {string}\n\t\t */\n\t\ttreeKey(node) {\n\t\t\tif (!node) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (typeof node.id === 'string' || typeof node.id === 'number') {\n\t\t\t\treturn String(node.id)\n\t\t\t}\n\t\t\tconst title = node[this.effectiveSidebarTitleField]\n\t\t\tif (typeof title === 'string') {\n\t\t\t\treturn title\n\t\t\t}\n\t\t\treturn JSON.stringify(node)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-wiki-page {\n\tdisplay: flex;\n\tgap: 24px;\n\tpadding: 20px;\n\tmax-width: 1200px;\n\tmargin: 0 auto;\n}\n\n.cn-wiki-page__sidebar {\n\tflex: 0 0 240px;\n\tborder-right: 1px solid var(--color-border);\n\tpadding-right: 16px;\n}\n\n.cn-wiki-page__tree {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-wiki-page__main {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n.cn-wiki-page__header {\n\tmargin-bottom: 16px;\n}\n\n.cn-wiki-page__title {\n\tfont-size: 1.6em;\n\tmargin: 0;\n\tcolor: var(--color-main-text);\n}\n\n.cn-wiki-page__body {\n\tline-height: 1.7;\n\tcolor: var(--color-main-text);\n}\n\n.cn-wiki-page__body :deep(h1),\n.cn-wiki-page__body :deep(h2),\n.cn-wiki-page__body :deep(h3) {\n\tcolor: var(--color-main-text);\n\tmargin-top: 1.4em;\n\tmargin-bottom: 0.5em;\n}\n\n.cn-wiki-page__body :deep(p) {\n\tmargin: 0 0 1em;\n}\n\n.cn-wiki-page__body :deep(code) {\n\tbackground: var(--color-background-dark);\n\tpadding: 2px 6px;\n\tborder-radius: var(--border-radius);\n\tfont-family: var(--font-face-monospace, monospace);\n\tfont-size: 0.9em;\n}\n\n.cn-wiki-page__body :deep(pre) {\n\tbackground: var(--color-background-dark);\n\tpadding: 12px;\n\tborder-radius: var(--border-radius);\n\toverflow-x: auto;\n}\n\n.cn-wiki-page__body :deep(pre code) {\n\tbackground: transparent;\n\tpadding: 0;\n}\n\n.cn-wiki-page__body :deep(blockquote) {\n\tborder-left: 3px solid var(--color-border);\n\tpadding-left: 12px;\n\tcolor: var(--color-text-lighter);\n\tmargin: 0 0 1em;\n}\n\n.cn-wiki-page__body :deep(table) {\n\tborder-collapse: collapse;\n\tmargin: 0 0 1em;\n}\n\n.cn-wiki-page__body :deep(th),\n.cn-wiki-page__body :deep(td) {\n\tborder: 1px solid var(--color-border);\n\tpadding: 6px 10px;\n}\n\n.cn-wiki-page--has-sidebar .cn-wiki-page__main {\n\tpadding-left: 8px;\n}\n</style>\n"],"names":["_createElementVNode","_createElementBlock","_Fragment","_renderList","_createBlock","_renderSlot","_toDisplayString","_createVNode"],"mappings":";;AA8EO,MAAA,UAAA,GAAA,EAAA,KAAM,EAAC,uBAAuB,GAAA;AAC7B,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,oBAAI,GAAA;AAYX,MAAA,UAAA,GAAA,EAAA,KAAK,EAAE,oBAAmB,GAAA;;;AASH,EAAA,KAAA,EAAA,sBAAA;;AACrB,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,qBAAqB,GAAA;;;;;;;;;IAlC9B,uBAAM,cAAc,EAAA,EAAA,2BAAA,EAAmC,QAAA,CAAA,UAAA,EAAA,CAAA,CAAA;AAAlC,IAAA,aAAW,EAAC,cAAc;;KAO/C,QAAA,CAAA,UAAA;kBAeI,CAAA,IAAA,CAAA,MAAA,EAAA,SAAA,EAAA;AAbN,UAAA,IAAI,EAAE,MAAI,CAAA,IAAA;AACV,UAAA,OAAC,EAAA,QAAA,CAAA,WAAA;iBAYK;AAXP,UAAAA,kBAAA,CAUG,OAVH,UAUG,EAAA;AATF,YAAAA,kBAAA,OAAA;gCACCC,kBAMI,CAAAC,QAAA,EAAA,IAAA,EAAAC,UAAA,CALY,MAAI,CAAA,IAAA,EAAA,CAAZ,IAAI,KAAA;qCADZC,WAMI,CAAA,yBAAA,EAAA;kBAJF,GAAG,EAAE,QAAO,CAAA,OAAA,CAAC,IAAA,CAAA;AACb,kBAAA,IAAI,EAAE,IAAI;AACV,kBAAA,UAAQ,EAAA,QAAA,CAAA,0BAAA;AACR,kBAAA,SAAS,EAAE,MAAE,CAAA,SAAA;AACb,kBAAA,OAAE,EAAA,QAAA,CAAA,WAAA;;;;;;;;MAULC,UASM,CAAA,IAAA,CAAA,MAAA,EAAA,QAAA,EAAA;AAPL,QAAA,KAAE,EAAA,QAAA,CAAA,aAAA;AACF,QAAA,OAAO,EAAE,MAAO,CAAA,OAAA;SAHjB,MASM;SALQ,QAAY,CAAA,aAAA;6CAIjB,oBAAA,EAAA;cAHRL,kBAEK,CAAA,IAAA,EAFL,UAEK,EAAAM,eAAA,CADD,QAAS,CAAA,aAAA,CAAA,EAAA,CAAA,YAAA;;;;QASX,MAAA,CAAA,OAAA;AAAJ,UAAAD,UAAA,2BAAA;YACCE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"CnWikiPage.vue3.js","sources":["../../../../src/components/CnWikiPage/CnWikiPage.vue?vue&type=template&id=25a3d3cf&lang.js"],"sourcesContent":["<!--\n CnWikiPage — Manifest-driven markdown article surface.\n\n Renders one wiki / docs / knowledge-base article declared in\n `pages[].config` for `type: \"wiki\"` pages. Closes the gap that\n forces every consumer's docs / help / article-detail route onto\n `type: \"custom\"`.\n\n The page reads its content from props rather than fetching itself.\n Following the precedent set by CnFilesPage and CnFormPage, the\n consumer (or a wiki-aware wrapper) populates `article` (object)\n and `tree` (array) — typically from a `useDetailView()` /\n list-call against the manifest-declared register/schema. This\n keeps the lib zero-dep on a particular OpenRegister fetcher\n while still making the manifest config the source of truth for\n field names.\n\n Markdown rendering is delegated to the `cnRenderMarkdown` composable\n which configures `marked` with `{ gfm: true, breaks: false }` —\n GitHub-flavoured markdown, no auto-`<br>` on newlines.\n\n Slots:\n - `#header` — overrides the default header. Scope `{ title, article }`.\n - `#sidebar` — overrides the default tree sidebar. Scope `{ tree, onClick }`.\n - `#body` — overrides the default markdown body. Scope `{ article, html }`.\n - `#empty` — replaces the empty-state when `article` is null.\n\n Events:\n - `@tree-click` — `node` whenever a sidebar tree node is clicked.\n - `@error` — error object if markdown parsing throws.\n\n Spec: REQ-MWPT-* (manifest-wiki-page-type).\n-->\n<template>\n\t<div class=\"cn-wiki-page\" data-testid=\"cn-wiki-page\" :class=\"{ 'cn-wiki-page--has-sidebar': hasSidebar }\">\n\t\t<!--\n\t\t\t@slot sidebar\n\t\t\t@description Replaces the default sidebar tree. Scoped props `{ tree, onClick }` —\n\t\t\t`onClick(node)` re-emits `@tree-click` with the node.\n\t\t-->\n\t\t<slot\n\t\t\tv-if=\"hasSidebar\"\n\t\t\tname=\"sidebar\"\n\t\t\t:tree=\"tree\"\n\t\t\t:onClick=\"onTreeClick\">\n\t\t\t<nav class=\"cn-wiki-page__sidebar\">\n\t\t\t\t<ul class=\"cn-wiki-page__tree\">\n\t\t\t\t\t<CnWikiTreeNode\n\t\t\t\t\t\tv-for=\"node in tree\"\n\t\t\t\t\t\t:key=\"treeKey(node)\"\n\t\t\t\t\t\t:node=\"node\"\n\t\t\t\t\t\t:titleField=\"effectiveSidebarTitleField\"\n\t\t\t\t\t\t:treeField=\"treeField\"\n\t\t\t\t\t\t@click=\"onTreeClick\" />\n\t\t\t\t</ul>\n\t\t\t</nav>\n\t\t</slot>\n\n\t\t<div class=\"cn-wiki-page__main\">\n\t\t\t<!--\n\t\t\t\t@slot header\n\t\t\t\t@description Replaces the default page header. Scoped props `{ title, article }`.\n\t\t\t-->\n\t\t\t<slot\n\t\t\t\tname=\"header\"\n\t\t\t\t:title=\"resolvedTitle\"\n\t\t\t\t:article=\"article\">\n\t\t\t\t<header v-if=\"resolvedTitle\" class=\"cn-wiki-page__header\">\n\t\t\t\t\t<h1 class=\"cn-wiki-page__title\">\n\t\t\t\t\t\t{{ resolvedTitle }}\n\t\t\t\t\t</h1>\n\t\t\t\t</header>\n\t\t\t</slot>\n\n\t\t\t<!--\n\t\t\t\t@slot empty\n\t\t\t\t@description Replaces the empty-state shown when `article` is null/undefined.\n\t\t\t-->\n\t\t\t<slot v-if=\"!article\" name=\"empty\">\n\t\t\t\t<NcEmptyContent\n\t\t\t\t\t:name=\"emptyText\"\n\t\t\t\t\t:description=\"emptyDescription\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<FileDocumentOutline :size=\"64\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcEmptyContent>\n\t\t\t</slot>\n\n\t\t\t<!--\n\t\t\t\t@slot body\n\t\t\t\t@description Replaces the default markdown body. Scoped props `{ article, html }` —\n\t\t\t\t`html` is the pre-parsed markdown so a custom renderer can wrap it.\n\t\t\t-->\n\t\t\t<slot\n\t\t\t\tv-else\n\t\t\t\tname=\"body\"\n\t\t\t\t:article=\"article\"\n\t\t\t\t:html=\"renderedBody\">\n\t\t\t\t<div\n\t\t\t\t\tv-if=\"renderedBody\"\n\t\t\t\t\tclass=\"cn-wiki-page__body\"\n\t\t\t\t\tv-html=\"renderedBody\" /><!-- eslint-disable-line vue/no-v-html -->\n\t\t\t\t<NcEmptyContent\n\t\t\t\t\tv-else\n\t\t\t\t\t:name=\"emptyBodyText\"\n\t\t\t\t\t:description=\"emptyBodyDescription\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<FileDocumentOutline :size=\"64\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcEmptyContent>\n\t\t\t</slot>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcEmptyContent } from '@nextcloud/vue'\nimport FileDocumentOutline from 'vue-material-design-icons/FileDocumentOutline.vue'\nimport CnWikiTreeNode from './CnWikiTreeNode.vue'\nimport { cnRenderMarkdown } from '../../composables/cnRenderMarkdown.js'\n\n/**\n * CnWikiPage — Manifest-driven markdown article surface.\n *\n * Reads `article` (object) and `tree` (array) as props rather than\n * fetching itself; consumers populate these from their own data\n * store or a wrapper composable. The manifest config still drives\n * which fields/registers are involved (via `contentField`,\n * `titleField`, `sidebarSchema`, …) so the validator stays\n * meaningful even though the lib does not own the fetch.\n *\n * Basic usage:\n *\n * <CnWikiPage\n * :article=\"article\"\n * :register=\"'pipelinq'\"\n * :schema=\"'article'\"\n * :content-field=\"'body'\" />\n *\n * With sidebar tree:\n *\n * <CnWikiPage\n * :article=\"article\"\n * :tree=\"categoryTree\"\n * :sidebar-schema=\"'category'\"\n *\n * @tree-click=\"onCategoryClick\" />\n */\nexport default {\n\tname: 'CnWikiPage',\n\n\tcomponents: {\n\t\tNcEmptyContent,\n\t\tFileDocumentOutline,\n\t\tCnWikiTreeNode,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The article record to render. When null/undefined the\n\t\t * component shows its empty-state. The shape is consumer-defined;\n\t\t * the component reads only `article[contentField]` and\n\t\t * `article[titleField]`.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tarticle: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * The (optional) sidebar tree. Each node:\n\t\t * `{ id, [titleField], children?: [...] }`. Empty array hides\n\t\t * the sidebar entirely.\n\t\t *\n\t\t * @type {Array<object>}\n\t\t */\n\t\ttree: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * OpenRegister register slug. Informational at this layer\n\t\t * (the consumer does the fetch); declared as a prop so the\n\t\t * manifest-driven prop forwarder in CnPageRenderer surfaces it\n\t\t * for child components / debugging.\n\t\t */\n\t\tregister: { // eslint-disable-line vue/no-unused-properties -- informational at this layer; CnPageRenderer forwards it from the manifest for child components.\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * OpenRegister schema slug. Informational at this layer —\n\t\t * see `register` above.\n\t\t */\n\t\tschema: { // eslint-disable-line vue/no-unused-properties -- informational at this layer; CnPageRenderer forwards it from the manifest for child components.\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Property on `article` holding the markdown body. Defaults\n\t\t * to `'body'`.\n\t\t */\n\t\tcontentField: {\n\t\t\ttype: String,\n\t\t\tdefault: 'body',\n\t\t},\n\n\t\t/**\n\t\t * Property on `article` holding the page title. Defaults\n\t\t * to `'title'`.\n\t\t */\n\t\ttitleField: {\n\t\t\ttype: String,\n\t\t\tdefault: 'title',\n\t\t},\n\n\t\t/**\n\t\t * `$route.params` key holding the article id. Informational at\n\t\t * this layer; declared so the consumer's data store can wire\n\t\t * the same default the validator and renderer both reference.\n\t\t */\n\t\tidParam: { // eslint-disable-line vue/no-unused-properties -- informational at this layer; CnPageRenderer forwards it from the manifest for child components.\n\t\t\ttype: String,\n\t\t\tdefault: 'id',\n\t\t},\n\n\t\t/**\n\t\t * Schema slug for the sidebar tree. When set AND `tree` is\n\t\t * non-empty the sidebar renders. Unset suppresses the sidebar\n\t\t * regardless of `tree` content.\n\t\t */\n\t\tsidebarSchema: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Register slug for the sidebar tree. Defaults to `register`\n\t\t * when unset (the common case).\n\t\t */\n\t\tsidebarRegister: { // eslint-disable-line vue/no-unused-properties -- informational at this layer; CnPageRenderer forwards it from the manifest for child components.\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Property on each sidebar node holding its child array.\n\t\t * Defaults to `'children'`.\n\t\t */\n\t\ttreeField: {\n\t\t\ttype: String,\n\t\t\tdefault: 'children',\n\t\t},\n\n\t\t/**\n\t\t * Property on each sidebar node holding its label. Defaults\n\t\t * to `titleField` (so `'title'` unless overridden).\n\t\t */\n\t\tsidebarTitleField: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** Empty-state heading when `article` is null. */\n\t\temptyText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'Article not found'),\n\t\t},\n\n\t\t/** Empty-state description when `article` is null. */\n\t\temptyDescription: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'The requested article could not be found.'),\n\t\t},\n\n\t\t/** Empty-body heading when the article has no `contentField`. */\n\t\temptyBodyText: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'No content'),\n\t\t},\n\n\t\t/** Empty-body description when the article has no `contentField`. */\n\t\temptyBodyDescription: {\n\t\t\ttype: String,\n\t\t\tdefault: () => t('nextcloud-vue', 'This article has no content yet.'),\n\t\t},\n\t},\n\n\temits: [\n\t\t/**\n\t\t * Emitted when a sidebar tree node is clicked. The payload is\n\t\t * the clicked node object (the same shape the consumer placed\n\t\t * in `tree`).\n\t\t */\n\t\t'tree-click',\n\t\t/**\n\t\t * Emitted when markdown parsing throws. Rare — `marked` and\n\t\t * the helper are defensive — but the contract is explicit so\n\t\t * consumers can log / surface a banner.\n\t\t */\n\t\t'error',\n\t],\n\n\tcomputed: {\n\t\t/**\n\t\t * Effective sidebar-title field — falls back to the article\n\t\t * title field when unset so consumers can leave it off in\n\t\t * the simple case.\n\t\t */\n\t\teffectiveSidebarTitleField() {\n\t\t\treturn this.sidebarTitleField || this.titleField\n\t\t},\n\n\t\t/**\n\t\t * Whether the sidebar should render. Both a non-empty tree\n\t\t * AND an explicit `sidebarSchema` are required — the schema\n\t\t * acts as the manifest-author's \"yes, I want a sidebar\" signal,\n\t\t * matching the spec's REQ-MWPT-5.\n\t\t */\n\t\thasSidebar() {\n\t\t\treturn Boolean(this.sidebarSchema) && Array.isArray(this.tree) && this.tree.length > 0\n\t\t},\n\n\t\t/** Article title resolved through `titleField`. */\n\t\tresolvedTitle() {\n\t\t\tif (!this.article) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst value = this.article[this.titleField]\n\t\t\treturn typeof value === 'string' ? value : ''\n\t\t},\n\n\t\t/**\n\t\t * Article body parsed to HTML through `cnRenderMarkdown`.\n\t\t * Empty string when the article is null or has no body.\n\t\t */\n\t\trenderedBody() {\n\t\t\tif (!this.article) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst source = this.article[this.contentField]\n\t\t\ttry {\n\t\t\t\treturn cnRenderMarkdown(source)\n\t\t\t} catch (error) {\n\t\t\t\tthis.$emit('error', error)\n\t\t\t\treturn ''\n\t\t\t}\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Re-emit `@tree-click` with the clicked node.\n\t\t *\n\t\t * @param {object} node The tree node the user clicked.\n\t\t */\n\t\tonTreeClick(node) {\n\t\t\tthis.$emit('tree-click', node)\n\t\t},\n\n\t\t/**\n\t\t * Stable v-for key for tree nodes — prefer `id`, fall back\n\t\t * to the title field, finally to JSON-stringified shape.\n\t\t *\n\t\t * @param {object} node Tree node.\n\t\t * @return {string}\n\t\t */\n\t\ttreeKey(node) {\n\t\t\tif (!node) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (typeof node.id === 'string' || typeof node.id === 'number') {\n\t\t\t\treturn String(node.id)\n\t\t\t}\n\t\t\tconst title = node[this.effectiveSidebarTitleField]\n\t\t\tif (typeof title === 'string') {\n\t\t\t\treturn title\n\t\t\t}\n\t\t\treturn JSON.stringify(node)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-wiki-page {\n\tdisplay: flex;\n\tgap: 24px;\n\tpadding: 20px;\n\tmax-width: 1200px;\n\tmargin: 0 auto;\n}\n\n.cn-wiki-page__sidebar {\n\tflex: 0 0 240px;\n\tborder-right: 1px solid var(--color-border);\n\tpadding-right: 16px;\n}\n\n.cn-wiki-page__tree {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n}\n\n.cn-wiki-page__main {\n\tflex: 1 1 auto;\n\tmin-width: 0;\n}\n\n.cn-wiki-page__header {\n\tmargin-bottom: 16px;\n}\n\n/* Without a sidebar the title starts at the left edge of the content, under\n the Nextcloud navigation toggle button (44px wide). Clear it as CnPageHeader\n does. With a sidebar the tree sits there instead and the title is clear. */\n.cn-wiki-page:not(.cn-wiki-page--has-sidebar) .cn-wiki-page__header {\n\tpadding-inline-start: 56px;\n}\n\n.cn-wiki-page__title {\n\tfont-size: 1.6em;\n\tmargin: 0;\n\tcolor: var(--color-main-text);\n}\n\n.cn-wiki-page__body {\n\tline-height: 1.7;\n\tcolor: var(--color-main-text);\n}\n\n.cn-wiki-page__body :deep(h1),\n.cn-wiki-page__body :deep(h2),\n.cn-wiki-page__body :deep(h3) {\n\tcolor: var(--color-main-text);\n\tmargin-top: 1.4em;\n\tmargin-bottom: 0.5em;\n}\n\n.cn-wiki-page__body :deep(p) {\n\tmargin: 0 0 1em;\n}\n\n.cn-wiki-page__body :deep(code) {\n\tbackground: var(--color-background-dark);\n\tpadding: 2px 6px;\n\tborder-radius: var(--border-radius);\n\tfont-family: var(--font-face-monospace, monospace);\n\tfont-size: 0.9em;\n}\n\n.cn-wiki-page__body :deep(pre) {\n\tbackground: var(--color-background-dark);\n\tpadding: 12px;\n\tborder-radius: var(--border-radius);\n\toverflow-x: auto;\n}\n\n.cn-wiki-page__body :deep(pre code) {\n\tbackground: transparent;\n\tpadding: 0;\n}\n\n.cn-wiki-page__body :deep(blockquote) {\n\tborder-left: 3px solid var(--color-border);\n\tpadding-left: 12px;\n\tcolor: var(--color-text-lighter);\n\tmargin: 0 0 1em;\n}\n\n.cn-wiki-page__body :deep(table) {\n\tborder-collapse: collapse;\n\tmargin: 0 0 1em;\n}\n\n.cn-wiki-page__body :deep(th),\n.cn-wiki-page__body :deep(td) {\n\tborder: 1px solid var(--color-border);\n\tpadding: 6px 10px;\n}\n\n.cn-wiki-page--has-sidebar .cn-wiki-page__main {\n\tpadding-left: 8px;\n}\n</style>\n"],"names":["_createElementVNode","_createElementBlock","_Fragment","_renderList","_createBlock","_renderSlot","_toDisplayString","_createVNode"],"mappings":";;AA8EO,MAAA,UAAA,GAAA,EAAA,KAAM,EAAC,uBAAuB,GAAA;AAC7B,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,oBAAI,GAAA;AAYX,MAAA,UAAA,GAAA,EAAA,KAAK,EAAE,oBAAmB,GAAA;;;AASH,EAAA,KAAA,EAAA,sBAAA;;AACrB,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,qBAAqB,GAAA;;;;;;;;;IAlC9B,uBAAM,cAAc,EAAA,EAAA,2BAAA,EAAmC,QAAA,CAAA,UAAA,EAAA,CAAA,CAAA;AAAlC,IAAA,aAAW,EAAC,cAAc;;KAO/C,QAAA,CAAA,UAAA;kBAeI,CAAA,IAAA,CAAA,MAAA,EAAA,SAAA,EAAA;AAbN,UAAA,IAAI,EAAE,MAAI,CAAA,IAAA;AACV,UAAA,OAAC,EAAA,QAAA,CAAA,WAAA;iBAYK;AAXP,UAAAA,kBAAA,CAUG,OAVH,UAUG,EAAA;AATF,YAAAA,kBAAA,OAAA;gCACCC,kBAMI,CAAAC,QAAA,EAAA,IAAA,EAAAC,UAAA,CALY,MAAI,CAAA,IAAA,EAAA,CAAZ,IAAI,KAAA;qCADZC,WAMI,CAAA,yBAAA,EAAA;kBAJF,GAAG,EAAE,QAAO,CAAA,OAAA,CAAC,IAAA,CAAA;AACb,kBAAA,IAAI,EAAE,IAAI;AACV,kBAAA,UAAQ,EAAA,QAAA,CAAA,0BAAA;AACR,kBAAA,SAAS,EAAE,MAAE,CAAA,SAAA;AACb,kBAAA,OAAE,EAAA,QAAA,CAAA,WAAA;;;;;;;;MAULC,UASM,CAAA,IAAA,CAAA,MAAA,EAAA,QAAA,EAAA;AAPL,QAAA,KAAE,EAAA,QAAA,CAAA,aAAA;AACF,QAAA,OAAO,EAAE,MAAO,CAAA,OAAA;SAHjB,MASM;SALQ,QAAY,CAAA,aAAA;6CAIjB,oBAAA,EAAA;cAHRL,kBAEK,CAAA,IAAA,EAFL,UAEK,EAAAM,eAAA,CADD,QAAS,CAAA,aAAA,CAAA,EAAA,CAAA,YAAA;;;;QASX,MAAA,CAAA,OAAA;AAAJ,UAAAD,UAAA,2BAAA;YACCE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -160,6 +160,19 @@ var script = {
160
160
  type: Boolean,
161
161
  default: true,
162
162
  },
163
+
164
+ /**
165
+ * Disable the Next / Submit button, whatever the step's `validate`
166
+ * would say. For a host that knows the wizard cannot go on yet, such as
167
+ * `CnSetupWizard` while a required app is missing.
168
+ *
169
+ * @type {boolean}
170
+ * @spec openspec/changes/setup-wizard-card-load-and-dependency-gate/specs/cn-setup-wizard/spec.md#requirement-the-wizard-checks-dependencies-before-any-step
171
+ */
172
+ nextDisabled: {
173
+ type: Boolean,
174
+ default: false,
175
+ },
163
176
  },
164
177
 
165
178
  emits: ['close', 'step-change', 'submit'],
@@ -276,6 +289,9 @@ var script = {
276
289
  * @return {Promise<void>}
277
290
  */
278
291
  async next() {
292
+ if (this.nextDisabled) {
293
+ return
294
+ }
279
295
  if (this.isLast) {
280
296
  return this.submit()
281
297
  }
@@ -347,6 +363,9 @@ var script = {
347
363
  * @return {Promise<void>}
348
364
  */
349
365
  async submit() {
366
+ if (this.nextDisabled) {
367
+ return
368
+ }
350
369
  const ok = await this.runValidation();
351
370
  if (!ok) {
352
371
  return
@@ -1 +1 @@
1
- {"version":3,"file":"CnWizardDialog.vue2.js","sources":["../../../../src/components/CnWizardDialog/CnWizardDialog.vue"],"sourcesContent":["<template>\n\t<NcDialog\n\t\t:name=\"dialogTitle\"\n\t\tsize=\"large\"\n\t\t:open=\"dialogOpen\"\n\t\t:noClose=\"loading || !cancellable\"\n\t\tdata-testid=\"cn-modal\"\n\t\tdata-testid-modal=\"cn-wizard-dialog\"\n\t\t@update:open=\"dialogOpen = $event\"\n\t\t@closing=\"onClose\">\n\t\t<!-- Result phase: rendered after a successful `submit()`. -->\n\t\t<div v-if=\"result !== null\"\n\t\t\tclass=\"cn-wizard-dialog__result\"\n\t\t\tdata-testid-phase=\"result\">\n\t\t\t<NcNoteCard v-if=\"result.success\" type=\"success\">\n\t\t\t\t{{ result.message || successText }}\n\t\t\t</NcNoteCard>\n\t\t\t<NcNoteCard v-else-if=\"result.error\" type=\"error\">\n\t\t\t\t{{ result.error }}\n\t\t\t</NcNoteCard>\n\t\t\t<!-- @slot result-extra Optional extra content (e.g. batch\n\t\t\t progress poller, summary table) rendered below the\n\t\t\t success/error banner. Scope: { result }. -->\n\t\t\t<slot name=\"result-extra\" :result=\"result\" />\n\t\t</div>\n\n\t\t<!-- Wizard phase: progress bar + current step body. -->\n\t\t<div v-else\n\t\t\tclass=\"cn-wizard-dialog__wizard\"\n\t\t\tdata-testid-phase=\"form\">\n\t\t\t<!-- Progress indicator. Click jumps back to a completed\n\t\t\t step when allowJumpBack is true. -->\n\t\t\t<ol class=\"cn-wizard-dialog__progress\" role=\"tablist\">\n\t\t\t\t<li v-for=\"(step, idx) in steps\"\n\t\t\t\t\t:key=\"step.id\"\n\t\t\t\t\tclass=\"cn-wizard-dialog__progress-item\"\n\t\t\t\t\t:class=\"{\n\t\t\t\t\t\t'cn-wizard-dialog__progress-item--active': idx === currentIndex,\n\t\t\t\t\t\t'cn-wizard-dialog__progress-item--done': idx < currentIndex,\n\t\t\t\t\t\t'cn-wizard-dialog__progress-item--clickable': allowJumpBack && idx < currentIndex,\n\t\t\t\t\t}\"\n\t\t\t\t\trole=\"tab\"\n\t\t\t\t\t:aria-selected=\"idx === currentIndex\"\n\t\t\t\t\t@click=\"allowJumpBack && idx < currentIndex ? jumpTo(step.id) : null\">\n\t\t\t\t\t<span class=\"cn-wizard-dialog__progress-dot\">\n\t\t\t\t\t\t<span v-if=\"idx < currentIndex\" class=\"cn-wizard-dialog__progress-check\" aria-hidden=\"true\">✓</span>\n\t\t\t\t\t\t<span v-else>{{ idx + 1 }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t<span class=\"cn-wizard-dialog__progress-label\">{{ step.label }}</span>\n\t\t\t\t\t<span v-if=\"idx < steps.length - 1\"\n\t\t\t\t\t\tclass=\"cn-wizard-dialog__progress-connector\"\n\t\t\t\t\t\t:class=\"{ 'cn-wizard-dialog__progress-connector--done': idx < currentIndex }\"\n\t\t\t\t\t\taria-hidden=\"true\" />\n\t\t\t\t</li>\n\t\t\t</ol>\n\n\t\t\t<!-- Validation error from the current step's validate() result. -->\n\t\t\t<NcNoteCard v-if=\"validationError\" type=\"error\">\n\t\t\t\t{{ validationError }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<!-- Current step body — a dynamic slot named by step id. -->\n\t\t\t<div class=\"cn-wizard-dialog__step-body\" :data-step-id=\"currentStep.id\">\n\t\t\t\t<!-- @slot step-{id} Per-step body. Scope: { next,\n\t\t\t\t back, jumpTo, submit, currentStep, stepIndex,\n\t\t\t\t totalSteps, stepData, setStepData, isFirst,\n\t\t\t\t isLast }. -->\n\t\t\t\t<slot\n\t\t\t\t\t:name=\"'step-' + currentStep.id\"\n\t\t\t\t\t:next=\"next\"\n\t\t\t\t\t:back=\"back\"\n\t\t\t\t\t:jumpTo=\"jumpTo\"\n\t\t\t\t\t:submit=\"submit\"\n\t\t\t\t\t:currentStep=\"currentStep\"\n\t\t\t\t\t:stepIndex=\"currentIndex\"\n\t\t\t\t\t:totalSteps=\"steps.length\"\n\t\t\t\t\t:stepData=\"stepData\"\n\t\t\t\t\t:setStepData=\"setStepData\"\n\t\t\t\t\t:isFirst=\"isFirst\"\n\t\t\t\t\t:isLast=\"isLast\" />\n\t\t\t</div>\n\t\t</div>\n\n\t\t<template #actions>\n\t\t\t<!-- Result phase actions: just Close. -->\n\t\t\t<NcButton v-if=\"result !== null\" @click=\"onClose\">\n\t\t\t\t{{ closeLabel }}\n\t\t\t</NcButton>\n\n\t\t\t<!-- Wizard phase actions: Cancel + Back + Next/Submit.\n\t\t\t The testids exist because these buttons are the only way\n\t\t\t through a wizard and their LABELS are translated: an e2e that\n\t\t\t clicks \"Next\" passes on an English instance and times out on a\n\t\t\t Dutch one, reporting a broken dialog rather than a missing\n\t\t\t string. -->\n\t\t\t<template v-else>\n\t\t\t\t<NcButton v-if=\"cancellable\" data-testid=\"cn-wizard-cancel\" @click=\"onClose\">\n\t\t\t\t\t{{ cancelLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton v-if=\"!isFirst\"\n\t\t\t\t\tdata-testid=\"cn-wizard-back\"\n\t\t\t\t\t:disabled=\"loading\"\n\t\t\t\t\t@click=\"back\">\n\t\t\t\t\t{{ backLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton variant=\"primary\"\n\t\t\t\t\tdata-testid=\"cn-wizard-next\"\n\t\t\t\t\t:disabled=\"loading\"\n\t\t\t\t\t@click=\"isLast ? submit() : next()\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<NcLoadingIcon v-if=\"loading\" :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ isLast ? submitLabel : nextLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</template>\n\t\t</template>\n\t</NcDialog>\n</template>\n\n<script>\nimport { NcButton, NcDialog, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\n\n/**\n * CnWizardDialog — Multi-step modal with per-step slots,\n * back/next navigation, optional per-step validation, and a single\n * result phase.\n *\n * Each step is declared in the `steps[]` prop (`{ id, label,\n * optional?, icon? }`) and rendered via a `#step-{id}` named slot\n * with the navigation API exposed through the slot scope. The\n * wizard owns the progress indicator, the back/next chrome, the\n * loading lifecycle, and the result-phase banner — the consumer\n * owns the per-step body + the data collection logic.\n *\n * Per-step validation is opt-in via the `validate` prop:\n *\n * ```js\n * async function validate(stepId, stepData) {\n * if (stepId === 'audience' && !stepData.cohort) {\n * return 'Pick a cohort first.'\n * }\n * return true\n * }\n * ```\n *\n * Returning `true` (or any truthy non-string) advances; returning a\n * string surfaces it as an error banner above the step body and\n * blocks the navigation.\n *\n * The result phase is entered by the consumer calling\n * `setResult({ success?, message?, error? })` after `@submit` fires.\n *\n * Use [`CnFormDialog`](../CnFormDialog/) for single-step forms.\n * Use [`CnExportWizard`](../CnExportWizard/) for the specific\n * scope+format+delivery export-trigger shape.\n *\n * ```vue\n * <CnWizardDialog\n * ref=\"wizard\"\n * dialog-title=\"Bulk enrol\"\n * :steps=\"[\n * { id: 'audience', label: 'Audience' },\n * { id: 'course', label: 'Course' },\n * { id: 'confirm', label: 'Confirm' },\n * ]\"\n * :validate=\"validateStep\"\n * @submit=\"onSubmit\"\n * @close=\"show = false\">\n * <template #step-audience=\"{ next, stepData, setStepData }\">\n * <CohortPicker :value=\"stepData.cohort\" @input=\"v => setStepData({ cohort: v })\" />\n * </template>\n * <template #step-course=\"{ next, back, stepData, setStepData }\">\n * <CoursePicker :value=\"stepData.course\" @input=\"v => setStepData({ course: v })\" />\n * </template>\n * <template #step-confirm=\"{ back, submit, stepData }\">\n * <p>Enrol {{ stepData.audience.cohort.size }} learners into {{ stepData.course.course.name }}?</p>\n * </template>\n * </CnWizardDialog>\n * ```\n */\nexport default {\n\tname: 'CnWizardDialog',\n\tcomponents: { NcDialog, NcButton, NcNoteCard, NcLoadingIcon },\n\tprops: {\n\t\t/**\n\t\t * Step declarations. Order is significant — the wizard\n\t\t * navigates linearly between adjacent steps unless the\n\t\t * consumer calls `jumpTo()` from a slot scope.\n\t\t *\n\t\t * @type {Array<{ id: string, label: string, optional?: boolean, icon?: string }>}\n\t\t */\n\t\tsteps: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t\tvalidator: (v) => Array.isArray(v) && v.length > 0\n\t\t\t\t&& v.every((s) => s && typeof s.id === 'string' && typeof s.label === 'string'),\n\t\t},\n\n\t\t/**\n\t\t * Dialog title shown in the NcDialog header.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdialogTitle: {\n\t\t\ttype: String,\n\t\t\tdefault: 'Wizard',\n\t\t},\n\n\t\t/**\n\t\t * Step id to start on. Defaults to the first step in `steps[]`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tinitialStep: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Per-step validator. Async function returning `true` to\n\t\t * advance, a string to surface as an error banner + block\n\t\t * navigation, or `false` to block silently. Called from\n\t\t * `next()` and `submit()`. When omitted, navigation always\n\t\t * proceeds.\n\t\t *\n\t\t * @type {(stepId: string, stepData: object) => Promise<boolean|string>}\n\t\t */\n\t\tvalidate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether the progress indicator allows jumping back to a\n\t\t * completed step via click. Forward jumps are NEVER allowed\n\t\t * via the progress indicator (consumer-side `jumpTo` is\n\t\t * unrestricted).\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tallowJumpBack: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Seed values for `stepData` (the cross-step object passed\n\t\t * to every slot scope). Useful for prefilling on edit-style\n\t\t * wizards.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tdefaults: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Cancel-button label (wizard phase). */\n\t\tcancelLabel: { type: String, default: 'Cancel' },\n\t\t/** Back-button label. */\n\t\tbackLabel: { type: String, default: 'Back' },\n\t\t/** Next-button label (intermediate steps). */\n\t\tnextLabel: { type: String, default: 'Next' },\n\t\t/** Submit-button label (last step). */\n\t\tsubmitLabel: { type: String, default: 'Submit' },\n\t\t/** Close-button label (result phase). */\n\t\tcloseLabel: { type: String, default: 'Close' },\n\t\t/** Success banner default text when result.message is empty. */\n\t\tsuccessText: { type: String, default: 'Done.' },\n\t\t/**\n\t\t * Whether the dialog can be dismissed (Cancel button, ESC, backdrop\n\t\t * click) before reaching the result phase. `false` hides Cancel and\n\t\t * disables all other close affordances — use for a step that must\n\t\t * be completed, not merely skipped. The result-phase Close button is\n\t\t * unaffected (a completed wizard is always dismissible).\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tcancellable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\t},\n\n\temits: ['close', 'step-change', 'submit'],\n\tdata() {\n\t\tconst initialIndex = this.resolveInitialIndex()\n\t\treturn {\n\t\t\tcurrentIndex: initialIndex,\n\t\t\tstepData: { ...this.defaults },\n\t\t\tloading: false,\n\t\t\tresult: null,\n\t\t\tvalidationError: '',\n\t\t\t/**\n\t\t\t * Drives NcDialog's `open` prop directly. NcDialog's own `open`\n\t\t\t * prop defaults to `true` and is NOT self-managing on close — its\n\t\t\t * internal `showModal` flag resets back to `true` ~300ms after\n\t\t\t * any close (animation bookkeeping for next time it's reopened\n\t\t\t * via the prop), so without an explicit `false` here the dialog\n\t\t\t * reopens itself right after Cancel/ESC/backdrop-click.\n\t\t\t */\n\t\t\tdialogOpen: true,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The currently-active step object from `steps[]`.\n\t\t *\n\t\t * @return {object} The current step definition.\n\t\t */\n\t\tcurrentStep() {\n\t\t\treturn this.steps[this.currentIndex] || this.steps[0]\n\t\t},\n\n\t\t/**\n\t\t * Whether the current step is the first one.\n\t\t *\n\t\t * @return {boolean} True when on the first step.\n\t\t */\n\t\tisFirst() {\n\t\t\treturn this.currentIndex === 0\n\t\t},\n\n\t\t/**\n\t\t * Whether the current step is the last one (drives the\n\t\t * Next → Submit label flip).\n\t\t *\n\t\t * @return {boolean} True when on the last step.\n\t\t */\n\t\tisLast() {\n\t\t\treturn this.currentIndex === this.steps.length - 1\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Resolve the index of `initialStep` (falling back to 0 when\n\t\t * unset / not found).\n\t\t *\n\t\t * @return {number} Step index in the range [0, steps.length).\n\t\t */\n\t\tresolveInitialIndex() {\n\t\t\tif (!this.initialStep) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tconst idx = this.steps.findIndex((s) => s.id === this.initialStep)\n\t\t\treturn idx >= 0 ? idx : 0\n\t\t},\n\n\t\t/**\n\t\t * Merge a partial step-data object onto the shared state.\n\t\t * Each slot scope receives this so consumers can write their\n\t\t * step's data without owning a `data()` block in their slot\n\t\t * component.\n\t\t *\n\t\t * @param {object} partial Fields to merge.\n\t\t * @return {void}\n\t\t */\n\t\tsetStepData(partial) {\n\t\t\tthis.stepData = { ...this.stepData, ...partial }\n\t\t},\n\n\t\t/**\n\t\t * Run the consumer's `validate` prop (if provided) for the\n\t\t * current step. Returns true on pass; sets `validationError`\n\t\t * and returns false on fail.\n\t\t *\n\t\t * @return {Promise<boolean>} Whether validation passed.\n\t\t */\n\t\tasync runValidation() {\n\t\t\tthis.validationError = ''\n\t\t\tif (typeof this.validate !== 'function') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst outcome = await this.validate(this.currentStep.id, this.stepData)\n\t\t\t\tif (outcome === true) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\tif (typeof outcome === 'string') {\n\t\t\t\t\tthis.validationError = outcome\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn Boolean(outcome)\n\t\t\t} catch (e) {\n\t\t\t\tthis.validationError = e && e.message ? e.message : 'Validation failed.'\n\t\t\t\treturn false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Advance to the next step after validation. Emits\n\t\t * `@step-change` with the new step id.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync next() {\n\t\t\tif (this.isLast) {\n\t\t\t\treturn this.submit()\n\t\t\t}\n\t\t\tconst ok = await this.runValidation()\n\t\t\tif (!ok) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.currentIndex += 1\n\t\t\t/**\n\t\t\t * @event step-change Emitted on every step navigation\n\t\t\t * (next / back / jumpTo / submit).\n\t\t\t * @type {{ stepId: string, stepIndex: number, direction: 'next'|'back'|'jump' }}\n\t\t\t */\n\t\t\tthis.$emit('step-change', {\n\t\t\t\tstepId: this.currentStep.id,\n\t\t\t\tstepIndex: this.currentIndex,\n\t\t\t\tdirection: 'next',\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Step back to the previous step. No validation runs on\n\t\t * back navigation — consumers can reach back without\n\t\t * losing the current step's draft.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tback() {\n\t\t\tif (this.isFirst) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.validationError = ''\n\t\t\tthis.currentIndex -= 1\n\t\t\tthis.$emit('step-change', {\n\t\t\t\tstepId: this.currentStep.id,\n\t\t\t\tstepIndex: this.currentIndex,\n\t\t\t\tdirection: 'back',\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Jump to an arbitrary step by id. Validation is skipped\n\t\t * (the consumer is responsible for re-running it before\n\t\t * `submit()` if it matters).\n\t\t *\n\t\t * @param {string} stepId Target step id.\n\t\t * @return {void}\n\t\t */\n\t\tjumpTo(stepId) {\n\t\t\tconst idx = this.steps.findIndex((s) => s.id === stepId)\n\t\t\tif (idx < 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.validationError = ''\n\t\t\tthis.currentIndex = idx\n\t\t\tthis.$emit('step-change', {\n\t\t\t\tstepId: this.currentStep.id,\n\t\t\t\tstepIndex: this.currentIndex,\n\t\t\t\tdirection: 'jump',\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Submit the wizard. Runs the current step's validation;\n\t\t * on pass flips `loading` on and emits `@submit` with the\n\t\t * accumulated `stepData`. The parent is responsible for\n\t\t * calling `setResult()` to flip into the result phase.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync submit() {\n\t\t\tconst ok = await this.runValidation()\n\t\t\tif (!ok) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\t/**\n\t\t\t * @event submit Emitted when the user reaches the final\n\t\t\t * step and clicks Submit (or `submit()` is called\n\t\t\t * programmatically from a slot scope). Payload is the\n\t\t\t * accumulated `stepData`.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('submit', { ...this.stepData })\n\t\t},\n\n\t\t/**\n\t\t * Public method called by the parent to switch the wizard\n\t\t * into the result phase.\n\t\t *\n\t\t * @param {object} result `{ success?, message?, error? }`.\n\t\t * @return {void}\n\t\t */\n\t\tsetResult(result) {\n\t\t\tthis.result = result || { success: true }\n\t\t\tthis.loading = false\n\t\t},\n\n\t\t/**\n\t\t * Public method for a NON-terminal submit failure: stops the\n\t\t * loading spinner and surfaces `message` as the error banner above\n\t\t * the (still-editable) current step, WITHOUT entering the result\n\t\t * phase — so the user can correct the input and resubmit. Use this\n\t\t * instead of `setResult({ error })` when the failure is recoverable\n\t\t * (e.g. a taken slug, a validation conflict).\n\t\t *\n\t\t * @param {string} message The error to show above the step body.\n\t\t * @return {void}\n\t\t */\n\t\tsetError(message) {\n\t\t\tthis.loading = false\n\t\t\tthis.validationError = message || ''\n\t\t},\n\n\t\t/**\n\t\t * Close-button handler. Resets local state so the next open\n\t\t * starts fresh and emits `@close`.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonClose() {\n\t\t\tthis.result = null\n\t\t\tthis.loading = false\n\t\t\tthis.validationError = ''\n\t\t\tthis.currentIndex = this.resolveInitialIndex()\n\t\t\tthis.stepData = { ...this.defaults }\n\t\t\tthis.dialogOpen = false\n\t\t\t/**\n\t\t\t * @event close Emitted when the dialog should close.\n\t\t\t */\n\t\t\tthis.$emit('close')\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-wizard-dialog__progress {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tjustify-content: center;\n\tgap: 0;\n\tpadding: 8px 0 4px;\n\tmargin: 0 0 20px;\n\tlist-style: none;\n}\n\n.cn-wizard-dialog__progress-item {\n\tposition: relative;\n\tflex: 1 1 0;\n\tmin-width: 0;\n\tmax-width: 180px;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 6px;\n\ttext-align: center;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8125rem;\n}\n\n.cn-wizard-dialog__progress-item--clickable {\n\tcursor: pointer;\n}\n\n/* Marker circle */\n.cn-wizard-dialog__progress-dot {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 34px;\n\theight: 34px;\n\tborder-radius: 50%;\n\tborder: 2px solid var(--color-border-dark);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n\tfont-weight: 600;\n\ttransition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;\n\tz-index: 1;\n}\n\n.cn-wizard-dialog__progress-check {\n\tfont-size: 16px;\n\tline-height: 1;\n}\n\n.cn-wizard-dialog__progress-label {\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n/* Connector line between this marker and the next, centered on the marker. */\n.cn-wizard-dialog__progress-connector {\n\tposition: absolute;\n\ttop: 17px;\n\tleft: calc(50% + 21px);\n\tright: calc(-50% + 21px);\n\theight: 2px;\n\tbackground: var(--color-border-dark);\n}\n\n.cn-wizard-dialog__progress-connector--done {\n\tbackground: var(--color-primary-element);\n}\n\n/* States */\n.cn-wizard-dialog__progress-item--active .cn-wizard-dialog__progress-dot {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n}\n\n.cn-wizard-dialog__progress-item--active .cn-wizard-dialog__progress-label {\n\tcolor: var(--color-main-text);\n\tfont-weight: 600;\n}\n\n.cn-wizard-dialog__progress-item--done .cn-wizard-dialog__progress-dot {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n}\n\n.cn-wizard-dialog__progress-item--done .cn-wizard-dialog__progress-label {\n\tcolor: var(--color-main-text);\n}\n\n.cn-wizard-dialog__progress-item--clickable:hover .cn-wizard-dialog__progress-label {\n\tcolor: var(--color-primary-element);\n}\n\n.cn-wizard-dialog__step-body {\n\tmin-height: 200px;\n}\n\n.cn-wizard-dialog__result {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n</style>\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"CnWizardDialog.vue2.js","sources":["../../../../src/components/CnWizardDialog/CnWizardDialog.vue"],"sourcesContent":["<template>\n\t<NcDialog\n\t\t:name=\"dialogTitle\"\n\t\tsize=\"large\"\n\t\t:open=\"dialogOpen\"\n\t\t:noClose=\"loading || !cancellable\"\n\t\tdata-testid=\"cn-modal\"\n\t\tdata-testid-modal=\"cn-wizard-dialog\"\n\t\t@update:open=\"dialogOpen = $event\"\n\t\t@closing=\"onClose\">\n\t\t<!-- Result phase: rendered after a successful `submit()`. -->\n\t\t<div v-if=\"result !== null\"\n\t\t\tclass=\"cn-wizard-dialog__result\"\n\t\t\tdata-testid-phase=\"result\">\n\t\t\t<NcNoteCard v-if=\"result.success\" type=\"success\">\n\t\t\t\t{{ result.message || successText }}\n\t\t\t</NcNoteCard>\n\t\t\t<NcNoteCard v-else-if=\"result.error\" type=\"error\">\n\t\t\t\t{{ result.error }}\n\t\t\t</NcNoteCard>\n\t\t\t<!-- @slot result-extra Optional extra content (e.g. batch\n\t\t\t progress poller, summary table) rendered below the\n\t\t\t success/error banner. Scope: { result }. -->\n\t\t\t<slot name=\"result-extra\" :result=\"result\" />\n\t\t</div>\n\n\t\t<!-- Wizard phase: progress bar + current step body. -->\n\t\t<div v-else\n\t\t\tclass=\"cn-wizard-dialog__wizard\"\n\t\t\tdata-testid-phase=\"form\">\n\t\t\t<!-- Progress indicator. Click jumps back to a completed\n\t\t\t step when allowJumpBack is true. -->\n\t\t\t<ol class=\"cn-wizard-dialog__progress\" role=\"tablist\">\n\t\t\t\t<li v-for=\"(step, idx) in steps\"\n\t\t\t\t\t:key=\"step.id\"\n\t\t\t\t\tclass=\"cn-wizard-dialog__progress-item\"\n\t\t\t\t\t:class=\"{\n\t\t\t\t\t\t'cn-wizard-dialog__progress-item--active': idx === currentIndex,\n\t\t\t\t\t\t'cn-wizard-dialog__progress-item--done': idx < currentIndex,\n\t\t\t\t\t\t'cn-wizard-dialog__progress-item--clickable': allowJumpBack && idx < currentIndex,\n\t\t\t\t\t}\"\n\t\t\t\t\trole=\"tab\"\n\t\t\t\t\t:aria-selected=\"idx === currentIndex\"\n\t\t\t\t\t@click=\"allowJumpBack && idx < currentIndex ? jumpTo(step.id) : null\">\n\t\t\t\t\t<span class=\"cn-wizard-dialog__progress-dot\">\n\t\t\t\t\t\t<span v-if=\"idx < currentIndex\" class=\"cn-wizard-dialog__progress-check\" aria-hidden=\"true\">✓</span>\n\t\t\t\t\t\t<span v-else>{{ idx + 1 }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t<span class=\"cn-wizard-dialog__progress-label\">{{ step.label }}</span>\n\t\t\t\t\t<span v-if=\"idx < steps.length - 1\"\n\t\t\t\t\t\tclass=\"cn-wizard-dialog__progress-connector\"\n\t\t\t\t\t\t:class=\"{ 'cn-wizard-dialog__progress-connector--done': idx < currentIndex }\"\n\t\t\t\t\t\taria-hidden=\"true\" />\n\t\t\t\t</li>\n\t\t\t</ol>\n\n\t\t\t<!-- Validation error from the current step's validate() result. -->\n\t\t\t<NcNoteCard v-if=\"validationError\" type=\"error\">\n\t\t\t\t{{ validationError }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<!-- Current step body — a dynamic slot named by step id. -->\n\t\t\t<div class=\"cn-wizard-dialog__step-body\" :data-step-id=\"currentStep.id\">\n\t\t\t\t<!-- @slot step-{id} Per-step body. Scope: { next,\n\t\t\t\t back, jumpTo, submit, currentStep, stepIndex,\n\t\t\t\t totalSteps, stepData, setStepData, isFirst,\n\t\t\t\t isLast }. -->\n\t\t\t\t<slot\n\t\t\t\t\t:name=\"'step-' + currentStep.id\"\n\t\t\t\t\t:next=\"next\"\n\t\t\t\t\t:back=\"back\"\n\t\t\t\t\t:jumpTo=\"jumpTo\"\n\t\t\t\t\t:submit=\"submit\"\n\t\t\t\t\t:currentStep=\"currentStep\"\n\t\t\t\t\t:stepIndex=\"currentIndex\"\n\t\t\t\t\t:totalSteps=\"steps.length\"\n\t\t\t\t\t:stepData=\"stepData\"\n\t\t\t\t\t:setStepData=\"setStepData\"\n\t\t\t\t\t:isFirst=\"isFirst\"\n\t\t\t\t\t:isLast=\"isLast\" />\n\t\t\t</div>\n\t\t</div>\n\n\t\t<template #actions>\n\t\t\t<!-- Result phase actions: just Close. -->\n\t\t\t<NcButton v-if=\"result !== null\" @click=\"onClose\">\n\t\t\t\t{{ closeLabel }}\n\t\t\t</NcButton>\n\n\t\t\t<!-- Wizard phase actions: Cancel + Back + Next/Submit.\n\t\t\t The testids exist because these buttons are the only way\n\t\t\t through a wizard and their LABELS are translated: an e2e that\n\t\t\t clicks \"Next\" passes on an English instance and times out on a\n\t\t\t Dutch one, reporting a broken dialog rather than a missing\n\t\t\t string. -->\n\t\t\t<template v-else>\n\t\t\t\t<NcButton v-if=\"cancellable\" data-testid=\"cn-wizard-cancel\" @click=\"onClose\">\n\t\t\t\t\t{{ cancelLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton v-if=\"!isFirst\"\n\t\t\t\t\tdata-testid=\"cn-wizard-back\"\n\t\t\t\t\t:disabled=\"loading\"\n\t\t\t\t\t@click=\"back\">\n\t\t\t\t\t{{ backLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton variant=\"primary\"\n\t\t\t\t\tdata-testid=\"cn-wizard-next\"\n\t\t\t\t\t:disabled=\"loading || nextDisabled\"\n\t\t\t\t\t@click=\"isLast ? submit() : next()\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<NcLoadingIcon v-if=\"loading\" :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ isLast ? submitLabel : nextLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</template>\n\t\t</template>\n\t</NcDialog>\n</template>\n\n<script>\nimport { NcButton, NcDialog, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\n\n/**\n * CnWizardDialog — Multi-step modal with per-step slots,\n * back/next navigation, optional per-step validation, and a single\n * result phase.\n *\n * Each step is declared in the `steps[]` prop (`{ id, label,\n * optional?, icon? }`) and rendered via a `#step-{id}` named slot\n * with the navigation API exposed through the slot scope. The\n * wizard owns the progress indicator, the back/next chrome, the\n * loading lifecycle, and the result-phase banner — the consumer\n * owns the per-step body + the data collection logic.\n *\n * Per-step validation is opt-in via the `validate` prop:\n *\n * ```js\n * async function validate(stepId, stepData) {\n * if (stepId === 'audience' && !stepData.cohort) {\n * return 'Pick a cohort first.'\n * }\n * return true\n * }\n * ```\n *\n * Returning `true` (or any truthy non-string) advances; returning a\n * string surfaces it as an error banner above the step body and\n * blocks the navigation.\n *\n * The result phase is entered by the consumer calling\n * `setResult({ success?, message?, error? })` after `@submit` fires.\n *\n * Use [`CnFormDialog`](../CnFormDialog/) for single-step forms.\n * Use [`CnExportWizard`](../CnExportWizard/) for the specific\n * scope+format+delivery export-trigger shape.\n *\n * ```vue\n * <CnWizardDialog\n * ref=\"wizard\"\n * dialog-title=\"Bulk enrol\"\n * :steps=\"[\n * { id: 'audience', label: 'Audience' },\n * { id: 'course', label: 'Course' },\n * { id: 'confirm', label: 'Confirm' },\n * ]\"\n * :validate=\"validateStep\"\n * @submit=\"onSubmit\"\n * @close=\"show = false\">\n * <template #step-audience=\"{ next, stepData, setStepData }\">\n * <CohortPicker :value=\"stepData.cohort\" @input=\"v => setStepData({ cohort: v })\" />\n * </template>\n * <template #step-course=\"{ next, back, stepData, setStepData }\">\n * <CoursePicker :value=\"stepData.course\" @input=\"v => setStepData({ course: v })\" />\n * </template>\n * <template #step-confirm=\"{ back, submit, stepData }\">\n * <p>Enrol {{ stepData.audience.cohort.size }} learners into {{ stepData.course.course.name }}?</p>\n * </template>\n * </CnWizardDialog>\n * ```\n */\nexport default {\n\tname: 'CnWizardDialog',\n\tcomponents: { NcDialog, NcButton, NcNoteCard, NcLoadingIcon },\n\tprops: {\n\t\t/**\n\t\t * Step declarations. Order is significant — the wizard\n\t\t * navigates linearly between adjacent steps unless the\n\t\t * consumer calls `jumpTo()` from a slot scope.\n\t\t *\n\t\t * @type {Array<{ id: string, label: string, optional?: boolean, icon?: string }>}\n\t\t */\n\t\tsteps: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t\tvalidator: (v) => Array.isArray(v) && v.length > 0\n\t\t\t\t&& v.every((s) => s && typeof s.id === 'string' && typeof s.label === 'string'),\n\t\t},\n\n\t\t/**\n\t\t * Dialog title shown in the NcDialog header.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdialogTitle: {\n\t\t\ttype: String,\n\t\t\tdefault: 'Wizard',\n\t\t},\n\n\t\t/**\n\t\t * Step id to start on. Defaults to the first step in `steps[]`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tinitialStep: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Per-step validator. Async function returning `true` to\n\t\t * advance, a string to surface as an error banner + block\n\t\t * navigation, or `false` to block silently. Called from\n\t\t * `next()` and `submit()`. When omitted, navigation always\n\t\t * proceeds.\n\t\t *\n\t\t * @type {(stepId: string, stepData: object) => Promise<boolean|string>}\n\t\t */\n\t\tvalidate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether the progress indicator allows jumping back to a\n\t\t * completed step via click. Forward jumps are NEVER allowed\n\t\t * via the progress indicator (consumer-side `jumpTo` is\n\t\t * unrestricted).\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tallowJumpBack: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Seed values for `stepData` (the cross-step object passed\n\t\t * to every slot scope). Useful for prefilling on edit-style\n\t\t * wizards.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tdefaults: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Cancel-button label (wizard phase). */\n\t\tcancelLabel: { type: String, default: 'Cancel' },\n\t\t/** Back-button label. */\n\t\tbackLabel: { type: String, default: 'Back' },\n\t\t/** Next-button label (intermediate steps). */\n\t\tnextLabel: { type: String, default: 'Next' },\n\t\t/** Submit-button label (last step). */\n\t\tsubmitLabel: { type: String, default: 'Submit' },\n\t\t/** Close-button label (result phase). */\n\t\tcloseLabel: { type: String, default: 'Close' },\n\t\t/** Success banner default text when result.message is empty. */\n\t\tsuccessText: { type: String, default: 'Done.' },\n\t\t/**\n\t\t * Whether the dialog can be dismissed (Cancel button, ESC, backdrop\n\t\t * click) before reaching the result phase. `false` hides Cancel and\n\t\t * disables all other close affordances — use for a step that must\n\t\t * be completed, not merely skipped. The result-phase Close button is\n\t\t * unaffected (a completed wizard is always dismissible).\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tcancellable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Disable the Next / Submit button, whatever the step's `validate`\n\t\t * would say. For a host that knows the wizard cannot go on yet, such as\n\t\t * `CnSetupWizard` while a required app is missing.\n\t\t *\n\t\t * @type {boolean}\n\t\t * @spec openspec/changes/setup-wizard-card-load-and-dependency-gate/specs/cn-setup-wizard/spec.md#requirement-the-wizard-checks-dependencies-before-any-step\n\t\t */\n\t\tnextDisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\temits: ['close', 'step-change', 'submit'],\n\tdata() {\n\t\tconst initialIndex = this.resolveInitialIndex()\n\t\treturn {\n\t\t\tcurrentIndex: initialIndex,\n\t\t\tstepData: { ...this.defaults },\n\t\t\tloading: false,\n\t\t\tresult: null,\n\t\t\tvalidationError: '',\n\t\t\t/**\n\t\t\t * Drives NcDialog's `open` prop directly. NcDialog's own `open`\n\t\t\t * prop defaults to `true` and is NOT self-managing on close — its\n\t\t\t * internal `showModal` flag resets back to `true` ~300ms after\n\t\t\t * any close (animation bookkeeping for next time it's reopened\n\t\t\t * via the prop), so without an explicit `false` here the dialog\n\t\t\t * reopens itself right after Cancel/ESC/backdrop-click.\n\t\t\t */\n\t\t\tdialogOpen: true,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The currently-active step object from `steps[]`.\n\t\t *\n\t\t * @return {object} The current step definition.\n\t\t */\n\t\tcurrentStep() {\n\t\t\treturn this.steps[this.currentIndex] || this.steps[0]\n\t\t},\n\n\t\t/**\n\t\t * Whether the current step is the first one.\n\t\t *\n\t\t * @return {boolean} True when on the first step.\n\t\t */\n\t\tisFirst() {\n\t\t\treturn this.currentIndex === 0\n\t\t},\n\n\t\t/**\n\t\t * Whether the current step is the last one (drives the\n\t\t * Next → Submit label flip).\n\t\t *\n\t\t * @return {boolean} True when on the last step.\n\t\t */\n\t\tisLast() {\n\t\t\treturn this.currentIndex === this.steps.length - 1\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Resolve the index of `initialStep` (falling back to 0 when\n\t\t * unset / not found).\n\t\t *\n\t\t * @return {number} Step index in the range [0, steps.length).\n\t\t */\n\t\tresolveInitialIndex() {\n\t\t\tif (!this.initialStep) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tconst idx = this.steps.findIndex((s) => s.id === this.initialStep)\n\t\t\treturn idx >= 0 ? idx : 0\n\t\t},\n\n\t\t/**\n\t\t * Merge a partial step-data object onto the shared state.\n\t\t * Each slot scope receives this so consumers can write their\n\t\t * step's data without owning a `data()` block in their slot\n\t\t * component.\n\t\t *\n\t\t * @param {object} partial Fields to merge.\n\t\t * @return {void}\n\t\t */\n\t\tsetStepData(partial) {\n\t\t\tthis.stepData = { ...this.stepData, ...partial }\n\t\t},\n\n\t\t/**\n\t\t * Run the consumer's `validate` prop (if provided) for the\n\t\t * current step. Returns true on pass; sets `validationError`\n\t\t * and returns false on fail.\n\t\t *\n\t\t * @return {Promise<boolean>} Whether validation passed.\n\t\t */\n\t\tasync runValidation() {\n\t\t\tthis.validationError = ''\n\t\t\tif (typeof this.validate !== 'function') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst outcome = await this.validate(this.currentStep.id, this.stepData)\n\t\t\t\tif (outcome === true) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\tif (typeof outcome === 'string') {\n\t\t\t\t\tthis.validationError = outcome\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn Boolean(outcome)\n\t\t\t} catch (e) {\n\t\t\t\tthis.validationError = e && e.message ? e.message : 'Validation failed.'\n\t\t\t\treturn false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Advance to the next step after validation. Emits\n\t\t * `@step-change` with the new step id.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync next() {\n\t\t\tif (this.nextDisabled) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.isLast) {\n\t\t\t\treturn this.submit()\n\t\t\t}\n\t\t\tconst ok = await this.runValidation()\n\t\t\tif (!ok) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.currentIndex += 1\n\t\t\t/**\n\t\t\t * @event step-change Emitted on every step navigation\n\t\t\t * (next / back / jumpTo / submit).\n\t\t\t * @type {{ stepId: string, stepIndex: number, direction: 'next'|'back'|'jump' }}\n\t\t\t */\n\t\t\tthis.$emit('step-change', {\n\t\t\t\tstepId: this.currentStep.id,\n\t\t\t\tstepIndex: this.currentIndex,\n\t\t\t\tdirection: 'next',\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Step back to the previous step. No validation runs on\n\t\t * back navigation — consumers can reach back without\n\t\t * losing the current step's draft.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tback() {\n\t\t\tif (this.isFirst) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.validationError = ''\n\t\t\tthis.currentIndex -= 1\n\t\t\tthis.$emit('step-change', {\n\t\t\t\tstepId: this.currentStep.id,\n\t\t\t\tstepIndex: this.currentIndex,\n\t\t\t\tdirection: 'back',\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Jump to an arbitrary step by id. Validation is skipped\n\t\t * (the consumer is responsible for re-running it before\n\t\t * `submit()` if it matters).\n\t\t *\n\t\t * @param {string} stepId Target step id.\n\t\t * @return {void}\n\t\t */\n\t\tjumpTo(stepId) {\n\t\t\tconst idx = this.steps.findIndex((s) => s.id === stepId)\n\t\t\tif (idx < 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.validationError = ''\n\t\t\tthis.currentIndex = idx\n\t\t\tthis.$emit('step-change', {\n\t\t\t\tstepId: this.currentStep.id,\n\t\t\t\tstepIndex: this.currentIndex,\n\t\t\t\tdirection: 'jump',\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Submit the wizard. Runs the current step's validation;\n\t\t * on pass flips `loading` on and emits `@submit` with the\n\t\t * accumulated `stepData`. The parent is responsible for\n\t\t * calling `setResult()` to flip into the result phase.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync submit() {\n\t\t\tif (this.nextDisabled) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst ok = await this.runValidation()\n\t\t\tif (!ok) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\t/**\n\t\t\t * @event submit Emitted when the user reaches the final\n\t\t\t * step and clicks Submit (or `submit()` is called\n\t\t\t * programmatically from a slot scope). Payload is the\n\t\t\t * accumulated `stepData`.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('submit', { ...this.stepData })\n\t\t},\n\n\t\t/**\n\t\t * Public method called by the parent to switch the wizard\n\t\t * into the result phase.\n\t\t *\n\t\t * @param {object} result `{ success?, message?, error? }`.\n\t\t * @return {void}\n\t\t */\n\t\tsetResult(result) {\n\t\t\tthis.result = result || { success: true }\n\t\t\tthis.loading = false\n\t\t},\n\n\t\t/**\n\t\t * Public method for a NON-terminal submit failure: stops the\n\t\t * loading spinner and surfaces `message` as the error banner above\n\t\t * the (still-editable) current step, WITHOUT entering the result\n\t\t * phase — so the user can correct the input and resubmit. Use this\n\t\t * instead of `setResult({ error })` when the failure is recoverable\n\t\t * (e.g. a taken slug, a validation conflict).\n\t\t *\n\t\t * @param {string} message The error to show above the step body.\n\t\t * @return {void}\n\t\t */\n\t\tsetError(message) {\n\t\t\tthis.loading = false\n\t\t\tthis.validationError = message || ''\n\t\t},\n\n\t\t/**\n\t\t * Close-button handler. Resets local state so the next open\n\t\t * starts fresh and emits `@close`.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonClose() {\n\t\t\tthis.result = null\n\t\t\tthis.loading = false\n\t\t\tthis.validationError = ''\n\t\t\tthis.currentIndex = this.resolveInitialIndex()\n\t\t\tthis.stepData = { ...this.defaults }\n\t\t\tthis.dialogOpen = false\n\t\t\t/**\n\t\t\t * @event close Emitted when the dialog should close.\n\t\t\t */\n\t\t\tthis.$emit('close')\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-wizard-dialog__progress {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tjustify-content: center;\n\tgap: 0;\n\tpadding: 8px 0 4px;\n\tmargin: 0 0 20px;\n\tlist-style: none;\n}\n\n.cn-wizard-dialog__progress-item {\n\tposition: relative;\n\tflex: 1 1 0;\n\tmin-width: 0;\n\tmax-width: 180px;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 6px;\n\ttext-align: center;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8125rem;\n}\n\n.cn-wizard-dialog__progress-item--clickable {\n\tcursor: pointer;\n}\n\n/* Marker circle */\n.cn-wizard-dialog__progress-dot {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 34px;\n\theight: 34px;\n\tborder-radius: 50%;\n\tborder: 2px solid var(--color-border-dark);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n\tfont-weight: 600;\n\ttransition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;\n\tz-index: 1;\n}\n\n.cn-wizard-dialog__progress-check {\n\tfont-size: 16px;\n\tline-height: 1;\n}\n\n.cn-wizard-dialog__progress-label {\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n/* Connector line between this marker and the next, centered on the marker. */\n.cn-wizard-dialog__progress-connector {\n\tposition: absolute;\n\ttop: 17px;\n\tleft: calc(50% + 21px);\n\tright: calc(-50% + 21px);\n\theight: 2px;\n\tbackground: var(--color-border-dark);\n}\n\n.cn-wizard-dialog__progress-connector--done {\n\tbackground: var(--color-primary-element);\n}\n\n/* States */\n.cn-wizard-dialog__progress-item--active .cn-wizard-dialog__progress-dot {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n}\n\n.cn-wizard-dialog__progress-item--active .cn-wizard-dialog__progress-label {\n\tcolor: var(--color-main-text);\n\tfont-weight: 600;\n}\n\n.cn-wizard-dialog__progress-item--done .cn-wizard-dialog__progress-dot {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n}\n\n.cn-wizard-dialog__progress-item--done .cn-wizard-dialog__progress-label {\n\tcolor: var(--color-main-text);\n}\n\n.cn-wizard-dialog__progress-item--clickable:hover .cn-wizard-dialog__progress-label {\n\tcolor: var(--color-primary-element);\n}\n\n.cn-wizard-dialog__step-body {\n\tmin-height: 200px;\n}\n\n.cn-wizard-dialog__result {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n</style>\n"],"names":[],"mappings":"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"}
@@ -81,7 +81,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
81
81
  createVNode(_component_NcButton, {
82
82
  variant: "primary",
83
83
  "data-testid": "cn-wizard-next",
84
- disabled: $data.loading,
84
+ disabled: $data.loading || $props.nextDisabled,
85
85
  onClick: _cache[0] || (_cache[0] = $event => ($options.isLast ? $options.submit() : $options.next()))
86
86
  }, {
87
87
  icon: withCtx(() => [
@@ -1 +1 @@
1
- {"version":3,"file":"CnWizardDialog.vue3.js","sources":["../../../../src/components/CnWizardDialog/CnWizardDialog.vue?vue&type=template&id=27d51a62&lang.js"],"sourcesContent":["<template>\n\t<NcDialog\n\t\t:name=\"dialogTitle\"\n\t\tsize=\"large\"\n\t\t:open=\"dialogOpen\"\n\t\t:noClose=\"loading || !cancellable\"\n\t\tdata-testid=\"cn-modal\"\n\t\tdata-testid-modal=\"cn-wizard-dialog\"\n\t\t@update:open=\"dialogOpen = $event\"\n\t\t@closing=\"onClose\">\n\t\t<!-- Result phase: rendered after a successful `submit()`. -->\n\t\t<div v-if=\"result !== null\"\n\t\t\tclass=\"cn-wizard-dialog__result\"\n\t\t\tdata-testid-phase=\"result\">\n\t\t\t<NcNoteCard v-if=\"result.success\" type=\"success\">\n\t\t\t\t{{ result.message || successText }}\n\t\t\t</NcNoteCard>\n\t\t\t<NcNoteCard v-else-if=\"result.error\" type=\"error\">\n\t\t\t\t{{ result.error }}\n\t\t\t</NcNoteCard>\n\t\t\t<!-- @slot result-extra Optional extra content (e.g. batch\n\t\t\t progress poller, summary table) rendered below the\n\t\t\t success/error banner. Scope: { result }. -->\n\t\t\t<slot name=\"result-extra\" :result=\"result\" />\n\t\t</div>\n\n\t\t<!-- Wizard phase: progress bar + current step body. -->\n\t\t<div v-else\n\t\t\tclass=\"cn-wizard-dialog__wizard\"\n\t\t\tdata-testid-phase=\"form\">\n\t\t\t<!-- Progress indicator. Click jumps back to a completed\n\t\t\t step when allowJumpBack is true. -->\n\t\t\t<ol class=\"cn-wizard-dialog__progress\" role=\"tablist\">\n\t\t\t\t<li v-for=\"(step, idx) in steps\"\n\t\t\t\t\t:key=\"step.id\"\n\t\t\t\t\tclass=\"cn-wizard-dialog__progress-item\"\n\t\t\t\t\t:class=\"{\n\t\t\t\t\t\t'cn-wizard-dialog__progress-item--active': idx === currentIndex,\n\t\t\t\t\t\t'cn-wizard-dialog__progress-item--done': idx < currentIndex,\n\t\t\t\t\t\t'cn-wizard-dialog__progress-item--clickable': allowJumpBack && idx < currentIndex,\n\t\t\t\t\t}\"\n\t\t\t\t\trole=\"tab\"\n\t\t\t\t\t:aria-selected=\"idx === currentIndex\"\n\t\t\t\t\t@click=\"allowJumpBack && idx < currentIndex ? jumpTo(step.id) : null\">\n\t\t\t\t\t<span class=\"cn-wizard-dialog__progress-dot\">\n\t\t\t\t\t\t<span v-if=\"idx < currentIndex\" class=\"cn-wizard-dialog__progress-check\" aria-hidden=\"true\">✓</span>\n\t\t\t\t\t\t<span v-else>{{ idx + 1 }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t<span class=\"cn-wizard-dialog__progress-label\">{{ step.label }}</span>\n\t\t\t\t\t<span v-if=\"idx < steps.length - 1\"\n\t\t\t\t\t\tclass=\"cn-wizard-dialog__progress-connector\"\n\t\t\t\t\t\t:class=\"{ 'cn-wizard-dialog__progress-connector--done': idx < currentIndex }\"\n\t\t\t\t\t\taria-hidden=\"true\" />\n\t\t\t\t</li>\n\t\t\t</ol>\n\n\t\t\t<!-- Validation error from the current step's validate() result. -->\n\t\t\t<NcNoteCard v-if=\"validationError\" type=\"error\">\n\t\t\t\t{{ validationError }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<!-- Current step body — a dynamic slot named by step id. -->\n\t\t\t<div class=\"cn-wizard-dialog__step-body\" :data-step-id=\"currentStep.id\">\n\t\t\t\t<!-- @slot step-{id} Per-step body. Scope: { next,\n\t\t\t\t back, jumpTo, submit, currentStep, stepIndex,\n\t\t\t\t totalSteps, stepData, setStepData, isFirst,\n\t\t\t\t isLast }. -->\n\t\t\t\t<slot\n\t\t\t\t\t:name=\"'step-' + currentStep.id\"\n\t\t\t\t\t:next=\"next\"\n\t\t\t\t\t:back=\"back\"\n\t\t\t\t\t:jumpTo=\"jumpTo\"\n\t\t\t\t\t:submit=\"submit\"\n\t\t\t\t\t:currentStep=\"currentStep\"\n\t\t\t\t\t:stepIndex=\"currentIndex\"\n\t\t\t\t\t:totalSteps=\"steps.length\"\n\t\t\t\t\t:stepData=\"stepData\"\n\t\t\t\t\t:setStepData=\"setStepData\"\n\t\t\t\t\t:isFirst=\"isFirst\"\n\t\t\t\t\t:isLast=\"isLast\" />\n\t\t\t</div>\n\t\t</div>\n\n\t\t<template #actions>\n\t\t\t<!-- Result phase actions: just Close. -->\n\t\t\t<NcButton v-if=\"result !== null\" @click=\"onClose\">\n\t\t\t\t{{ closeLabel }}\n\t\t\t</NcButton>\n\n\t\t\t<!-- Wizard phase actions: Cancel + Back + Next/Submit.\n\t\t\t The testids exist because these buttons are the only way\n\t\t\t through a wizard and their LABELS are translated: an e2e that\n\t\t\t clicks \"Next\" passes on an English instance and times out on a\n\t\t\t Dutch one, reporting a broken dialog rather than a missing\n\t\t\t string. -->\n\t\t\t<template v-else>\n\t\t\t\t<NcButton v-if=\"cancellable\" data-testid=\"cn-wizard-cancel\" @click=\"onClose\">\n\t\t\t\t\t{{ cancelLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton v-if=\"!isFirst\"\n\t\t\t\t\tdata-testid=\"cn-wizard-back\"\n\t\t\t\t\t:disabled=\"loading\"\n\t\t\t\t\t@click=\"back\">\n\t\t\t\t\t{{ backLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton variant=\"primary\"\n\t\t\t\t\tdata-testid=\"cn-wizard-next\"\n\t\t\t\t\t:disabled=\"loading\"\n\t\t\t\t\t@click=\"isLast ? submit() : next()\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<NcLoadingIcon v-if=\"loading\" :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ isLast ? submitLabel : nextLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</template>\n\t\t</template>\n\t</NcDialog>\n</template>\n\n<script>\nimport { NcButton, NcDialog, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\n\n/**\n * CnWizardDialog — Multi-step modal with per-step slots,\n * back/next navigation, optional per-step validation, and a single\n * result phase.\n *\n * Each step is declared in the `steps[]` prop (`{ id, label,\n * optional?, icon? }`) and rendered via a `#step-{id}` named slot\n * with the navigation API exposed through the slot scope. The\n * wizard owns the progress indicator, the back/next chrome, the\n * loading lifecycle, and the result-phase banner — the consumer\n * owns the per-step body + the data collection logic.\n *\n * Per-step validation is opt-in via the `validate` prop:\n *\n * ```js\n * async function validate(stepId, stepData) {\n * if (stepId === 'audience' && !stepData.cohort) {\n * return 'Pick a cohort first.'\n * }\n * return true\n * }\n * ```\n *\n * Returning `true` (or any truthy non-string) advances; returning a\n * string surfaces it as an error banner above the step body and\n * blocks the navigation.\n *\n * The result phase is entered by the consumer calling\n * `setResult({ success?, message?, error? })` after `@submit` fires.\n *\n * Use [`CnFormDialog`](../CnFormDialog/) for single-step forms.\n * Use [`CnExportWizard`](../CnExportWizard/) for the specific\n * scope+format+delivery export-trigger shape.\n *\n * ```vue\n * <CnWizardDialog\n * ref=\"wizard\"\n * dialog-title=\"Bulk enrol\"\n * :steps=\"[\n * { id: 'audience', label: 'Audience' },\n * { id: 'course', label: 'Course' },\n * { id: 'confirm', label: 'Confirm' },\n * ]\"\n * :validate=\"validateStep\"\n * @submit=\"onSubmit\"\n * @close=\"show = false\">\n * <template #step-audience=\"{ next, stepData, setStepData }\">\n * <CohortPicker :value=\"stepData.cohort\" @input=\"v => setStepData({ cohort: v })\" />\n * </template>\n * <template #step-course=\"{ next, back, stepData, setStepData }\">\n * <CoursePicker :value=\"stepData.course\" @input=\"v => setStepData({ course: v })\" />\n * </template>\n * <template #step-confirm=\"{ back, submit, stepData }\">\n * <p>Enrol {{ stepData.audience.cohort.size }} learners into {{ stepData.course.course.name }}?</p>\n * </template>\n * </CnWizardDialog>\n * ```\n */\nexport default {\n\tname: 'CnWizardDialog',\n\tcomponents: { NcDialog, NcButton, NcNoteCard, NcLoadingIcon },\n\tprops: {\n\t\t/**\n\t\t * Step declarations. Order is significant — the wizard\n\t\t * navigates linearly between adjacent steps unless the\n\t\t * consumer calls `jumpTo()` from a slot scope.\n\t\t *\n\t\t * @type {Array<{ id: string, label: string, optional?: boolean, icon?: string }>}\n\t\t */\n\t\tsteps: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t\tvalidator: (v) => Array.isArray(v) && v.length > 0\n\t\t\t\t&& v.every((s) => s && typeof s.id === 'string' && typeof s.label === 'string'),\n\t\t},\n\n\t\t/**\n\t\t * Dialog title shown in the NcDialog header.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdialogTitle: {\n\t\t\ttype: String,\n\t\t\tdefault: 'Wizard',\n\t\t},\n\n\t\t/**\n\t\t * Step id to start on. Defaults to the first step in `steps[]`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tinitialStep: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Per-step validator. Async function returning `true` to\n\t\t * advance, a string to surface as an error banner + block\n\t\t * navigation, or `false` to block silently. Called from\n\t\t * `next()` and `submit()`. When omitted, navigation always\n\t\t * proceeds.\n\t\t *\n\t\t * @type {(stepId: string, stepData: object) => Promise<boolean|string>}\n\t\t */\n\t\tvalidate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether the progress indicator allows jumping back to a\n\t\t * completed step via click. Forward jumps are NEVER allowed\n\t\t * via the progress indicator (consumer-side `jumpTo` is\n\t\t * unrestricted).\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tallowJumpBack: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Seed values for `stepData` (the cross-step object passed\n\t\t * to every slot scope). Useful for prefilling on edit-style\n\t\t * wizards.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tdefaults: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Cancel-button label (wizard phase). */\n\t\tcancelLabel: { type: String, default: 'Cancel' },\n\t\t/** Back-button label. */\n\t\tbackLabel: { type: String, default: 'Back' },\n\t\t/** Next-button label (intermediate steps). */\n\t\tnextLabel: { type: String, default: 'Next' },\n\t\t/** Submit-button label (last step). */\n\t\tsubmitLabel: { type: String, default: 'Submit' },\n\t\t/** Close-button label (result phase). */\n\t\tcloseLabel: { type: String, default: 'Close' },\n\t\t/** Success banner default text when result.message is empty. */\n\t\tsuccessText: { type: String, default: 'Done.' },\n\t\t/**\n\t\t * Whether the dialog can be dismissed (Cancel button, ESC, backdrop\n\t\t * click) before reaching the result phase. `false` hides Cancel and\n\t\t * disables all other close affordances — use for a step that must\n\t\t * be completed, not merely skipped. The result-phase Close button is\n\t\t * unaffected (a completed wizard is always dismissible).\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tcancellable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\t},\n\n\temits: ['close', 'step-change', 'submit'],\n\tdata() {\n\t\tconst initialIndex = this.resolveInitialIndex()\n\t\treturn {\n\t\t\tcurrentIndex: initialIndex,\n\t\t\tstepData: { ...this.defaults },\n\t\t\tloading: false,\n\t\t\tresult: null,\n\t\t\tvalidationError: '',\n\t\t\t/**\n\t\t\t * Drives NcDialog's `open` prop directly. NcDialog's own `open`\n\t\t\t * prop defaults to `true` and is NOT self-managing on close — its\n\t\t\t * internal `showModal` flag resets back to `true` ~300ms after\n\t\t\t * any close (animation bookkeeping for next time it's reopened\n\t\t\t * via the prop), so without an explicit `false` here the dialog\n\t\t\t * reopens itself right after Cancel/ESC/backdrop-click.\n\t\t\t */\n\t\t\tdialogOpen: true,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The currently-active step object from `steps[]`.\n\t\t *\n\t\t * @return {object} The current step definition.\n\t\t */\n\t\tcurrentStep() {\n\t\t\treturn this.steps[this.currentIndex] || this.steps[0]\n\t\t},\n\n\t\t/**\n\t\t * Whether the current step is the first one.\n\t\t *\n\t\t * @return {boolean} True when on the first step.\n\t\t */\n\t\tisFirst() {\n\t\t\treturn this.currentIndex === 0\n\t\t},\n\n\t\t/**\n\t\t * Whether the current step is the last one (drives the\n\t\t * Next → Submit label flip).\n\t\t *\n\t\t * @return {boolean} True when on the last step.\n\t\t */\n\t\tisLast() {\n\t\t\treturn this.currentIndex === this.steps.length - 1\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Resolve the index of `initialStep` (falling back to 0 when\n\t\t * unset / not found).\n\t\t *\n\t\t * @return {number} Step index in the range [0, steps.length).\n\t\t */\n\t\tresolveInitialIndex() {\n\t\t\tif (!this.initialStep) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tconst idx = this.steps.findIndex((s) => s.id === this.initialStep)\n\t\t\treturn idx >= 0 ? idx : 0\n\t\t},\n\n\t\t/**\n\t\t * Merge a partial step-data object onto the shared state.\n\t\t * Each slot scope receives this so consumers can write their\n\t\t * step's data without owning a `data()` block in their slot\n\t\t * component.\n\t\t *\n\t\t * @param {object} partial Fields to merge.\n\t\t * @return {void}\n\t\t */\n\t\tsetStepData(partial) {\n\t\t\tthis.stepData = { ...this.stepData, ...partial }\n\t\t},\n\n\t\t/**\n\t\t * Run the consumer's `validate` prop (if provided) for the\n\t\t * current step. Returns true on pass; sets `validationError`\n\t\t * and returns false on fail.\n\t\t *\n\t\t * @return {Promise<boolean>} Whether validation passed.\n\t\t */\n\t\tasync runValidation() {\n\t\t\tthis.validationError = ''\n\t\t\tif (typeof this.validate !== 'function') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst outcome = await this.validate(this.currentStep.id, this.stepData)\n\t\t\t\tif (outcome === true) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\tif (typeof outcome === 'string') {\n\t\t\t\t\tthis.validationError = outcome\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn Boolean(outcome)\n\t\t\t} catch (e) {\n\t\t\t\tthis.validationError = e && e.message ? e.message : 'Validation failed.'\n\t\t\t\treturn false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Advance to the next step after validation. Emits\n\t\t * `@step-change` with the new step id.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync next() {\n\t\t\tif (this.isLast) {\n\t\t\t\treturn this.submit()\n\t\t\t}\n\t\t\tconst ok = await this.runValidation()\n\t\t\tif (!ok) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.currentIndex += 1\n\t\t\t/**\n\t\t\t * @event step-change Emitted on every step navigation\n\t\t\t * (next / back / jumpTo / submit).\n\t\t\t * @type {{ stepId: string, stepIndex: number, direction: 'next'|'back'|'jump' }}\n\t\t\t */\n\t\t\tthis.$emit('step-change', {\n\t\t\t\tstepId: this.currentStep.id,\n\t\t\t\tstepIndex: this.currentIndex,\n\t\t\t\tdirection: 'next',\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Step back to the previous step. No validation runs on\n\t\t * back navigation — consumers can reach back without\n\t\t * losing the current step's draft.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tback() {\n\t\t\tif (this.isFirst) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.validationError = ''\n\t\t\tthis.currentIndex -= 1\n\t\t\tthis.$emit('step-change', {\n\t\t\t\tstepId: this.currentStep.id,\n\t\t\t\tstepIndex: this.currentIndex,\n\t\t\t\tdirection: 'back',\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Jump to an arbitrary step by id. Validation is skipped\n\t\t * (the consumer is responsible for re-running it before\n\t\t * `submit()` if it matters).\n\t\t *\n\t\t * @param {string} stepId Target step id.\n\t\t * @return {void}\n\t\t */\n\t\tjumpTo(stepId) {\n\t\t\tconst idx = this.steps.findIndex((s) => s.id === stepId)\n\t\t\tif (idx < 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.validationError = ''\n\t\t\tthis.currentIndex = idx\n\t\t\tthis.$emit('step-change', {\n\t\t\t\tstepId: this.currentStep.id,\n\t\t\t\tstepIndex: this.currentIndex,\n\t\t\t\tdirection: 'jump',\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Submit the wizard. Runs the current step's validation;\n\t\t * on pass flips `loading` on and emits `@submit` with the\n\t\t * accumulated `stepData`. The parent is responsible for\n\t\t * calling `setResult()` to flip into the result phase.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync submit() {\n\t\t\tconst ok = await this.runValidation()\n\t\t\tif (!ok) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\t/**\n\t\t\t * @event submit Emitted when the user reaches the final\n\t\t\t * step and clicks Submit (or `submit()` is called\n\t\t\t * programmatically from a slot scope). Payload is the\n\t\t\t * accumulated `stepData`.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('submit', { ...this.stepData })\n\t\t},\n\n\t\t/**\n\t\t * Public method called by the parent to switch the wizard\n\t\t * into the result phase.\n\t\t *\n\t\t * @param {object} result `{ success?, message?, error? }`.\n\t\t * @return {void}\n\t\t */\n\t\tsetResult(result) {\n\t\t\tthis.result = result || { success: true }\n\t\t\tthis.loading = false\n\t\t},\n\n\t\t/**\n\t\t * Public method for a NON-terminal submit failure: stops the\n\t\t * loading spinner and surfaces `message` as the error banner above\n\t\t * the (still-editable) current step, WITHOUT entering the result\n\t\t * phase — so the user can correct the input and resubmit. Use this\n\t\t * instead of `setResult({ error })` when the failure is recoverable\n\t\t * (e.g. a taken slug, a validation conflict).\n\t\t *\n\t\t * @param {string} message The error to show above the step body.\n\t\t * @return {void}\n\t\t */\n\t\tsetError(message) {\n\t\t\tthis.loading = false\n\t\t\tthis.validationError = message || ''\n\t\t},\n\n\t\t/**\n\t\t * Close-button handler. Resets local state so the next open\n\t\t * starts fresh and emits `@close`.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonClose() {\n\t\t\tthis.result = null\n\t\t\tthis.loading = false\n\t\t\tthis.validationError = ''\n\t\t\tthis.currentIndex = this.resolveInitialIndex()\n\t\t\tthis.stepData = { ...this.defaults }\n\t\t\tthis.dialogOpen = false\n\t\t\t/**\n\t\t\t * @event close Emitted when the dialog should close.\n\t\t\t */\n\t\t\tthis.$emit('close')\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-wizard-dialog__progress {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tjustify-content: center;\n\tgap: 0;\n\tpadding: 8px 0 4px;\n\tmargin: 0 0 20px;\n\tlist-style: none;\n}\n\n.cn-wizard-dialog__progress-item {\n\tposition: relative;\n\tflex: 1 1 0;\n\tmin-width: 0;\n\tmax-width: 180px;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 6px;\n\ttext-align: center;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8125rem;\n}\n\n.cn-wizard-dialog__progress-item--clickable {\n\tcursor: pointer;\n}\n\n/* Marker circle */\n.cn-wizard-dialog__progress-dot {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 34px;\n\theight: 34px;\n\tborder-radius: 50%;\n\tborder: 2px solid var(--color-border-dark);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n\tfont-weight: 600;\n\ttransition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;\n\tz-index: 1;\n}\n\n.cn-wizard-dialog__progress-check {\n\tfont-size: 16px;\n\tline-height: 1;\n}\n\n.cn-wizard-dialog__progress-label {\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n/* Connector line between this marker and the next, centered on the marker. */\n.cn-wizard-dialog__progress-connector {\n\tposition: absolute;\n\ttop: 17px;\n\tleft: calc(50% + 21px);\n\tright: calc(-50% + 21px);\n\theight: 2px;\n\tbackground: var(--color-border-dark);\n}\n\n.cn-wizard-dialog__progress-connector--done {\n\tbackground: var(--color-primary-element);\n}\n\n/* States */\n.cn-wizard-dialog__progress-item--active .cn-wizard-dialog__progress-dot {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n}\n\n.cn-wizard-dialog__progress-item--active .cn-wizard-dialog__progress-label {\n\tcolor: var(--color-main-text);\n\tfont-weight: 600;\n}\n\n.cn-wizard-dialog__progress-item--done .cn-wizard-dialog__progress-dot {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n}\n\n.cn-wizard-dialog__progress-item--done .cn-wizard-dialog__progress-label {\n\tcolor: var(--color-main-text);\n}\n\n.cn-wizard-dialog__progress-item--clickable:hover .cn-wizard-dialog__progress-label {\n\tcolor: var(--color-primary-element);\n}\n\n.cn-wizard-dialog__step-body {\n\tmin-height: 200px;\n}\n\n.cn-wizard-dialog__result {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n</style>\n"],"names":["_createBlock","_createElementBlock","_Fragment","_createVNode","_createTextVNode","_toDisplayString","_openBlock","_renderSlot","_createElementVNode","_renderList","_normalizeClass"],"mappings":"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"}
1
+ {"version":3,"file":"CnWizardDialog.vue3.js","sources":["../../../../src/components/CnWizardDialog/CnWizardDialog.vue?vue&type=template&id=27d51a62&lang.js"],"sourcesContent":["<template>\n\t<NcDialog\n\t\t:name=\"dialogTitle\"\n\t\tsize=\"large\"\n\t\t:open=\"dialogOpen\"\n\t\t:noClose=\"loading || !cancellable\"\n\t\tdata-testid=\"cn-modal\"\n\t\tdata-testid-modal=\"cn-wizard-dialog\"\n\t\t@update:open=\"dialogOpen = $event\"\n\t\t@closing=\"onClose\">\n\t\t<!-- Result phase: rendered after a successful `submit()`. -->\n\t\t<div v-if=\"result !== null\"\n\t\t\tclass=\"cn-wizard-dialog__result\"\n\t\t\tdata-testid-phase=\"result\">\n\t\t\t<NcNoteCard v-if=\"result.success\" type=\"success\">\n\t\t\t\t{{ result.message || successText }}\n\t\t\t</NcNoteCard>\n\t\t\t<NcNoteCard v-else-if=\"result.error\" type=\"error\">\n\t\t\t\t{{ result.error }}\n\t\t\t</NcNoteCard>\n\t\t\t<!-- @slot result-extra Optional extra content (e.g. batch\n\t\t\t progress poller, summary table) rendered below the\n\t\t\t success/error banner. Scope: { result }. -->\n\t\t\t<slot name=\"result-extra\" :result=\"result\" />\n\t\t</div>\n\n\t\t<!-- Wizard phase: progress bar + current step body. -->\n\t\t<div v-else\n\t\t\tclass=\"cn-wizard-dialog__wizard\"\n\t\t\tdata-testid-phase=\"form\">\n\t\t\t<!-- Progress indicator. Click jumps back to a completed\n\t\t\t step when allowJumpBack is true. -->\n\t\t\t<ol class=\"cn-wizard-dialog__progress\" role=\"tablist\">\n\t\t\t\t<li v-for=\"(step, idx) in steps\"\n\t\t\t\t\t:key=\"step.id\"\n\t\t\t\t\tclass=\"cn-wizard-dialog__progress-item\"\n\t\t\t\t\t:class=\"{\n\t\t\t\t\t\t'cn-wizard-dialog__progress-item--active': idx === currentIndex,\n\t\t\t\t\t\t'cn-wizard-dialog__progress-item--done': idx < currentIndex,\n\t\t\t\t\t\t'cn-wizard-dialog__progress-item--clickable': allowJumpBack && idx < currentIndex,\n\t\t\t\t\t}\"\n\t\t\t\t\trole=\"tab\"\n\t\t\t\t\t:aria-selected=\"idx === currentIndex\"\n\t\t\t\t\t@click=\"allowJumpBack && idx < currentIndex ? jumpTo(step.id) : null\">\n\t\t\t\t\t<span class=\"cn-wizard-dialog__progress-dot\">\n\t\t\t\t\t\t<span v-if=\"idx < currentIndex\" class=\"cn-wizard-dialog__progress-check\" aria-hidden=\"true\">✓</span>\n\t\t\t\t\t\t<span v-else>{{ idx + 1 }}</span>\n\t\t\t\t\t</span>\n\t\t\t\t\t<span class=\"cn-wizard-dialog__progress-label\">{{ step.label }}</span>\n\t\t\t\t\t<span v-if=\"idx < steps.length - 1\"\n\t\t\t\t\t\tclass=\"cn-wizard-dialog__progress-connector\"\n\t\t\t\t\t\t:class=\"{ 'cn-wizard-dialog__progress-connector--done': idx < currentIndex }\"\n\t\t\t\t\t\taria-hidden=\"true\" />\n\t\t\t\t</li>\n\t\t\t</ol>\n\n\t\t\t<!-- Validation error from the current step's validate() result. -->\n\t\t\t<NcNoteCard v-if=\"validationError\" type=\"error\">\n\t\t\t\t{{ validationError }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<!-- Current step body — a dynamic slot named by step id. -->\n\t\t\t<div class=\"cn-wizard-dialog__step-body\" :data-step-id=\"currentStep.id\">\n\t\t\t\t<!-- @slot step-{id} Per-step body. Scope: { next,\n\t\t\t\t back, jumpTo, submit, currentStep, stepIndex,\n\t\t\t\t totalSteps, stepData, setStepData, isFirst,\n\t\t\t\t isLast }. -->\n\t\t\t\t<slot\n\t\t\t\t\t:name=\"'step-' + currentStep.id\"\n\t\t\t\t\t:next=\"next\"\n\t\t\t\t\t:back=\"back\"\n\t\t\t\t\t:jumpTo=\"jumpTo\"\n\t\t\t\t\t:submit=\"submit\"\n\t\t\t\t\t:currentStep=\"currentStep\"\n\t\t\t\t\t:stepIndex=\"currentIndex\"\n\t\t\t\t\t:totalSteps=\"steps.length\"\n\t\t\t\t\t:stepData=\"stepData\"\n\t\t\t\t\t:setStepData=\"setStepData\"\n\t\t\t\t\t:isFirst=\"isFirst\"\n\t\t\t\t\t:isLast=\"isLast\" />\n\t\t\t</div>\n\t\t</div>\n\n\t\t<template #actions>\n\t\t\t<!-- Result phase actions: just Close. -->\n\t\t\t<NcButton v-if=\"result !== null\" @click=\"onClose\">\n\t\t\t\t{{ closeLabel }}\n\t\t\t</NcButton>\n\n\t\t\t<!-- Wizard phase actions: Cancel + Back + Next/Submit.\n\t\t\t The testids exist because these buttons are the only way\n\t\t\t through a wizard and their LABELS are translated: an e2e that\n\t\t\t clicks \"Next\" passes on an English instance and times out on a\n\t\t\t Dutch one, reporting a broken dialog rather than a missing\n\t\t\t string. -->\n\t\t\t<template v-else>\n\t\t\t\t<NcButton v-if=\"cancellable\" data-testid=\"cn-wizard-cancel\" @click=\"onClose\">\n\t\t\t\t\t{{ cancelLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton v-if=\"!isFirst\"\n\t\t\t\t\tdata-testid=\"cn-wizard-back\"\n\t\t\t\t\t:disabled=\"loading\"\n\t\t\t\t\t@click=\"back\">\n\t\t\t\t\t{{ backLabel }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton variant=\"primary\"\n\t\t\t\t\tdata-testid=\"cn-wizard-next\"\n\t\t\t\t\t:disabled=\"loading || nextDisabled\"\n\t\t\t\t\t@click=\"isLast ? submit() : next()\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<NcLoadingIcon v-if=\"loading\" :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ isLast ? submitLabel : nextLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</template>\n\t\t</template>\n\t</NcDialog>\n</template>\n\n<script>\nimport { NcButton, NcDialog, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\n\n/**\n * CnWizardDialog — Multi-step modal with per-step slots,\n * back/next navigation, optional per-step validation, and a single\n * result phase.\n *\n * Each step is declared in the `steps[]` prop (`{ id, label,\n * optional?, icon? }`) and rendered via a `#step-{id}` named slot\n * with the navigation API exposed through the slot scope. The\n * wizard owns the progress indicator, the back/next chrome, the\n * loading lifecycle, and the result-phase banner — the consumer\n * owns the per-step body + the data collection logic.\n *\n * Per-step validation is opt-in via the `validate` prop:\n *\n * ```js\n * async function validate(stepId, stepData) {\n * if (stepId === 'audience' && !stepData.cohort) {\n * return 'Pick a cohort first.'\n * }\n * return true\n * }\n * ```\n *\n * Returning `true` (or any truthy non-string) advances; returning a\n * string surfaces it as an error banner above the step body and\n * blocks the navigation.\n *\n * The result phase is entered by the consumer calling\n * `setResult({ success?, message?, error? })` after `@submit` fires.\n *\n * Use [`CnFormDialog`](../CnFormDialog/) for single-step forms.\n * Use [`CnExportWizard`](../CnExportWizard/) for the specific\n * scope+format+delivery export-trigger shape.\n *\n * ```vue\n * <CnWizardDialog\n * ref=\"wizard\"\n * dialog-title=\"Bulk enrol\"\n * :steps=\"[\n * { id: 'audience', label: 'Audience' },\n * { id: 'course', label: 'Course' },\n * { id: 'confirm', label: 'Confirm' },\n * ]\"\n * :validate=\"validateStep\"\n * @submit=\"onSubmit\"\n * @close=\"show = false\">\n * <template #step-audience=\"{ next, stepData, setStepData }\">\n * <CohortPicker :value=\"stepData.cohort\" @input=\"v => setStepData({ cohort: v })\" />\n * </template>\n * <template #step-course=\"{ next, back, stepData, setStepData }\">\n * <CoursePicker :value=\"stepData.course\" @input=\"v => setStepData({ course: v })\" />\n * </template>\n * <template #step-confirm=\"{ back, submit, stepData }\">\n * <p>Enrol {{ stepData.audience.cohort.size }} learners into {{ stepData.course.course.name }}?</p>\n * </template>\n * </CnWizardDialog>\n * ```\n */\nexport default {\n\tname: 'CnWizardDialog',\n\tcomponents: { NcDialog, NcButton, NcNoteCard, NcLoadingIcon },\n\tprops: {\n\t\t/**\n\t\t * Step declarations. Order is significant — the wizard\n\t\t * navigates linearly between adjacent steps unless the\n\t\t * consumer calls `jumpTo()` from a slot scope.\n\t\t *\n\t\t * @type {Array<{ id: string, label: string, optional?: boolean, icon?: string }>}\n\t\t */\n\t\tsteps: {\n\t\t\ttype: Array,\n\t\t\trequired: true,\n\t\t\tvalidator: (v) => Array.isArray(v) && v.length > 0\n\t\t\t\t&& v.every((s) => s && typeof s.id === 'string' && typeof s.label === 'string'),\n\t\t},\n\n\t\t/**\n\t\t * Dialog title shown in the NcDialog header.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tdialogTitle: {\n\t\t\ttype: String,\n\t\t\tdefault: 'Wizard',\n\t\t},\n\n\t\t/**\n\t\t * Step id to start on. Defaults to the first step in `steps[]`.\n\t\t *\n\t\t * @type {string}\n\t\t */\n\t\tinitialStep: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Per-step validator. Async function returning `true` to\n\t\t * advance, a string to surface as an error banner + block\n\t\t * navigation, or `false` to block silently. Called from\n\t\t * `next()` and `submit()`. When omitted, navigation always\n\t\t * proceeds.\n\t\t *\n\t\t * @type {(stepId: string, stepData: object) => Promise<boolean|string>}\n\t\t */\n\t\tvalidate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Whether the progress indicator allows jumping back to a\n\t\t * completed step via click. Forward jumps are NEVER allowed\n\t\t * via the progress indicator (consumer-side `jumpTo` is\n\t\t * unrestricted).\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tallowJumpBack: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Seed values for `stepData` (the cross-step object passed\n\t\t * to every slot scope). Useful for prefilling on edit-style\n\t\t * wizards.\n\t\t *\n\t\t * @type {object}\n\t\t */\n\t\tdefaults: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/** Cancel-button label (wizard phase). */\n\t\tcancelLabel: { type: String, default: 'Cancel' },\n\t\t/** Back-button label. */\n\t\tbackLabel: { type: String, default: 'Back' },\n\t\t/** Next-button label (intermediate steps). */\n\t\tnextLabel: { type: String, default: 'Next' },\n\t\t/** Submit-button label (last step). */\n\t\tsubmitLabel: { type: String, default: 'Submit' },\n\t\t/** Close-button label (result phase). */\n\t\tcloseLabel: { type: String, default: 'Close' },\n\t\t/** Success banner default text when result.message is empty. */\n\t\tsuccessText: { type: String, default: 'Done.' },\n\t\t/**\n\t\t * Whether the dialog can be dismissed (Cancel button, ESC, backdrop\n\t\t * click) before reaching the result phase. `false` hides Cancel and\n\t\t * disables all other close affordances — use for a step that must\n\t\t * be completed, not merely skipped. The result-phase Close button is\n\t\t * unaffected (a completed wizard is always dismissible).\n\t\t *\n\t\t * @type {boolean}\n\t\t */\n\t\tcancellable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true,\n\t\t},\n\n\t\t/**\n\t\t * Disable the Next / Submit button, whatever the step's `validate`\n\t\t * would say. For a host that knows the wizard cannot go on yet, such as\n\t\t * `CnSetupWizard` while a required app is missing.\n\t\t *\n\t\t * @type {boolean}\n\t\t * @spec openspec/changes/setup-wizard-card-load-and-dependency-gate/specs/cn-setup-wizard/spec.md#requirement-the-wizard-checks-dependencies-before-any-step\n\t\t */\n\t\tnextDisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: false,\n\t\t},\n\t},\n\n\temits: ['close', 'step-change', 'submit'],\n\tdata() {\n\t\tconst initialIndex = this.resolveInitialIndex()\n\t\treturn {\n\t\t\tcurrentIndex: initialIndex,\n\t\t\tstepData: { ...this.defaults },\n\t\t\tloading: false,\n\t\t\tresult: null,\n\t\t\tvalidationError: '',\n\t\t\t/**\n\t\t\t * Drives NcDialog's `open` prop directly. NcDialog's own `open`\n\t\t\t * prop defaults to `true` and is NOT self-managing on close — its\n\t\t\t * internal `showModal` flag resets back to `true` ~300ms after\n\t\t\t * any close (animation bookkeeping for next time it's reopened\n\t\t\t * via the prop), so without an explicit `false` here the dialog\n\t\t\t * reopens itself right after Cancel/ESC/backdrop-click.\n\t\t\t */\n\t\t\tdialogOpen: true,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The currently-active step object from `steps[]`.\n\t\t *\n\t\t * @return {object} The current step definition.\n\t\t */\n\t\tcurrentStep() {\n\t\t\treturn this.steps[this.currentIndex] || this.steps[0]\n\t\t},\n\n\t\t/**\n\t\t * Whether the current step is the first one.\n\t\t *\n\t\t * @return {boolean} True when on the first step.\n\t\t */\n\t\tisFirst() {\n\t\t\treturn this.currentIndex === 0\n\t\t},\n\n\t\t/**\n\t\t * Whether the current step is the last one (drives the\n\t\t * Next → Submit label flip).\n\t\t *\n\t\t * @return {boolean} True when on the last step.\n\t\t */\n\t\tisLast() {\n\t\t\treturn this.currentIndex === this.steps.length - 1\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Resolve the index of `initialStep` (falling back to 0 when\n\t\t * unset / not found).\n\t\t *\n\t\t * @return {number} Step index in the range [0, steps.length).\n\t\t */\n\t\tresolveInitialIndex() {\n\t\t\tif (!this.initialStep) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\tconst idx = this.steps.findIndex((s) => s.id === this.initialStep)\n\t\t\treturn idx >= 0 ? idx : 0\n\t\t},\n\n\t\t/**\n\t\t * Merge a partial step-data object onto the shared state.\n\t\t * Each slot scope receives this so consumers can write their\n\t\t * step's data without owning a `data()` block in their slot\n\t\t * component.\n\t\t *\n\t\t * @param {object} partial Fields to merge.\n\t\t * @return {void}\n\t\t */\n\t\tsetStepData(partial) {\n\t\t\tthis.stepData = { ...this.stepData, ...partial }\n\t\t},\n\n\t\t/**\n\t\t * Run the consumer's `validate` prop (if provided) for the\n\t\t * current step. Returns true on pass; sets `validationError`\n\t\t * and returns false on fail.\n\t\t *\n\t\t * @return {Promise<boolean>} Whether validation passed.\n\t\t */\n\t\tasync runValidation() {\n\t\t\tthis.validationError = ''\n\t\t\tif (typeof this.validate !== 'function') {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst outcome = await this.validate(this.currentStep.id, this.stepData)\n\t\t\t\tif (outcome === true) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\tif (typeof outcome === 'string') {\n\t\t\t\t\tthis.validationError = outcome\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn Boolean(outcome)\n\t\t\t} catch (e) {\n\t\t\t\tthis.validationError = e && e.message ? e.message : 'Validation failed.'\n\t\t\t\treturn false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Advance to the next step after validation. Emits\n\t\t * `@step-change` with the new step id.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync next() {\n\t\t\tif (this.nextDisabled) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.isLast) {\n\t\t\t\treturn this.submit()\n\t\t\t}\n\t\t\tconst ok = await this.runValidation()\n\t\t\tif (!ok) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.currentIndex += 1\n\t\t\t/**\n\t\t\t * @event step-change Emitted on every step navigation\n\t\t\t * (next / back / jumpTo / submit).\n\t\t\t * @type {{ stepId: string, stepIndex: number, direction: 'next'|'back'|'jump' }}\n\t\t\t */\n\t\t\tthis.$emit('step-change', {\n\t\t\t\tstepId: this.currentStep.id,\n\t\t\t\tstepIndex: this.currentIndex,\n\t\t\t\tdirection: 'next',\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Step back to the previous step. No validation runs on\n\t\t * back navigation — consumers can reach back without\n\t\t * losing the current step's draft.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tback() {\n\t\t\tif (this.isFirst) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.validationError = ''\n\t\t\tthis.currentIndex -= 1\n\t\t\tthis.$emit('step-change', {\n\t\t\t\tstepId: this.currentStep.id,\n\t\t\t\tstepIndex: this.currentIndex,\n\t\t\t\tdirection: 'back',\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Jump to an arbitrary step by id. Validation is skipped\n\t\t * (the consumer is responsible for re-running it before\n\t\t * `submit()` if it matters).\n\t\t *\n\t\t * @param {string} stepId Target step id.\n\t\t * @return {void}\n\t\t */\n\t\tjumpTo(stepId) {\n\t\t\tconst idx = this.steps.findIndex((s) => s.id === stepId)\n\t\t\tif (idx < 0) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.validationError = ''\n\t\t\tthis.currentIndex = idx\n\t\t\tthis.$emit('step-change', {\n\t\t\t\tstepId: this.currentStep.id,\n\t\t\t\tstepIndex: this.currentIndex,\n\t\t\t\tdirection: 'jump',\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Submit the wizard. Runs the current step's validation;\n\t\t * on pass flips `loading` on and emits `@submit` with the\n\t\t * accumulated `stepData`. The parent is responsible for\n\t\t * calling `setResult()` to flip into the result phase.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync submit() {\n\t\t\tif (this.nextDisabled) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst ok = await this.runValidation()\n\t\t\tif (!ok) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\t/**\n\t\t\t * @event submit Emitted when the user reaches the final\n\t\t\t * step and clicks Submit (or `submit()` is called\n\t\t\t * programmatically from a slot scope). Payload is the\n\t\t\t * accumulated `stepData`.\n\t\t\t * @type {object}\n\t\t\t */\n\t\t\tthis.$emit('submit', { ...this.stepData })\n\t\t},\n\n\t\t/**\n\t\t * Public method called by the parent to switch the wizard\n\t\t * into the result phase.\n\t\t *\n\t\t * @param {object} result `{ success?, message?, error? }`.\n\t\t * @return {void}\n\t\t */\n\t\tsetResult(result) {\n\t\t\tthis.result = result || { success: true }\n\t\t\tthis.loading = false\n\t\t},\n\n\t\t/**\n\t\t * Public method for a NON-terminal submit failure: stops the\n\t\t * loading spinner and surfaces `message` as the error banner above\n\t\t * the (still-editable) current step, WITHOUT entering the result\n\t\t * phase — so the user can correct the input and resubmit. Use this\n\t\t * instead of `setResult({ error })` when the failure is recoverable\n\t\t * (e.g. a taken slug, a validation conflict).\n\t\t *\n\t\t * @param {string} message The error to show above the step body.\n\t\t * @return {void}\n\t\t */\n\t\tsetError(message) {\n\t\t\tthis.loading = false\n\t\t\tthis.validationError = message || ''\n\t\t},\n\n\t\t/**\n\t\t * Close-button handler. Resets local state so the next open\n\t\t * starts fresh and emits `@close`.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonClose() {\n\t\t\tthis.result = null\n\t\t\tthis.loading = false\n\t\t\tthis.validationError = ''\n\t\t\tthis.currentIndex = this.resolveInitialIndex()\n\t\t\tthis.stepData = { ...this.defaults }\n\t\t\tthis.dialogOpen = false\n\t\t\t/**\n\t\t\t * @event close Emitted when the dialog should close.\n\t\t\t */\n\t\t\tthis.$emit('close')\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-wizard-dialog__progress {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tjustify-content: center;\n\tgap: 0;\n\tpadding: 8px 0 4px;\n\tmargin: 0 0 20px;\n\tlist-style: none;\n}\n\n.cn-wizard-dialog__progress-item {\n\tposition: relative;\n\tflex: 1 1 0;\n\tmin-width: 0;\n\tmax-width: 180px;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 6px;\n\ttext-align: center;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 0.8125rem;\n}\n\n.cn-wizard-dialog__progress-item--clickable {\n\tcursor: pointer;\n}\n\n/* Marker circle */\n.cn-wizard-dialog__progress-dot {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 34px;\n\theight: 34px;\n\tborder-radius: 50%;\n\tborder: 2px solid var(--color-border-dark);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 14px;\n\tfont-weight: 600;\n\ttransition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;\n\tz-index: 1;\n}\n\n.cn-wizard-dialog__progress-check {\n\tfont-size: 16px;\n\tline-height: 1;\n}\n\n.cn-wizard-dialog__progress-label {\n\tmax-width: 100%;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n/* Connector line between this marker and the next, centered on the marker. */\n.cn-wizard-dialog__progress-connector {\n\tposition: absolute;\n\ttop: 17px;\n\tleft: calc(50% + 21px);\n\tright: calc(-50% + 21px);\n\theight: 2px;\n\tbackground: var(--color-border-dark);\n}\n\n.cn-wizard-dialog__progress-connector--done {\n\tbackground: var(--color-primary-element);\n}\n\n/* States */\n.cn-wizard-dialog__progress-item--active .cn-wizard-dialog__progress-dot {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n}\n\n.cn-wizard-dialog__progress-item--active .cn-wizard-dialog__progress-label {\n\tcolor: var(--color-main-text);\n\tfont-weight: 600;\n}\n\n.cn-wizard-dialog__progress-item--done .cn-wizard-dialog__progress-dot {\n\tborder-color: var(--color-primary-element);\n\tbackground: var(--color-primary-element);\n\tcolor: var(--color-primary-element-text, #fff);\n}\n\n.cn-wizard-dialog__progress-item--done .cn-wizard-dialog__progress-label {\n\tcolor: var(--color-main-text);\n}\n\n.cn-wizard-dialog__progress-item--clickable:hover .cn-wizard-dialog__progress-label {\n\tcolor: var(--color-primary-element);\n}\n\n.cn-wizard-dialog__step-body {\n\tmin-height: 200px;\n}\n\n.cn-wizard-dialog__result {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 12px;\n}\n</style>\n"],"names":["_createBlock","_createElementBlock","_Fragment","_createVNode","_createTextVNode","_toDisplayString","_openBlock","_renderSlot","_createElementVNode","_renderList","_normalizeClass"],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ import { useAppStatus } from './useAppStatus.js';
2
+
3
+ /**
4
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
5
+ * SPDX-License-Identifier: EUPL-1.2
6
+ */
7
+
8
+
9
+ /**
10
+ * Resolve `manifest.dependencies` entries to their live status.
11
+ *
12
+ * Accepts the manifest's own shape: an app-id string (a REQUIRED dependency)
13
+ * or `{ id, name?, required? }` where `required` defaults to true. Each entry
14
+ * becomes `{ id, name, required, installed, enabled }`.
15
+ *
16
+ * `serverStatuses` is the optional `dependency_statuses` initial state an app
17
+ * injects from PHP, keyed by app id: `{ installed, enabled }`. It wins over
18
+ * the browser heuristic, because only the server can tell "installed but
19
+ * disabled" from "not installed". Without it, `useAppStatus` answers, which
20
+ * is the same resolution `CnAppRoot` uses for its dependency-missing phase.
21
+ *
22
+ * @param {Array<string|{id: string, name?: string, required?: boolean}>} entries The declared dependencies.
23
+ * @param {object} [serverStatuses] Server-reported statuses keyed by app id.
24
+ * @return {Array<{id: string, name: string, required: boolean, installed: boolean, enabled: boolean}>} One row per valid entry.
25
+ *
26
+ * @spec openspec/changes/setup-wizard-card-load-and-dependency-gate/specs/cn-setup-wizard/spec.md#requirement-the-wizard-checks-dependencies-before-any-step
27
+ *
28
+ * @example
29
+ * const missing = checkDependencies(manifest.dependencies)
30
+ * .filter((d) => d.required && !(d.installed && d.enabled))
31
+ */
32
+ function checkDependencies(entries, serverStatuses = {}) {
33
+ const list = Array.isArray(entries) ? entries : [];
34
+ const server = (serverStatuses && typeof serverStatuses === 'object') ? serverStatuses : {};
35
+ return list
36
+ .map((entry) => {
37
+ const isObject = entry !== null && typeof entry === 'object';
38
+ const id = isObject ? entry.id : entry;
39
+ if (typeof id !== 'string' || id === '') {
40
+ return null
41
+ }
42
+ const required = isObject ? entry.required !== false : true;
43
+ const name = (isObject && entry.name) || id;
44
+ const reported = server[id];
45
+ if (reported !== undefined && reported !== null) {
46
+ return { id, name, required, installed: !!reported.installed, enabled: !!reported.enabled }
47
+ }
48
+ const status = useAppStatus(id);
49
+ return { id, name, required, installed: !!status.installed.value, enabled: !!status.enabled.value }
50
+ })
51
+ .filter((row) => row !== null)
52
+ }
53
+
54
+ /**
55
+ * Whether a resolved dependency row is usable: installed AND enabled.
56
+ *
57
+ * @param {{installed: boolean, enabled: boolean}} row A row from `checkDependencies`.
58
+ * @return {boolean} True when the app can be used.
59
+ */
60
+ function isDependencyResolved(row) {
61
+ return !!row && row.installed === true && row.enabled === true
62
+ }
63
+
64
+ export { checkDependencies, isDependencyResolved };
65
+ //# sourceMappingURL=useDependencyCheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDependencyCheck.js","sources":["../../../src/composables/useDependencyCheck.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n */\n\nimport { useAppStatus } from './useAppStatus.js'\n\n/**\n * Resolve `manifest.dependencies` entries to their live status.\n *\n * Accepts the manifest's own shape: an app-id string (a REQUIRED dependency)\n * or `{ id, name?, required? }` where `required` defaults to true. Each entry\n * becomes `{ id, name, required, installed, enabled }`.\n *\n * `serverStatuses` is the optional `dependency_statuses` initial state an app\n * injects from PHP, keyed by app id: `{ installed, enabled }`. It wins over\n * the browser heuristic, because only the server can tell \"installed but\n * disabled\" from \"not installed\". Without it, `useAppStatus` answers, which\n * is the same resolution `CnAppRoot` uses for its dependency-missing phase.\n *\n * @param {Array<string|{id: string, name?: string, required?: boolean}>} entries The declared dependencies.\n * @param {object} [serverStatuses] Server-reported statuses keyed by app id.\n * @return {Array<{id: string, name: string, required: boolean, installed: boolean, enabled: boolean}>} One row per valid entry.\n *\n * @spec openspec/changes/setup-wizard-card-load-and-dependency-gate/specs/cn-setup-wizard/spec.md#requirement-the-wizard-checks-dependencies-before-any-step\n *\n * @example\n * const missing = checkDependencies(manifest.dependencies)\n * .filter((d) => d.required && !(d.installed && d.enabled))\n */\nexport function checkDependencies(entries, serverStatuses = {}) {\n\tconst list = Array.isArray(entries) ? entries : []\n\tconst server = (serverStatuses && typeof serverStatuses === 'object') ? serverStatuses : {}\n\treturn list\n\t\t.map((entry) => {\n\t\t\tconst isObject = entry !== null && typeof entry === 'object'\n\t\t\tconst id = isObject ? entry.id : entry\n\t\t\tif (typeof id !== 'string' || id === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst required = isObject ? entry.required !== false : true\n\t\t\tconst name = (isObject && entry.name) || id\n\t\t\tconst reported = server[id]\n\t\t\tif (reported !== undefined && reported !== null) {\n\t\t\t\treturn { id, name, required, installed: !!reported.installed, enabled: !!reported.enabled }\n\t\t\t}\n\t\t\tconst status = useAppStatus(id)\n\t\t\treturn { id, name, required, installed: !!status.installed.value, enabled: !!status.enabled.value }\n\t\t})\n\t\t.filter((row) => row !== null)\n}\n\n/**\n * Whether a resolved dependency row is usable: installed AND enabled.\n *\n * @param {{installed: boolean, enabled: boolean}} row A row from `checkDependencies`.\n * @return {boolean} True when the app can be used.\n */\nexport function isDependencyResolved(row) {\n\treturn !!row && row.installed === true && row.enabled === true\n}\n"],"names":[],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,iBAAiB,CAAC,OAAO,EAAE,cAAc,GAAG,EAAE,EAAE;AAChE,CAAC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,GAAG,GAAE;AACnD,CAAC,MAAM,MAAM,GAAG,CAAC,cAAc,IAAI,OAAO,cAAc,KAAK,QAAQ,IAAI,cAAc,GAAG,GAAE;AAC5F,CAAC,OAAO,IAAI;AACZ,GAAG,GAAG,CAAC,CAAC,KAAK,KAAK;AAClB,GAAG,MAAM,QAAQ,GAAG,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,SAAQ;AAC/D,GAAG,MAAM,EAAE,GAAG,QAAQ,GAAG,KAAK,CAAC,EAAE,GAAG,MAAK;AACzC,GAAG,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,KAAK,EAAE,EAAE;AAC5C,IAAI,OAAO,IAAI;AACf,IAAI;AACJ,GAAG,MAAM,QAAQ,GAAG,QAAQ,GAAG,KAAK,CAAC,QAAQ,KAAK,KAAK,GAAG,KAAI;AAC9D,GAAG,MAAM,IAAI,GAAG,CAAC,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,GAAE;AAC9C,GAAG,MAAM,QAAQ,GAAG,MAAM,CAAC,EAAE,EAAC;AAC9B,GAAG,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,IAAI,EAAE;AACpD,IAAI,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC,QAAQ,CAAC,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE;AAC/F,IAAI;AACJ,GAAG,MAAM,MAAM,GAAG,YAAY,CAAC,EAAE,EAAC;AAClC,GAAG,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE;AACtG,GAAG,CAAC;AACJ,GAAG,MAAM,CAAC,CAAC,GAAG,KAAK,GAAG,KAAK,IAAI,CAAC;AAChC,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,oBAAoB,CAAC,GAAG,EAAE;AAC1C,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,SAAS,KAAK,IAAI,IAAI,GAAG,CAAC,OAAO,KAAK,IAAI;AAC/D;;;;"}