@conduction/nextcloud-vue 2.56.0-beta.65 → 2.56.0-beta.67

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/CnActionButtons/CnActionButtons.vue.js +1 -1
  2. package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js +4 -6
  3. package/dist/esm/components/CnActionButtons/CnActionButtons.vue2.js.map +1 -1
  4. package/dist/esm/components/CnActionButtons/CnActionButtons.vue3.js.map +1 -1
  5. package/dist/esm/components/CnActionDialog/CnActionDialog.vue.js +1 -1
  6. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +3 -2
  7. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
  8. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js +4 -2
  9. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -1
  10. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js +1 -1
  11. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +3 -2
  12. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -1
  13. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js +2 -1
  14. package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js.map +1 -1
  15. package/dist/esm/components/CnBoardView/CnBoardView.vue.js +1 -1
  16. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +54 -5
  17. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  18. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +16 -2
  19. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  20. package/dist/esm/components/CnCardGrid/CnCardGrid.vue.js +1 -1
  21. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +10 -0
  22. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
  23. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js +2 -1
  24. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
  25. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
  26. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +22 -2
  27. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  28. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +17 -3
  29. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  30. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  31. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  32. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  33. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue.js +1 -1
  34. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue2.js.map +1 -1
  35. package/dist/esm/components/CnChoiceCards/CnChoiceCards.vue3.js.map +1 -1
  36. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  37. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +18 -3
  38. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  39. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +2 -2
  40. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  41. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +52 -13
  42. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  43. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +31 -3
  44. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  45. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  46. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +48 -11
  47. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  48. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +160 -97
  49. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  50. package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
  51. package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
  52. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +2 -2
  53. package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
  54. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +222 -15
  55. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  56. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +24 -7
  57. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  58. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue.js +1 -1
  59. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js +21 -1
  60. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js.map +1 -1
  61. package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue3.js.map +1 -1
  62. package/dist/esm/components/CnObjectCard/CnObjectCard.vue.js +1 -1
  63. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +36 -2
  64. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
  65. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +44 -5
  66. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
  67. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js +1 -0
  68. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js.map +1 -1
  69. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js +22 -2
  70. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js.map +1 -1
  71. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js +36 -16
  72. package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js.map +1 -1
  73. package/dist/esm/components/CnReportsPage/CnReportsPage.vue.js +1 -1
  74. package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js +29 -12
  75. package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js.map +1 -1
  76. package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js +3 -1
  77. package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js.map +1 -1
  78. package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue.js +1 -1
  79. package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue2.js.map +1 -1
  80. package/dist/esm/components/CnSearchSelect/CnSearchSelect.vue3.js.map +1 -1
  81. package/dist/esm/composables/useRefLabels.js +26 -6
  82. package/dist/esm/composables/useRefLabels.js.map +1 -1
  83. package/dist/esm/integrations/registry.js +4 -0
  84. package/dist/esm/integrations/registry.js.map +1 -1
  85. package/dist/esm/l10n/en.json.js +3 -1
  86. package/dist/esm/l10n/en.json.js.map +1 -1
  87. package/dist/esm/l10n/nl.json.js +3 -1
  88. package/dist/esm/l10n/nl.json.js.map +1 -1
  89. package/dist/esm/library-style.js +1 -1
  90. package/dist/esm/nextcloud-vue.css +680 -449
  91. package/dist/esm/store/plugins/liveUpdates.js +3 -1
  92. package/dist/esm/store/plugins/liveUpdates.js.map +1 -1
  93. package/dist/esm/store/plugins/logs.js +2 -1
  94. package/dist/esm/store/plugins/logs.js.map +1 -1
  95. package/dist/esm/utils/actionLink.js +104 -1
  96. package/dist/esm/utils/actionLink.js.map +1 -1
  97. package/dist/esm/utils/detailActionModel.js +28 -1
  98. package/dist/esm/utils/detailActionModel.js.map +1 -1
  99. package/dist/esm/utils/fillTemplate.js +13 -5
  100. package/dist/esm/utils/fillTemplate.js.map +1 -1
  101. package/dist/esm/utils/headerFieldChips.js +21 -2
  102. package/dist/esm/utils/headerFieldChips.js.map +1 -1
  103. package/dist/esm/utils/mapList.js +26 -11
  104. package/dist/esm/utils/mapList.js.map +1 -1
  105. package/dist/esm/utils/savedViewMenu.js +12 -5
  106. package/dist/esm/utils/savedViewMenu.js.map +1 -1
  107. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  108. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  109. package/dist/nextcloud-vue.cjs.js +2800 -1933
  110. package/dist/nextcloud-vue.cjs.js.map +2 -2
  111. package/dist/nextcloud-vue.css +680 -449
  112. package/l10n/en.json +3 -1
  113. package/l10n/nl.json +3 -1
  114. package/package.json +1 -1
  115. package/src/components/CnActionButtons/CnActionButtons.vue +4 -6
  116. package/src/components/CnActionDialog/CnActionDialog.vue +32 -9
  117. package/src/components/CnAssignDialog/CnAssignDialog.vue +4 -2
  118. package/src/components/CnBoardView/CnBoardView.vue +58 -5
  119. package/src/components/CnCardGrid/CnCardGrid.md +1 -0
  120. package/src/components/CnCardGrid/CnCardGrid.vue +11 -0
  121. package/src/components/CnCellRenderer/CnCellRenderer.vue +27 -3
  122. package/src/components/CnChartWidget/CnChartWidget.vue +6 -0
  123. package/src/components/CnChoiceCards/CnChoiceCards.vue +0 -3
  124. package/src/components/CnDashboardPage/CnDashboardPage.vue +20 -5
  125. package/src/components/CnDetailPage/CnDetailPage.vue +62 -14
  126. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +100 -5
  127. package/src/components/CnFormDialog/CnFormDialog.vue +1 -1
  128. package/src/components/CnIndexPage/CnIndexPage.vue +233 -18
  129. package/src/components/CnNcDashboardList/CnNcDashboardList.vue +21 -1
  130. package/src/components/CnObjectCard/CnObjectCard.md +1 -0
  131. package/src/components/CnObjectCard/CnObjectCard.vue +51 -5
  132. package/src/components/CnPartyPicker/CnPartyPicker.vue +62 -16
  133. package/src/components/CnReportsPage/CnReportsPage.vue +31 -12
  134. package/src/components/CnSearchSelect/CnSearchSelect.vue +8 -0
  135. package/src/composables/useRefLabels.js +25 -5
  136. package/src/css/badge.css +15 -13
  137. package/src/css/board.css +0 -1
  138. package/src/css/dialog.css +53 -3
  139. package/src/css/index-page.css +26 -1
  140. package/src/css/look-board-detail.css +36 -15
  141. package/src/css/look-board-index.css +67 -15
  142. package/src/css/look-board.css +32 -18
  143. package/src/integrations/registry.js +4 -0
  144. package/src/schemas/app-manifest-v2.schema.json +61 -13
  145. package/src/store/plugins/liveUpdates.js +3 -1
  146. package/src/store/plugins/logs.js +2 -1
  147. package/src/utils/actionLink.js +103 -0
  148. package/src/utils/detailActionModel.js +27 -0
  149. package/src/utils/fillTemplate.js +13 -5
  150. package/src/utils/headerFieldChips.js +21 -2
  151. package/src/utils/mapList.js +26 -11
  152. package/src/utils/savedViewMenu.js +12 -5
  153. package/src/utils/validateManifestV2.compiled.js +3 -3
