@conduction/nextcloud-vue 2.69.0 → 2.71.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 (153) hide show
  1. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +25 -0
  2. package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
  3. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +155 -152
  4. package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
  5. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +77 -0
  6. package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
  7. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +8 -8
  8. package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
  9. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +151 -13
  10. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  11. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +3 -1
  12. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  13. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue.js +1 -0
  14. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue.js.map +1 -1
  15. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js +28 -0
  16. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js.map +1 -1
  17. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js +4 -2
  18. package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js.map +1 -1
  19. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js +25 -1
  20. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js.map +1 -1
  21. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue3.js +32 -17
  22. package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue3.js.map +1 -1
  23. package/dist/esm/components/CnCapabilityTable/CnCapabilityTable.vue2.js +14 -1
  24. package/dist/esm/components/CnCapabilityTable/CnCapabilityTable.vue2.js.map +1 -1
  25. package/dist/esm/components/CnCapabilityTable/CnCapabilityTable.vue3.js.map +1 -1
  26. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +43 -2
  27. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
  28. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js +3 -3
  29. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
  30. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +25 -1
  31. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  32. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +40 -43
  33. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  34. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +35 -1
  35. package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
  36. package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
  37. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +54 -3
  38. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  39. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +6 -3
  40. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  41. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +28 -0
  42. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  43. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +3 -2
  44. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  45. package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js +12 -0
  46. package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js.map +1 -1
  47. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +9 -0
  48. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
  49. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
  50. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +29 -2
  51. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  52. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +71 -34
  53. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  54. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +54 -7
  55. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
  56. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +26 -8
  57. package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
  58. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +165 -0
  59. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  60. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +69 -26
  61. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  62. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js.map +1 -1
  63. package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js.map +1 -1
  64. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +4 -1
  65. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
  66. package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
  67. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +1 -1
  68. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
  69. package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
  70. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  71. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +46 -32
  72. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  73. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +17 -3
  74. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -1
  75. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +25 -24
  76. package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -1
  77. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
  78. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
  79. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +27 -0
  80. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  81. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +7 -0
  82. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  83. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js +24 -1
  84. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js.map +1 -1
  85. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js +1 -1
  86. package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js.map +1 -1
  87. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +11 -0
  88. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
  89. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +4 -2
  90. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
  91. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +30 -9
  92. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
  93. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
  94. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +15 -2
  95. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -1
  96. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js +31 -32
  97. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -1
  98. package/dist/esm/composables/useWalkthrough.js +11 -3
  99. package/dist/esm/composables/useWalkthrough.js.map +1 -1
  100. package/dist/esm/nextcloud-vue.css +198 -0
  101. package/dist/esm/utils/capabilityComparison.js +29 -1
  102. package/dist/esm/utils/capabilityComparison.js.map +1 -1
  103. package/dist/esm/utils/columnFilters.js +54 -4
  104. package/dist/esm/utils/columnFilters.js.map +1 -1
  105. package/dist/esm/utils/userAutocomplete.js +69 -3
  106. package/dist/esm/utils/userAutocomplete.js.map +1 -1
  107. package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
  108. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  109. package/dist/nextcloud-vue.cjs.js +2778 -1630
  110. package/dist/nextcloud-vue.cjs.js.map +2 -2
  111. package/dist/nextcloud-vue.css +198 -0
  112. package/package.json +1 -1
  113. package/src/components/CnActionsBar/CnActionsBar.md +2 -0
  114. package/src/components/CnActionsBar/CnActionsBar.vue +28 -2
  115. package/src/components/CnAppNav/CnAppNav.vue +85 -8
  116. package/src/components/CnAppRoot/CnAppRoot.vue +153 -13
  117. package/src/components/CnBreadcrumbs/CnBreadcrumbs.vue +45 -0
  118. package/src/components/CnCalendarWidget/CnCalendarWidget.vue +32 -4
  119. package/src/components/CnCapabilityTable/CnCapabilityTable.vue +14 -0
  120. package/src/components/CnDashboardGrid/CnDashboardGrid.md +2 -0
  121. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +54 -2
  122. package/src/components/CnDashboardPage/CnDashboardPage.vue +46 -19
  123. package/src/components/CnDataTable/CnDataTable.vue +35 -1
  124. package/src/components/CnDetailPage/CnDetailPage.md +2 -0
  125. package/src/components/CnDetailPage/CnDetailPage.vue +57 -3
  126. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +30 -1
  127. package/src/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue +12 -0
  128. package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +9 -0
  129. package/src/components/CnFormDialog/CnFormDialog.vue +62 -2
  130. package/src/components/CnHeaderWidget/CnHeaderWidget.vue +95 -8
  131. package/src/components/CnIndexPage/CnIndexPage.vue +194 -7
  132. package/src/components/CnNextStepCard/CnNextStepCard.vue +11 -0
  133. package/src/components/CnObjectListWidget/CnObjectListWidget.vue +4 -1
  134. package/src/components/CnObjectSidebar/CnNotesTab.vue +1 -1
  135. package/src/components/CnSetupWizard/CnSetupWizard.vue +7 -0
  136. package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +34 -7
  137. package/src/components/CnStagesWidget/CnStagesWidget.vue +19 -0
  138. package/src/components/CnStatWidget/CnStatWidget.vue +36 -0
  139. package/src/components/CnStatsBlock/CnStatsBlock.md +2 -0
  140. package/src/components/CnStatsBlock/CnStatsBlock.vue +25 -2
  141. package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +13 -0
  142. package/src/components/CnWalkthrough/CnWalkthrough.vue +30 -9
  143. package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +24 -3
  144. package/src/composables/useWalkthrough.js +11 -3
  145. package/src/css/dashboard.css +44 -0
  146. package/src/css/detail-page.css +10 -0
  147. package/src/css/index-page.css +12 -0
  148. package/src/css/kpi-card.css +15 -0
  149. package/src/schemas/app-manifest-v2.schema.json +47 -5
  150. package/src/utils/capabilityComparison.js +28 -0
  151. package/src/utils/columnFilters.js +54 -4
  152. package/src/utils/userAutocomplete.js +68 -2
  153. package/src/utils/validateManifestV2.compiled.js +2 -2
