@conduction/nextcloud-vue 2.62.0 → 2.64.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 (209) 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 +425 -39
  8. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  9. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +119 -42
  10. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  11. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +186 -9
  12. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  13. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +65 -42
  14. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  15. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +61 -16
  16. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
  17. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
  18. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js +20 -0
  19. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue2.js.map +1 -1
  20. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js +3 -2
  21. package/dist/esm/components/CnAuditTrailWidget/CnAuditTrailWidget.vue3.js.map +1 -1
  22. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +33 -2
  23. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  24. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  25. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  26. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js +53 -37
  27. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  28. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +29 -0
  29. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  30. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +152 -135
  31. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  32. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js +10 -0
  33. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue.js.map +1 -0
  34. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js +293 -0
  35. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue2.js.map +1 -0
  36. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js +189 -0
  37. package/dist/esm/components/CnDataTable/CnColumnFilterPopover.vue3.js.map +1 -0
  38. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +253 -9
  39. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  40. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +126 -50
  41. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  42. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +66 -14
  43. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  44. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +23 -14
  45. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  46. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  47. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +2 -1
  48. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  49. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js +3 -1
  50. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue2.js.map +1 -1
  51. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js +1 -1
  52. package/dist/esm/components/CnFieldHelper/CnFieldHelper.vue3.js.map +1 -1
  53. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +234 -9
  54. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  55. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +96 -57
  56. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  57. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +81 -4
  58. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
  59. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +22 -5
  60. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
  61. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +81 -2
  62. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  63. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +7 -3
  64. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  65. package/dist/esm/components/CnIndexPage/selfModeActions.js +3 -3
  66. package/dist/esm/components/CnIndexPage/selfModeActions.js.map +1 -1
  67. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +41 -2
  68. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  69. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +8 -3
  70. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  71. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js +121 -12
  72. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue2.js.map +1 -1
  73. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js +3 -1
  74. package/dist/esm/components/CnResourceSelect/CnResourceSelect.vue3.js.map +1 -1
  75. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js +326 -1
  76. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  77. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +112 -26
  78. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  79. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +63 -2
  80. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
  81. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js +96 -37
  82. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
  83. package/dist/esm/components/CnStagesWidget/stagesModel.js +5 -2
  84. package/dist/esm/components/CnStagesWidget/stagesModel.js.map +1 -1
  85. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +52 -2
  86. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  87. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +6 -6
  88. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  89. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +14 -2
  90. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  91. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +2 -1
  92. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  93. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js +10 -0
  94. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue.js.map +1 -0
  95. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js +337 -0
  96. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue2.js.map +1 -0
  97. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js +102 -0
  98. package/dist/esm/components/CnTimelineWidget/CnTimelineWidget.vue3.js.map +1 -0
  99. package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js +34 -0
  100. package/dist/esm/components/CnTimelineWidget/dashboardRegistration.js.map +1 -0
  101. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +131 -8
  102. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
  103. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js +40 -38
  104. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
  105. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +8 -6
  106. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
  107. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  108. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  109. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +73 -1
  110. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
  111. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +14 -2
  112. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
  113. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js +19 -0
  114. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue2.js.map +1 -1
  115. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js +1 -1
  116. package/dist/esm/components/CnWizardDialog/CnWizardDialog.vue3.js.map +1 -1
  117. package/dist/esm/composables/useDependencyCheck.js +65 -0
  118. package/dist/esm/composables/useDependencyCheck.js.map +1 -0
  119. package/dist/esm/composables/useWalkthrough.js +218 -6
  120. package/dist/esm/composables/useWalkthrough.js.map +1 -1
  121. package/dist/esm/index.js +2 -0
  122. package/dist/esm/index.js.map +1 -1
  123. package/dist/esm/l10n/en.json.js +45 -1
  124. package/dist/esm/l10n/en.json.js.map +1 -1
  125. package/dist/esm/l10n/nl.json.js +45 -1
  126. package/dist/esm/l10n/nl.json.js.map +1 -1
  127. package/dist/esm/nextcloud-vue.css +780 -81
  128. package/dist/esm/store/useObjectStore.js +8 -0
  129. package/dist/esm/store/useObjectStore.js.map +1 -1
  130. package/dist/esm/utils/columnFilters.js +311 -0
  131. package/dist/esm/utils/columnFilters.js.map +1 -0
  132. package/dist/esm/utils/groupAutocomplete.js +123 -0
  133. package/dist/esm/utils/groupAutocomplete.js.map +1 -0
  134. package/dist/esm/utils/libraryWidgetKeys.js +2 -0
  135. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  136. package/dist/esm/utils/pickerOptions.js +198 -0
  137. package/dist/esm/utils/pickerOptions.js.map +1 -0
  138. package/dist/esm/utils/schema.js +83 -7
  139. package/dist/esm/utils/schema.js.map +1 -1
  140. package/dist/esm/utils/schemaRefSlug.js +19 -3
  141. package/dist/esm/utils/schemaRefSlug.js.map +1 -1
  142. package/dist/esm/utils/timelineEvents.js +174 -0
  143. package/dist/esm/utils/timelineEvents.js.map +1 -0
  144. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  145. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  146. package/dist/esm/utils/walkthroughSignals.js +88 -0
  147. package/dist/esm/utils/walkthroughSignals.js.map +1 -0
  148. package/dist/nextcloud-vue.cjs.js +13088 -7937
  149. package/dist/nextcloud-vue.cjs.js.map +2 -2
  150. package/dist/nextcloud-vue.css +780 -81
  151. package/l10n/en.json +45 -0
  152. package/l10n/nl.json +45 -0
  153. package/package.json +1 -1
  154. package/src/components/CnAdminActionCard/CnAdminActionCard.vue +234 -0
  155. package/src/components/CnAdminActionCard/index.js +4 -0
  156. package/src/components/CnAppNav/CnAppNav.vue +568 -43
  157. package/src/components/CnAppRoot/CnAppRoot.md +4 -0
  158. package/src/components/CnAppRoot/CnAppRoot.vue +223 -16
  159. package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +61 -16
  160. package/src/components/CnAuditTrailWidget/CnAuditTrailWidget.vue +22 -1
  161. package/src/components/CnCellRenderer/CnCellRenderer.vue +33 -2
  162. package/src/components/CnChoiceCards/CnChoiceCards.vue +56 -31
  163. package/src/components/CnDashboardPage/CnDashboardPage.vue +45 -4
  164. package/src/components/CnDataTable/CnColumnFilterPopover.vue +524 -0
  165. package/src/components/CnDataTable/CnDataTable.md +3 -0
  166. package/src/components/CnDataTable/CnDataTable.vue +305 -5
  167. package/src/components/CnDetailPage/CnDetailPage.md +4 -0
  168. package/src/components/CnDetailPage/CnDetailPage.vue +60 -1
  169. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +2 -2
  170. package/src/components/CnFieldHelper/CnFieldHelper.vue +4 -2
  171. package/src/components/CnFormDialog/CnFormDialog.vue +290 -31
  172. package/src/components/CnHeaderWidget/CnHeaderWidget.vue +116 -6
  173. package/src/components/CnIndexPage/CnIndexPage.vue +87 -4
  174. package/src/components/CnIndexPage/selfModeActions.js +3 -3
  175. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +46 -2
  176. package/src/components/CnResourceSelect/CnResourceSelect.vue +123 -12
  177. package/src/components/CnSetupWizard/CnSetupWizard.vue +392 -3
  178. package/src/components/CnStagesWidget/CnStagesWidget.vue +180 -2
  179. package/src/components/CnStagesWidget/stagesModel.js +5 -2
  180. package/src/components/CnStatWidget/CnStatWidget.vue +57 -3
  181. package/src/components/CnTabsWidget/CnTabsWidget.vue +15 -2
  182. package/src/components/CnTimelineWidget/CnTimelineWidget.vue +477 -0
  183. package/src/components/CnTimelineWidget/dashboardRegistration.js +32 -0
  184. package/src/components/CnTimelineWidget/index.js +4 -0
  185. package/src/components/CnWalkthrough/CnWalkthrough.vue +143 -10
  186. package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
  187. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +1 -0
  188. package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +4 -0
  189. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +107 -1
  190. package/src/components/CnWizardDialog/CnWizardDialog.vue +20 -1
  191. package/src/components/index.js +2 -0
  192. package/src/composables/useDependencyCheck.js +61 -0
  193. package/src/composables/useWalkthrough.js +217 -5
  194. package/src/css/dashboard.css +49 -0
  195. package/src/css/detail-page.css +29 -4
  196. package/src/css/kpi-card.css +24 -1
  197. package/src/css/table.css +99 -6
  198. package/src/index.js +2 -0
  199. package/src/schemas/app-manifest-v2.schema.json +135 -3
  200. package/src/store/useObjectStore.js +8 -0
  201. package/src/utils/columnFilters.js +324 -0
  202. package/src/utils/groupAutocomplete.js +119 -0
  203. package/src/utils/libraryWidgetKeys.js +2 -0
  204. package/src/utils/pickerOptions.js +194 -0
  205. package/src/utils/schema.js +83 -7
  206. package/src/utils/schemaRefSlug.js +19 -3
  207. package/src/utils/timelineEvents.js +171 -0
  208. package/src/utils/validateManifestV2.compiled.js +2 -2
  209. package/src/utils/walkthroughSignals.js +94 -0