package/l10n/en.json CHANGED
@@ -1912,7 +1912,9 @@
1912
1912
  "New team": "New team",
1913
1913
  "Why it is moving": "Why it is moving",
1914
1914
  "Use \"{name}\"": "Use \"{name}\"",
1915
- "It was not handed over.": "It was not handed over."
1915
+ "It was not handed over.": "It was not handed over.",
1916
+ "This search could not be read.": "This search could not be read.",
1917
+ "{source} could not be searched, so this list may be incomplete.": "{source} could not be searched, so this list may be incomplete."
1916
1918
  },
1917
1919
  "plurals": {
1918
1920
  "{count} member": [
package/l10n/nl.json CHANGED
@@ -2124,7 +2124,9 @@
2124
2124
  "New team": "Nieuw team",
2125
2125
  "Why it is moving": "Waarom het overgaat",
2126
2126
  "Use \"{name}\"": "\"{name}\" gebruiken",
2127
- "It was not handed over.": "Het is niet overgedragen."
2127
+ "It was not handed over.": "Het is niet overgedragen.",
2128
+ "This search could not be read.": "Deze zoekopdracht kon niet worden gelezen.",
2129
+ "{source} could not be searched, so this list may be incomplete.": "{source} kon niet worden doorzocht, dus deze lijst is mogelijk niet compleet."
2128
2130
  },
2129
2131
  "plurals": {
2130
2132
  "{count} member": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/nextcloud-vue",
3
- "version": "2.56.0-beta.65",
3
+ "version": "2.56.0-beta.67",
4
4
  "description": "Shared Vue component library for Conduction Nextcloud apps — complements @nextcloud/vue with higher-level components, OpenRegister integration, and NL Design System support",
5
5
  "homepage": "https://github.com/ConductionNL/nextcloud-vue#readme",
6
6
  "bugs": {
@@ -329,12 +329,10 @@ export default {
329
329
  * browser keeps middle-click, "open in new tab" and the link semantics
330
330
  * assistive tech announces.
331
331
  *
332
- * `href` is JS-composition-only, like `onSelect` — manifest JSON cannot
333
- * carry it (the schema's `target` field already names the `navigate`
334
- * destination URL, and `additionalProperties: false` rejects an
335
- * unlisted `href`). A manifest author reaching for a link should use
336
- * `type: "navigate"` with an external `target`, which auto-derives the
337
- * anchor/`window.open` behaviour instead.
332
+ * `href` is used as given, so it must be a finished URL. A manifest
333
+ * `headerActions[]` entry may carry `href` too: the page (CnDetailPage,
334
+ * CnDashboardPage) resolves an instance path through `generateUrl()`
335
+ * and sets `target` before the entry reaches this component.
338
336
  *
339
337
  * @type {Array<object>}
340
338
  */
@@ -31,7 +31,7 @@
31
31
  {{ section.title }}
32
32
  </h3>
33
33
  <dl class="cn-action-dialog__facts">
34
- <div v-for="fact in section.facts" :key="fact.label">
34
+ <div v-for="fact in section.facts" :key="fact.label" class="cn-action-dialog__fact">
35
35
  <dt>{{ fact.label }}</dt>
36
36
  <dd>{{ fact.value }}</dd>
37
37
  </div>
@@ -87,6 +87,7 @@
87
87
  v-model="reason"
88
88
  :label="reasonLabel"
89
89
  :labelOutside="false"
90
+ :rows="config.reason.rows || 2"
90
91
  resize="vertical"
91
92
  data-testid="cn-action-dialog-reason" />
92
93
  <span v-if="config.reason.hint" class="cn-action-dialog__muted">{{ text(config.reason.hint) }}</span>
@@ -176,8 +177,9 @@ let reasonSeq = 0
176
177
  * required?, options?: [{ value, label, fills? }] }]`: inputs sent beside
177
178
  * the reason, required by default; a select option's `fills` sets other
178
179
  * fields that are still empty.
179
- * - `reason`: `{ label?, hint?, required?, field? }`: a text area; required by
180
- * default; sent as `field` (default `reason`).
180
+ * - `reason`: `{ label?, hint?, required?, field?, rows? }`: a text area; required by
181
+ * default; sent as `field` (default `reason`). `rows` is its height in lines
182
+ * (default 2; board DqBesluit draws 4).
181
183
  * - `note`: `{ variant?, title?, text }`: a read-only note.
182
184
  * - `confirm`: `{ label, icon?, request?: { method?, url, body? }, action? }`.
183
185
  * `request` posts itself (`body` templates filled with the record and
@@ -604,17 +606,27 @@ export default {
604
606
 
605
607
  .cn-action-dialog__title {
606
608
  margin: 0 0 8px;
607
- font-size: 15px;
608
- font-weight: 700;
609
+ font-size: 14px;
610
+ font-weight: 600;
609
611
  }
610
612
 
613
+ /* Facts in columns of at least 200px, each a label over its value above a
614
+ hairline (DqArchiveren "Bewaring volgens het zaaktype"). */
611
615
  .cn-action-dialog__facts {
612
616
  display: grid;
613
- grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
614
- gap: 8px 16px;
617
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
618
+ gap: 12px 24px;
615
619
  margin: 0;
616
620
  }
617
621
 
622
+ .cn-action-dialog__fact {
623
+ display: flex;
624
+ flex-direction: column;
625
+ gap: 3px;
626
+ padding-bottom: 10px;
627
+ border-bottom: 1px solid var(--color-border);
628
+ }
629
+
618
630
  .cn-action-dialog__facts dt {
619
631
  color: var(--color-text-maxcontrast);
620
632
  font-size: 13px;
@@ -622,12 +634,15 @@ export default {
622
634
 
623
635
  .cn-action-dialog__facts dd {
624
636
  margin: 0;
637
+ font-size: 15px;
638
+ font-weight: 500;
625
639
  }
626
640
 
641
+ /* Checklist rows divided by hairlines, the detail at the row's end
642
+ (DqArchiveren "Klaar om te archiveren"). */
627
643
  .cn-action-dialog__checks {
628
644
  display: flex;
629
645
  flex-direction: column;
630
- gap: 8px;
631
646
  margin: 0;
632
647
  padding: 0;
633
648
  list-style: none;
@@ -637,7 +652,15 @@ export default {
637
652
  display: flex;
638
653
  flex-wrap: wrap;
639
654
  align-items: center;
640
- gap: 8px;
655
+ gap: 2px 12px;
656
+ padding: 10px 0;
657
+ border-top: 1px solid var(--color-border);
658
+ font-size: 15px;
659
+ }
660
+
661
+ .cn-action-dialog__check-label {
662
+ flex: 1 1 auto;
663
+ font-weight: 600;
641
664
  }
642
665
 
643
666
  .cn-action-dialog__check-icon {
@@ -34,6 +34,7 @@
34
34
  v-model="note"
35
35
  :label="noteLabel"
36
36
  :labelOutside="false"
37
+ :rows="noteConfig.rows || 3"
37
38
  resize="vertical"
38
39
  data-testid="cn-assign-dialog-note" />
39
40
  <span v-if="noteConfig.hint" class="cn-assign-dialog__hint">{{ text(noteConfig.hint) }}</span>
@@ -139,8 +140,9 @@ export function withTypedAssignee(found, typed, label) {
139
140
  * by `currentAs`) says so, in place of `hint`; `allowTyped`
140
141
  * offers what was typed as a choice; the id is sent as `field` (default
141
142
  * `assignee`) and its kind as `kindField` when set.
142
- * - `note`: `{ label?, hint?, required?, field? }`, required by default and
143
- * sent as `field` (default `note`); `false` drops it.
143
+ * - `note`: `{ label?, hint?, required?, field?, rows? }`, required by default and
144
+ * sent as `field` (default `note`); `false` drops it. `rows` is the text
145
+ * area's height in lines (default 3, as board DqOverdragen draws it).
144
146
  * - `toggles`: `[{ field, label, note? }]`: switches that start off and are
145
147
  * sent as booleans.
146
148
  * - `confirm`: `{ label?, icon?, request?: { method?, url, body? }, action? }`.
@@ -228,7 +228,11 @@
228
228
  class="cn-board-view__pill"
229
229
  data-testid="cn-board-card-pill"
230
230
  :label="roleText(card, roles.pill)"
231
- :variant="pillVariant(card)" />
231
+ :variant="pillVariant(card)">
232
+ <template v-if="pillIcon(card)" #icon>
233
+ <CnIcon :name="pillIcon(card)" :size="14" />
234
+ </template>
235
+ </CnStatusBadge>
232
236
  </span>
233
237
  <span
234
238
  v-if="dueText(card) !== '' || ownerName(card) !== ''"
@@ -352,12 +356,14 @@
352
356
 
353
357
  <script>
354
358
  import { translate as t } from '@nextcloud/l10n'
359
+ import CnIcon from '../CnIcon/CnIcon.vue'
355
360
  import CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'
356
361
  import { normalizeLook } from '../../composables/useLook.js'
357
362
  import { isBadgeVariant } from '../../utils/badgeVariants.js'
358
363
  import { buildBoardColumns, normalizeAllowed, resolveStageColor } from '../../utils/boardColumns.js'
359
364
  import { buildSwimlanes } from '../../utils/boardSwimlanes.js'
360
365
  import { DROP_OUTCOMES, runBoardDrop } from '../../utils/boardTransition.js'
366
+ import { pillLook } from '../../utils/detailActionModel.js'
361
367
  import { daysUntil, dueStateForRow } from '../../utils/dueRule.js'
362
368
  import { formatMetricValue } from '../../utils/formatMetric.js'
363
369
  import { readPath } from '../../utils/readPath.js'
@@ -393,7 +399,7 @@ import '../../css/board.css'
393
399
  export default {
394
400
  name: 'CnBoardView',
395
401
 
396
- components: { CnStatusBadge },
402
+ components: { CnIcon, CnStatusBadge },
397
403
 
398
404
  inject: {
399
405
  /**
@@ -489,9 +495,10 @@ export default {
489
495
 
490
496
  /**
491
497
  * The board look's card roles (manifest `config.board.card`):
492
- * `{ title, sub, pill, due, owner, pillColors? }`. `sub` is a field or
493
- * a list of fields joined by a middle dot; `pillColors` maps a pill
494
- * value to a badge variant. Missing, the first of `cardFields` is the
498
+ * `{ title, sub, pill, due, owner, pillColors?, resolve? }`. `sub` is a
499
+ * field or a list of fields joined by a middle dot; `pillColors` maps a
500
+ * pill value to a badge variant; `resolve: { colorField?, iconField? }`
501
+ * takes the pill's tone and icon from the record it points at. Missing, the first of `cardFields` is the
495
502
  * title and the rest form the sub line. The Nextcloud look ignores it.
496
503
  *
497
504
  * @type {{title?: string, sub?: string|Array<string>, pill?: string, due?: string, owner?: string, pillColors?: object}|null}
@@ -502,6 +509,20 @@ export default {
502
509
  default: null,
503
510
  },
504
511
 
512
+ /**
513
+ * The referenced records' colour and icon names, by field and id
514
+ * (`{ [field]: { [id]: { color, icon } } }`, CnIndexPage reads them).
515
+ * With `cardRoles.resolve: { colorField?, iconField? }` the card's pill
516
+ * takes its record's tone and icon from here.
517
+ *
518
+ * @type {object}
519
+ * @spec openspec/changes/pill-color-field/specs/pill-color-field/spec.md#requirement-a-card-pill-takes-its-tone-and-icon-from-the-referenced-record
520
+ */
521
+ pillLooks: {
522
+ type: Object,
523
+ default: () => ({}),
524
+ },
525
+
505
526
  /**
506
527
  * The field on a column's state that holds its dot colour (manifest
507
528
  * `config.board.colorField`). Empty, the lifecycle state's declared
@@ -1064,11 +1085,43 @@ export default {
1064
1085
  * @return {string} A CnStatusBadge variant.
1065
1086
  */
1066
1087
  pillVariant(card) {
1088
+ const look = this.pillLookOf(card)
1089
+ if (look.variant) {
1090
+ return look.variant
1091
+ }
1067
1092
  const raw = this.fieldText(card, this.roles.pill)
1068
1093
  const variant = this.roles.pillColors[raw] ?? this.roles.pillColors[raw.toLowerCase()]
1069
1094
  return isBadgeVariant(variant) ? variant : 'default'
1070
1095
  },
1071
1096
 
1097
+ /**
1098
+ * The pill's icon, from the record it points at (`cardRoles.resolve`).
1099
+ *
1100
+ * @param {object} card The card's row.
1101
+ * @return {string} An icon name, or ''.
1102
+ * @spec openspec/changes/pill-color-field/specs/pill-color-field/spec.md#requirement-a-card-pill-takes-its-tone-and-icon-from-the-referenced-record
1103
+ */
1104
+ pillIcon(card) {
1105
+ return this.pillLookOf(card).icon || ''
1106
+ },
1107
+
1108
+ /**
1109
+ * The tone and icon of the record the card's pill points at, when
1110
+ * `cardRoles.resolve` asks for them; empty otherwise.
1111
+ *
1112
+ * @param {object} card The card's row.
1113
+ * @return {{variant?: string, icon?: string}} The look.
1114
+ */
1115
+ pillLookOf(card) {
1116
+ const given = this.cardRoles && typeof this.cardRoles === 'object' ? this.cardRoles : null
1117
+ if (!given || !given.resolve || !this.roles.pill || !this.pillLooks) {
1118
+ return {}
1119
+ }
1120
+ const raw = readPath(card, this.roles.pill)
1121
+ const looks = this.pillLooks[this.roles.pill]
1122
+ return pillLook(looks && raw !== null && raw !== undefined && typeof raw !== 'object' ? looks[String(raw)] : null)
1123
+ },
1124
+
1072
1125
  /**
1073
1126
  * The footer's due text: "Due today", "Due 2 Nov", or "Overdue 2 Nov"
1074
1127
  * for a date in the past. Words, so the late tone is not colour alone.
@@ -107,6 +107,7 @@ Custom card slot — replace the default CnObjectCard with your own rendering:
107
107
  | `selectable` | `Boolean` | `false` | When `true`, each card shows a selection checkbox and the grid emits `select` events |
108
108
  | `selectedIds` | `Array` | `[]` | Array of currently selected object IDs (matched against `object[rowKey]`) |
109
109
  | `accentOf` | `Function` | `null` | `(object) => { variant, icon?, label? } \| null`, each card's status accent (see CnObjectCard `accent`) |
110
+ | `pillLooks` | `Object` | `{}` | The referenced records' colour and icon names, by field and id (`{ [field]: { [id]: { color, icon } } }`), passed to each CnObjectCard for the record layout's pills that declare `resolve`. |
110
111
 
111
112
  ## Board look props
112
113
 
@@ -38,6 +38,7 @@
38
38
  :selected="isSelected(object)"
39
39
  :cardFields="cardFields"
40
40
  :layout="cardLayout"
41
+ :pillLooks="pillLooks"
41
42
  :cardFieldColumns="cardFieldColumns"
42
43
  :status="statusOf ? statusOf(object) : null"
43
44
  :leading="leadingOf ? leadingOf(object) : null"
@@ -179,6 +180,16 @@ export default {
179
180
  default: null,
180
181
  },
181
182
 
183
+ /**
184
+ * The referenced records' colour and icon names, by field and id
185
+ * (`{ [field]: { [id]: { color, icon } } }`), passed through to
186
+ * CnObjectCard for the record layout's pills that declare `resolve`.
187
+ */
188
+ pillLooks: {
189
+ type: Object,
190
+ default: () => ({}),
191
+ },
192
+
182
193
  /** The page's columns by key, passed through to CnObjectCard. */
183
194
  cardFieldColumns: {
184
195
  type: Object,
@@ -50,7 +50,11 @@
50
50
  :label="labelledValue"
51
51
  :variant="badgeVariant"
52
52
  :colorMap="badgeColorMap"
53
- :colorKey="badgeColorKey" />
53
+ :colorKey="badgeColorKey">
54
+ <template v-if="badgeLook.icon" #icon>
55
+ <CnIcon :name="badgeLook.icon" :size="14" class="cn-cell-renderer__badge-icon" />
56
+ </template>
57
+ </CnStatusBadge>
54
58
  <span v-else class="cn-cell-renderer__dash">—</span>
55
59
  </template>
56
60
 
@@ -376,6 +380,7 @@ import CnGroupNameCell from './CnGroupNameCell.vue'
376
380
  import { normalizeLook } from '../../composables/useLook.js'
377
381
  import { ageInDays, boardDateLocale, formatAge, formatBoardDate, formatRelativeDate, formatTimeOfDay, resolveAgeVariant } from '../../utils/boardDate.js'
378
382
  import { parseDateValue, resolveDateVariant } from '../../utils/dateVariant.js'
383
+ import { pillLook } from '../../utils/detailActionModel.js'
379
384
  import { resolveDue } from '../../utils/dueText.js'
380
385
  import { formatMoney } from '../../utils/formatMetric.js'
381
386
  import { objectFieldValue } from '../../utils/objectName.js'
@@ -1177,9 +1182,25 @@ export default {
1177
1182
  return Object.keys(map).some((key) => key.toLowerCase() === raw) ? String(this.value) : ''
1178
1183
  },
1179
1184
 
1180
- /** Variant for the built-in `badge` widget — `widgetProps.variant` or `'default'`. */
1185
+ /**
1186
+ * What the built-in `badge` widget takes from the record its value
1187
+ * points at (`widgetProps.looks`, filled by CnIndexPage from
1188
+ * `widgetProps.resolve`): a tone and an icon, or nothing.
1189
+ *
1190
+ * @return {{variant?: string, colorMap?: null, icon?: string}}
1191
+ * @spec openspec/changes/pill-color-field/specs/pill-color-field/spec.md#requirement-a-badge-cell-takes-its-tone-and-icon-from-the-referenced-record
1192
+ */
1193
+ badgeLook() {
1194
+ const looks = this.widgetProps && this.widgetProps.looks
1195
+ if (!looks || typeof looks !== 'object' || !this.hasValue || typeof this.value === 'object') {
1196
+ return {}
1197
+ }
1198
+ return pillLook(looks[String(this.value)])
1199
+ },
1200
+
1201
+ /** Variant for the built-in `badge` widget: the referenced record's tone, else `widgetProps.variant`, else `'default'`. */
1181
1202
  badgeVariant() {
1182
- return (this.widgetProps && this.widgetProps.variant) || 'default'
1203
+ return this.badgeLook.variant || (this.widgetProps && this.widgetProps.variant) || 'default'
1183
1204
  },
1184
1205
 
1185
1206
  /**
@@ -1194,6 +1215,9 @@ export default {
1194
1215
  * @return {object|null}
1195
1216
  */
1196
1217
  badgeColorMap() {
1218
+ if (this.badgeLook.variant) {
1219
+ return null
1220
+ }
1197
1221
  return (this.widgetProps && this.widgetProps.colorMap) || this.enumColorMap || null
1198
1222
  },
1199
1223
 
@@ -2830,6 +2830,7 @@ export default {
2830
2830
  }
2831
2831
 
2832
2832
  .cn-chart-widget__matrix-head {
2833
+ --nldesign-color-on-surface: var(--color-text-maxcontrast);
2833
2834
  color: var(--color-text-maxcontrast);
2834
2835
  padding: 6px;
2835
2836
  }
@@ -2866,7 +2867,11 @@ export default {
2866
2867
  outline-offset: -3px;
2867
2868
  }
2868
2869
 
2870
+ /* A cell is a span: its ink also goes to --nldesign-color-on-surface, which a
2871
+ theme that forces the body ink onto every span (thematiq) reads. Level 3
2872
+ leaves the theme's body ink in place: white on its tint is below 4.5:1. */
2869
2873
  .cn-chart-widget__matrix-cell--1 {
2874
+ --nldesign-color-on-surface: var(--color-primary-element-light-text);
2870
2875
  background: var(--color-primary-element-light);
2871
2876
  color: var(--color-primary-element-light-text);
2872
2877
  }
@@ -2882,6 +2887,7 @@ export default {
2882
2887
  }
2883
2888
 
2884
2889
  .cn-chart-widget__matrix-cell--4 {
2890
+ --nldesign-color-on-surface: var(--color-primary-element-text);
2885
2891
  background: var(--color-primary-element);
2886
2892
  color: var(--color-primary-element-text);
2887
2893
  }
@@ -522,7 +522,6 @@ export default {
522
522
  still legible (DqVolgendeStap), not faded. */
523
523
  .cn-choice-cards__row--disabled,
524
524
  .cn-choice-cards__row--disabled:hover {
525
- --nldesign-color-on-surface: var(--color-text-maxcontrast);
526
525
  border-color: var(--color-border);
527
526
  background: var(--color-background-hover);
528
527
  color: var(--color-text-maxcontrast);
@@ -532,7 +531,6 @@ export default {
532
531
  /* The title takes the muted ink itself: a theme that colours every label
533
532
  with `!important` (thematiq) would otherwise hand it the label colour. */
534
533
  .cn-choice-cards__row--disabled .cn-choice-cards__row-title {
535
- --nldesign-color-on-surface: var(--color-text-maxcontrast);
536
534
  color: var(--color-text-maxcontrast);
537
535
  }
538
536
 
@@ -600,7 +598,6 @@ export default {
600
598
  with `!important` (thematiq) reaches a span that only inherits. */
601
599
  .cn-choice-cards__row-description,
602
600
  .cn-choice-cards__reason {
603
- --nldesign-color-on-surface: var(--color-text-maxcontrast);
604
601
  font-size: 13px;
605
602
  font-weight: 400;
606
603
  color: var(--color-text-maxcontrast);
@@ -96,7 +96,7 @@
96
96
  <template v-if="!isBoardLook">
97
97
  <CnActionButtons
98
98
  v-if="headerActions && headerActions.length"
99
- :actions="headerActions"
99
+ :actions="linkedHeaderActions"
100
100
  data-testid="cn-dashboard-page-header-actions" />
101
101
  <!-- @slot header-actions Inline buttons rendered in the dashboard header next to the edit toggle. Used by every existing consumer (decidesk, launchpad, opencatalogi, pipelinq, procest). -->
102
102
  <slot name="header-actions" />
@@ -169,7 +169,7 @@
169
169
  <CnBuildiqEditButton />
170
170
  <CnActionButtons
171
171
  v-if="headerActions && headerActions.length"
172
- :actions="headerActions"
172
+ :actions="linkedHeaderActions"
173
173
  data-testid="cn-dashboard-page-header-actions" />
174
174
  <slot name="header-actions" />
175
175
  <slot name="actions" />
@@ -1042,6 +1042,7 @@ import { useIntegrationRegistry } from '../../composables/useIntegrationRegistry
1042
1042
  import { normalizeLook } from '../../composables/useLook.js'
1043
1043
  import { pageViews } from '../../mixins/pageViews.js'
1044
1044
  import { mergeUserLayout, resolveUserLayoutApi } from '../../store/plugins/dashboardLayouts.js'
1045
+ import { headerActionAsLink } from '../../utils/actionLink.js'
1045
1046
  import { isAppInstalled } from '../../utils/appInstalled.js'
1046
1047
  import { contentHeaderLink } from '../../utils/contentHeaderLink.js'
1047
1048
  import { pickDeclaredProps } from '../../utils/declaredProps.js'
@@ -1693,9 +1694,12 @@ export default {
1693
1694
  * in the dashboard header via CnActionButtons — `open-form` (schema
1694
1695
  * create dialog), `api-call` (POST/PUT + toast + refresh), `toggle`
1695
1696
  * (two-way state button), `navigate` / `open-modal` / `refresh`, each
1696
- * with an optional `visibleWhen` predicate. Empty (the default) renders
1697
- * nothing; the `#header-actions` slot still works alongside it for
1698
- * bespoke buttons.
1697
+ * with an optional `visibleWhen` predicate. An entry with `href` is a
1698
+ * link button, never dispatched: an absolute `http(s)`, `mailto:`,
1699
+ * `tel:` or `//host/...` URL as written, an instance path (leading `/`) through
1700
+ * `generateUrl()`, in a new tab with `target: '_blank'`. Empty (the
1701
+ * default) renders nothing; the `#header-actions` slot still works
1702
+ * alongside it for bespoke buttons.
1699
1703
  *
1700
1704
  * @type {Array<object>}
1701
1705
  */
@@ -2102,6 +2106,17 @@ export default {
2102
2106
  return typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key
2103
2107
  },
2104
2108
 
2109
+ /**
2110
+ * The header actions handed to CnActionButtons, an entry with `href`
2111
+ * in its link form (resolved URL, anchor target, no handler).
2112
+ *
2113
+ * @spec openspec/changes/header-action-href/specs/header-action-href/spec.md#requirement-a-header-action-with-href-is-a-link-that-runs-and-emits-nothing
2114
+ * @return {Array<object>}
2115
+ */
2116
+ linkedHeaderActions() {
2117
+ return (this.headerActions || []).map(headerActionAsLink)
2118
+ },
2119
+
2105
2120
  /**
2106
2121
  * The greeting for the time of day, with the reader's first (or
2107
2122
  * full) name when known, or '' when `greeting` is off.