@@ -444,3 +444,5 @@ When the schema-driven fetch answers 404 the page shows a not-found state whose
444
444
  ### Header card and header widget
445
445
 
446
446
  `headerCard` (default `false`) draws the header as a bordered card. `headerWidget` (default `''`) names a widget in `widgets` to render inside the header under the title, without a card of its own and out of the body grid: the stages bars of the board's case card.
447
+
448
+ `showWidgetActions` (Boolean, default `true`): `false` (manifest `config.showWidgetActions: false`) drops the overflow Actions menu from the cards of the body grid and the side column, unless a widget definition sets `showActions: true`.
@@ -48,6 +48,7 @@
48
48
  class="cn-detail-page__breadcrumbs"
49
49
  :crumbs="breadcrumbCrumbs"
50
50
  :rootText="breadcrumbCrumbs[0].icon === undefined"
51
+ :separator="breadcrumbSeparator"
51
52
  data-testid="cn-detail-page-breadcrumbs" />
52
53
  <!-- Header -->
53
54
  <div
@@ -749,6 +750,7 @@
749
750
  :widget="findWidget(item)"
750
751
  chrome="card"
751
752
  :showCardTitle="showCardTitle(item)"
753
+ :showActions="showWidgetActions"
752
754
  :objectId="objectId"
753
755
  :object="currentObject"
754
756
  :objectType="resolvedObjectType"
@@ -959,6 +961,7 @@
959
961
  :widget="widget"
960
962
  chrome="card"
961
963
  :showCardTitle="true"
964
+ :showActions="showWidgetActions"
962
965
  :objectId="objectId"
963
966
  :object="currentObject"
964
967
  :objectType="resolvedObjectType"