@@ -25,7 +25,12 @@
25
25
  the greyed-out area reaches the app, which is the point. A centered
26
26
  step spotlights nothing, so its backdrop stays interactive and
27
27
  dismisses the tour. -->
28
- <template v-if="isCentered || !rect">
28
+ <!-- Quiet: an app dialog is open, or the step's target belongs to
29
+ another page. No dim and no cutout, so the overlay never sits on
30
+ top of the dialog and never invites a click on the wrong page.
31
+ Only the docked coachmark stays. -->
32
+ <template v-if="quiet" />
33
+ <template v-else-if="isCentered || !rect">
29
34
  <div v-if="isCentered"
30
35
  class="cn-walkthrough__dim cn-walkthrough__dim--full"
31
36
  @click.self="onBackdrop" />
@@ -50,7 +55,7 @@
50
55
  <!-- Coachmark card -->
51
56
  <div ref="card"
52
57
  class="cn-walkthrough__card"
53
- :class="`cn-walkthrough__card--${cardPlacement}`"
58
+ :class="[`cn-walkthrough__card--${cardPlacement}`, { 'cn-walkthrough__card--docked': quiet }]"
54
59
  :style="cardStyle">
55
60
  <NcButton class="cn-walkthrough__close"
56
61
  variant="tertiary"
