@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
@@ -35,7 +35,45 @@
35
35
  {{ tr('No stages to show') }}
36
36
  </p>
37
37
  <template v-else>
38
+ <!-- The bars variant (`variant: "bars"`): equal columns, a thin bar
39
+ per step coloured by state, the label under it and an optional
40
+ date line. No description: eight Woo steps with their
41
+ descriptions crowded into five lines and clipped the first
42
+ label. The current step says so for a screen reader. -->
43
+ <ol
44
+ v-if="isBars"
45
+ class="cn-stages-widget__bars"
46
+ :aria-label="ariaLabel"
47
+ data-testid="cn-stages-widget-bars">
48
+ <li
49
+ v-for="stage in barStages"
50
+ :key="stage.id"
51
+ class="cn-stages-widget__bar-step"
52
+ :class="`cn-stages-widget__bar-step--${stage.state}`"
53
+ :aria-current="stage.state === 'current' ? 'step' : null"
54
+ :data-testid="`cn-stages-widget-bar-${stage.id}`">
55
+ <span class="cn-stages-widget__bar" aria-hidden="true" />
56
+ <component
57
+ :is="stage.clickable ? 'button' : 'span'"
58
+ :type="stage.clickable ? 'button' : null"
59
+ class="cn-stages-widget__bar-label"
60
+ :title="stage.hint || stage.label"
61
+ :aria-disabled="stage.disabled && stage.clickable ? 'true' : null"
62
+ :data-testid="`cn-stages-widget-stage-${stage.id}`"
63
+ @click="onBarClick(stage)">
64
+ {{ stage.label }}<span v-if="stage.state === 'current'" class="cn-stages-widget__sr-only"> ({{ tr('current step') }})</span>
65
+ </component>
66
+ <span v-if="stage.date" class="cn-stages-widget__bar-date">{{ stage.date }}</span>
67
+ <span
68
+ v-if="stage.reason"
69
+ :class="stage.reasonVisible ? 'cn-stages-widget__reason' : 'cn-stages-widget__sr-only'"
70
+ :data-testid="`cn-stages-widget-reason-${stage.id}`">
71
+ {{ stage.reason }}
72
+ </span>
73
+ </li>
74
+ </ol>
38
75
  <CnTimelineStages
76
+ v-else
39
77
  :stages="timelineStages"
40
78
  :currentStage="currentStageId"
41
79
  :orientation="orientation"