@@ -2235,18 +2238,38 @@ export default {
2235
2238
  * belongs to; the record's display name follows as the current
2236
2239
  * crumb. `label` goes through the host translate function and shows
2237
2240
  * as text; `icon` (an MDI name, e.g. `Home`) draws that icon instead,
2238
- * with the label as its accessible name. Null (the default) draws no
2241
+ * with the label as its accessible name. `currentField` names a field
2242
+ * of the record (a dotted path, e.g. `identifier`) whose value is the
2243
+ * current crumb instead of the display name; an empty value falls back
2244
+ * to the display name. `separator` draws that text (e.g. `/`) between
2245
+ * the crumbs instead of the chevron. Null (the default) draws no
2239
2246
  * trail.
2240
2247
  *
2241
2248
  * @spec openspec/changes/zuiddrecht-pixel-gaps/specs/zuiddrecht-pixel-gaps/spec.md#requirement-a-detail-page-drops-its-eyebrow-and-shows-a-breadcrumb
2242
2249
  * @spec openspec/changes/zuiddrecht-pixel-gaps-2/specs/zuiddrecht-pixel-gaps-2/spec.md#requirement-a-declared-breadcrumb-label-shows-as-text
2243
- * @type {{label: string, route?: string, params?: object, href?: string, icon?: string}|null}
2250
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-breadcrumb-can-name-the-record-by-a-field-and-use-a-text-separator
2251
+ * @type {{label: string, route?: string, params?: object, href?: string, icon?: string, currentField?: string, separator?: string}|null}
2244
2252
  */
2245
2253
  breadcrumb: {
2246
2254
  type: Object,
2247
2255
  default: null,
2248
2256
  },
2249
2257
 
2258
+ /**
2259
+ * Whether the record's widget cards (the body grid and the side
2260
+ * column) keep their overflow Actions menu when their definition does
2261
+ * not say (`showActions`). `false` (manifest
2262
+ * `config.showWidgetActions: false`) drops it, as on a dashboard; a
2263
+ * widget with `showActions: true` keeps its menu. A card that offers
2264
+ * an Add action keeps the menu that holds it. True by default.
2265
+ *
2266
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-detail-page-can-drop-the-widget-actions-menu
2267
+ */
2268
+ showWidgetActions: {
2269
+ type: Boolean,
2270
+ default: true,
2271
+ },
2272
+
2250
2273
  /**
2251
2274
  * A column of cards beside the body (manifest `config.sideColumn`),
2252
2275
  * for the record's facts: deadline, requester, handler, links. Each
@@ -3438,7 +3461,38 @@ export default {
3438
3461
  } else if (crumb.href) {
3439
3462
  first.href = crumb.href
3440
3463
  }
3441
- return [first, { label: this.displayTitle }]
3464
+ return [first, { label: this.breadcrumbCurrentLabel }]
3465
+ },
3466
+
3467
+ /**
3468
+ * The current crumb: the record's `breadcrumb.currentField` value
3469
+ * (the case number on a case page) when declared and filled, else the
3470
+ * display name.
3471
+ *
3472
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-breadcrumb-can-name-the-record-by-a-field-and-use-a-text-separator
3473
+ * @return {string}
3474
+ */
3475
+ breadcrumbCurrentLabel() {
3476
+ const field = this.breadcrumb && this.breadcrumb.currentField
3477
+ if (typeof field === 'string' && field !== '') {
3478
+ const value = stageOf(this.resolvedObject, field)
3479
+ if (value !== '') {
3480
+ return value
3481
+ }
3482
+ }
3483
+ return this.displayTitle
3484
+ },
3485
+
3486
+ /**
3487
+ * The text drawn between the crumbs (`breadcrumb.separator`), or ''
3488
+ * for the chevron.
3489
+ *
3490
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-breadcrumb-can-name-the-record-by-a-field-and-use-a-text-separator
3491
+ * @return {string}
3492
+ */
3493
+ breadcrumbSeparator() {
3494
+ const separator = this.breadcrumb && this.breadcrumb.separator
3495
+ return typeof separator === 'string' ? separator : ''
3442
3496
  },
3443
3497
 
3444
3498
  /**
@@ -85,7 +85,7 @@
85
85
  v-else-if="isData && schemaObject"
86
86
  :title="resolvedTitle"
87
87
  :chromeless="isBare"
88
- :showActions="!isBare"
88
+ :showActions="!isBare && actionsShown"
89
89
  :icon="widget.icon || null"
90
90
  :schema="schemaObject"
91
91
  :objectData="object"
@@ -165,6 +165,7 @@
165
165
  v-else-if="renderer && isContentOnly && !isBare"
166
166
  :title="widget.title || ''"
167
167
  titleIconPosition="left"
168
+ :showActions="actionsShown || catalogAddEnabled"
168
169
  :showRefresh="false"
169
170
  :showRequestFeature="false"
170
171
  class="cn-detail-page__catalog-card">
@@ -473,6 +474,21 @@ export default {
473
474
  default: null,
474
475
  },
475
476
 
477
+ /**
478
+ * Whether a widget on a card keeps its overflow Actions menu when its
479
+ * definition does not say (`showActions`). `false` (CnDetailPage
480
+ * `showWidgetActions: false`) drops it from the record's data cards; a
481
+ * definition with `showActions: true` keeps it, one with
482
+ * `showActions: false` drops it whatever this says. True by default,
483
+ * which is the menu every card had.
484
+ *
485
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-detail-page-can-drop-the-widget-actions-menu
486
+ */
487
+ showActions: {
488
+ type: Boolean,
489
+ default: true,
490
+ },
491
+
476
492
  /**
477
493
  * The consumer's component registry, consulted before the built-in
478
494
  * catalog so a custom widget type overrides a built-in (REQ-MVR-005).
@@ -839,6 +855,19 @@ export default {
839
855
  return this.content.allowCreate !== false
840
856
  },
841
857
 
858
+ /**
859
+ * Whether the widget's overflow Actions menu renders: the definition's
860
+ * own `showActions` when it is a boolean, else the surface's
861
+ * `showActions`.
862
+ *
863
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-detail-page-can-drop-the-widget-actions-menu
864
+ * @return {boolean} true when the menu renders.
865
+ */
866
+ actionsShown() {
867
+ const own = this.widget && this.widget.showActions
868
+ return typeof own === 'boolean' ? own : this.showActions
869
+ },
870
+
842
871
  /**
843
872
  * The card header rule, with the surface's layout-driven answer winning
844
873
  * when it supplied one.
@@ -40,6 +40,7 @@
40
40
  // unconditionally.
41
41
  import { loadState } from '@nextcloud/initial-state'
42
42
  import CnFeaturesAndRoadmapView from '../CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue'
43
+ import { warnCapabilityComparisonDeprecated } from '../../utils/capabilityComparison.js'
43
44
  import { DEFAULT_FORGE } from '../../utils/forge.js'
44
45
 
45
46
  /**
@@ -175,6 +176,11 @@ export default {
175
176
  * Manifest config > initialState (`features_roadmap_capabilities`) >
176
177
  * null. When null the view renders its two stops as it always has.
177
178
  *
179
+ * Deprecated: publish the comparison on the app's docs site and link to it (as dossiq #3312 and pipelinq #2195 did). Still rendered; removal waits for a major version. The same goes for the `features_roadmap_capabilities` initial
180
+ * state key. A development build warns once when either is set.
181
+ *
182
+ * @deprecated since 2.66.0, publish the comparison on the app's docs site and link to it.
183
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-the-in-app-capability-comparison-is-deprecated
178
184
  * @type {object|null}
179
185
  */
180
186
  capabilityComparison: {
@@ -298,5 +304,11 @@ export default {
298
304
  return readInitialState(this.effectiveAppId, 'features_roadmap_capabilities', null)
299
305
  },
300
306
  },
307
+
308
+ mounted() {
309
+ if (this.resolvedCapabilityComparison) {
310
+ warnCapabilityComparisonDeprecated('CnFeaturesAndRoadmapPage `capabilityComparison` / the `features_roadmap_capabilities` initial state')
311
+ }
312
+ },
301
313
  }