@@ -183,7 +188,7 @@ export default {
183
188
  translate: { type: Function, default: null },
184
189
  },
185
190
 
186
- emits: ['complete', 'dismiss', 'step-change', 'advance', 'handoff'],
191
+ emits: ['complete', 'dismiss', 'pause', 'progress', 'step-change', 'advance', 'handoff'],
187
192
 
188
193
  setup(props) {
189
194
  const wt = useWalkthrough(props.appId, props.manifest, {
@@ -200,6 +205,10 @@ export default {
200
205
  cardPos: { top: 0, left: 0 },
201
206
  cardPlacement: 'bottom',
202
207
  targetEl: null,
208
+ // An app dialog (NcModal / NcDialog) is open over the page.
209
+ appModalOpen: false,
210
+ // The step's target was found on another page than the current one.
211
+ offPage: false,
203
212
  }
204
213
  },
205
214
 
@@ -228,6 +237,18 @@ export default {
228
237
  return this.wt.isLast.value
229
238
  },
230
239
 
240
+ /**
241
+ * Whether the overlay steps back: no dim, no cutout, a docked card.
242
+ * True while an app dialog is open or the step's target is on
243
+ * another page.
244
+ *
245
+ * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
246
+ * @return {boolean}
247
+ */
248
+ quiet() {
249
+ return this.appModalOpen || this.offPage
250
+ },
251
+
231
252
  isCentered() {
232
253
  return this.step && this.step.placement === 'center'
233
254
  },
@@ -321,6 +342,9 @@ export default {
321
342
  },
322
343
 
323
344
  cardStyle() {
345
+ if (this.quiet) {
346
+ return {}
347
+ }
324
348
  if (this.isCentered || !this.rect) {
325
349
  return { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' }
326
350
  }
@@ -339,6 +363,18 @@ export default {
339
363
  * @type {{ stepId: string, index: number }}
340
364
  */
341
365
  this.$emit('step-change', { stepId: newStep.id, index: this.index })
366
+ /**
367
+ * @event progress Emitted on every step change so the host can remember where the user is.
368
+ * @type {{ tourId: string, stepId: string, index: number }}
369
+ */
370
+ this.$emit('progress', {
371
+ tourId: (this.wt.activeTour.value && this.wt.activeTour.value.id) || '',
372
+ stepId: newStep.id,
373
+ index: this.index,
374
+ })
375
+ // A new step finds its own page again.
376
+ this._homeRoute = null
377
+ this.offPage = false
342
378
  this.$nextTick(() => this.locateTarget())
343
379
  }
344
380
  },
@@ -377,6 +413,9 @@ export default {
377
413
  // A qualifying auto-start tour whose first-step page is NOT the current
378
414
  // route — held here until the user navigates to that page (see hookRouter).
379
415
  this._pendingAutoTour = null
416
+ // The route the active step's target was first found on (see locateTarget).
417
+ this._homeRoute = null
418
+ this._modalObs = null
380
419
  },
381
420
 
382
421
  mounted() {
@@ -386,12 +425,27 @@ export default {
386
425
  window.addEventListener('scroll', this._onScroll, true)
387
426
  window.addEventListener('resize', this._onScroll)
388
427
  this._onKey = (e) => {
389
- if (e.key === 'Escape') {
390
- this.onBackdrop()
428
+ if (e.key !== 'Escape' || !this.active) {
429
+ return
391
430
  }
431
+ // ESC while an app dialog is open belongs to that dialog. It used
432
+ // to dismiss the tour too, so closing a create form ended it.
433
+ if (this.isAppModalOpen()) {
434
+ return
435
+ }
436
+ this.onBackdrop()
392
437
  }
393
438
  window.addEventListener('keydown', this._onKey)
394
- this._onObjectCreated = (e) => this.wt.notify({ kind: 'object-created', object: (e && e.detail) || {} })
439
+ this._onObjectCreated = (e) => {
440
+ const d = (e && e.detail) || {}
441
+ this.wt.notify({
442
+ kind: 'object-created',
443
+ register: d.register,
444
+ schema: d.schema,
445
+ object: d.object && typeof d.object === 'object' ? d.object : d,
446
+ })
447
+ }
448
+ this.watchAppModals()
395
449
  window.addEventListener('cn-walkthrough:object-created', this._onObjectCreated)
396
450
  this.hookRouter()
397
451
  if (this.active) {
@@ -404,6 +458,10 @@ export default {
404
458
  window.removeEventListener('resize', this._onScroll)
405
459
  window.removeEventListener('keydown', this._onKey)
406
460
  window.removeEventListener('cn-walkthrough:object-created', this._onObjectCreated)
461
+ if (this._modalObs) {
462
+ this._modalObs.disconnect()
463
+ this._modalObs = null
464
+ }
407
465
  if (typeof this._routeUnhook === 'function') {
408
466
  this._routeUnhook()
409
467
  }
@@ -411,6 +469,51 @@ export default {
411
469
  },
412
470
 
413
471
  methods: {
472
+ /**
473
+ * Whether an app dialog (NcModal / NcDialog, or any other modal
474
+ * dialog that is not this overlay) is open.
475
+ *
476
+ * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
477
+ * @return {boolean}
478
+ */
479
+ isAppModalOpen() {
480
+ if (typeof document === 'undefined') {
481
+ return false
482
+ }
483
+ const own = this.$el
484
+ const nodes = document.querySelectorAll('.modal-mask, [role="dialog"][aria-modal="true"]')
485
+ for (const node of nodes) {
486
+ if (own && typeof own.contains === 'function' && (own === node || own.contains(node))) {
487
+ continue
488
+ }
489
+ return true
490
+ }
491
+ return false
492
+ },
493
+
494
+ /**
495
+ * Follow app dialogs opening and closing, so the overlay steps back
496
+ * while one is open and comes back after.
497
+ *
498
+ * @return {void}
499
+ */
500
+ watchAppModals() {
501
+ const update = () => {
502
+ const open = this.isAppModalOpen()
503
+ if (open !== this.appModalOpen) {
504
+ this.appModalOpen = open
505
+ if (!open && this.active) {
506
+ this.$nextTick(() => this.locateTarget())
507
+ }
508
+ }
509
+ }
510
+ update()
511
+ if (typeof window !== 'undefined' && window.MutationObserver) {
512
+ this._modalObs = new MutationObserver(update)
513
+ this._modalObs.observe(document.body, { childList: true, subtree: true })
514
+ }
515
+ },
516
+
414
517
  /**
415
518
  * Translate an i18n key/text via the `translate` prop when given.
416
519
  *
@@ -598,6 +701,23 @@ export default {
598
701
  return
599
702
  }
600
703
  const el = this.resolveTarget(this.step)
704
+ // A step's target belongs to the page it was first found on. The
705
+ // same `index-add` button exists on every list page, and a step
706
+ // left half-done on Products used to spotlight the Clients page's
707
+ // button too, inviting a click that belonged to another step.
708
+ const kind = (this.step.target && this.step.target.kind) || ''
709
+ const routeName = this.$route ? this.$route.name : null
710
+ if (el && kind !== 'page' && kind !== 'nav-item' && routeName) {
711
+ if (this._homeRoute === null) {
712
+ this._homeRoute = routeName
713
+ } else if (this._homeRoute !== routeName) {
714
+ this.offPage = true
715
+ this.targetEl = null
716
+ this.rect = null
717
+ return
718
+ }
719
+ }
720
+ this.offPage = false
601
721
  this.targetEl = el
602
722
  if (!el) {
603
723
  // Optional step whose target is absent → skip; else wait for it.
@@ -862,6 +982,10 @@ export default {
862
982
  * @return {void}
863
983
  */
864
984
  focusCard() {
985
+ // Never take focus from an open app dialog.
986
+ if (this.appModalOpen) {
987
+ return
988
+ }
865
989
  this.$nextTick(() => {
866
990
  const btn = this.$refs.firstBtn && this.$refs.firstBtn.$el
867
991
  if (btn && typeof btn.focus === 'function') {
@@ -1058,16 +1182,20 @@ export default {
1058
1182
  },
1059
1183
 
1060
1184
  /**
1061
- * Dismiss the tour from a backdrop click or ESC.
1185
+ * Pause the tour from a backdrop click or ESC. The tour hides and
1186
+ * keeps its step; only Skip (the close button) and Finish end it.
1187
+ * Pressing ESC used to dismiss it, and the host recorded that as
1188
+ * "seen", so the tour never came back.
1062
1189
  *
1190
+ * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
1063
1191
  * @return {void}
1064
1192
  */
1065
1193
  onBackdrop() {
1066
- this.wt.dismiss()
1194
+ this.wt.pause()
1067
1195
  /**
1068
- * @event dismiss Emitted when the user dismisses the tour (backdrop / ESC).
1196
+ * @event pause Emitted when the user hides the tour (backdrop / ESC). The tour can continue later.
1069
1197
  */
1070
- this.$emit('dismiss')
1198
+ this.$emit('pause')
1071
1199
  },
1072
1200
  },
1073
1201
  }
@@ -1126,6 +1254,11 @@ export default {
1126
1254
  pointer-events: auto;
1127
1255
  }
1128
1256
 
1257
+ .cn-walkthrough__card--docked {
1258
+ inset-block-end: 16px;
1259
+ inset-inline-start: 16px;
1260
+ }
1261
+
1129
1262
  .cn-walkthrough__close {
1130
1263
  /* !important beats NcButton's own `position: relative`, which would otherwise
1131
1264
  leave the close button in-flow at the card's top-left. */
@@ -25,6 +25,7 @@ import CnObjectGeoWidget from '../CnObjectGeoWidget/CnObjectGeoWidget.vue'
25
25
  import CnObjectMetadataWidget from '../CnObjectMetadataWidget/CnObjectMetadataWidget.vue'
26
26
  import CnRelatedObjectsWidget from '../CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue'
27
27
  import CnTextWidget from '../CnTextWidget/CnTextWidget.vue'
28
+ import CnTimelineWidget from '../CnTimelineWidget/CnTimelineWidget.vue'
28
29
  import CnWidgetCardGrid from '../CnWidgetCardGrid/CnWidgetCardGrid.vue'
29
30
  import CnWidgetFormRenderer from '../CnWidgetFormRenderer/CnWidgetFormRenderer.vue'
30
31
  import CnWidgetMapViewer from '../CnWidgetMapViewer/CnWidgetMapViewer.vue'
@@ -84,6 +85,7 @@ export const BUILT_IN_WIDGETS = {
84
85
  integration: CnIntegrationWidget,
85
86
  banner: CnBannerWidget,
86
87
  'audit-trail': CnAuditTrailWidget,
88
+ timeline: CnTimelineWidget,
87
89
  header: CnHeaderWidget,
88
90
  text: CnTextWidget,
89
91
  divider: CnDividerWidget,
@@ -72,6 +72,7 @@ import '../CnObjectDataWidget/dashboardRegistration.js'
72
72
  import '../CnObjectGeoWidget/dashboardRegistration.js'
73
73
  import '../CnWidgetObjectTable/dashboardRegistration.js'
74
74
  import '../CnAuditTrailWidget/dashboardRegistration.js'
75
+ import '../CnTimelineWidget/dashboardRegistration.js'
75
76
  import '../CnKbSearchWidget/index.js'
76
77
  import '../CnInteractionFormWidget/index.js'
77
78
  import '../CnBannerWidget/index.js'
@@ -163,3 +163,7 @@ Request a feature / Report a bug / Documentation render on every widget; the sha
163
163
  ## High contrast
164
164
 
165
165
  `high-contrast` declares that this widget wants a high contrast treatment. The flag reaches the theme; the component picks no colour. See [the reference page](../../../docs/components/cn-widget-wrapper.md#high-contrast).
166
+
167
+ ### A text link in the header (`headerLink`)
168
+
169
+ `headerLink` (`{ label, route?, params?, query?, href? }`) draws one text link in the header before the overflow menu, such as "All deadlines".
@@ -69,6 +69,17 @@
69
69
  It used to gate both, so a caller who switched the menu off
70
70
  also lost the Save button for an inline edit, silently. -->
71
71
  <div v-if="actionsAreVisible()" class="cn-widget-wrapper__actions">
72
+ <!-- One text link in the header (`headerLink`): "All deadlines",
73
+ "To the board". A route is a router link, an href a plain
74
+ anchor. Sits before the slot and the overflow menu. -->
75
+ <component
76
+ :is="headerLinkTag"
77
+ v-if="headerLinkTag"
78
+ class="cn-widget-wrapper__header-link"
79
+ data-testid="cn-widget-wrapper-header-link"
80
+ v-bind="headerLinkAttrs">
81
+ {{ headerLinkLabel }}
82
+ </component>
72
83
  <!-- @slot actions Custom action buttons rendered before the
73
84
  built-in overflow menu. -->
74
85
  <slot name="actions" />
@@ -351,6 +362,22 @@ export default {
351
362
  default: () => [],
352
363
  },
353
364
 
365
+ /**
366
+ * One text link in the header's actions group, before the overflow
367
+ * menu: `{ label, route?, params?, query?, href? }`. A `route` is a
368
+ * named router link (needs a router), an `href` a plain anchor. The
369
+ * label goes through the host translate function. Null (the
370
+ * default) draws nothing. Manifest: `headerLink` on the widget
371
+ * definition or its layout entry.
372
+ *
373
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-widget-header-carries-a-text-link
374
+ * @type {{label: string, route?: string, params?: object, query?: object, href?: string}|null}
375
+ */
376
+ headerLink: {
377
+ type: Object,
378
+ default: null,
379
+ },
380
+
354
381
  /**
355
382
  * Whether the header's overflow action menu (Refresh / Documentation /
356
383
  * Request-a-feature + any `#action-items`) renders. Shown by default;
@@ -634,6 +661,62 @@ export default {
634
661
  return this.translate ?? this.cnTranslate
635
662
  },
636
663
 
664
+ /**
665
+ * The header link's tag: `router-link` for a route (when a router is
666
+ * mounted), `a` for an href, '' when there is no usable link.
667
+ *
668
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-widget-header-carries-a-text-link
669
+ * @return {string}
670
+ */
671
+ headerLinkTag() {
672
+ const link = this.headerLink
673
+ if (!link || typeof link.label !== 'string' || link.label === '') {
674
+ return ''
675
+ }
676
+ if (link.route && this.$router) {
677
+ return 'router-link'
678
+ }
679
+ if (typeof link.href === 'string' && link.href !== '') {
680
+ return 'a'
681
+ }
682
+ return ''
683
+ },
684
+
685
+ /**
686
+ * The header link's attributes for its tag.
687
+ *
688
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-widget-header-carries-a-text-link
689
+ * @return {object}
690
+ */
691
+ headerLinkAttrs() {
692
+ const link = this.headerLink || {}
693
+ if (this.headerLinkTag === 'router-link') {
694
+ const to = { name: link.route }
695
+ if (link.params && typeof link.params === 'object') {
696
+ to.params = link.params
697
+ }
698
+ if (link.query && typeof link.query === 'object') {
699
+ to.query = link.query
700
+ }
701
+ return { to }
702
+ }
703
+ if (this.headerLinkTag === 'a') {
704
+ return { href: link.href }
705
+ }
706
+ return {}
707
+ },
708
+
709
+ /**
710
+ * The header link's text, through the host translate function.
711
+ *
712
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-widget-header-carries-a-text-link
713
+ * @return {string}
714
+ */
715
+ headerLinkLabel() {
716
+ const label = this.headerLink && this.headerLink.label
717
+ return label ? this.effectiveTranslate(label) : ''
718
+ },
719
+
637
720
  /**
638
721
  * The RENDERED widget title — `displayTitle` run through the host
639
722
  * translate function. Kept separate from `displayTitle` on purpose:
@@ -877,7 +960,7 @@ export default {
877
960
  * @return {boolean} True when the group would hold a visible control.
878
961
  */
879
962
  actionsAreVisible() {
880
- return this.showActions || this.actionsSlotRenders()
963
+ return this.showActions || this.actionsSlotRenders() || Boolean(this.headerLinkTag)
881
964
  },
882
965
 
883
966
  /**
@@ -1127,6 +1210,29 @@ export default {
1127
1210
  flex-shrink: 0;
1128
1211
  }
1129
1212
 
1213
+ /* The header's text link (`headerLink`). Theme hooks: --cn-widget-header-link-size
1214
+ and --cn-widget-header-link-weight. */
1215
+ .cn-widget-wrapper__header-link {
1216
+ align-self: center;
1217
+ padding: 0 calc(1 * var(--default-grid-baseline));
1218
+ color: var(--color-primary-element);
1219
+ font-size: var(--cn-widget-header-link-size, 14px);
1220
+ font-weight: var(--cn-widget-header-link-weight, 600);
1221
+ text-decoration: none;
1222
+ white-space: nowrap;
1223
+ }
1224
+
1225
+ .cn-widget-wrapper__header-link:hover,
1226
+ .cn-widget-wrapper__header-link:focus-visible {
1227
+ text-decoration: underline;
1228
+ }
1229
+
1230
+ .cn-widget-wrapper__header-link:focus-visible {
1231
+ outline: 2px solid var(--color-primary-element);
1232
+ outline-offset: 2px;
1233
+ border-radius: var(--border-radius-small, 4px);
1234
+ }
1235
+
1130
1236
  /* Every widget's header icon is coloured — `--cn-widget-icon-color` is set on
1131
1237
  the header from titleIconStyle, and defaults to the theme colour if a
1132
1238
  wrapper is ever rendered without it. Material-design-icon components paint
@@ -105,7 +105,7 @@
105
105
  </NcButton>
106
106
  <NcButton variant="primary"
107
107
  data-testid="cn-wizard-next"
108
- :disabled="loading"
108
+ :disabled="loading || nextDisabled"
109
109
  @click="isLast ? submit() : next()">
110
110
  <template #icon>
111
111
  <NcLoadingIcon v-if="loading" :size="20" />
@@ -280,6 +280,19 @@ export default {
280
280
  type: Boolean,
281
281
  default: true,
282
282
  },
283
+
284
+ /**
285
+ * Disable the Next / Submit button, whatever the step's `validate`
286
+ * would say. For a host that knows the wizard cannot go on yet, such as
287
+ * `CnSetupWizard` while a required app is missing.
288
+ *
289
+ * @type {boolean}
290
+ * @spec openspec/changes/setup-wizard-card-load-and-dependency-gate/specs/cn-setup-wizard/spec.md#requirement-the-wizard-checks-dependencies-before-any-step
291
+ */
292
+ nextDisabled: {
293
+ type: Boolean,
294
+ default: false,
295
+ },
283
296
  },
284
297
 
285
298
  emits: ['close', 'step-change', 'submit'],
@@ -396,6 +409,9 @@ export default {
396
409
  * @return {Promise<void>}
397
410
  */
398
411
  async next() {
412
+ if (this.nextDisabled) {
413
+ return
414
+ }
399
415
  if (this.isLast) {
400
416
  return this.submit()
401
417
  }
@@ -467,6 +483,9 @@ export default {
467
483
  * @return {Promise<void>}
468
484
  */
469
485
  async submit() {
486
+ if (this.nextDisabled) {
487
+ return
488
+ }
470
489
  const ok = await this.runValidation()
471
490
  if (!ok) {
472
491
  return
@@ -17,6 +17,7 @@ export { CnLeafDependencySettings } from './CnLeafDependencySettings/index.js'
17
17
  export { CnStatsBlock } from './CnStatsBlock/index.js'
18
18
  export { CnStructuredDocReview } from './CnStructuredDocReview/index.js'
19
19
  export { CnConfigurationCard } from './CnConfigurationCard/index.js'
20
+ export { CnAdminActionCard } from './CnAdminActionCard/index.js'
20
21
  export { CnVersionInfoCard } from './CnVersionInfoCard/index.js'
21
22
  export { CnAdminSettingsShell } from './CnAdminSettingsShell/index.js'
22
23
  export { CnCellRenderer } from './CnCellRenderer/index.js'
@@ -103,6 +104,7 @@ export { CnSearchPage } from './CnSearchPage/index.js'
103
104
  export { CnCommandPalette } from './CnCommandPalette/index.js'
104
105
  export { CnTabbedFormDialog } from './CnTabbedFormDialog/index.js'
105
106
  export { CnTimelineStages } from './CnTimelineStages/index.js'
107
+ export { CnTimelineWidget } from './CnTimelineWidget/index.js'
106
108
  export { CnTreeView } from './CnTreeView/index.js'
107
109
  export { CnUserActionMenu } from './CnUserActionMenu/index.js'
108
110
  export { CnNotesCard } from './CnNotesCard/index.js'
@@ -0,0 +1,61 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ */
5
+
6
+ import { useAppStatus } from './useAppStatus.js'
7
+
8
+ /**
9
+ * Resolve `manifest.dependencies` entries to their live status.
10
+ *
11
+ * Accepts the manifest's own shape: an app-id string (a REQUIRED dependency)
12
+ * or `{ id, name?, required? }` where `required` defaults to true. Each entry
13
+ * becomes `{ id, name, required, installed, enabled }`.
14
+ *
15
+ * `serverStatuses` is the optional `dependency_statuses` initial state an app
16
+ * injects from PHP, keyed by app id: `{ installed, enabled }`. It wins over
17
+ * the browser heuristic, because only the server can tell "installed but
18
+ * disabled" from "not installed". Without it, `useAppStatus` answers, which
19
+ * is the same resolution `CnAppRoot` uses for its dependency-missing phase.
20
+ *
21
+ * @param {Array<string|{id: string, name?: string, required?: boolean}>} entries The declared dependencies.
22
+ * @param {object} [serverStatuses] Server-reported statuses keyed by app id.
23
+ * @return {Array<{id: string, name: string, required: boolean, installed: boolean, enabled: boolean}>} One row per valid entry.
24
+ *
25
+ * @spec openspec/changes/setup-wizard-card-load-and-dependency-gate/specs/cn-setup-wizard/spec.md#requirement-the-wizard-checks-dependencies-before-any-step
26
+ *
27
+ * @example
28
+ * const missing = checkDependencies(manifest.dependencies)
29
+ * .filter((d) => d.required && !(d.installed && d.enabled))
30
+ */
31
+ export function checkDependencies(entries, serverStatuses = {}) {
32
+ const list = Array.isArray(entries) ? entries : []
33
+ const server = (serverStatuses && typeof serverStatuses === 'object') ? serverStatuses : {}
34
+ return list
35
+ .map((entry) => {
36
+ const isObject = entry !== null && typeof entry === 'object'
37
+ const id = isObject ? entry.id : entry
38
+ if (typeof id !== 'string' || id === '') {
39
+ return null
40
+ }
41
+ const required = isObject ? entry.required !== false : true
42
+ const name = (isObject && entry.name) || id
43
+ const reported = server[id]
44
+ if (reported !== undefined && reported !== null) {
45
+ return { id, name, required, installed: !!reported.installed, enabled: !!reported.enabled }
46
+ }
47
+ const status = useAppStatus(id)
48
+ return { id, name, required, installed: !!status.installed.value, enabled: !!status.enabled.value }
49
+ })
50
+ .filter((row) => row !== null)
51
+ }
52
+
53
+ /**
54
+ * Whether a resolved dependency row is usable: installed AND enabled.
55
+ *
56
+ * @param {{installed: boolean, enabled: boolean}} row A row from `checkDependencies`.
57
+ * @return {boolean} True when the app can be used.
58
+ */
59
+ export function isDependencyResolved(row) {
60
+ return !!row && row.installed === true && row.enabled === true
61
+ }