@@ -310,13 +348,15 @@ export default {
310
348
  * `currentField`, `stagesEndpoint` or `stagesSource`, `transition`
311
349
  * (`{ kind: 'lifecycle' }` by default, or `{ kind: 'field' }`, or
312
350
  * absent for a read-only strip), `unreachableReason`, `orientation`,
313
- * `size`, `ariaLabel` and `label`.
351
+ * `size`, `ariaLabel`, `label` and `variant` (`"bars"` draws a thin
352
+ * coloured bar per step with the label under it and no description;
353
+ * the source's `dateField` adds a date line).
314
354
  *
315
355
  * `ariaLabel` names the strip for a screen reader. It falls back to
316
356
  * `label`, the placement's own title, so a titled card does not have to
317
357
  * repeat itself, and then to "Stages".
318
358
  *
319
- * @type {{currentField?: string, stagesEndpoint?: object, stagesSource?: object, transition?: object, unreachableReason?: string, orientation?: ('horizontal'|'vertical'), size?: ('medium'|'small'), ariaLabel?: string, label?: string}}
359
+ * @type {{currentField?: string, stagesEndpoint?: object, stagesSource?: object, transition?: object, unreachableReason?: string, orientation?: ('horizontal'|'vertical'), size?: ('medium'|'small'), ariaLabel?: string, label?: string, variant?: ('dots'|'bars')}}
320
360
  */
321
361
  content: {
322
362
  type: Object,
@@ -723,6 +763,45 @@ export default {
723
763
  return label ? this.effectiveTranslate(label) : this.tr('Stages')
724
764
  },
725
765
 
766
+ /**
767
+ * Whether the bars variant is on (`content.variant: "bars"`). Off by
768
+ * default: the dot strip renders as it always has.
769
+ *
770
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-stages-widget-draws-bars
771
+ * @return {boolean}
772
+ */
773
+ isBars() {
774
+ return this.content.variant === 'bars'
775
+ },
776
+
777
+ /**
778
+ * The stages for the bars variant: each with its `state` (`done`
779
+ * before the current stage, `current`, `todo` after it, every stage
780
+ * `todo` when none is current), its `date`, and whether it is a
781
+ * button (the strip is interactive and the stage is not the current
782
+ * one).
783
+ *
784
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-stages-widget-draws-bars
785
+ * @return {Array<object>}
786
+ */
787
+ barStages() {
788
+ const current = this.stages.findIndex((s) => s.id === this.currentStageId)
789
+ return this.timelineStages.map((stage, index) => {
790
+ let state = 'todo'
791
+ if (current >= 0 && index < current) {
792
+ state = 'done'
793
+ } else if (index === current) {
794
+ state = 'current'
795
+ }
796
+ return {
797
+ ...stage,
798
+ date: this.stages[index]?.date || '',
799
+ state,
800
+ clickable: this.interactive && state !== 'current',
801
+ }
802
+ })
803
+ },
804
+
726
805
  /** @return {'horizontal'|'vertical'} The layout direction. */
727
806
  orientation() {
728
807
  return this.content.orientation === 'vertical' ? 'vertical' : 'horizontal'
@@ -969,6 +1048,26 @@ export default {
969
1048
  * @param {{stage: {id: string}}} payload The CnTimelineStages event.
970
1049
  * @return {void}
971
1050
  */
1051
+ /**
1052
+ * A click on a bar-variant step: a disabled step reports why (as
1053
+ * CnTimelineStages does through `stage-blocked`), any other goes
1054
+ * through the shared move path.
1055
+ *
1056
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-stages-widget-draws-bars
1057
+ * @param {object} stage The bar step.
1058
+ * @return {void}
1059
+ */
1060
+ onBarClick(stage) {
1061
+ if (!stage.clickable) {
1062
+ return
1063
+ }
1064
+ if (stage.disabled) {
1065
+ this.onStageBlocked({ stage })
1066
+ return
1067
+ }
1068
+ this.onStageClick({ stage })
1069
+ },
1070
+
972
1071
  onStageClick({ stage }) {
973
1072
  if (!this.canMove || !stage) {
974
1073
  return
@@ -1340,6 +1439,85 @@ export default {
1340
1439
  color: var(--color-text-maxcontrast);
1341
1440
  }
1342
1441
 
1442
+ /* The bars variant: equal columns, a thin bar per step. Theme hooks:
1443
+ --cn-stages-bar-height, --cn-stages-bar-radius, --cn-stages-bar-done,
1444
+ --cn-stages-bar-current, --cn-stages-bar-todo. */
1445
+ .cn-stages-widget__bars {
1446
+ display: grid;
1447
+ grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
1448
+ grid-auto-flow: column;
1449
+ gap: 8px;
1450
+ margin: 0;
1451
+ padding: 0;
1452
+ list-style: none;
1453
+ }
1454
+
1455
+ .cn-stages-widget__bar-step {
1456
+ display: flex;
1457
+ flex-direction: column;
1458
+ gap: 8px;
1459
+ min-width: 0;
1460
+ }
1461
+
1462
+ .cn-stages-widget__bar {
1463
+ display: block;
1464
+ height: var(--cn-stages-bar-height, 6px);
1465
+ border-radius: var(--cn-stages-bar-radius, 3px);
1466
+ background: var(--cn-stages-bar-todo, var(--color-border));
1467
+ }
1468
+
1469
+ .cn-stages-widget__bar-step--done .cn-stages-widget__bar {
1470
+ background: var(--cn-stages-bar-done, var(--color-primary-element));
1471
+ }
1472
+
1473
+ .cn-stages-widget__bar-step--current .cn-stages-widget__bar {
1474
+ background: var(--cn-stages-bar-current, var(--color-error));
1475
+ }
1476
+
1477
+ .cn-stages-widget__bar-label {
1478
+ display: block;
1479
+ margin: 0;
1480
+ padding: 0;
1481
+ border: 0;
1482
+ background: none;
1483
+ color: var(--color-text-maxcontrast);
1484
+ font: inherit;
1485
+ font-size: 14px;
1486
+ text-align: start;
1487
+ overflow: hidden;
1488
+ text-overflow: ellipsis;
1489
+ white-space: nowrap;
1490
+ }
1491
+
1492
+ button.cn-stages-widget__bar-label {
1493
+ cursor: pointer;
1494
+ }
1495
+
1496
+ button.cn-stages-widget__bar-label[aria-disabled="true"] {
1497
+ cursor: not-allowed;
1498
+ }
1499
+
1500
+ button.cn-stages-widget__bar-label:focus-visible {
1501
+ outline: 2px solid var(--color-primary-element);
1502
+ outline-offset: 2px;
1503
+ border-radius: var(--border-radius-small, 4px);
1504
+ }
1505
+
1506
+ .cn-stages-widget__bar-step--done .cn-stages-widget__bar-label {
1507
+ color: var(--color-main-text);
1508
+ font-weight: 600;
1509
+ }
1510
+
1511
+ .cn-stages-widget__bar-step--current .cn-stages-widget__bar-label {
1512
+ color: var(--color-main-text);
1513
+ font-weight: 700;
1514
+ }
1515
+
1516
+ .cn-stages-widget__bar-date {
1517
+ font-size: 12px;
1518
+ color: var(--color-text-maxcontrast);
1519
+ }
1520
+
1343
1521
  /* Text for screen readers only. */
1344
1522
  .cn-stages-widget__sr-only {
1345
1523
  position: absolute;
@@ -85,8 +85,9 @@ export function isTrue(value) {
85
85
  * the rows keep the order they arrived in.
86
86
  *
87
87
  * @param {Array<object>} rows The stage rows.
88
- * @param {{idField?: string, labelField?: string, descriptionField?: string, orderField?: string, finalField?: string}} [cfg] The field mapping.
89
- * @return {Array<{id: string, label: string, subtitle: string, final: boolean}>} The stages.
88
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-stages-widget-draws-bars
89
+ * @param {{idField?: string, labelField?: string, descriptionField?: string, dateField?: string, orderField?: string, finalField?: string}} [cfg] The field mapping.
90
+ * @return {Array<{id: string, label: string, subtitle: string, date: string, final: boolean}>} The stages.
90
91
  */
91
92
  export function normalizeStages(rows, cfg = {}) {
92
93
  const list = Array.isArray(rows) ? rows.filter((r) => r && typeof r === 'object') : []
@@ -108,6 +109,8 @@ export function normalizeStages(rows, cfg = {}) {
108
109
  id: rowId(row, cfg.idField),
109
110
  label,
110
111
  subtitle: cfg.descriptionField ? labelText(getByPath(row, cfg.descriptionField)) : '',
112
+ // An optional date line (`dateField`), drawn by the bars variant.
113
+ date: cfg.dateField ? labelText(getByPath(row, cfg.dateField)) : '',
111
114
  final: cfg.finalField ? isTrue(getByPath(row, cfg.finalField)) : false,
112
115
  }
113
116
  })
@@ -94,7 +94,11 @@
94
94
  {{ formattedTrend }}
95
95
  </span>
96
96
  </template>
97
- <span v-if="!displayLoading && !displayError && content.caption" class="cn-kpi-card__label cn-stat-widget__caption">
97
+ <span
98
+ v-if="!displayLoading && !displayError && resolvedCaption"
99
+ class="cn-kpi-card__label cn-stat-widget__caption"
100
+ :class="captionVariantClass"
101
+ data-testid="cn-stat-widget-caption">
98
102
  {{ resolvedCaption }}
99
103
  </span>
100
104
  </div>
@@ -461,7 +465,7 @@ export default {
461
465
  * counted between LOCAL CALENDAR DAYS, not as elapsed milliseconds, so
462
466
  * the time of day on a deadline never moves the answer.
463
467
  *
464
- * @type {{label?: string, icon?: string, iconColor?: string, valueColor?: string, caption?: string, route?: (object|string), clickRoute?: (object|string), link?: string, format?: {style?: string, currency?: string, decimals?: number, prefix?: string, suffix?: string}, source?: {kind?: string, register?: string, schema?: string, metric?: string, field?: string, filter?: object, url?: string, path?: string, params?: object}, endpointSource?: {url: string, method?: string, params?: object, responsePath?: string}, valueField?: string, limitField?: string, limit?: number, dateRange?: {presets?: Array<{id: string, label?: string, from?: string, to?: string}>, default?: string}, previousField?: string, deltaField?: string, goodDirection?: ('up'|'down'), variant?: ('default'|'primary'|'success'|'warning'|'error'|'danger'), variantWhen?: Array<{op: string, value: unknown, variant: string, icon?: string}>, objectField?: (string|{field: string, resolve?: {register: string, schema: string, labelField?: string, variantField?: string, variantMap?: {[key: string]: string}}}), display?: ('text'|'badge'|'countdown'), countdown?: {unit?: 'days', warnAt?: number, dangerAt?: number, futureLabel?: string, todayLabel?: string, pastLabel?: string, emptyText?: string}, emptyText?: string, overrides?: Array<{when: {field: string, op?: string, value?: unknown}, label?: string, variant?: string, icon?: string}>}}
468
+ * @type {{label?: string, icon?: string, iconColor?: string, valueColor?: string, caption?: string, route?: (object|string), clickRoute?: (object|string), link?: string, format?: {style?: string, currency?: string, decimals?: number, prefix?: string, suffix?: string}, source?: {kind?: string, register?: string, schema?: string, metric?: string, field?: string, filter?: object, url?: string, path?: string, params?: object}, endpointSource?: {url: string, method?: string, params?: object, responsePath?: string}, valueField?: string, limitField?: string, limit?: number, dateRange?: {presets?: Array<{id: string, label?: string, from?: string, to?: string}>, default?: string}, previousField?: string, deltaField?: string, goodDirection?: ('up'|'down'), variant?: ('default'|'primary'|'success'|'warning'|'error'|'danger'), variantWhen?: Array<{op: string, value: unknown, variant: string, icon?: string}>, objectField?: (string|{field: string, resolve?: {register: string, schema: string, labelField?: string, variantField?: string, variantMap?: {[key: string]: string}}}), display?: ('text'|'badge'|'countdown'), countdown?: {unit?: 'days', warnAt?: number, dangerAt?: number, futureLabel?: string, todayLabel?: string, pastLabel?: string, emptyText?: string}, emptyText?: string, captionVariant?: ('default'|'primary'|'success'|'warning'|'error'|'danger'), captionVariantWhen?: Array<{op: string, value: unknown, variant: string}>, overrides?: Array<{when: {field: string, op?: string, value?: unknown}, label?: string, variant?: string, icon?: string, caption?: string, captionVariant?: string}>}}
465
469
  */
466
470
  content: {
467
471
  type: Object,
@@ -605,7 +609,10 @@ export default {
605
609
  * @return {string}
606
610
  */
607
611
  resolvedCaption() {
608
- const caption = this.content.caption
612
+ const override = this.activeOverride
613
+ const caption = (override && typeof override.caption === 'string' && override.caption !== '')
614
+ ? override.caption
615
+ : this.content.caption
609
616
  if (!caption) {
610
617
  return ''
611
618
  }
@@ -615,11 +622,58 @@ export default {
615
622
  }
616
623
  const payload = this.endpointMode ? this.epData : null
617
624
  return translated.replace(/\{([A-Za-z0-9_.]+)\}/g, (whole, path) => {
625
+ // `{value}` is the tile's own number, so a caption can read
626
+ // "{value} due today" on a register-counted tile that has no
627
+ // payload to read fields from.
628
+ if (path === 'value' && (payload === null || getByPath(payload, path) === undefined)) {
629
+ return this.formattedValue === undefined || this.formattedValue === null ? '' : String(this.formattedValue)
630
+ }
618
631
  const v = getByPath(payload, path)
619
632
  return (v === undefined || v === null) ? '' : String(v)
620
633
  }).replace(/\s{2,}/g, ' ').trim()
621
634
  },
622
635
 
636
+ /**
637
+ * The first `content.captionVariantWhen` rule the tile's value
638
+ * matches (`[{ op, value, variant }]`, the `variantWhen` shape), or
639
+ * null. Colours the caption on the value without recolouring the
640
+ * tile: "1 due today" turns red while the number stays ink.
641
+ *
642
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-stat-tile-colours-its-caption-by-rule
643
+ * @return {object|null}
644
+ */
645
+ activeCaptionRule() {
646
+ const rules = this.content.captionVariantWhen
647
+ if (!Array.isArray(rules) || rules.length === 0) {
648
+ return null
649
+ }
650
+ const current = this.displayValue
651
+ if (current === null || current === undefined) {
652
+ return null
653
+ }
654
+ return rules.find((r) => r && this.matchesRule(current, r)) || null
655
+ },
656
+
657
+ /**
658
+ * The caption's colour class: the matching override's
659
+ * `captionVariant`, else `content.captionVariant`, as
660
+ * `cn-kpi-card__label--<variant>`. '' for `default` or an unknown
661
+ * variant, so an uncoloured caption is what it always was.
662
+ *
663
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-stat-tile-colours-its-caption-by-rule
664
+ * @return {string}
665
+ */
666
+ captionVariantClass() {
667
+ const override = this.activeOverride
668
+ const variant = (override && override.captionVariant)
669
+ || (this.activeCaptionRule && this.activeCaptionRule.variant)
670
+ || this.content.captionVariant
671
+ if (!variant || variant === 'default' || !Object.hasOwn(VARIANT_COLORS, variant)) {
672
+ return ''
673
+ }
674
+ return `cn-kpi-card__label--${variant === 'danger' ? 'error' : variant}`
675
+ },
676
+
623
677
  /**
624
678
  * The unwrapped detail-page object context for token resolution, or null
625
679
  * on surfaces (dashboards) that don't provide one.
@@ -8,6 +8,7 @@
8
8
  <CnTabs
9
9
  :aria-label="stripLabel"
10
10
  :moreLabel="moreLabel"
11
+ :variant="stripVariant"
11
12
  class="cn-tabs-widget__tabs"
12
13
  @update:activeIndex="onTabChange">
13
14
  <!-- One Actions menu for the whole widget, bound to whichever child
@@ -246,9 +247,10 @@ export default {
246
247
  *
247
248
  * `maxVisibleTabs` caps the strip: later tabs go under "More".
248
249
  * `hideEmpty: true` moves a tab whose count is 0 there too.
249
- * `moreLabel` names that menu.
250
+ * `moreLabel` names that menu. `variant: "segmented"` draws the strip
251
+ * as a pill switch (CnTabs' segmented variant); `line` is the default.
250
252
  *
251
- * @type {{ tabs?: Array<{widgetId: string, label?: string, icon?: string, count?: number, countField?: string, overflow?: boolean}>, ariaLabel?: string, maxVisibleTabs?: number, hideEmpty?: boolean, moreLabel?: string }}
253
+ * @type {{ tabs?: Array<{widgetId: string, label?: string, icon?: string, count?: number, countField?: string, overflow?: boolean}>, ariaLabel?: string, maxVisibleTabs?: number, hideEmpty?: boolean, moreLabel?: string, variant?: ('line'|'segmented') }}
252
254
  */
253
255
  content: {
254
256
  type: Object,
@@ -464,6 +466,17 @@ export default {
464
466
  return this.activeTab?.label || ''
465
467
  },
466
468
 
469
+ /**
470
+ * The strip's look: `segmented` when the content asks for it, else
471
+ * CnTabs' default `line`. An unknown value falls back to `line`.
472
+ *
473
+ * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-tabs-widget-renders-a-segmented-strip
474
+ * @return {('line'|'segmented')} The CnTabs variant.
475
+ */
476
+ stripVariant() {
477
+ return this.content?.variant === 'segmented' ? 'segmented' : 'line'
478
+ },
479
+
467
480
  /**
468
481
  * Accessible name for the tab strip.
469
482
  *