302
314
  </script>
@@ -114,6 +114,7 @@ import CnFeaturesAndRoadmapSidebar from '../CnFeaturesAndRoadmapSidebar/CnFeatur
114
114
  import CnFeaturesTab from '../CnFeaturesTab/CnFeaturesTab.vue'
115
115
  import CnRoadmapTab from '../CnRoadmapTab/CnRoadmapTab.vue'
116
116
  import CnSupportDialog from '../CnSupportDialog/CnSupportDialog.vue'
117
+ import { warnCapabilityComparisonDeprecated } from '../../utils/capabilityComparison.js'
117
118
  import { buildFeatureRequestUrl, DEFAULT_FORGE } from '../../utils/forge.js'
118
119
 
119
120
  const DEFAULT_OPENBUILT_PATH = '/apps/openbuilt'
@@ -214,6 +215,11 @@ export default {
214
215
  * When null (default) the toggle keeps its two stops and the view
215
216
  * renders exactly as it does for every app that passes nothing.
216
217
  *
218
+ * Deprecated: publish the comparison on the app's docs site and link to it (as dossiq #3312 and pipelinq #2195 did). Still rendered; removal waits for a major version.
219
+ * A development build warns once when it is set.
220
+ *
221
+ * @deprecated since 2.66.0, publish the comparison on the app's docs site and link to it.
222
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-the-in-app-capability-comparison-is-deprecated
217
223
  * @type {object|null}
218
224
  */
219
225
  capabilityComparison: {
@@ -527,6 +533,9 @@ export default {
527
533
 
528
534
  mounted() {
529
535
  this.publishHoistedSidebar()
536
+ if (this.capabilityComparison) {
537
+ warnCapabilityComparisonDeprecated('CnFeaturesAndRoadmapView `capabilityComparison`')
538
+ }
530
539
  },
531
540
 
532
541
  beforeUnmount() {
@@ -250,6 +250,17 @@
250
250
  <!-- @binding {object} option The option being rendered (`{ id, label }`). -->
251
251
  <slot :name="'field-' + field.key + '-option'" v-bind="optionProps" />
252
252
  </template>
253
+ <!-- NcSelect's own option splits a label in two spans
254
+ (NcEllipsisedOption, keeping its last characters
255
+ visible), and in a narrow dropdown the halves broke
256
+ onto two lines mid-word: "Agricu lture". A plain
257
+ label wraps at spaces only. NcSelectUsers keeps its
258
+ avatar rendering. -->
259
+ <template
260
+ v-else-if="!isUserField(field)"
261
+ #option="optionProps">
262
+ <span class="cn-form-dialog__option">{{ optionText(optionProps) }}</span>
263
+ </template>
253
264
  <template
254
265
  v-if="$slots['field-' + field.key + '-selected-option']"
255
266
  #selected-option="optionProps">
@@ -284,11 +295,17 @@
284
295
  #option="optionProps">
285
296
  <slot :name="'field-' + field.key + '-option'" v-bind="optionProps" />
286
297
  </template>
298
+ <template v-else #option="optionProps">
299
+ <span class="cn-form-dialog__option">{{ optionText(optionProps) }}</span>
300
+ </template>
287
301
  <template
288
302
  v-if="$slots['field-' + field.key + '-selected-option']"
289
303
  #selected-option="optionProps">
290
304
  <slot :name="'field-' + field.key + '-selected-option'" v-bind="optionProps" />
291
305
  </template>
306
+ <template v-else #selected-option="optionProps">
307
+ <span class="cn-form-dialog__option">{{ optionText(optionProps) }}</span>
308
+ </template>
292
309
  </NcSelect>
293
310
  <CnFieldHelper
294
311
  :text="field.description"
@@ -318,11 +335,17 @@
318
335
  #option="optionProps">
319
336
  <slot :name="'field-' + field.key + '-option'" v-bind="optionProps" />
320
337
  </template>
338
+ <template v-else #option="optionProps">
339
+ <span class="cn-form-dialog__option">{{ optionText(optionProps) }}</span>
340
+ </template>
321
341
  <template
322
342
  v-if="$slots['field-' + field.key + '-selected-option']"
323
343
  #selected-option="optionProps">
324
344
  <slot :name="'field-' + field.key + '-selected-option'" v-bind="optionProps" />
325
345
  </template>
346
+ <template v-else #selected-option="optionProps">
347
+ <span class="cn-form-dialog__option">{{ optionText(optionProps) }}</span>
348
+ </template>
326
349
  </NcSelect>
327
350
  <CnFieldHelper
328
351
  :text="field.description"
@@ -2452,6 +2475,26 @@ export default {
2452
2475
  * @param {object} field The field definition
2453
2476
  * @return {boolean}
2454
2477
  */
2478
+ /**
2479
+ * The text a select option shows: its label, else a display name, a
2480
+ * name or the id. A plain string option (a tag) is its own text.
2481
+ *
2482
+ * @spec openspec/changes/audit-round-lib-fixes/specs/schema-utilities/spec.md
2483
+ * @param {object|string} option The option (slot props).
2484
+ * @return {string} The text.
2485
+ */
2486
+ optionText(option) {
2487
+ if (option === null || option === undefined) {
2488
+ return ''
2489
+ }
2490
+ if (typeof option !== 'object') {
2491
+ return String(option)
2492
+ }
2493
+ const text = [option.label, option.displayName, option.name, option.id]
2494
+ .find((v) => v !== undefined && v !== null && v !== '')
2495
+ return text === undefined ? '' : String(text)
2496
+ },
2497
+
2455
2498
  isUserField(field) {
2456
2499
  return !!(field && field.userPicker && !field.userPicker.multiple)
2457
2500
  },
@@ -3519,8 +3562,15 @@ export default {
3519
3562
  if (this.isIdPickerField(field)) {
3520
3563
  // Reference / user fields store the chosen id (UUID / UID),
3521
3564
  // not the full option. Cache its label so the selection displays.
3565
+ // A user option can carry only `displayName` (NcSelectUsers' own
3566
+ // shape), and without that fallback the chip showed the uid.
3522
3567
  if (option && option.id) {
3523
- this.referenceLabels = { ...this.referenceLabels, [option.id]: option.label || String(option.id) }
3568
+ const name = option.label || option.displayName || this.referenceLabels[option.id]
3569
+ if (name) {
3570
+ this.referenceLabels = { ...this.referenceLabels, [option.id]: name }
3571
+ } else if (this.isUserField(field)) {
3572
+ this.resolveUserLabel(option.id)
3573
+ }
3524
3574
  }
3525
3575
  this.updateField(field.key, option ? option.id : null)
3526
3576
  // Reference options are label-only ({id,label}) — pass the id so
@@ -3587,7 +3637,7 @@ export default {
3587
3637
  const labels = {}
3588
3638
  for (const o of list) {
3589
3639
  if (o && o.id) {
3590
- labels[o.id] = o.label || String(o.id)
3640
+ labels[o.id] = o.label || o.displayName || this.referenceLabels[o.id] || String(o.id)
3591
3641
  }
3592
3642
  }
3593
3643
  if (Object.keys(labels).length > 0) {
@@ -3861,6 +3911,16 @@ export default {
3861
3911
  </script>
3862
3912
 
3863
3913
  <style scoped>
3914
+ /* A select option breaks between words only, never inside one. */
3915
+ .cn-form-dialog__option {
3916
+ display: block;
3917
+ min-width: 0;
3918
+ white-space: normal;
3919
+ overflow-wrap: normal;
3920
+ word-break: normal;
3921
+ hyphens: manual;
3922
+ }
3923
+
3864
3924
  .cn-form-dialog__draft-actions {
3865
3925
  display: flex;
3866
3926
  gap: 8px;
@@ -4,12 +4,25 @@
4
4
  -->
5
5
 
6
6
  <template>
7
- <div class="cn-header-widget" :class="{ 'cn-header-widget--plain': isPlain, 'cn-header-widget--ground': isGround, 'cn-header-widget--with-views': viewOptions.length > 0 }" :style="wrapperStyle">
7
+ <div class="cn-header-widget" :class="{ 'cn-header-widget--plain': isPlain, 'cn-header-widget--ground': isGround, 'cn-header-widget--with-views': viewOptions.length > 0, 'cn-header-widget--with-emblem': emblem !== null }" :style="wrapperStyle">
8
8
  <div
9
9
  v-if="hasOverlay"
10
10
  class="cn-header-widget__overlay"
11
11
  :style="overlayStyle"
12
12
  aria-hidden="true" />
13
+ <!-- The emblem beside the greeting (`content.emblem`): a URL, or
14
+ `true` for the theme's emblem (--nldesign-emblem-url). Decoration. -->
15
+ <img
16
+ v-if="emblem && emblem !== true"
17
+ class="cn-header-widget__emblem"
18
+ :src="emblem"
19
+ alt=""
20
+ data-testid="cn-header-widget-emblem">
21
+ <span
22
+ v-else-if="emblem === true"
23
+ class="cn-header-widget__emblem cn-header-widget__emblem--theme"
24
+ aria-hidden="true"
25
+ data-testid="cn-header-widget-emblem" />
13
26
  <div class="cn-header-widget__content" :style="contentStyle">
14
27
  <p
15
28
  v-if="dateLine"
@@ -140,7 +153,7 @@ export default {
140
153
  * Persisted widget content: `{title, subtitle, backgroundImageUrl,
141
154
  * backgroundImageFileId, backgroundColor, overlayMode, overlayColor,
142
155
  * overlayOpacity, textColor, textAlign, verticalAlign, height, cta,
143
- * greeting, showDate, plain, ground, views}`. All fields are optional except
156
+ * greeting, showDate, kicker, emblem, plain, ground, views}`. All fields are optional except
144
157
  * `title` (or `greeting`); unknown enum values collapse to documented
145
158
  * defaults and the renderer never throws. `views` is
146
159
  * `{ ariaLabel?, options: [{ label, route, params? } | { label, view }] }`:
@@ -151,7 +164,12 @@ export default {
151
164
  * own. `ground: true`
152
165
  * draws the greeting on the page ground: the plain look with no
153
166
  * padding, a 32px heading and the date line 6px above it (a dashboard
154
- * also drops the widget's card for it).
167
+ * also drops the widget's card for it). A ground greeting takes its
168
+ * own height rather than its grid cell's, so a view switch sits on
169
+ * the heading's line. `kicker` (an i18n key) prefixes the date line, as
170
+ * "Customer contact · Tuesday 6 October". `emblem` (a URL, or `true`
171
+ * for the theme's `--nldesign-emblem-url`) draws the emblem beside the
172
+ * date line and heading, 52px high (`--cn-header-emblem-size`).
155
173
  *
156
174
  * @type {object}
157
175
  */
@@ -396,13 +414,30 @@ export default {
396
414
  },
397
415
 
398
416
  /**
399
- * Today's date written out ("Monday 5 October 2026"), or '' when
400
- * `showDate` is off.
417
+ * The kicker line above the heading: `content.kicker` (through the
418
+ * host translate function, e.g. "Customer contact") and today's date
419
+ * written out ("Monday 5 October 2026") when `showDate` is on, joined
420
+ * by " · ". '' when neither is set. Without `kicker` this is the date
421
+ * line as before.
401
422
  *
402
423
  * @return {string}
403
424
  * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header
404
425
  */
405
426
  dateLine() {
427
+ const kicker = this.content && typeof this.content.kicker === 'string' && this.content.kicker !== ''
428
+ ? this.effectiveTranslate(this.content.kicker)
429
+ : ''
430
+ const date = this.dateText
431
+ return [kicker, date].filter((part) => part !== '').join(' · ')
432
+ },
433
+
434
+ /**
435
+ * Today's date written out, or '' when `showDate` is off.
436
+ *
437
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-greeting-kicker-can-carry-a-prefix
438
+ * @return {string}
439
+ */
440
+ dateText() {
406
441
  if (!this.content || this.content.showDate !== true) {
407
442
  return ''
408
443
  }
@@ -434,6 +469,21 @@ export default {
434
469
  }
435
470
  },
436
471
 
472
+ /**
473
+ * The emblem beside the greeting: the URL `content.emblem` names,
474
+ * `true` for the theme's emblem, or null without the key.
475
+ *
476
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-greeting-can-carry-the-emblem
477
+ * @return {string|true|null}
478
+ */
479
+ emblem() {
480
+ const value = this.content && this.content.emblem
481
+ if (value === true) {
482
+ return true
483
+ }
484
+ return typeof value === 'string' && value !== '' ? resolveImageUrl(value) : null
485
+ },
486
+
437
487
  /** Whether a non-empty subtitle is set. */
438
488
  hasSubtitle() {
439
489
  return this.subtitle !== ''
@@ -574,7 +624,12 @@ export default {
574
624
  return this.isLightColor(this.backgroundColor) ? '#000000' : '#ffffff'
575
625
  },
576
626
 
577
- /** Inline style for the banner wrapper. */
627
+ /**
628
+ * Inline style for the banner wrapper.
629
+ *
630
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-ground-greeting-lines-its-switch-up-with-the-heading
631
+ * @return {object}
632
+ */
578
633
  wrapperStyle() {
579
634
  const style = {
580
635
  position: 'relative',
@@ -583,7 +638,11 @@ export default {
583
638
  // widget. A fixed pixel height ignored the cell, which made the
584
639
  // banner un-resizable and made it overflow/scroll inside a
585
640
  // smaller cell. The grid controls the size now.
586
- height: '100%',
641
+ // On the page ground there is no banner to fill the cell with:
642
+ // the greeting takes its own height, so the view switch lines
643
+ // up with the heading instead of the bottom of a taller cell,
644
+ // and a size-to-content cell can shrink to it.
645
+ height: this.isGround ? 'auto' : '100%',
587
646
  overflow: 'hidden',
588
647
  'background-color': this.backgroundColor,
589
648
  }
@@ -619,6 +678,7 @@ export default {
619
678
  * Inline style for the content flex container.
620
679
  *
621
680
  * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-greeting-header
681
+ * @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-ground-greeting-lines-its-switch-up-with-the-heading
622
682
  */
623
683
  contentStyle() {
624
684
  return {
@@ -627,7 +687,7 @@ export default {
627
687
  // Beside a view switch the content shares the row instead of
628
688
  // taking the whole width (see .cn-header-widget--with-views).
629
689
  width: this.viewOptions.length > 0 ? 'auto' : '100%',
630
- height: '100%',
690
+ height: this.isGround ? 'auto' : '100%',
631
691
  display: 'flex',
632
692
  'flex-direction': 'column',
633
693
  'align-items': this.flexAlignFromTextAlign,
@@ -834,6 +894,33 @@ export default {
834
894
  width: auto;
835
895
  }
836
896
 
897
+ /* The emblem beside the greeting (`content.emblem`): on one row with the
898
+ date line and heading, centred on them, as LpStart draws it. */
899
+ .cn-header-widget--with-emblem {
900
+ display: flex;
901
+ column-gap: var(--cn-header-emblem-gap, 16px);
902
+ }
903
+
904
+ .cn-header-widget--with-emblem .cn-header-widget__content {
905
+ flex: 1 1 auto;
906
+ width: auto;
907
+ min-width: 0;
908
+ }
909
+
910
+ .cn-header-widget__emblem {
911
+ position: relative;
912
+ z-index: 1;
913
+ flex: none;
914
+ align-self: center;
915
+ height: var(--cn-header-emblem-size, 52px);
916
+ width: auto;
917
+ }
918
+
919
+ .cn-header-widget__emblem--theme {
920
+ width: var(--cn-header-emblem-size, 52px);
921
+ background: var(--nldesign-emblem-url) center / contain no-repeat;
922
+ }
923
+
837
924
  .cn-header-widget__views {
838
925
  position: relative;
839
926
  z-index: 1;